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