Changelog#
This project uses semantic versioning
UNRELEASED#
13.2.0 (2026-06-03)#
Add Python-friendly
RunReportwrapper that returnsCommandDeclobjects as rule keys instead of raw egglog s-expression strings, with pretty-printed Python syntax instr()output #416
13.1.0 (2026-03-25)#
Improve high-level Python ergonomics and docs #397
Add
EGraph.freeze(), returning aFrozenEGraphsnapshot that can be pretty-printed back into replayable high-level Python actions for debugging and inspection.Add a variadic
EGraph(*actions, seminaive=True, save_egglog_string=False)constructor so actions can be registered at construction time, and exportActionLikefromegglogfor typing code that works withEGraph.register(...)and the constructor.Add richer general-purpose builtin helpers used by the new containers/polynomials work, including additional
Vec,MultiSet, numeric, and string operations.Expand the Python integration reference with examples for
run,stats,function_values,freeze,display, andsaturate, and clarify that proof-mode commands exposed in low-level bindings are not yet supported as a full high-level Python workflow.Add OpenTelemetry tracing docs and the linked
pytesttracing workflow for debugging Python and Rust spans together.Add the new containers/polynomials write-up.
Fix several high-level bugs:
auto-prefix generated
letbindings with$so recorded programs and round-tripped output are valid egglog;keep schedules and default-rewrite rules live after materialization so later declarations are not missed;
fix empty Python container conversions and several higher-order callable / nested-lambda inference edge cases.
Update the experimental
egglog.exp.array_apimodule.
13.0.1 (2026-03-04)#
13.0.0 (2026-03-03)#
Support using facts as union actions, add conversions to multisets, and update multiset example #382
Fix lookup of cost model based on value (see zulip for issue)
12.0.0 (2025-11-16)#
Add support for setting report level with
egraph.set_report_level#375Make docs builds fail on notebook execution errors and fix all doc issues #369
Add WIP
egglog.exp.any_exprcode for tracing arbitrary expressions with Python fallback #366BREAKING: Remove support for Python 3.11 now that pyo3 has dropped support.
Allow mutating methods to update their underlying expression via
Expr.__replace_expr__, and ensure default rewrites return the mutated receiver when usingmutates_selformutates_first_arg.BREAKING: Store
PyObjectvalues ascloudpicklebytes instead of live references so duplicates merge by value;.valuenow returns a fresh copy and the sort accepts objects likeNonethat previously failed.Adds a
__call__method (andcall_extendedfor kwargs) toPyObjectto replacepy_eval_fn, which is now deprecated.Improve doctest support, teaching expressions about their
__module__,__dir__, and special methods.Surface original Python exceptions from the runtime and tighten pretty-printing of values that cannot be re-parsed to make debugging e-graph executions easier.
Update the bundled Egglog crate, visualizer, and related dev dependencies (including
ipykernel) to pick up the latest backend fixes.
11.4.0 (2025-10-02)#
Add ability to create custom model and pass them in to extract #357
11.3.0 (2025-09-12)#
11.2.0 (2025-09-03)#
Add support for
set_costaction to have row level costs for extraction #343Add
egraph.function_values(fn)to export all function values likeprint-function#340Add
egraph.stats()method to print overall stats #339Add
all_function_sizesandfunction_sizeEGraph methods #338Fix execution of docs #337
Emit warnings when functions omitted when visualizing #336
Bump Egglog version #335
11.1.0 (2025-08-21)#
Allow changing number of threads with env variable #330
11.0.0 (2025-08-08)#
Change conversion between binary operators to consider converting both types #320
Add ability to parse egglog expressions into Python values #319
Deprecates
.eval()method on primitives in favor of.valuewhich can be used with pattern matching.
Support methods like on expressions #315
Upgrade egglog which includes new backend.
Fixes implementation of the Python Object sort to work with objects with dupliating hashes but the same value. Also changes the representation to be an index into a list instead of the ID, making egglog programs more deterministic.
Prefix constant declerations and unbound variables to not shadow let variables
BREAKING: Remove
simplifysince it was removed upstream. You can manually replace it with an insert, run, then extract.
Change how anonymous functions are converted to remove metaprogramming and lift only the unbound variables as args
Add support for getting the “value” of a function type with
.eval(), i.e.assert UnstableFn(f).eval() == f.
10.0.2 (2025-06-22)#
Fix using
f64Likewhen not importing star (also properly includes removal ofCallablespecial case from previous release).Fix Python 3.10 compatibility
10.0.1 (2025-04-06)#
Fix bug on resolving types if not all imported to your module #286
Also stops special casing including
Callableas a global. So if you previously included this in aTYPE_CHECKINGblock so it wasn’t available at runtime you will have to move this to a runtime import if used in a type alias.
10.0.0 (2025-03-28)#
Change builtins to not evaluate values in egraph and changes facts to compare structural equality instead of using an egraph when converting to a boolean, removing magic context (
EGraph.currentandSchedule.current) that was added in release 9.0.0.Fix bug that improperly upcasted values for ==
9.0.1 (2025-03-20)#
Add missing i64.log2 method to the bindings
9.0.0 (2025-03-20)#
Evaluating Primitives#
Previously, if you had an egglog primitive object like an i64, you would have to call
egraph.eval(i) to get back an int. Now you can just call int(i). This will implicitly create an e-graph and use it to extract the int value of the expression. This also means you can use this to evaluate compound expressions, like int(i64(1) + 10).
This is also supported for container types, like vecs and sets. You can also use the .eval() method on any primitive to get the Python object.
For example:
>>> from egglog import *
>>> Vec(i64(1), i64(2))[0]
Vec(1, 2)[0]
>>> int(Vec(i64(1), i64(2))[0])
1
>>> list(Vec(i64(1), i64(2)))
[i64(1), i64(2)]
>>> Rational(1, 2).eval()
Fraction(1, 2)
You can also manually set the e-graph to use, instead of it having to create a new one, with the egraph.set_current context manager:
>>> egraph = EGraph()
>>> x = egraph.let("x", i64(1))
>>> x + 2
x + 2
>>> (x + 2).eval()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/saul/p/egg-smol-python/python/egglog/builtins.py", line 134, in eval
value = _extract_lit(self)
^^^^^^^^^^^^^^^^^^
File "/Users/saul/p/egg-smol-python/python/egglog/builtins.py", line 1031, in _extract_lit
report = (EGraph.current or EGraph())._run_extract(cast("RuntimeExpr", e), 0)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/saul/p/egg-smol-python/python/egglog/egraph.py", line 1047, in _run_extract
self._egraph.run_program(
EggSmolError: At 0:0 of
Unbound symbol %x
When running commands:
(extract (+ %x 2) 0)
Extracting: (+ %x 2)
>>> with egraph.set_current():
... print((x + 2).eval())
...
3
There is a tradeoff here for more ease of use at the expense of some added implicit behavior using global state.
Equal and Not Equal operators#
Previously, if you wanted to create an equality fact, you would have to do eq(x).to(y). And similarly, if you wanted to create a not equal expression, you would have to do ne(x).to(y). I had set it up this way so that you could implement __eq__ and __ne__ on your custom data types to provide other functions (for monkeytyping purposes) and still be able to use the equality operators from egglog.
However, ergonmically this was a bit painful, so with this release, the == and != methods are now supported on all egglog expressions. If you override them for your types, you can still use the functions:
>>> i64(1) == i64(2)
eq(i64(1)).to(i64(2))
>>> i64(1) != i64(2)
ne(i64(1)).to(i64(2))
>>> class A(Expr):
... def __init__(self) -> None: ...
... def __eq__(self, other: "A") -> "A": ...
...
>>> A() == A()
A() == A()
>>> eq(A()).to(A())
eq(A()).to(A())
Evaluating Facts#
Similar to the above with primitives, you can now evaluate facts and see if they are true. This will implicitly create and run them on the e-graph. For example:
>>> i64(10) == i64(9) + 1
eq(i64(10)).to(i64(9) + 1)
>>> bool(i64(10) == i64(9) + 1)
True
Again, you can set the current e-graph with the context manager to use that instead:
>>> egraph = EGraph()
>>> s = egraph.let("s", MultiSet(i64(1), 2))
>>> with egraph.set_current():
... assert s.contains(1)
Experimental Array API Support#
This release also continues to experiment with a proof of concept array API implementation that allows deferred optimization and analysis. It is still very much a work in progress, but open to collaboration and feedback. The goal is to see egglog might be possible to be used as an end to end array compiler.
The changes in this release allow more functional programming constructrs to be used to create arrays, and then allow their properties to be optimized. For example, we can create a linalg function (in an example inspired by Siu):
from egglog.exp.array_api import *
@function(ruleset=array_api_ruleset, subsume=True)
def linalg_norm(X: NDArrayLike, axis: TupleIntLike) -> NDArray:
X = cast(NDArray, X)
return NDArray(
X.shape.deselect(axis),
X.dtype,
lambda k: ndindex(X.shape.select(axis))
.foldl_value(lambda carry, i: carry + ((x := X.index(i + k)).conj() * x).real(), init=0.0)
.sqrt(),
)
Then we are able to check the shape of the output based on the input:
>>> X = constant("X", NDArray)
>>> assume_shape(X, (3, 2, 3, 4))
>>> res = linalg_norm(X, (0, 1))
>>> assert res.shape.eval() == (Int(3), Int(4))
As well as see the symbolic form of it’s output:
>>> i = constant("i", Int)
>>> j = constant("j", Int)
>>> idxed = res.index((i, j))
>>> EGraph().simplify(idxed, array_api_schedule)
(
(
(
(
(
(X.index(TupleInt.from_vec(Vec[Int](Int(0), Int(0), i, j))).conj() * X.index(TupleInt.from_vec(Vec[Int](Int(0), Int(0), i, j)))).real()
+ (X.index(TupleInt.from_vec(Vec[Int](Int(0), Int(1), i, j))).conj() * X.index(TupleInt.from_vec(Vec[Int](Int(0), Int(1), i, j)))).real()
)
+ (X.index(TupleInt.from_vec(Vec[Int](Int(1), Int(0), i, j))).conj() * X.index(TupleInt.from_vec(Vec[Int](Int(1), Int(0), i, j)))).real()
)
+ (X.index(TupleInt.from_vec(Vec[Int](Int(1), Int(1), i, j))).conj() * X.index(TupleInt.from_vec(Vec[Int](Int(1), Int(1), i, j)))).real()
)
+ (X.index(TupleInt.from_vec(Vec[Int](Int(2), Int(0), i, j))).conj() * X.index(TupleInt.from_vec(Vec[Int](Int(2), Int(0), i, j)))).real()
)
+ (X.index(TupleInt.from_vec(Vec[Int](Int(2), Int(1), i, j))).conj() * X.index(TupleInt.from_vec(Vec[Int](Int(2), Int(1), i, j)))).real()
).sqrt()
All changes#
Fix pretty printing of lambda functions
Add support for subsuming rewrite generated by default function and method definitions
Add better error message when using @function in class (thanks @shinawy)
Add error method if
@methoddecorator is in wrong placeSubsumes lambda functions after replacing
Add working loopnest test and rewrite array api suport to be more general
Improve tracebacks on failing conversions.
Use
add_notefor exception to add more context, instead of raising a new exception, to make it easier to debug.Add conversions from generic types to be supported at runtime and typing level (so can go from
(1, 2, 3)toTupleInt)Open files with webbrowser instead of internal graphviz util for better support
Add support for not visualizing when using
.saturate()method #254Upgrade [egglog](https://github.com/egraphs-good/egglog/compare/b0d b06832264c9b22694bd3de2bdacd55bbe9e32…saulshanabrook:egg-smol:889ca7635368d7e382e16a93b2883aba82f1078f) #258
This includes a few big changes to the underlying bindings, which I won’t go over in full detail here. See the pyi diff for all public changes.
Creates seperate parent classes for
BuiltinExprvsExpr(aka eqsort aka user defined expressions). This is to allow us statically to differentiate between the two, to be more precise about what behavior is allowed. For example,unioncan only takeExprand notBuiltinExpr.Removes deprecated support for modules and building functions off of the e-egraph.
Updates function constructor to remove
defaultandon_merge. You also can’t set acostwhen you use amergefunction or return a primitive.eqnow only takes two args, instead of being able to compare any number of values.
Removes
evalmethod fromEGraphand moves primitive evaluation to methods on each builtin and supportint(...)type conversions on primitives. #265Change how to set global EGraph context with
with egraph.set_current()andEGraph.currentand add support for setting global schedule as well withwith schedule.set_current()andSchedule.current. #265Adds support for using
==and!=directly on values instead ofeqandnefunctions. #265Add multiset, bigint, and bigrat builtins
8.0.1 (2024-10-24)#
Upgrade dependencies including egglog
Fix bug with non glob star import
Fix bug extracting functions
8.0.0 (2024-10-17)#
Adds ability to use anonymous functions where callables are needed. These are automatically transformed to egglog functions with default rewrites.
Upgrade egglog
Adds source annotations to expressions for tracebacks
Adds ability to inline other functions besides primitives in serialized output
Adds
removeandsetmethods toVecUpgrades to use the new egraph-visualizer so we can have interactive visualizations
7.2.0 (2024-05-23)#
7.1.0 (2024-05-03)#
New Feaatures#
-
Adds
bindings.UnstableCombinedRulsetto commandsAdds
UnstableFnsort
Adds support for first class functions as values using Python’s built in
Callablesyntax andpartial.Adds way to combine ruleset with
r1 | r2syntax or the experimentalunstable_combine_rulesets(*rs, name=None)function.
Minor improvements#
Fixes a bug where you could not write binary dunder methods (like
__add__) that didn’t have symetric argumentsUse function name as ruleset name by default when creating ruleset from function
Adds ability to refer to methods and property off of classes instead of only off of instances (i.e.
Math.__add__(x, y))
7.0.0 (2024-04-27)#
Defers adding rules in functions until they are used, so that you can use types that are not present yet.
Removes ability to set custom default ruleset for egraph. Either just use the empty default ruleset or explicitly set it for every run
Automatically mark Python builtin operators as preserved if they must return a real Python value
Properly pretty print all items (rewrites, actions, exprs, etc) so that expressions are de-duplicated and state is handled correctly.
Add automatic releases from github manual action
6.1.0 (2024-03-06)#
6.0.1 (2024-02-28)#
Upgrade dependencies, including egglog
Fix bug where saturate wasn’t properly getting translated.
6.0.0 (2024-02-06)#
Remove modules / Auto register functions/classes#
You can now create classes and functions without an EGraph! They will be automatically registered on any EGraph if they are used in any of the rules or commands. This means the methods to add functions/classes on an EGraph are deprecated and moved to the top level module:
egraph.class_-> Removed, simply subclass fromegglog.Expregraph.method->egglog.methodegraph.function->egglog.functionegraph.relation->egglog.relationegraph.ruleset->egglog.Rulesetegraph.Module-> Removed
The goal of this change is to remove the complexity of Modules and remove the need to think about what functions/classes
need to be registered for each EGraph.
In turn, if you want to collect a set of rules, you can do that with a ruleset. Whenever you now run a ruleset or schedule, the ruleset will be automatically registered on the EGraph.
For backwards compatability, the existing methods and functions are preserved, to make this easier to adopt. They will all now raise deprication warnings.
Allow future type references in classes#
Classes can now reference types that have not been defined yet, as long as they are defined before the class is used in a rule or expression. For example:
class A(Expr):
def __init__(self, b: B) -> None: ...
class B(Expr):
...
Top level commands#
We can now simplify and check expressions without explicity making an EGraph:
check(<fact>, [<schedule>], *[<actions>])
# is equivalent to
e = EGraph()
e.register(*<actions>)
e.run(<schedule>)
e.check(<fact>)
simplify(<expr>, [<schedule>])
# is equivalent to
EGraph().simplify(<expr>, [<schedule>])
5.0.0 (2024-01-16)#
Move egglog
!=function to be called withne(x).to(y)instead ofx != yso that user defined expressions can
4.0.1 (2023-11-27)#
Fix keyword args for
__init__methods (#96)[https://github.com/metadsl/egglog-python/pull/96].
4.0.0 (2023-11-24)#
Fix
as_egglog_stringproprety.Move
EGraph.eval_fntopy_eval_fnsince it doesn’t need theEGraphanymore.
3.1.0 (2023-11-21)#
Update graphs to include more compact Python names of functions (#79)[https://github.com/metadsl/egglog-python/pull/79].
Add
as_egglog_stringproperty to get egglog source from e-graph (#82)[https://github.com/metadsl/egglog-python/pull/82].Add
include_costflag toegraph.extractto return the integer cost as well as an expression (#86)[https://github.com/metadsl/egglog-python/pull/86].Automatically try converting arguments to
eq,rewrite,set_, andunionto the correct type (#84)[https://github.com/metadsl/egglog-python/pull/84].Update RTD name to new project name of
egglog-pythonfromegg-smol-python(#18)[https://github.com/egraphs-good/egglog-python/pull/18].Move project to egraphs-good org!
3.0.0 (2023-11-19)#
Add support for outputing the serialization e-graph from the low level bindings. Note that this is not yet exposed a the high level yet.
This removes the existing to graphviz function on the EGraph low level binding and moves it to a method on the serialized EGraph.
See (#78)[https://github.com/egraphs-good/egglog-python/pull/78] for more details.
2.0.0 (2023-11-17)#
Simplify accessing primitives#
Previously, there was no public way of turning an egglog primitive, i.e. i64(10), into a Python primitive, i.e. int(10). Now there is a egraph.eval(...) method which will evaluate a primitive expression and return a Python object.
We also change the PyObject primitive to behave similarly. Instead of calling egraph.load_object(pyobj) you can now call egraph.eval(pyobj) to get the underlying Python object. Also, to unify it with the other objects, you can create a PyObject by using the constructor instead of egraph.save_object(pyobj).
Bug fixes#
Properly expose
birewriteat top level (#72)[https://github.com/egraphs-good/egglog-python/pull/72].Fix generation of graphviz interactive SVGs in docs.
Enhancements#
Added PyData lighting talk and Portland state talk to explanations.
Add experimental
jitdecorator to wrap all ndarray/numba functionality together.Switch to Ruff for linting
1.0.1 (2023-10-26)#
Adds youtube video to presentation slides.
1.0.0 (2023-10-26)#
Breaking Changes#
Test on Python 3.9 - 3.11, stop testing on 3.8 to follow Scientific Python versioning policy
Bump egglog dep
Adds
BoolbuiltinRename
PrintTablecommand toPrintFunctionChange extract command back to taking an expression instead of a fact
Adds
numeranddenomfunctions toRationalsort.Adds
terms_encodingboolean flag for creating an EGraphAllow print size command to be called with no args to print all sizes
Add
rebuildmethod for sets, maps, and vecs.
New Features#
Add ability to print egglog string of module with
.as_egglog_stringAdd ability to visualize changes in egraph as it runs with
.saturate()Add ability to make functions and module unextractable as well as increase the cost of a whole module.
Convert reflected methods based on both types
Allow specifying custom costs for conversions
In
py_execmake a temporary file with source for tracebacksAdd an experimental Array API implementation with a scikit-learn test
0.7.0 (2023-10-04)#
Bump egglog dep
New Features#
Adds ability for custom user defined types in a union for proper static typing with conversions #49
Adds
py_evalfunction toEGraphas a helper to eval Python code. #49Adds on hover behavior for edges in graphviz SVG output to make them easier to trace #49
Adds
egglog.exp.program_genmodule that will compile expressions into Python statements/functions #49Adds
py_execprimitive function for executing Python code #49
Bug fixes#
Clean up example in tutorial with demand based expression generation #49
0.6.0 (2023-09-20)#
Bump egglog dep
Breaking Changes#
Switches
RunReportto include more granular timings
New Features#
Uncategorized#
Added initial supported for Python objects #31
Renamed
BaseExprtoExprfor succinctnessStarted adding tutorial for using with array API and sklearn], using this to drive the support for more Python integration
Added a PyObject sort with the
save_objectandload_objectegraphs methods and theexecAdded more general mechanism to upcast Python arguments into egglog expressions, by registering
convertersAdded support for default arguments (this required refactoring declerations so that pretty printing can lookup expressions)
Added support for properties
Added support for passing args as keywords
Add support for pure Python methods, using the
preservekwarg to implement functions like__bool__on expressions.Fix
__str__method when pretty printing breaks.Added to/from i64 to i64 methods.
Upgraded
egg-smoldependency (changes)
Add support for functions which mutates their args, like
__setitem__#35Makes conversions transitive #38
Add support for reflective operators #39
Make reflective operators map directly to non-reflective #40
Includes latest egglog changes #42
Switches to termdag introduced in egglog #176
Removes custom fork of egglog now that visualizations are in core
Adds int and float to string functions
Switches
definetolet
Tidy up notebook appearence #43
Display expressions as code in Jupyter notebook
Display all expressions when graphing
Start adding to string support #45
Fix adding rules for sorts defined in other modules
Split out array API into multiple module
tidy up docs homepage
0.5.1 (2023-07-18)#
Added support for negation on
f64sortUpgraded
egg-smoldependency (changes)
0.5.0 (2023-05-03)#
Renamed
config()torun()to better matchegglogcommandFixed
relationtype signatureAdded default limit of 1 to
run()to matchegglogcommand and moved to second argUpgraded
egglogdependency (changes)Added
Setsort and removed set method fromMapAdded
VecsortAdded support for variable args for builtin functions, to use in creation of
VecandSetsorts.Added suport for joining
Strings
Switch generated egg names to use
.as seperate (i.e.Math.__add__) instead of_(i.e.Math___add__)Adds support for modules to define functions/sorts/rules without executing them, for reuse in other modules
Moved simplifying and running rulesets to the
runandsimplifymethods on theEGraphfrom those methods on theRulesetsince we can now createRulsets for modules which don’t have an EGraph attached and can’t be run
Fixed extracting classmethods which required generic args to cls
Added support for alternative way of creating variables using functions
Add NDarray example
Render EGraphs with
graphvizin the notebook (used in progress egglog PR).Add images to doc examples
Add
%%egglogmagic to the notebook
0.4.0 (2023-05-03)#
Change name to
egglogfromegg-smol, to mirror upstream change. Note that all previous versions are published under theegg-smolPyPi package while this and later are underegglog.
0.3.1 (2023-05-02)#
Fix bug calling methods on paramterized types (e.g.
Map[i64, i64].empty().insert(i64(0), i64(1)))Fix bug for Unit type (egg name is
Unitnotunit)Use
@class_decorator to force subclassingExprWorkaround extracting definitions until upstream is fixed
Rename
Map.map_removetoMap.remove.Add lambda calculus example
0.3.0 (2023-04-26)#
0.2.0 (2023-03-27)#
This release adds support for a high level API for e-graphs.
There is an examples of the high level API in the tutorials.