Skip to content

Commit f2486a5

Browse files
Improve changelog
1 parent ee538a9 commit f2486a5

File tree

1 file changed

+154
-0
lines changed

1 file changed

+154
-0
lines changed

docs/changelog.md

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,160 @@ _This project uses semantic versioning_
44

55
## UNRELEASED
66

7+
### Evaluating Primitives
8+
9+
Previously, if you had an egglog primitive object like an `i64`, you would have to call
10+
`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)`.
11+
12+
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.
13+
14+
For example:
15+
16+
````python
17+
>>> from egglog import *
18+
>>> Vec(i64(1), i64(2))[0]
19+
Vec(1, 2)[0]
20+
>>> int(Vec(i64(1), i64(2))[0])
21+
1
22+
>>> list(Vec(i64(1), i64(2)))
23+
[i64(1), i64(2)]
24+
>>> Rational(1, 2).eval()
25+
Fraction(1, 2)
26+
>>>
27+
28+
29+
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:
30+
31+
```python
32+
>>> egraph = EGraph()
33+
>>> x = egraph.let("x", i64(1))
34+
>>> x + 2
35+
x + 2
36+
>>> (x + 2).eval()
37+
Traceback (most recent call last):
38+
File "<stdin>", line 1, in <module>
39+
File "/Users/saul/p/egg-smol-python/python/egglog/builtins.py", line 134, in eval
40+
value = _extract_lit(self)
41+
^^^^^^^^^^^^^^^^^^
42+
File "/Users/saul/p/egg-smol-python/python/egglog/builtins.py", line 1031, in _extract_lit
43+
report = (EGraph.current or EGraph())._run_extract(cast("RuntimeExpr", e), 0)
44+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
45+
File "/Users/saul/p/egg-smol-python/python/egglog/egraph.py", line 1047, in _run_extract
46+
self._egraph.run_program(
47+
EggSmolError: At 0:0 of
48+
Unbound symbol %x
49+
When running commands:
50+
(extract (+ %x 2) 0)
51+
52+
Extracting: (+ %x 2)
53+
>>> with egraph.set_current():
54+
... print((x + 2).eval())
55+
...
56+
3
57+
````
58+
59+
There is a tradeoff here for more ease of use at the expense of some added implicit behavior using global state.
60+
61+
### Equal and Not Equal operators
62+
63+
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.
64+
65+
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:
66+
67+
```python
68+
>>> i64(1) == i64(2)
69+
eq(i64(1)).to(i64(2))
70+
>>> i64(1) != i64(2)
71+
ne(i64(1)).to(i64(2))
72+
>>> class A(Expr):
73+
... def __init__(self) -> None: ...
74+
... def __eq__(self, other: "A") -> "A": ...
75+
...
76+
>>> A() == A()
77+
A() == A()
78+
>>> eq(A()).to(A())
79+
eq(A()).to(A())
80+
```
81+
82+
### Evaluating Facts
83+
84+
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:
85+
86+
```python
87+
>>> i64(10) == i64(9) + 1
88+
eq(i64(10)).to(i64(9) + 1)
89+
>>> bool(i64(10) == i64(9) + 1)
90+
True
91+
```
92+
93+
Again, you can set the current e-graph with the context manager to use that instead:
94+
95+
```python
96+
>>> egraph = EGraph()
97+
>>> s = egraph.let("s", MultiSet(i64(1), 2))
98+
>>> with egraph.set_current():
99+
... assert s.contains(1)
100+
```
101+
102+
### Experimental Array API Support
103+
104+
This release also continues to experiment with a proof of concept array API implementation
105+
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.
106+
107+
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
108+
a linalg function (in [an example inspired by Siu](https://gist.github.com/sklam/5e5737137d48d6e5b816d14a90076f1d)):
109+
110+
```python
111+
from egglog.exp.array_api import *
112+
113+
@function(ruleset=array_api_ruleset, subsume=True)
114+
def linalg_norm(X: NDArrayLike, axis: TupleIntLike) -> NDArray:
115+
X = cast(NDArray, X)
116+
return NDArray(
117+
X.shape.deselect(axis),
118+
X.dtype,
119+
lambda k: ndindex(X.shape.select(axis))
120+
.foldl_value(lambda carry, i: carry + ((x := X.index(i + k)).conj() * x).real(), init=0.0)
121+
.sqrt(),
122+
)
123+
```
124+
125+
Then we are able to check the shape of the output based on the input:
126+
127+
```python
128+
>>> X = constant("X", NDArray)
129+
>>> assume_shape(X, (3, 2, 3, 4))
130+
>>> res = linalg_norm(X, (0, 1))
131+
>>> assert res.shape.eval() == (Int(3), Int(4))
132+
```
133+
134+
As well as see the symbolic form of it's output:
135+
136+
```python
137+
>>> i = constant("i", Int)
138+
>>> j = constant("j", Int)
139+
>>> idxed = res.index((i, j))
140+
>>> EGraph().simplify(idxed, array_api_schedule)
141+
(
142+
(
143+
(
144+
(
145+
(
146+
(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()
147+
+ (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()
148+
)
149+
+ (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()
150+
)
151+
+ (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()
152+
)
153+
+ (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()
154+
)
155+
+ (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()
156+
).sqrt()
157+
```
158+
159+
### All changes
160+
7161
- Fix pretty printing of lambda functions
8162
- Add support for subsuming rewrite generated by default function and method definitions
9163
- Add better error message when using @function in class (thanks @shinawy)

0 commit comments

Comments
 (0)