egglog 4.0.1__tar.gz → 5.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of egglog might be problematic. Click here for more details.
- {egglog-4.0.1 → egglog-5.0.0}/.pre-commit-config.yaml +1 -1
- {egglog-4.0.1 → egglog-5.0.0}/Cargo.lock +1 -1
- {egglog-4.0.1 → egglog-5.0.0}/Cargo.toml +1 -1
- {egglog-4.0.1 → egglog-5.0.0}/PKG-INFO +12 -12
- {egglog-4.0.1 → egglog-5.0.0}/README.md +1 -1
- {egglog-4.0.1 → egglog-5.0.0}/docs/changelog.md +5 -0
- egglog-5.0.0/docs/explanation/2023_12_02_congruence_closure-1.png +0 -0
- egglog-5.0.0/docs/explanation/2023_12_02_congruence_closure-2.png +0 -0
- egglog-5.0.0/docs/explanation/2023_12_02_congruence_closure.md +188 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/reference/egglog-translation.md +3 -3
- {egglog-4.0.1 → egglog-5.0.0}/docs/reference/python-integration.md +2 -1
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/declarations.py +1 -4
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/egraph.py +33 -15
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/examples/bool.py +1 -1
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/examples/lambda_.py +4 -4
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/examples/resolution.py +1 -1
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/exp/array_api.py +3 -3
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/exp/array_api_program_gen.py +2 -1
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/exp/program_gen.py +9 -9
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/runtime.py +7 -9
- {egglog-4.0.1 → egglog-5.0.0}/python/tests/test_array_api.py +0 -1
- {egglog-4.0.1 → egglog-5.0.0}/python/tests/test_high_level.py +25 -1
- {egglog-4.0.1 → egglog-5.0.0}/test-data/unit/check-high-level.test +2 -2
- {egglog-4.0.1 → egglog-5.0.0}/.github/workflows/CI.yml +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/.gitignore +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/.readthedocs.yaml +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/LICENSE +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/conftest.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/.gitignore +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/_templates/comments.html +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/conf.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/environment.yml +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/explanation/.gitignore +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/explanation/2023_07_presentation.ipynb +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/explanation/2023_11_09_portland_state.ipynb +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/explanation/2023_11_17_pytensor.ipynb +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/explanation/2023_11_pydata_lightning_talk.ipynb +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/explanation/big_graph.svg +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/explanation/define_and_define.md +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/explanation/ecosystem-graph.png +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/explanation/egg.png +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/explanation/indexing_pushdown.ipynb +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/explanation/moa.png +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/explanation/optional_values.md +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/explanation/pldi_2023_presentation.ipynb +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/explanation.md +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/how-to-guides.md +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/index.md +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/reference/bindings.md +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/reference/contributing.md +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/reference/high-level.md +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/reference/usage.md +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/reference.md +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/tutorials/getting-started.ipynb +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/tutorials/screenshot-1.png +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/tutorials/screenshot-2.png +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/tutorials/sklearn.ipynb +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/docs/tutorials.md +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/pyproject.toml +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/__init__.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/bindings.pyi +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/builtins.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/config.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/examples/README.rst +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/examples/__init__.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/examples/eqsat_basic.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/examples/fib.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/examples/matrix.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/examples/ndarrays.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/examples/schedule_demo.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/exp/__init__.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/exp/array_api_jit.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/exp/array_api_numba.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/graphviz_widget.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/ipython_magic.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/py.typed +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/type_constraint_solver.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/widget.css +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/egglog/widget.js +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/tests/__snapshots__/test_array_api/test_sklearn_lda[optimized].py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/tests/__snapshots__/test_array_api/test_sklearn_lda[original].py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/tests/__snapshots__/test_array_api/test_to_source.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/tests/__snapshots__/test_program_gen/test_to_string.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/tests/conftest.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/tests/test_bindings.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/tests/test_convert.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/tests/test_modules.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/tests/test_program_gen.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/tests/test_py_object_sort.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/tests/test_runtime.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/tests/test_type_constraint_solver.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/python/tests/test_typing.py +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/rust-toolchain +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/src/conversions.rs +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/src/egraph.rs +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/src/error.rs +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/src/lib.rs +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/src/py_object_sort.rs +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/src/serialize.rs +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/src/utils.rs +0 -0
- {egglog-4.0.1 → egglog-5.0.0}/stubtest_allow +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: egglog
|
|
3
|
-
Version:
|
|
3
|
+
Version: 5.0.0
|
|
4
4
|
Classifier: Environment :: MacOS X
|
|
5
5
|
Classifier: Environment :: Win32 (MS Windows)
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -19,18 +19,13 @@ Classifier: Typing :: Typed
|
|
|
19
19
|
Requires-Dist: typing-extensions
|
|
20
20
|
Requires-Dist: black
|
|
21
21
|
Requires-Dist: graphviz
|
|
22
|
-
Requires-Dist: scikit-learn; extra == 'array'
|
|
23
|
-
Requires-Dist: array_api_compat; extra == 'array'
|
|
24
|
-
Requires-Dist: numba; (python_version<"3.12") and extra == 'array'
|
|
25
|
-
Requires-Dist: pre-commit; extra == 'dev'
|
|
26
|
-
Requires-Dist: ruff; extra == 'dev'
|
|
27
|
-
Requires-Dist: mypy; extra == 'dev'
|
|
28
|
-
Requires-Dist: anywidget[dev]; extra == 'dev'
|
|
29
|
-
Requires-Dist: egglog[docs,test]; extra == 'dev'
|
|
30
22
|
Requires-Dist: pytest; extra == 'test'
|
|
31
23
|
Requires-Dist: mypy; extra == 'test'
|
|
32
24
|
Requires-Dist: syrupy; extra == 'test'
|
|
33
25
|
Requires-Dist: egglog[array]; extra == 'test'
|
|
26
|
+
Requires-Dist: scikit-learn; extra == 'array'
|
|
27
|
+
Requires-Dist: array_api_compat; extra == 'array'
|
|
28
|
+
Requires-Dist: numba; (python_version<"3.12") and extra == 'array'
|
|
34
29
|
Requires-Dist: pydata-sphinx-theme; extra == 'docs'
|
|
35
30
|
Requires-Dist: myst-nb; extra == 'docs'
|
|
36
31
|
Requires-Dist: sphinx-autodoc-typehints; extra == 'docs'
|
|
@@ -43,10 +38,15 @@ Requires-Dist: egglog[array]; extra == 'docs'
|
|
|
43
38
|
Requires-Dist: line-profiler; extra == 'docs'
|
|
44
39
|
Requires-Dist: sphinxcontrib-mermaid; extra == 'docs'
|
|
45
40
|
Requires-Dist: ablog; extra == 'docs'
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
Requires-Dist: pre-commit; extra == 'dev'
|
|
42
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
43
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
44
|
+
Requires-Dist: anywidget[dev]; extra == 'dev'
|
|
45
|
+
Requires-Dist: egglog[docs,test]; extra == 'dev'
|
|
48
46
|
Provides-Extra: test
|
|
47
|
+
Provides-Extra: array
|
|
49
48
|
Provides-Extra: docs
|
|
49
|
+
Provides-Extra: dev
|
|
50
50
|
License-File: LICENSE
|
|
51
51
|
Summary: e-graphs in Python built around the the egglog rust library
|
|
52
52
|
License: MIT
|
|
@@ -60,7 +60,7 @@ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
|
60
60
|
`egglog` is a Python package that provides bindings to the Rust library [`egglog`](https://github.com/egraphs-good/egglog/),
|
|
61
61
|
allowing you to use e-graphs in Python for optimization, symbolic computation, and analysis.
|
|
62
62
|
|
|
63
|
-
Please see the [documentation](https://egglog-python.readthedocs.io/
|
|
63
|
+
Please see the [documentation](https://egglog-python.readthedocs.io/) for more information.
|
|
64
64
|
|
|
65
65
|
Come say hello [on the e-graphs Zulip](https://egraphs.zulipchat.com/#narrow/stream/375765-egglog/) or [open an issue](https://github.com/egraphs-good/egglog-python/issues/new/choose)!
|
|
66
66
|
|
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
`egglog` is a Python package that provides bindings to the Rust library [`egglog`](https://github.com/egraphs-good/egglog/),
|
|
6
6
|
allowing you to use e-graphs in Python for optimization, symbolic computation, and analysis.
|
|
7
7
|
|
|
8
|
-
Please see the [documentation](https://egglog-python.readthedocs.io/
|
|
8
|
+
Please see the [documentation](https://egglog-python.readthedocs.io/) for more information.
|
|
9
9
|
|
|
10
10
|
Come say hello [on the e-graphs Zulip](https://egraphs.zulipchat.com/#narrow/stream/375765-egglog/) or [open an issue](https://github.com/egraphs-good/egglog-python/issues/new/choose)!
|
|
@@ -4,6 +4,11 @@ _This project uses semantic versioning_
|
|
|
4
4
|
|
|
5
5
|
## UNRELEASED
|
|
6
6
|
|
|
7
|
+
## 5.0.0 (2024-01-16)
|
|
8
|
+
|
|
9
|
+
- Move egglog `!=` function to be called with `ne(x).to(y)` instead of `x != y` so that user defined expressions
|
|
10
|
+
can
|
|
11
|
+
|
|
7
12
|
## 4.0.1 (2023-11-27)
|
|
8
13
|
|
|
9
14
|
- Fix keyword args for `__init__` methods (#96)[https://github.com/metadsl/egglog-python/pull/96].
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
---
|
|
2
|
+
file_format: mystnb
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
```{post} 2023-12-02
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
# Original Congruence Closure Paper
|
|
10
|
+
|
|
11
|
+
_In this post, I re-create the examples from the original 1980 paper defining the "congruence closure" of a graph in Python._
|
|
12
|
+
|
|
13
|
+
## Background
|
|
14
|
+
|
|
15
|
+
Over the past few days, I have been learning more about the differences in egglog between equality based types
|
|
16
|
+
and primitive values (see [issue](https://github.com/egraphs-good/egglog/issues/298) and [pr](https://github.com/egraphs-good/egglog/pull/309)).
|
|
17
|
+
|
|
18
|
+
It got me thinking more about the core e-graph data structures and so I spent today finally taking a look at
|
|
19
|
+
some of the papers [Talia Ringer reccomended](https://github.com/saulshanabrook/saulshanabrook/discussions/27#:~:text=Licata%20at%20Wesleyan-,Talia%20Ringer,-I%20was%20talking)
|
|
20
|
+
from the [e-graph week of their proof automation class](https://dependenttyp.es/classes/fa2022/readings/17-egraphs.html), which include:
|
|
21
|
+
|
|
22
|
+
- ["Congruence Closure in Intensional Type Theory" by Daniel Selsam and Leonardo de Moura in 2016](https://arxiv.org/pdf/1701.04391.pdf) on congruence close with dependent types.
|
|
23
|
+
- ["Congruence Closure in Cubical Type Theory" by Emil Holm Gjørup and Bas Spitters](https://cs.au.dk/~spitters/Emil.pdf) as a follow up on the above to adapt it to cubical type theory.
|
|
24
|
+
- ["Cubical methods in homotopy type theory and univalent foundations" by Anders Mörtberg in 2021](https://www.cambridge.org/core/journals/mathematical-structures-in-computer-science/article/cubical-methods-in-homotopy-type-theory-and-univalent-foundations/ECB3FE6B4A0B19AED2D3A2D785C38AF9) as an introduction to cubical type theory.
|
|
25
|
+
|
|
26
|
+
In the first sentance of the intro of the first paper it introduces the concept of "congruence closure" as important for program verification:
|
|
27
|
+
|
|
28
|
+
> Congruence closure procedures are used extensively in automated reasoning,
|
|
29
|
+
> since almost all proofs in both program verification and formalized mathematics
|
|
30
|
+
> require reasoning about equalities [23].
|
|
31
|
+
|
|
32
|
+
I went to follow that citation and read the original paper [**"Fast decision procedures based on congruence closure"** by Nelson and Oppen in 1980](https://dl.acm.org/doi/10.1145/322186.322198).
|
|
33
|
+
|
|
34
|
+
It was fun to see the original definitions of the congruence closure and to see what problems they were using it to solve.
|
|
35
|
+
|
|
36
|
+
In this post, I thought it would be cool to re-create the examples using the `egglog` library in Python. We won't get into the looking at how to implement the core algorithms, feel free to refer to the paper for those, but just to use the library
|
|
37
|
+
to help give us a visual and tactile feel for how the structures work.
|
|
38
|
+
|
|
39
|
+
It's nice to see that the concepts from the paper map very closely to the interface of the library, which I think is a testamont to the design of the abstractions in the underlying [`egglog` rust library](https://github.com/egraphs-good/egglog).
|
|
40
|
+
|
|
41
|
+
## "3. The Quantifier-Free Theory of Equality"
|
|
42
|
+
|
|
43
|
+
Again, if take a look at just the first sentance of the paper, it gets straight to the point:
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+
|
|
47
|
+
Let's see if we can encode this in `egglog`. Since it is strongly typed, we have to defined a dummy type `T` to represent the type of all terms. Since all functions need a fixed number of args, we have to define two functions `f` which takes
|
|
48
|
+
two args and `f1` which takes one arg:
|
|
49
|
+
|
|
50
|
+
```{code-cell} python
|
|
51
|
+
from egglog import *
|
|
52
|
+
|
|
53
|
+
egraph = EGraph()
|
|
54
|
+
|
|
55
|
+
@egraph.class_
|
|
56
|
+
class T: pass
|
|
57
|
+
|
|
58
|
+
a = egraph.constant("a", T)
|
|
59
|
+
b = egraph.constant("b", T)
|
|
60
|
+
|
|
61
|
+
@egraph.function
|
|
62
|
+
def f(x: T, y: T) -> T: pass
|
|
63
|
+
|
|
64
|
+
@egraph.function
|
|
65
|
+
def f1(x: T) -> T: pass
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Now we can get started trying to prove the examples.
|
|
69
|
+
|
|
70
|
+
First, we can start by trying to prove that `f(a, b) = a` implies `f(f(a, b), b) = a`:
|
|
71
|
+
|
|
72
|
+
```{code-cell} python
|
|
73
|
+
|
|
74
|
+
with egraph:
|
|
75
|
+
# start with our initial assumption
|
|
76
|
+
egraph.register(
|
|
77
|
+
union(f(a, b)).with_(a)
|
|
78
|
+
)
|
|
79
|
+
# Verify that we can prove the result
|
|
80
|
+
egraph.check(
|
|
81
|
+
eq(f(f(a, b), b)).to(a)
|
|
82
|
+
)
|
|
83
|
+
egraph.display()
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
One way to visually prove this is to see that `a` is in the same equivalence class (shows up as a cluster around multiple nodes in the graph) as `f(f(a, b), b)`, if we follow the arguments of `f`, the first is self referential to the same e-class, so we can follow it once more to get to a `f(f(a, b), b)` node.
|
|
87
|
+
|
|
88
|
+
The second example is a bit more interesting. We can display the e-graph twice. Once after adding the first assumption `f(f(f(a)))` and then again after adding the second assumption `f(f(f(f(f(a)))))`:
|
|
89
|
+
|
|
90
|
+
```{code-cell} python
|
|
91
|
+
|
|
92
|
+
with egraph:
|
|
93
|
+
egraph.register(
|
|
94
|
+
union(f1(f1(f1(a)))).with_(a),
|
|
95
|
+
)
|
|
96
|
+
egraph.display()
|
|
97
|
+
egraph.register(
|
|
98
|
+
union(f1(f1(f1(f1(f1(a)))))).with_(a),
|
|
99
|
+
)
|
|
100
|
+
egraph.check(eq(f1(a)).to(a))
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
We see that now `f(a)` is in the same e-class as `a` and so we can prove that `f(a) = a`.
|
|
104
|
+
|
|
105
|
+
I really enjoy how by converting these equivalence into these graphical forms makes proving equivalences about them intuitive and visual.
|
|
106
|
+
|
|
107
|
+
## "4. Extension to Theories of List Structure"
|
|
108
|
+
|
|
109
|
+
Now lets take a look at the second use case it outlines, which is to be able to prove properties about lists:
|
|
110
|
+
|
|
111
|
+

|
|
112
|
+
|
|
113
|
+
Again we start be definying our required functions:
|
|
114
|
+
|
|
115
|
+
```{code-cell} python
|
|
116
|
+
@egraph.function
|
|
117
|
+
def cons(x: T, y: T) -> T: pass
|
|
118
|
+
|
|
119
|
+
@egraph.function
|
|
120
|
+
def car(x: T) -> T: pass
|
|
121
|
+
|
|
122
|
+
@egraph.function
|
|
123
|
+
def cdr(x: T) -> T: pass
|
|
124
|
+
|
|
125
|
+
@egraph.function(default=Unit())
|
|
126
|
+
def not_atom(x: T) -> Unit: pass
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The funny one out of the bunch is the `not_atom` function. In `egglog` we represent a function that is just a fact
|
|
130
|
+
as one that returns the `Unit` type. We can also call it a "relation". It doesn't return anything meaningful, but just its existinace in the graph gives us information about its arguments.
|
|
131
|
+
|
|
132
|
+
In this case, we also have certain axioms about our functions:
|
|
133
|
+
|
|
134
|
+
```{code-cell} python
|
|
135
|
+
@egraph.register
|
|
136
|
+
def _list_axioms(x: T, y: T, z: T):
|
|
137
|
+
# CAR(CONS(x, y)) = x
|
|
138
|
+
yield rewrite(car(cons(x, y))).to(x)
|
|
139
|
+
# CDR(CONS(x, y)) = y
|
|
140
|
+
yield rewrite(cdr(cons(x, y))).to(y)
|
|
141
|
+
# ¬ATOM(x) ⊃ CONS(CAR(x), CDR(x)) = x
|
|
142
|
+
yield rule(not_atom(x)).then(union(cons(car(x), cdr(x))).with_(x))
|
|
143
|
+
# ¬ATOM(CONS(x, y))
|
|
144
|
+
yield rule(eq(z).to(cons(x, y))).then(not_atom(z))
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The first two are quite simple, we can rewrite a more complex term into a simpler one.
|
|
148
|
+
|
|
149
|
+
The third, requires us to the more powerful `rule` construct (which `rewrite` ends up compiling into) to say that if we know that `x` is not an atom, then we can add the fact that `x` is a `cons` of its `car` and `cdr`.
|
|
150
|
+
|
|
151
|
+
The fourth is similar, saying that if we find term that is a `cons` of two terms, then we know that term is not an atom.
|
|
152
|
+
|
|
153
|
+
Now we can actually try to prove the example theorum. First we start with its assumptions:
|
|
154
|
+
|
|
155
|
+
```{code-cell} python
|
|
156
|
+
x = egraph.constant("x", T)
|
|
157
|
+
y = egraph.constant("y", T)
|
|
158
|
+
|
|
159
|
+
egraph.register(
|
|
160
|
+
# CAR(x) = CAR(y)
|
|
161
|
+
union(car(x)).with_(car(y)),
|
|
162
|
+
# CDR(x) = CDR(y)
|
|
163
|
+
union(cdr(x)).with_(cdr(y)),
|
|
164
|
+
# ¬ATOM(x)
|
|
165
|
+
not_atom(x),
|
|
166
|
+
# ¬ATOM(y)
|
|
167
|
+
not_atom(y),
|
|
168
|
+
)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Then we can step-by-step run the rules until we can check for our result and prove it:
|
|
172
|
+
|
|
173
|
+
```{code-cell} python
|
|
174
|
+
egraph.display()
|
|
175
|
+
egraph.run(1)
|
|
176
|
+
egraph.display()
|
|
177
|
+
egraph.run(1)
|
|
178
|
+
egraph.display()
|
|
179
|
+
egraph.check(eq(x).to(y))
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
We can see that it start by running the rule `¬ATOM(x) ⊃ CONS(CAR(x), CDR(x)) = x` on both `x` and `y` since both are not atoms. It unions each with the `cons` of their `car` and `cdr`.
|
|
183
|
+
|
|
184
|
+
Then it runs the `CAR(CONS(x, y)) = x` and `CDR(CONS(x, y)) = y` rules to unify x and y!
|
|
185
|
+
|
|
186
|
+
## Conclusion
|
|
187
|
+
|
|
188
|
+
I had fun translating these examples into `egglog`. I also took a look over the other paper mentioned in the begining and might write about those in a future post. They were obviously more complex, and I understand less of them, since they are about dependent types and cubical type theory. However, it was interesting to see the connetion between things like the theory of the univalance axiom and questions like formalizing the equivalence of different programming languages abstractions. I really enjoyed [Voevodsky's lectures on the foundation of mathematics](https://www.math.ias.edu/vladimir/lectures#:~:text=Foundations%20of%20mathematics%20%2D%20their%20past%2C%20present%20and%20future) intuitiavely it seems like the perspective of treating equivalence as a path seems like an interesting approach. Also, I can see how it relates to open questions of storing proofs of equivalences in egglog as well as how to think about the "directionality" of a equality relational more intentionally, as the addition of features like `merge` functions in `egglog` allow.
|
|
@@ -52,14 +52,14 @@ Rational(1, 2) / Rational(2, 1)
|
|
|
52
52
|
|
|
53
53
|
### `!=` Operator
|
|
54
54
|
|
|
55
|
-
The `!=` function in egglog works on any two types with the same sort. In Python, this is
|
|
55
|
+
The `!=` function in egglog works on any two types with the same sort. In Python, this is mapped to the `ne` function:
|
|
56
56
|
|
|
57
57
|
```{code-cell} python
|
|
58
58
|
# egg: (!= 10 2)
|
|
59
|
-
i64(10)
|
|
59
|
+
ne(i64(10)).to(i64(2))
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
This is
|
|
62
|
+
This is a two part function so that we can statically check both sides are the same type.
|
|
63
63
|
|
|
64
64
|
## Declaring Sorts
|
|
65
65
|
|
|
@@ -208,6 +208,7 @@ Most of the Python special dunder (= "double under") methods are supported as we
|
|
|
208
208
|
- `__le__`
|
|
209
209
|
- `__eq__`
|
|
210
210
|
- `__ne__`
|
|
211
|
+
- `__ne__`
|
|
211
212
|
- `__gt__`
|
|
212
213
|
- `__ge__`
|
|
213
214
|
- `__add__`
|
|
@@ -231,7 +232,7 @@ Most of the Python special dunder (= "double under") methods are supported as we
|
|
|
231
232
|
- `__setitem__`
|
|
232
233
|
- `__delitem__`
|
|
233
234
|
|
|
234
|
-
Currently `__divmod__` is not supported, since it returns multiple results
|
|
235
|
+
Currently `__divmod__` is not supported, since it returns multiple results.
|
|
235
236
|
|
|
236
237
|
Also these methods are currently used in the runtime class and cannot be overridden currently, although we could change this
|
|
237
238
|
if the need arises:
|
|
@@ -269,7 +269,7 @@ class ModuleDeclarations:
|
|
|
269
269
|
return decls.get_egg_fn(ref)
|
|
270
270
|
except KeyError:
|
|
271
271
|
pass
|
|
272
|
-
raise KeyError(f"Callable ref {ref} not found")
|
|
272
|
+
raise KeyError(f"Callable ref {ref!r} not found")
|
|
273
273
|
|
|
274
274
|
def get_egg_sort(self, ref: JustTypeRef) -> str:
|
|
275
275
|
for decls in self.all_decls:
|
|
@@ -770,9 +770,6 @@ class CallDecl:
|
|
|
770
770
|
if self in context.names:
|
|
771
771
|
return context.names[self]
|
|
772
772
|
ref, args = self.callable, [a.expr for a in self.args]
|
|
773
|
-
# Special case != since it doesn't have a decl
|
|
774
|
-
if isinstance(ref, MethodRef) and ref.method_name == "__ne__":
|
|
775
|
-
return f"{args[0].pretty(context)} != {args[1].pretty(context)}"
|
|
776
773
|
function_decl = context.mod_decls.get_function_decl(ref)
|
|
777
774
|
# Determine how many of the last arguments are defaults, by iterating from the end and comparing the arg with the default
|
|
778
775
|
n_defaults = 0
|
|
@@ -54,6 +54,7 @@ __all__ = [
|
|
|
54
54
|
"rewrite",
|
|
55
55
|
"birewrite",
|
|
56
56
|
"eq",
|
|
57
|
+
"ne",
|
|
57
58
|
"panic",
|
|
58
59
|
"let",
|
|
59
60
|
"delete",
|
|
@@ -258,8 +259,6 @@ class _BaseModule(ABC):
|
|
|
258
259
|
unextractable=unextractable,
|
|
259
260
|
)
|
|
260
261
|
|
|
261
|
-
# Register != as a method so we can print it as a string
|
|
262
|
-
self._mod_decls._decl.register_callable_ref(MethodRef(cls_name, "__ne__"), "!=")
|
|
263
262
|
return RuntimeClass(self._mod_decls, cls_name)
|
|
264
263
|
|
|
265
264
|
# We seperate the function and method overloads to make it simpler to know if we are modifying a function or method,
|
|
@@ -663,6 +662,8 @@ class _Builtins(_BaseModule):
|
|
|
663
662
|
msg = "Builtins already initialized"
|
|
664
663
|
raise RuntimeError(msg)
|
|
665
664
|
_BUILTIN_DECLS = self._mod_decls._decl
|
|
665
|
+
# Register != operator
|
|
666
|
+
_BUILTIN_DECLS.register_callable_ref(FunctionRef("!="), "!=")
|
|
666
667
|
|
|
667
668
|
def _process_commands(self, cmds: Iterable[bindings._Command]) -> None:
|
|
668
669
|
"""
|
|
@@ -1152,22 +1153,10 @@ class Expr(metaclass=_ExprMetaclass):
|
|
|
1152
1153
|
Expression base class, which adds suport for != to all expression types.
|
|
1153
1154
|
"""
|
|
1154
1155
|
|
|
1155
|
-
def __ne__(self
|
|
1156
|
-
"""
|
|
1157
|
-
Compare whether to expressions are not equal.
|
|
1158
|
-
|
|
1159
|
-
:param self: The expression to compare.
|
|
1160
|
-
:param other_expr: The other expression to compare to, which must be of the same type.
|
|
1161
|
-
:meta public:
|
|
1162
|
-
"""
|
|
1156
|
+
def __ne__(self, other: NoReturn) -> NoReturn: # type: ignore[override, empty-body]
|
|
1163
1157
|
...
|
|
1164
1158
|
|
|
1165
1159
|
def __eq__(self, other: NoReturn) -> NoReturn: # type: ignore[override, empty-body]
|
|
1166
|
-
"""
|
|
1167
|
-
Equality is currently not supported.
|
|
1168
|
-
|
|
1169
|
-
We only add this method so that if you try to use it MyPy will warn you.
|
|
1170
|
-
"""
|
|
1171
1160
|
...
|
|
1172
1161
|
|
|
1173
1162
|
|
|
@@ -1492,6 +1481,11 @@ def eq(expr: EXPR) -> _EqBuilder[EXPR]:
|
|
|
1492
1481
|
return _EqBuilder(expr)
|
|
1493
1482
|
|
|
1494
1483
|
|
|
1484
|
+
def ne(expr: EXPR) -> _NeBuilder[EXPR]:
|
|
1485
|
+
"""Check if the given expression is not equal to the given value."""
|
|
1486
|
+
return _NeBuilder(expr)
|
|
1487
|
+
|
|
1488
|
+
|
|
1495
1489
|
def panic(message: str) -> Action:
|
|
1496
1490
|
"""Raise an error with the given message."""
|
|
1497
1491
|
return Panic(message)
|
|
@@ -1596,6 +1590,30 @@ class _EqBuilder(Generic[EXPR]):
|
|
|
1596
1590
|
return f"eq({self.expr})"
|
|
1597
1591
|
|
|
1598
1592
|
|
|
1593
|
+
@dataclass
|
|
1594
|
+
class _NeBuilder(Generic[EXPR]):
|
|
1595
|
+
expr: EXPR
|
|
1596
|
+
|
|
1597
|
+
def to(self, expr: EXPR) -> Unit:
|
|
1598
|
+
l_expr = cast(RuntimeExpr, self.expr)
|
|
1599
|
+
return cast(
|
|
1600
|
+
Unit,
|
|
1601
|
+
RuntimeExpr(
|
|
1602
|
+
BUILTINS._mod_decls,
|
|
1603
|
+
TypedExprDecl(
|
|
1604
|
+
JustTypeRef("Unit"),
|
|
1605
|
+
CallDecl(
|
|
1606
|
+
FunctionRef("!="),
|
|
1607
|
+
(l_expr.__egg_typed_expr__, convert_to_same_type(expr, l_expr).__egg_typed_expr__),
|
|
1608
|
+
),
|
|
1609
|
+
),
|
|
1610
|
+
),
|
|
1611
|
+
)
|
|
1612
|
+
|
|
1613
|
+
def __str__(self) -> str:
|
|
1614
|
+
return f"ne({self.expr})"
|
|
1615
|
+
|
|
1616
|
+
|
|
1599
1617
|
@dataclass
|
|
1600
1618
|
class _SetBuilder(Generic[EXPR]):
|
|
1601
1619
|
lhs: Expr
|
|
@@ -13,7 +13,7 @@ egraph = EGraph()
|
|
|
13
13
|
egraph.check(eq(T & T).to(T))
|
|
14
14
|
egraph.check(eq(T & F).to(F))
|
|
15
15
|
egraph.check(eq(T | F).to(T))
|
|
16
|
-
egraph.check((T | F)
|
|
16
|
+
egraph.check(ne(T | F).to(F))
|
|
17
17
|
|
|
18
18
|
egraph.check(eq(i64(1).bool_lt(2)).to(T))
|
|
19
19
|
egraph.check(eq(i64(2).bool_lt(1)).to(F))
|
|
@@ -117,7 +117,7 @@ egraph.register(
|
|
|
117
117
|
union(t.eval()).with_(Val(i1 + i2))
|
|
118
118
|
),
|
|
119
119
|
rule(eq(t).to(t1 == t2), eq(t1.eval()).to(t2.eval())).then(union(t.eval()).with_(Val.TRUE)),
|
|
120
|
-
rule(eq(t).to(t1 == t2), eq(t1.eval()).to(v1), eq(t2.eval()).to(v2), v1
|
|
120
|
+
rule(eq(t).to(t1 == t2), eq(t1.eval()).to(v1), eq(t2.eval()).to(v2), ne(v1).to(v2)).then(
|
|
121
121
|
union(t.eval()).with_(Val.FALSE)
|
|
122
122
|
),
|
|
123
123
|
rule(eq(v).to(t.eval())).then(union(t).with_(Term.val(v))),
|
|
@@ -154,12 +154,12 @@ egraph.register(
|
|
|
154
154
|
# let-var-same
|
|
155
155
|
rewrite(let_(x, t, Term.var(x))).to(t),
|
|
156
156
|
# let-var-diff
|
|
157
|
-
rewrite(let_(x, t, Term.var(y))).to(Term.var(y), x
|
|
157
|
+
rewrite(let_(x, t, Term.var(y))).to(Term.var(y), ne(x).to(y)),
|
|
158
158
|
# let-lam-same
|
|
159
159
|
rewrite(let_(x, t, lam(x, t1))).to(lam(x, t1)),
|
|
160
160
|
# let-lam-diff
|
|
161
|
-
rewrite(let_(x, t, lam(y, t1))).to(lam(y, let_(x, t, t1)), x
|
|
162
|
-
rule(eq(t).to(let_(x, t1, lam(y, t2))), x
|
|
161
|
+
rewrite(let_(x, t, lam(y, t1))).to(lam(y, let_(x, t, t1)), ne(x).to(y), eq(fv).to(freer(t)), fv.not_contains(y)),
|
|
162
|
+
rule(eq(t).to(let_(x, t1, lam(y, t2))), ne(x).to(y), eq(fv).to(freer(t1)), fv.contains(y)).then(
|
|
163
163
|
union(t).with_(lam(t.v(), let_(x, t1, let_(y, Term.var(t.v()), t2))))
|
|
164
164
|
),
|
|
165
165
|
)
|
|
@@ -278,7 +278,7 @@ class Int(Expr):
|
|
|
278
278
|
@array_api_module.register
|
|
279
279
|
def _int(i: i64, j: i64, r: Boolean, o: Int):
|
|
280
280
|
yield rewrite(Int(i) == Int(i)).to(TRUE)
|
|
281
|
-
yield rule(eq(r).to(Int(i) == Int(j)), i
|
|
281
|
+
yield rule(eq(r).to(Int(i) == Int(j)), ne(i).to(j)).then(union(r).with_(FALSE))
|
|
282
282
|
|
|
283
283
|
yield rewrite(Int(i) >= Int(i)).to(TRUE)
|
|
284
284
|
yield rule(eq(r).to(Int(i) >= Int(j)), i > j).then(union(r).with_(TRUE))
|
|
@@ -666,7 +666,7 @@ def _tuple_value(
|
|
|
666
666
|
# Includes
|
|
667
667
|
rewrite(TupleValue.EMPTY.includes(v)).to(FALSE),
|
|
668
668
|
rewrite(TupleValue(v).includes(v)).to(TRUE),
|
|
669
|
-
rewrite(TupleValue(v).includes(v2)).to(FALSE, v
|
|
669
|
+
rewrite(TupleValue(v).includes(v2)).to(FALSE, ne(v).to(v2)),
|
|
670
670
|
rewrite((ti + ti2).includes(v)).to(ti.includes(v) | ti2.includes(v)),
|
|
671
671
|
]
|
|
672
672
|
|
|
@@ -1503,7 +1503,7 @@ def _assume_value_one_of(x: NDArray, v: Value, vs: TupleValue, idx: TupleInt):
|
|
|
1503
1503
|
def _ndarray_value_isfinite(arr: NDArray, x: Value, xs: TupleValue, i: Int, f: f64, b: Boolean):
|
|
1504
1504
|
yield rewrite(Value.int(i).isfinite()).to(TRUE)
|
|
1505
1505
|
yield rewrite(Value.bool(b).isfinite()).to(TRUE)
|
|
1506
|
-
yield rewrite(Value.float(Float(f)).isfinite()).to(TRUE, f
|
|
1506
|
+
yield rewrite(Value.float(Float(f)).isfinite()).to(TRUE, ne(f).to(f64(math.nan)))
|
|
1507
1507
|
|
|
1508
1508
|
# a sum of an array is finite if all the values are finite
|
|
1509
1509
|
yield rewrite(isfinite(sum(arr))).to(NDArray.scalar(Value.bool(arr.index(ALL_INDICES).isfinite())))
|
|
@@ -126,7 +126,8 @@ def _float_program(f: Float, g: Float, f64_: f64, i: Int, r: Rational):
|
|
|
126
126
|
yield rewrite(float_program(f * g)).to(Program("(") + float_program(f) + " * " + float_program(g) + ")")
|
|
127
127
|
yield rewrite(float_program(f / g)).to(Program("(") + float_program(f) + " / " + float_program(g) + ")")
|
|
128
128
|
yield rewrite(float_program(Float.rational(r))).to(
|
|
129
|
-
Program("float(") + Program(r.numer.to_string()) + " / " + Program(r.denom.to_string()) + ")",
|
|
129
|
+
Program("float(") + Program(r.numer.to_string()) + " / " + Program(r.denom.to_string()) + ")",
|
|
130
|
+
ne(r.denom).to(i64(1)),
|
|
130
131
|
)
|
|
131
132
|
yield rewrite(float_program(Float.rational(r))).to(
|
|
132
133
|
Program("float(") + Program(r.numer.to_string()) + ")", eq(r.denom).to(i64(1))
|
|
@@ -182,7 +182,7 @@ def _compile(
|
|
|
182
182
|
yield rule(
|
|
183
183
|
stmt,
|
|
184
184
|
p.compile(i),
|
|
185
|
-
p1.parent
|
|
185
|
+
ne(p1.parent).to(p),
|
|
186
186
|
eq(s1).to(p1.expr),
|
|
187
187
|
).then(
|
|
188
188
|
set_(p.statements).to(join(s1, "\n")),
|
|
@@ -214,7 +214,7 @@ def _compile(
|
|
|
214
214
|
# Otherwise, if its not equal to either input, its not an identifier
|
|
215
215
|
yield rule(program_add, eq(p.expr).to(p1.expr), eq(b).to(p1.is_identifer)).then(set_(p.is_identifer).to(b))
|
|
216
216
|
yield rule(program_add, eq(p.expr).to(p2.expr), eq(b).to(p2.is_identifer)).then(set_(p.is_identifer).to(b))
|
|
217
|
-
yield rule(program_add, p.expr
|
|
217
|
+
yield rule(program_add, ne(p.expr).to(p1.expr), ne(p.expr).to(p2.expr)).then(set_(p.is_identifer).to(Bool(False)))
|
|
218
218
|
|
|
219
219
|
# Set parent of p1
|
|
220
220
|
yield rule(program_add, p.compile(i)).then(
|
|
@@ -228,7 +228,7 @@ def _compile(
|
|
|
228
228
|
yield rule(program_add, p.compile(i), p1.next_sym).then(set_(p2.parent).to(p))
|
|
229
229
|
|
|
230
230
|
# Compile p2, if p1 parent not equal, but p2 parent equal
|
|
231
|
-
yield rule(program_add, p.compile(i), p1.parent
|
|
231
|
+
yield rule(program_add, p.compile(i), ne(p1.parent).to(p), eq(p2.parent).to(p)).then(p2.compile(i))
|
|
232
232
|
|
|
233
233
|
# Compile p2, if p1 parent eqal
|
|
234
234
|
yield rule(program_add, p.compile(i2), eq(p1.parent).to(p), eq(i).to(p1.next_sym), eq(p2.parent).to(p)).then(
|
|
@@ -259,8 +259,8 @@ def _compile(
|
|
|
259
259
|
yield rule(
|
|
260
260
|
program_add,
|
|
261
261
|
p.compile(i),
|
|
262
|
-
p1.parent
|
|
263
|
-
p2.parent
|
|
262
|
+
ne(p1.parent).to(p),
|
|
263
|
+
ne(p2.parent).to(p),
|
|
264
264
|
).then(
|
|
265
265
|
set_(p.statements).to(String("")),
|
|
266
266
|
set_(p.next_sym).to(i),
|
|
@@ -269,7 +269,7 @@ def _compile(
|
|
|
269
269
|
yield rule(
|
|
270
270
|
program_add,
|
|
271
271
|
eq(p1.parent).to(p),
|
|
272
|
-
p2.parent
|
|
272
|
+
ne(p2.parent).to(p),
|
|
273
273
|
eq(s1).to(p1.statements),
|
|
274
274
|
eq(i).to(p1.next_sym),
|
|
275
275
|
).then(
|
|
@@ -280,7 +280,7 @@ def _compile(
|
|
|
280
280
|
yield rule(
|
|
281
281
|
program_add,
|
|
282
282
|
eq(p2.parent).to(p),
|
|
283
|
-
p1.parent
|
|
283
|
+
ne(p1.parent).to(p),
|
|
284
284
|
eq(s2).to(p2.statements),
|
|
285
285
|
eq(i).to(p2.next_sym),
|
|
286
286
|
).then(
|
|
@@ -319,7 +319,7 @@ def _compile(
|
|
|
319
319
|
# 1. b. If p1 parent is not p, then just use assign as statement, next sym of i
|
|
320
320
|
yield rule(
|
|
321
321
|
program_assign,
|
|
322
|
-
p1.parent
|
|
322
|
+
ne(p1.parent).to(p),
|
|
323
323
|
p.compile(i),
|
|
324
324
|
eq(s2).to(p1.expr),
|
|
325
325
|
eq(p1.is_identifer).to(Bool(False)),
|
|
@@ -347,7 +347,7 @@ def _compile(
|
|
|
347
347
|
# 1. b. If p1 parent is not p, then just use assign as statement, next sym of i
|
|
348
348
|
yield rule(
|
|
349
349
|
program_assign,
|
|
350
|
-
p1.parent
|
|
350
|
+
ne(p1.parent).to(p),
|
|
351
351
|
p.compile(i),
|
|
352
352
|
eq(s2).to(p1.expr),
|
|
353
353
|
eq(p1.is_identifer).to(Bool(True)),
|
|
@@ -435,15 +435,13 @@ class RuntimeMethod:
|
|
|
435
435
|
self.__egg_callable_ref__ = PropertyRef(self.class_name, self.__egg_method_name__)
|
|
436
436
|
else:
|
|
437
437
|
self.__egg_callable_ref__ = MethodRef(self.class_name, self.__egg_method_name__)
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
self.
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
except KeyError as e:
|
|
446
|
-
raise AttributeError(f"Class {self.class_name} does not have method {self.__egg_method_name__}") from e
|
|
438
|
+
try:
|
|
439
|
+
self.__egg_fn_decl__ = self.__egg_self__.__egg_decls__.get_function_decl(self.__egg_callable_ref__)
|
|
440
|
+
except KeyError as e:
|
|
441
|
+
msg = f"Class {self.class_name} does not have method {self.__egg_method_name__}"
|
|
442
|
+
if self.__egg_method_name__ == "__ne__":
|
|
443
|
+
msg += ". Did you mean to use the ne(...).to(...)?"
|
|
444
|
+
raise AttributeError(msg) from e
|
|
447
445
|
|
|
448
446
|
def __call__(self, *args: object, **kwargs) -> RuntimeExpr | None:
|
|
449
447
|
args = (self.__egg_self__, *args)
|
|
@@ -359,9 +359,33 @@ def test_f64_negation() -> None:
|
|
|
359
359
|
|
|
360
360
|
def test_not_equals():
|
|
361
361
|
egraph = EGraph()
|
|
362
|
-
egraph.check(i64(10)
|
|
362
|
+
egraph.check(ne(i64(10)).to(i64(2)))
|
|
363
363
|
|
|
364
364
|
|
|
365
|
+
def test_custom_equality():
|
|
366
|
+
egraph = EGraph()
|
|
367
|
+
|
|
368
|
+
@egraph.class_
|
|
369
|
+
class Boolean(Expr):
|
|
370
|
+
def __init__(self, value: BoolLike) -> None:
|
|
371
|
+
...
|
|
372
|
+
|
|
373
|
+
def __eq__(self, other: Boolean) -> Boolean: # type: ignore[override]
|
|
374
|
+
...
|
|
375
|
+
|
|
376
|
+
def __ne__(self, other: Boolean) -> Boolean: # type: ignore[override]
|
|
377
|
+
...
|
|
378
|
+
|
|
379
|
+
egraph.register(rewrite(Boolean(True) == Boolean(True)).to(Boolean(False)))
|
|
380
|
+
egraph.register(rewrite(Boolean(True) != Boolean(True)).to(Boolean(True)))
|
|
381
|
+
|
|
382
|
+
should_be_true = Boolean(True) == Boolean(True)
|
|
383
|
+
should_be_false = Boolean(True) != Boolean(True)
|
|
384
|
+
egraph.register(should_be_true, should_be_false)
|
|
385
|
+
egraph.run(10)
|
|
386
|
+
egraph.check(eq(should_be_true).to(Boolean(False)))
|
|
387
|
+
egraph.check(eq(should_be_false).to(Boolean(True)))
|
|
388
|
+
|
|
365
389
|
class TestMutate:
|
|
366
390
|
def test_setitem_defaults(self):
|
|
367
391
|
egraph = EGraph()
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
from egglog import *
|
|
3
3
|
_ = i64(0) == i64(0) # E: Unsupported operand types for == ("i64" and "i64")
|
|
4
4
|
|
|
5
|
-
[case
|
|
5
|
+
[case notEqNotAllowed]
|
|
6
6
|
from egglog import *
|
|
7
|
-
_ = i64(0) != i64(0) #
|
|
7
|
+
_ = i64(0) != i64(0) # E: Unsupported operand types for != ("i64" and "i64")
|
|
8
8
|
|
|
9
9
|
[case eqToAllowed]
|
|
10
10
|
from egglog import *
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|