egglog 11.2.0__cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

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 (46) hide show
  1. egglog/__init__.py +13 -0
  2. egglog/bindings.cpython-314-x86_64-linux-gnu.so +0 -0
  3. egglog/bindings.pyi +734 -0
  4. egglog/builtins.py +1133 -0
  5. egglog/config.py +8 -0
  6. egglog/conversion.py +286 -0
  7. egglog/declarations.py +912 -0
  8. egglog/deconstruct.py +173 -0
  9. egglog/egraph.py +1875 -0
  10. egglog/egraph_state.py +680 -0
  11. egglog/examples/README.rst +5 -0
  12. egglog/examples/__init__.py +3 -0
  13. egglog/examples/bignum.py +32 -0
  14. egglog/examples/bool.py +38 -0
  15. egglog/examples/eqsat_basic.py +44 -0
  16. egglog/examples/fib.py +28 -0
  17. egglog/examples/higher_order_functions.py +42 -0
  18. egglog/examples/jointree.py +67 -0
  19. egglog/examples/lambda_.py +287 -0
  20. egglog/examples/matrix.py +175 -0
  21. egglog/examples/multiset.py +60 -0
  22. egglog/examples/ndarrays.py +144 -0
  23. egglog/examples/resolution.py +84 -0
  24. egglog/examples/schedule_demo.py +34 -0
  25. egglog/exp/__init__.py +3 -0
  26. egglog/exp/array_api.py +2019 -0
  27. egglog/exp/array_api_jit.py +51 -0
  28. egglog/exp/array_api_loopnest.py +74 -0
  29. egglog/exp/array_api_numba.py +69 -0
  30. egglog/exp/array_api_program_gen.py +510 -0
  31. egglog/exp/program_gen.py +425 -0
  32. egglog/exp/siu_examples.py +32 -0
  33. egglog/ipython_magic.py +41 -0
  34. egglog/pretty.py +509 -0
  35. egglog/py.typed +0 -0
  36. egglog/runtime.py +712 -0
  37. egglog/thunk.py +97 -0
  38. egglog/type_constraint_solver.py +113 -0
  39. egglog/version_compat.py +87 -0
  40. egglog/visualizer.css +1 -0
  41. egglog/visualizer.js +35777 -0
  42. egglog/visualizer_widget.py +39 -0
  43. egglog-11.2.0.dist-info/METADATA +74 -0
  44. egglog-11.2.0.dist-info/RECORD +46 -0
  45. egglog-11.2.0.dist-info/WHEEL +4 -0
  46. egglog-11.2.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,425 @@
1
+ # mypy: disable-error-code="empty-body"
2
+ """
3
+ Builds up imperative string expressions from a functional expression.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import TypeAlias
9
+
10
+ from egglog import *
11
+
12
+
13
+ class Program(Expr):
14
+ """
15
+ Semanticallly represents an expression with a number of ordered statements that it depends on to run.
16
+
17
+ The expression and statements are all represented as strings.
18
+ """
19
+
20
+ def __init__(self, expr: StringLike, is_identifier: BoolLike = Bool(False)) -> None:
21
+ """
22
+ Create a program based on a string expression.
23
+ """
24
+
25
+ def __add__(self, other: ProgramLike) -> Program:
26
+ """
27
+ Concats the strings of the two expressions and also the statements.
28
+ """
29
+
30
+ @method(unextractable=True)
31
+ def statement(self, statement: ProgramLike) -> Program:
32
+ """
33
+ Uses the expression of the statement and adds that as a statement to the program.
34
+ """
35
+
36
+ def assign(self) -> Program:
37
+ """
38
+ Returns a new program with the expression assigned to a gensym.
39
+ """
40
+
41
+ def function_two(self, arg1: ProgramLike, arg2: ProgramLike, name: StringLike = String("__fn")) -> Program:
42
+ """
43
+ Returns a new program defining a function with two arguments.
44
+ """
45
+
46
+ def function_three(
47
+ self, arg1: ProgramLike, arg2: ProgramLike, arg3: ProgramLike, name: StringLike = String("__fn")
48
+ ) -> Program:
49
+ """
50
+ Returns a new program defining a function with three arguments.
51
+ """
52
+
53
+ def expr_to_statement(self) -> Program:
54
+ """
55
+ Returns a new program with the expression as a statement and the new expression empty.
56
+ """
57
+
58
+ @property
59
+ def expr(self) -> String:
60
+ """
61
+ Returns the expression of the program, if it's been compiled
62
+ """
63
+
64
+ @property
65
+ def statements(self) -> String:
66
+ """
67
+ Returns the statements of the program, if it's been compiled
68
+ """
69
+
70
+ @property
71
+ def next_sym(self) -> i64:
72
+ """
73
+ Returns the next gensym to use. This is set after calling `compile(i)` on a program.
74
+ """
75
+
76
+ # TODO: Replace w/ def next_sym(self) -> i64: ... ?
77
+ def compile(self, next_sym: i64 = i64(0)) -> Unit:
78
+ """
79
+ Triggers compilation of the program.
80
+ """
81
+
82
+ @method(merge=lambda old, _new: old) # type: ignore[prop-decorator]
83
+ @property
84
+ def parent(self) -> Program:
85
+ """
86
+ Returns the parent of the program, if it's been compiled into the parent.
87
+
88
+ Only keeps the original parent, not any additional ones, so that each set of statements is only added once.
89
+ """
90
+
91
+ @property
92
+ def is_identifer(self) -> Bool:
93
+ """
94
+ Returns whether the expression is an identifier. Used so that we don't re-assign any identifiers.
95
+ """
96
+
97
+
98
+ ProgramLike: TypeAlias = Program | StringLike
99
+
100
+
101
+ converter(String, Program, Program)
102
+
103
+
104
+ class EvalProgram(Expr):
105
+ def __init__(self, program: Program, globals: object) -> None:
106
+ """
107
+ Evaluates the program and saves as the py_object
108
+ """
109
+
110
+ # Only allow it to be set once, b/c hash of functions not stable
111
+ @method(merge=lambda old, _new: old) # type: ignore[prop-decorator]
112
+ @property
113
+ def as_py_object(self) -> PyObject:
114
+ """
115
+ Returns the python object of the program, if it's been evaluated.
116
+ """
117
+
118
+
119
+ @ruleset
120
+ def eval_program_rulseset(ep: EvalProgram, p: Program, expr: String, statements: String, g: PyObject):
121
+ # When we evaluate a program, we first want to compile to a string
122
+ yield rule(EvalProgram(p, g)).then(p.compile())
123
+ # Then we want to evaluate the statements/expr
124
+ yield rule(
125
+ eq(ep).to(EvalProgram(p, g)),
126
+ eq(p.statements).to(statements),
127
+ eq(p.expr).to(expr),
128
+ ).then(
129
+ set_(ep.as_py_object).to(
130
+ py_eval(
131
+ "l['___res']",
132
+ PyObject.dict(PyObject.from_string("l"), py_exec(join(statements, "\n", "___res = ", expr), g)),
133
+ )
134
+ ),
135
+ )
136
+
137
+
138
+ @ruleset
139
+ def program_gen_ruleset(
140
+ s: String,
141
+ s1: String,
142
+ s2: String,
143
+ s3: String,
144
+ s4: String,
145
+ s5: String,
146
+ s6: String,
147
+ p: Program,
148
+ p1: Program,
149
+ p2: Program,
150
+ p3: Program,
151
+ p4: Program,
152
+ i: i64,
153
+ i2: i64,
154
+ b: Bool,
155
+ ):
156
+ ##
157
+ # Expression
158
+ ##
159
+
160
+ # Compiling a string just gives that string
161
+ yield rule(eq(p).to(Program(s, b)), p.compile(i)).then(
162
+ set_(p.expr).to(s),
163
+ set_(p.statements).to(String("")),
164
+ set_(p.next_sym).to(i),
165
+ set_(p.is_identifer).to(b),
166
+ )
167
+
168
+ ##
169
+ # Statement
170
+ ##
171
+ # Compiling a statement means that we should use the expression of the statement as a statement and use the expression of the first
172
+ yield rewrite(p1.statement(p2)).to(p1 + p2.expr_to_statement())
173
+
174
+ ##
175
+ # Expr to statement
176
+ ##
177
+ stmt = eq(p).to(p1.expr_to_statement())
178
+ # 1. Set parent and is_identifier to false, since its empty
179
+ yield rule(stmt, p.compile(i)).then(set_(p1.parent).to(p), set_(p.is_identifer).to(Bool(False)))
180
+ # 2. Compile p1 if parent set
181
+ yield rule(stmt, p.compile(i), eq(p1.parent).to(p)).then(p1.compile(i))
182
+ # 3.a. If parent not set, set statements to expr
183
+ yield rule(
184
+ stmt,
185
+ p.compile(i),
186
+ ne(p1.parent).to(p),
187
+ eq(s1).to(p1.expr),
188
+ ).then(
189
+ set_(p.statements).to(join(s1, "\n")),
190
+ set_(p.next_sym).to(i),
191
+ set_(p.expr).to(String("")),
192
+ )
193
+ # 3.b. If parent set, set statements to expr + statements
194
+ yield rule(
195
+ stmt,
196
+ eq(p1.parent).to(p),
197
+ eq(s1).to(p1.expr),
198
+ eq(s2).to(p1.statements),
199
+ eq(i).to(p1.next_sym),
200
+ ).then(
201
+ set_(p.statements).to(join(s2, s1, "\n")),
202
+ set_(p.next_sym).to(i),
203
+ set_(p.expr).to(String("")),
204
+ )
205
+
206
+ ##
207
+ # Addition
208
+ ##
209
+
210
+ # Compiling an addition is the same as compiling one, then the other, then setting the expression as the addition
211
+ # of the two
212
+ program_add = eq(p).to(p1 + p2)
213
+
214
+ # If the resulting expression is either of the inputs, then its an identifer if those are
215
+ # Otherwise, if its not equal to either input, its not an identifier
216
+ yield rule(program_add, eq(p.expr).to(p1.expr), eq(b).to(p1.is_identifer)).then(set_(p.is_identifer).to(b))
217
+ yield rule(program_add, eq(p.expr).to(p2.expr), eq(b).to(p2.is_identifer)).then(set_(p.is_identifer).to(b))
218
+ yield rule(program_add, ne(p.expr).to(p1.expr), ne(p.expr).to(p2.expr)).then(set_(p.is_identifer).to(Bool(False)))
219
+
220
+ # Set parent of p1
221
+ yield rule(program_add, p.compile(i)).then(
222
+ set_(p1.parent).to(p),
223
+ )
224
+
225
+ # Compile p1, if p1 parent equal
226
+ yield rule(program_add, p.compile(i), eq(p1.parent).to(p)).then(p1.compile(i))
227
+
228
+ # Set parent of p2, once p1 compiled
229
+ yield rule(program_add, p.compile(i), p1.next_sym).then(set_(p2.parent).to(p))
230
+
231
+ # Compile p2, if p1 parent not equal, but p2 parent equal
232
+ yield rule(program_add, p.compile(i), ne(p1.parent).to(p), eq(p2.parent).to(p)).then(p2.compile(i))
233
+
234
+ # Compile p2, if p1 parent eqal
235
+ yield rule(program_add, p.compile(i2), eq(p1.parent).to(p), eq(i).to(p1.next_sym), eq(p2.parent).to(p)).then(
236
+ p2.compile(i)
237
+ )
238
+
239
+ # Set p expr to join of p1 and p2
240
+ yield rule(
241
+ program_add,
242
+ eq(s1).to(p1.expr),
243
+ eq(s2).to(p2.expr),
244
+ ).then(
245
+ set_(p.expr).to(join(s1, s2)),
246
+ )
247
+ # Set p statements to join and next sym to p2 if both parents set
248
+ yield rule(
249
+ program_add,
250
+ eq(p1.parent).to(p),
251
+ eq(p2.parent).to(p),
252
+ eq(s1).to(p1.statements),
253
+ eq(s2).to(p2.statements),
254
+ eq(i).to(p2.next_sym),
255
+ ).then(
256
+ set_(p.statements).to(join(s1, s2)),
257
+ set_(p.next_sym).to(i),
258
+ )
259
+ # Set p statements to empty and next sym to i if neither parents set
260
+ yield rule(
261
+ program_add,
262
+ p.compile(i),
263
+ ne(p1.parent).to(p),
264
+ ne(p2.parent).to(p),
265
+ ).then(
266
+ set_(p.statements).to(String("")),
267
+ set_(p.next_sym).to(i),
268
+ )
269
+ # Set p statements to p1 and next sym to p1 if p1 parent set and p2 parent not set
270
+ yield rule(
271
+ program_add,
272
+ eq(p1.parent).to(p),
273
+ ne(p2.parent).to(p),
274
+ eq(s1).to(p1.statements),
275
+ eq(i).to(p1.next_sym),
276
+ ).then(
277
+ set_(p.statements).to(s1),
278
+ set_(p.next_sym).to(i),
279
+ )
280
+ # Set p statements to p2 and next sym to p2 if p2 parent set and p1 parent not set
281
+ yield rule(
282
+ program_add,
283
+ eq(p2.parent).to(p),
284
+ ne(p1.parent).to(p),
285
+ eq(s2).to(p2.statements),
286
+ eq(i).to(p2.next_sym),
287
+ ).then(
288
+ set_(p.statements).to(s2),
289
+ set_(p.next_sym).to(i),
290
+ )
291
+
292
+ ##
293
+ # Assign
294
+ ##
295
+
296
+ # Compiling an assign is the same as compiling the expression, adding an assign statement, then setting the
297
+ # expression as the gensym, and setting is_identifier to true
298
+ program_assign = eq(p).to(p1.assign())
299
+ # Set parent
300
+ yield rule(program_assign, p.compile(i)).then(set_(p1.parent).to(p), set_(p.is_identifer).to(Bool(True)))
301
+ # If parent set, compile the expression
302
+ yield rule(program_assign, p.compile(i), eq(p1.parent).to(p)).then(p1.compile(i))
303
+
304
+ # 1. If p1 is not an identifier, then we must create a new one
305
+
306
+ # 1. a. If p1 parent is p, then use statements of p, next sym of p
307
+ symbol = join(String("_"), i.to_string())
308
+ yield rule(
309
+ program_assign,
310
+ eq(p1.parent).to(p),
311
+ eq(s1).to(p1.statements),
312
+ eq(i).to(p1.next_sym),
313
+ eq(s2).to(p1.expr),
314
+ eq(p1.is_identifer).to(Bool(False)),
315
+ ).then(
316
+ set_(p.statements).to(join(s1, symbol, " = ", s2, "\n")),
317
+ set_(p.expr).to(symbol),
318
+ set_(p.next_sym).to(i + 1),
319
+ )
320
+ # 1. b. If p1 parent is not p, then just use assign as statement, next sym of i
321
+ yield rule(
322
+ program_assign,
323
+ ne(p1.parent).to(p),
324
+ p.compile(i),
325
+ eq(s2).to(p1.expr),
326
+ eq(p1.is_identifer).to(Bool(False)),
327
+ ).then(
328
+ set_(p.statements).to(join(symbol, " = ", s2, "\n")),
329
+ set_(p.expr).to(symbol),
330
+ set_(p.next_sym).to(i + 1),
331
+ )
332
+
333
+ # 2. If p1 is an identifier, then program assign is a no-op
334
+
335
+ # 1. a. If p1 parent is p, then use statements of p, next sym of p
336
+ yield rule(
337
+ program_assign,
338
+ eq(p1.parent).to(p),
339
+ eq(s1).to(p1.statements),
340
+ eq(i).to(p1.next_sym),
341
+ eq(s2).to(p1.expr),
342
+ eq(p1.is_identifer).to(Bool(True)),
343
+ ).then(
344
+ set_(p.statements).to(s1),
345
+ set_(p.expr).to(s2),
346
+ set_(p.next_sym).to(i),
347
+ )
348
+ # 1. b. If p1 parent is not p, then just use assign as statement, next sym of i
349
+ yield rule(
350
+ program_assign,
351
+ ne(p1.parent).to(p),
352
+ p.compile(i),
353
+ eq(s2).to(p1.expr),
354
+ eq(p1.is_identifer).to(Bool(True)),
355
+ ).then(
356
+ set_(p.statements).to(String("")),
357
+ set_(p.expr).to(s2),
358
+ set_(p.next_sym).to(i),
359
+ )
360
+
361
+ ##
362
+ # Function two
363
+ ##
364
+
365
+ # When compiling a function, the two args, p2 and p3, should get compiled when we compile p1, and should just be vars.
366
+ fn_two = eq(p).to(p1.function_two(p2, p3, s1))
367
+ # 1. Set parents of both args to p and compile them
368
+ # Assumes that this if the first thing to compile, so no need to check, and assumes that compiling args doesn't result in any
369
+ # change in the next sym
370
+ yield rule(fn_two, p.compile(i)).then(
371
+ set_(p2.parent).to(p),
372
+ set_(p3.parent).to(p),
373
+ set_(p1.parent).to(p),
374
+ p2.compile(i),
375
+ p3.compile(i),
376
+ p1.compile(i),
377
+ set_(p.is_identifer).to(Bool(True)),
378
+ )
379
+ # 2. Set statements to function body and the next sym to i
380
+ yield rule(
381
+ fn_two,
382
+ p.compile(i),
383
+ eq(s2).to(p1.expr),
384
+ eq(s3).to(p1.statements),
385
+ eq(s4).to(p2.expr),
386
+ eq(s5).to(p3.expr),
387
+ ).then(
388
+ set_(p.statements).to(
389
+ join("def ", s1, "(", s4, ", ", s5, "):\n ", s3.replace("\n", "\n "), "return ", s2, "\n")
390
+ ),
391
+ set_(p.next_sym).to(i),
392
+ set_(p.expr).to(s1),
393
+ )
394
+
395
+ ##
396
+ # Function three
397
+ ##
398
+
399
+ fn_three = eq(p).to(p1.function_three(p2, p3, p4, s1))
400
+ yield rule(fn_three, p.compile(i)).then(
401
+ set_(p2.parent).to(p),
402
+ set_(p3.parent).to(p),
403
+ set_(p1.parent).to(p),
404
+ set_(p4.parent).to(p),
405
+ p2.compile(i),
406
+ p3.compile(i),
407
+ p1.compile(i),
408
+ p4.compile(i),
409
+ set_(p.is_identifer).to(Bool(True)),
410
+ )
411
+ yield rule(
412
+ fn_three,
413
+ p.compile(i),
414
+ eq(s2).to(p1.expr),
415
+ eq(s3).to(p1.statements),
416
+ eq(s4).to(p2.expr),
417
+ eq(s5).to(p3.expr),
418
+ eq(s6).to(p4.expr),
419
+ ).then(
420
+ set_(p.statements).to(
421
+ join("def ", s1, "(", s4, ", ", s5, ", ", s6, "):\n ", s3.replace("\n", "\n "), "return ", s2, "\n")
422
+ ),
423
+ set_(p.next_sym).to(i),
424
+ set_(p.expr).to(s1),
425
+ )
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ # https://github.com/sklam/pyasir/blob/c363ff4f8f91177700ad4108dd5042b9b97d8289/pyasir/tests/test_fib.py
5
+
6
+ # In progress - should be able to re-create this
7
+ # @df.func
8
+ # def fib_ir(n: pyasir.Int64) -> pyasir.Int64:
9
+ # @df.switch(n <= 1)
10
+ # def swt(n):
11
+ # @df.case(1)
12
+ # def case0(n):
13
+ # return 1
14
+
15
+ # @df.case(0)
16
+ # def case1(n):
17
+ # return fib_ir(n - 1) + fib_ir(n - 2)
18
+
19
+ # yield case0
20
+ # yield case1
21
+
22
+ # r = swt(n)
23
+ # return r
24
+
25
+
26
+ # With something like this:
27
+ # @egglog.function
28
+ # def fib(n: Int) -> Int:
29
+ # return (n <= Int(1)).if_int(
30
+ # Int(1),
31
+ # fib(n - Int(1)) + fib(n - Int(2)),
32
+ # )
@@ -0,0 +1,41 @@
1
+ from .bindings import EGraph
2
+
3
+ EGRAPH_VAR = "_MAGIC_EGRAPH"
4
+
5
+ try:
6
+ get_ipython() # type: ignore[name-defined]
7
+ IN_IPYTHON = True
8
+ except NameError:
9
+ IN_IPYTHON = False
10
+
11
+ if IN_IPYTHON:
12
+ import graphviz
13
+ from IPython.core.magic import needs_local_scope, register_cell_magic
14
+
15
+ @needs_local_scope
16
+ @register_cell_magic
17
+ def egglog(line, cell, local_ns):
18
+ """
19
+ Run an egglog program.
20
+
21
+ Usage:
22
+
23
+ %%egglog [output] [continue] [graph]
24
+ (egglog program)
25
+
26
+ If `output` is specified, the output of the program will be printed.
27
+ If `continue` is specified, the program will be run in the same EGraph as the previous cell.
28
+ If `graph` is specified, the EGraph will be displayed as a graph.
29
+ """
30
+ if EGRAPH_VAR in local_ns and "continue" in line:
31
+ e = local_ns[EGRAPH_VAR]
32
+ else:
33
+ e = EGraph()
34
+ local_ns[EGRAPH_VAR] = e
35
+ cmds = e.parse_program(cell)
36
+ res = e.run_program(*cmds)
37
+ if "output" in line:
38
+ print("\n".join(res))
39
+ if "graph" in line:
40
+ return graphviz.Source(e.to_graphviz_string())
41
+ return None