egglog 4.0.0__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.

Files changed (101) hide show
  1. {egglog-4.0.0 → egglog-5.0.0}/.pre-commit-config.yaml +1 -1
  2. {egglog-4.0.0 → egglog-5.0.0}/Cargo.lock +1 -1
  3. {egglog-4.0.0 → egglog-5.0.0}/Cargo.toml +1 -1
  4. {egglog-4.0.0 → egglog-5.0.0}/PKG-INFO +8 -8
  5. {egglog-4.0.0 → egglog-5.0.0}/README.md +1 -1
  6. {egglog-4.0.0 → egglog-5.0.0}/docs/changelog.md +10 -1
  7. egglog-5.0.0/docs/explanation/2023_12_02_congruence_closure-1.png +0 -0
  8. egglog-5.0.0/docs/explanation/2023_12_02_congruence_closure-2.png +0 -0
  9. egglog-5.0.0/docs/explanation/2023_12_02_congruence_closure.md +188 -0
  10. {egglog-4.0.0 → egglog-5.0.0}/docs/reference/egglog-translation.md +3 -3
  11. {egglog-4.0.0 → egglog-5.0.0}/docs/reference/python-integration.md +2 -1
  12. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/declarations.py +1 -4
  13. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/egraph.py +33 -15
  14. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/examples/bool.py +1 -1
  15. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/examples/lambda_.py +4 -4
  16. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/examples/resolution.py +1 -1
  17. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/exp/array_api.py +3 -3
  18. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/exp/array_api_program_gen.py +2 -1
  19. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/exp/program_gen.py +9 -9
  20. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/runtime.py +17 -14
  21. {egglog-4.0.0 → egglog-5.0.0}/python/tests/test_array_api.py +0 -1
  22. {egglog-4.0.0 → egglog-5.0.0}/python/tests/test_high_level.py +38 -1
  23. {egglog-4.0.0 → egglog-5.0.0}/test-data/unit/check-high-level.test +2 -2
  24. {egglog-4.0.0 → egglog-5.0.0}/.github/workflows/CI.yml +0 -0
  25. {egglog-4.0.0 → egglog-5.0.0}/.gitignore +0 -0
  26. {egglog-4.0.0 → egglog-5.0.0}/.readthedocs.yaml +0 -0
  27. {egglog-4.0.0 → egglog-5.0.0}/LICENSE +0 -0
  28. {egglog-4.0.0 → egglog-5.0.0}/conftest.py +0 -0
  29. {egglog-4.0.0 → egglog-5.0.0}/docs/.gitignore +0 -0
  30. {egglog-4.0.0 → egglog-5.0.0}/docs/_templates/comments.html +0 -0
  31. {egglog-4.0.0 → egglog-5.0.0}/docs/conf.py +0 -0
  32. {egglog-4.0.0 → egglog-5.0.0}/docs/environment.yml +0 -0
  33. {egglog-4.0.0 → egglog-5.0.0}/docs/explanation/.gitignore +0 -0
  34. {egglog-4.0.0 → egglog-5.0.0}/docs/explanation/2023_07_presentation.ipynb +0 -0
  35. {egglog-4.0.0 → egglog-5.0.0}/docs/explanation/2023_11_09_portland_state.ipynb +0 -0
  36. {egglog-4.0.0 → egglog-5.0.0}/docs/explanation/2023_11_17_pytensor.ipynb +0 -0
  37. {egglog-4.0.0 → egglog-5.0.0}/docs/explanation/2023_11_pydata_lightning_talk.ipynb +0 -0
  38. {egglog-4.0.0 → egglog-5.0.0}/docs/explanation/big_graph.svg +0 -0
  39. {egglog-4.0.0 → egglog-5.0.0}/docs/explanation/define_and_define.md +0 -0
  40. {egglog-4.0.0 → egglog-5.0.0}/docs/explanation/ecosystem-graph.png +0 -0
  41. {egglog-4.0.0 → egglog-5.0.0}/docs/explanation/egg.png +0 -0
  42. {egglog-4.0.0 → egglog-5.0.0}/docs/explanation/indexing_pushdown.ipynb +0 -0
  43. {egglog-4.0.0 → egglog-5.0.0}/docs/explanation/moa.png +0 -0
  44. {egglog-4.0.0 → egglog-5.0.0}/docs/explanation/optional_values.md +0 -0
  45. {egglog-4.0.0 → egglog-5.0.0}/docs/explanation/pldi_2023_presentation.ipynb +0 -0
  46. {egglog-4.0.0 → egglog-5.0.0}/docs/explanation.md +0 -0
  47. {egglog-4.0.0 → egglog-5.0.0}/docs/how-to-guides.md +0 -0
  48. {egglog-4.0.0 → egglog-5.0.0}/docs/index.md +0 -0
  49. {egglog-4.0.0 → egglog-5.0.0}/docs/reference/bindings.md +0 -0
  50. {egglog-4.0.0 → egglog-5.0.0}/docs/reference/contributing.md +0 -0
  51. {egglog-4.0.0 → egglog-5.0.0}/docs/reference/high-level.md +0 -0
  52. {egglog-4.0.0 → egglog-5.0.0}/docs/reference/usage.md +0 -0
  53. {egglog-4.0.0 → egglog-5.0.0}/docs/reference.md +0 -0
  54. {egglog-4.0.0 → egglog-5.0.0}/docs/tutorials/getting-started.ipynb +0 -0
  55. {egglog-4.0.0 → egglog-5.0.0}/docs/tutorials/screenshot-1.png +0 -0
  56. {egglog-4.0.0 → egglog-5.0.0}/docs/tutorials/screenshot-2.png +0 -0
  57. {egglog-4.0.0 → egglog-5.0.0}/docs/tutorials/sklearn.ipynb +0 -0
  58. {egglog-4.0.0 → egglog-5.0.0}/docs/tutorials.md +0 -0
  59. {egglog-4.0.0 → egglog-5.0.0}/pyproject.toml +0 -0
  60. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/__init__.py +0 -0
  61. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/bindings.pyi +0 -0
  62. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/builtins.py +0 -0
  63. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/config.py +0 -0
  64. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/examples/README.rst +0 -0
  65. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/examples/__init__.py +0 -0
  66. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/examples/eqsat_basic.py +0 -0
  67. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/examples/fib.py +0 -0
  68. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/examples/matrix.py +0 -0
  69. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/examples/ndarrays.py +0 -0
  70. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/examples/schedule_demo.py +0 -0
  71. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/exp/__init__.py +0 -0
  72. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/exp/array_api_jit.py +0 -0
  73. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/exp/array_api_numba.py +0 -0
  74. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/graphviz_widget.py +0 -0
  75. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/ipython_magic.py +0 -0
  76. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/py.typed +0 -0
  77. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/type_constraint_solver.py +0 -0
  78. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/widget.css +0 -0
  79. {egglog-4.0.0 → egglog-5.0.0}/python/egglog/widget.js +0 -0
  80. {egglog-4.0.0 → egglog-5.0.0}/python/tests/__snapshots__/test_array_api/test_sklearn_lda[optimized].py +0 -0
  81. {egglog-4.0.0 → egglog-5.0.0}/python/tests/__snapshots__/test_array_api/test_sklearn_lda[original].py +0 -0
  82. {egglog-4.0.0 → egglog-5.0.0}/python/tests/__snapshots__/test_array_api/test_to_source.py +0 -0
  83. {egglog-4.0.0 → egglog-5.0.0}/python/tests/__snapshots__/test_program_gen/test_to_string.py +0 -0
  84. {egglog-4.0.0 → egglog-5.0.0}/python/tests/conftest.py +0 -0
  85. {egglog-4.0.0 → egglog-5.0.0}/python/tests/test_bindings.py +0 -0
  86. {egglog-4.0.0 → egglog-5.0.0}/python/tests/test_convert.py +0 -0
  87. {egglog-4.0.0 → egglog-5.0.0}/python/tests/test_modules.py +0 -0
  88. {egglog-4.0.0 → egglog-5.0.0}/python/tests/test_program_gen.py +0 -0
  89. {egglog-4.0.0 → egglog-5.0.0}/python/tests/test_py_object_sort.py +0 -0
  90. {egglog-4.0.0 → egglog-5.0.0}/python/tests/test_runtime.py +0 -0
  91. {egglog-4.0.0 → egglog-5.0.0}/python/tests/test_type_constraint_solver.py +0 -0
  92. {egglog-4.0.0 → egglog-5.0.0}/python/tests/test_typing.py +0 -0
  93. {egglog-4.0.0 → egglog-5.0.0}/rust-toolchain +0 -0
  94. {egglog-4.0.0 → egglog-5.0.0}/src/conversions.rs +0 -0
  95. {egglog-4.0.0 → egglog-5.0.0}/src/egraph.rs +0 -0
  96. {egglog-4.0.0 → egglog-5.0.0}/src/error.rs +0 -0
  97. {egglog-4.0.0 → egglog-5.0.0}/src/lib.rs +0 -0
  98. {egglog-4.0.0 → egglog-5.0.0}/src/py_object_sort.rs +0 -0
  99. {egglog-4.0.0 → egglog-5.0.0}/src/serialize.rs +0 -0
  100. {egglog-4.0.0 → egglog-5.0.0}/src/utils.rs +0 -0
  101. {egglog-4.0.0 → egglog-5.0.0}/stubtest_allow +0 -0
@@ -5,7 +5,7 @@ ci:
5
5
  skip: [mypy, docs, stubtest]
6
6
  repos:
7
7
  - repo: https://github.com/astral-sh/ruff-pre-commit
8
- rev: v0.1.6
8
+ rev: v0.1.9
9
9
  hooks:
10
10
  - id: ruff
11
11
  args: [--fix, --exit-non-zero-on-fix]
@@ -312,7 +312,7 @@ dependencies = [
312
312
 
313
313
  [[package]]
314
314
  name = "egglog-python"
315
- version = "4.0.0"
315
+ version = "5.0.0"
316
316
  dependencies = [
317
317
  "egglog",
318
318
  "egraph-serialize",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "egglog-python"
3
- version = "4.0.0"
3
+ version = "5.0.0"
4
4
  edition = "2021"
5
5
 
6
6
  # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: egglog
3
- Version: 4.0.0
3
+ Version: 5.0.0
4
4
  Classifier: Environment :: MacOS X
5
5
  Classifier: Environment :: Win32 (MS Windows)
6
6
  Classifier: Intended Audience :: Developers
@@ -26,11 +26,6 @@ Requires-Dist: egglog[array]; extra == 'test'
26
26
  Requires-Dist: scikit-learn; extra == 'array'
27
27
  Requires-Dist: array_api_compat; extra == 'array'
28
28
  Requires-Dist: numba; (python_version<"3.12") and extra == 'array'
29
- Requires-Dist: pre-commit; extra == 'dev'
30
- Requires-Dist: ruff; extra == 'dev'
31
- Requires-Dist: mypy; extra == 'dev'
32
- Requires-Dist: anywidget[dev]; extra == 'dev'
33
- Requires-Dist: egglog[docs,test]; extra == 'dev'
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'
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'
46
46
  Provides-Extra: test
47
47
  Provides-Extra: array
48
- Provides-Extra: dev
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/en/latest/?badge=latest) for more information.
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/en/latest/?badge=latest) for more information.
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)!
@@ -2,7 +2,16 @@
2
2
 
3
3
  _This project uses semantic versioning_
4
4
 
5
- ## Unreleased
5
+ ## UNRELEASED
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
+
12
+ ## 4.0.1 (2023-11-27)
13
+
14
+ - Fix keyword args for `__init__` methods (#96)[https://github.com/metadsl/egglog-python/pull/96].
6
15
 
7
16
  ## 4.0.0 (2023-11-24)
8
17
 
@@ -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
+ ![](./2023_12_02_congruence_closure-1.png)
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
+ ![](./2023_12_02_congruence_closure-2.png)
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 supported by overloading the `__ne__` operator, which is done by default in all builtin and custom sorts:
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) != i64(2)
59
+ ne(i64(10)).to(i64(2))
60
60
  ```
61
61
 
62
- This is checked statically, based on the `__ne__` definition in `Expr`, so that only sorts that have the same sort can be compared.
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 and `__ne__` will shadow the builtin `!=` egglog operator.
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: EXPR, other_expr: EXPR) -> Unit: # type: ignore[override, empty-body]
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) != 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 != v2).then(
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 != y),
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 != y, eq(fv).to(freer(t)), fv.not_contains(y)),
162
- rule(eq(t).to(let_(x, t1, lam(y, t2))), x != y, eq(fv).to(freer(t1)), fv.contains(y)).then(
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
  )
@@ -78,7 +78,7 @@ egraph.register(
78
78
  union(~p0 | (~p1 | (p2 | F))).with_(T),
79
79
  )
80
80
  egraph.run(10)
81
- egraph.check(T != F)
81
+ egraph.check(ne(T).to(F))
82
82
  egraph.check(eq(p0).to(F))
83
83
  egraph.check(eq(p2).to(F))
84
84
  egraph
@@ -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 != j).then(union(r).with_(FALSE))
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 != v2),
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 != f64(math.nan))
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()) + ")", r.denom != i64(1)
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 != p,
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 != p1.expr, p.expr != p2.expr).then(set_(p.is_identifer).to(Bool(False)))
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 != p, eq(p2.parent).to(p)).then(p2.compile(i))
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 != p,
263
- p2.parent != p,
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 != p,
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 != p,
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 != p,
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 != p,
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)),
@@ -206,7 +206,7 @@ class RuntimeClass:
206
206
  __egg_decls__: ModuleDeclarations
207
207
  __egg_name__: str
208
208
 
209
- def __call__(self, *args: object) -> RuntimeExpr | None:
209
+ def __call__(self, *args: object, **kwargs: object) -> RuntimeExpr | None:
210
210
  """
211
211
  Create an instance of this kind by calling the __init__ classmethod
212
212
  """
@@ -222,7 +222,7 @@ class RuntimeClass:
222
222
  assert len(args) == 0
223
223
  return RuntimeExpr(self.__egg_decls__, TypedExprDecl(JustTypeRef(self.__egg_name__), LitDecl(None)))
224
224
 
225
- return RuntimeClassMethod(self.__egg_decls__, self.__egg_name__, "__init__")(*args)
225
+ return RuntimeClassMethod(self.__egg_decls__, self.__egg_name__, "__init__")(*args, **kwargs)
226
226
 
227
227
  def __dir__(self) -> list[str]:
228
228
  cls_decl = self.__egg_decls__.get_class_decl(self.__egg_name__)
@@ -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
- # Special case for __ne__ which does not have a normal function defintion since
439
- # it relies of type parameters
440
- if self.__egg_method_name__ == "__ne__":
441
- self.__egg_fn_decl__ = None
442
- else:
443
- try:
444
- self.__egg_fn_decl__ = self.__egg_self__.__egg_decls__.get_function_decl(self.__egg_callable_ref__)
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)
@@ -527,7 +525,12 @@ class RuntimeExpr:
527
525
  # Define each of the special methods, since we have already declared them for pretty printing
528
526
  for name in list(BINARY_METHODS) + list(UNARY_METHODS) + ["__getitem__", "__call__", "__setitem__", "__delitem__"]:
529
527
 
530
- def _special_method(self: RuntimeExpr, *args: object, __name: str = name) -> RuntimeExpr | None:
528
+ def _special_method(
529
+ self: RuntimeExpr,
530
+ *args: object,
531
+ __name: str = name,
532
+ **kwargs: object,
533
+ ) -> RuntimeExpr | None:
531
534
  # First, try to resolve as preserved method
532
535
  try:
533
536
  method = self.__egg_decls__.get_class_decl(self.__egg_typed_expr__.tp.name).preserved_methods[__name]
@@ -541,9 +544,9 @@ for name in list(BINARY_METHODS) + list(UNARY_METHODS) + ["__getitem__", "__call
541
544
  return call_method_min_conversion(self, args[0], __name)
542
545
  except ConvertError:
543
546
  return NotImplemented
544
- return RuntimeMethod(self, __name)(*args)
547
+ return RuntimeMethod(self, __name)(*args, **kwargs)
545
548
  else:
546
- return method(self, *args)
549
+ return method(self, *args, **kwargs)
547
550
 
548
551
  setattr(RuntimeExpr, name, _special_method)
549
552
 
@@ -1,4 +1,3 @@
1
- # import pytest
2
1
  from egglog.exp.array_api import *
3
2
  from egglog.exp.array_api_numba import array_api_numba_module
4
3
  from egglog.exp.array_api_program_gen import *
@@ -234,6 +234,19 @@ def test_keyword_args():
234
234
  assert expr_parts(foo(y=i64(2), x=i64(1))) == pos
235
235
 
236
236
 
237
+ def test_keyword_args_init():
238
+ egraph = EGraph()
239
+
240
+ @egraph.class_
241
+ class Foo(Expr):
242
+ def __init__(self, x: i64Like) -> None:
243
+ ...
244
+
245
+
246
+ assert expr_parts(Foo(1)) == expr_parts(Foo(x=1))
247
+
248
+
249
+
237
250
  def test_modules() -> None:
238
251
  m = Module()
239
252
 
@@ -346,8 +359,32 @@ def test_f64_negation() -> None:
346
359
 
347
360
  def test_not_equals():
348
361
  egraph = EGraph()
349
- egraph.check(i64(10) != i64(2))
362
+ egraph.check(ne(i64(10)).to(i64(2)))
363
+
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
+ ...
350
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)))
351
388
 
352
389
  class TestMutate:
353
390
  def test_setitem_defaults(self):
@@ -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 notEqAllowed]
5
+ [case notEqNotAllowed]
6
6
  from egglog import *
7
- _ = i64(0) != i64(0) # type: Unit
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