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
egglog/config.py ADDED
@@ -0,0 +1,8 @@
1
+ """
2
+ Global configuration for egglog.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ # Whether to display the type of each node in the graph when printing.
8
+ SHOW_TYPES = False
egglog/conversion.py ADDED
@@ -0,0 +1,290 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import defaultdict
4
+ from collections.abc import Callable
5
+ from contextlib import contextmanager
6
+ from contextvars import ContextVar
7
+ from dataclasses import dataclass
8
+ from typing import TYPE_CHECKING, Any, TypeVar, cast
9
+
10
+ from .declarations import *
11
+ from .pretty import *
12
+ from .runtime import *
13
+ from .thunk import *
14
+ from .type_constraint_solver import TypeConstraintError
15
+
16
+ if TYPE_CHECKING:
17
+ from collections.abc import Generator
18
+
19
+ from .egraph import BaseExpr
20
+ from .type_constraint_solver import TypeConstraintSolver
21
+
22
+ __all__ = ["ConvertError", "convert", "converter", "get_type_args"]
23
+ # Mapping from (source type, target type) to and function which takes in the runtimes values of the source and return the target
24
+ CONVERSIONS: dict[tuple[type | JustTypeRef, JustTypeRef], tuple[int, Callable[[Any], RuntimeExpr]]] = {}
25
+ # Global declerations to store all convertable types so we can query if they have certain methods or not
26
+ _CONVERSION_DECLS = Declarations.create()
27
+ # Defer a list of declerations to be added to the global declerations, so that we can not trigger them procesing
28
+ # until we need them
29
+ _TO_PROCESS_DECLS: list[DeclerationsLike] = []
30
+
31
+
32
+ def retrieve_conversion_decls() -> Declarations:
33
+ _CONVERSION_DECLS.update(*_TO_PROCESS_DECLS)
34
+ _TO_PROCESS_DECLS.clear()
35
+ return _CONVERSION_DECLS
36
+
37
+
38
+ T = TypeVar("T")
39
+ V = TypeVar("V", bound="BaseExpr")
40
+
41
+
42
+ class ConvertError(Exception):
43
+ pass
44
+
45
+
46
+ def converter(from_type: type[T], to_type: type[V], fn: Callable[[T], V], cost: int = 1) -> None:
47
+ """
48
+ Register a converter from some type to an egglog type.
49
+ """
50
+ to_type_name = process_tp(to_type)
51
+ if not isinstance(to_type_name, JustTypeRef):
52
+ raise TypeError(f"Expected return type to be a egglog type, got {to_type_name}")
53
+ _register_converter(process_tp(from_type), to_type_name, cast("Callable[[Any], RuntimeExpr]", fn), cost)
54
+
55
+
56
+ def _register_converter(a: type | JustTypeRef, b: JustTypeRef, a_b: Callable[[Any], RuntimeExpr], cost: int) -> None:
57
+ """
58
+ Registers a converter from some type to an egglog type, if not already registered.
59
+
60
+ Also adds transitive converters, i.e. if registering A->B and there is already B->C, then A->C will be registered.
61
+ Also, if registering A->B and there is already D->A, then D->B will be registered.
62
+ """
63
+ if a == b:
64
+ return
65
+ if (a, b) in CONVERSIONS and CONVERSIONS[(a, b)][0] <= cost:
66
+ return
67
+ CONVERSIONS[(a, b)] = (cost, a_b)
68
+ for (c, d), (other_cost, c_d) in list(CONVERSIONS.items()):
69
+ if _is_type_compatible(b, c):
70
+ _register_converter(
71
+ a, d, _ComposedConverter(a_b, c_d, c.args if isinstance(c, JustTypeRef) else ()), cost + other_cost
72
+ )
73
+ if _is_type_compatible(a, d):
74
+ _register_converter(
75
+ c, b, _ComposedConverter(c_d, a_b, a.args if isinstance(a, JustTypeRef) else ()), cost + other_cost
76
+ )
77
+
78
+
79
+ def _is_type_compatible(source: type | JustTypeRef, target: type | JustTypeRef) -> bool:
80
+ """
81
+ Types must be equal or also support unbound to bound typevar like B -> B[C]
82
+ """
83
+ if source == target:
84
+ return True
85
+ if isinstance(source, JustTypeRef) and isinstance(target, JustTypeRef) and source.args and not target.args:
86
+ return source.ident == target.ident
87
+ # TODO: Support case where B[T] where T is typevar is mapped to B[C]
88
+ return False
89
+
90
+
91
+ @dataclass
92
+ class _ComposedConverter:
93
+ """
94
+ A converter which is composed of multiple converters.
95
+
96
+ _ComposeConverter(a_b, b_c) is equivalent to lambda x: b_c(a_b(x))
97
+
98
+ We use the dataclass instead of the lambda to make it easier to debug.
99
+ """
100
+
101
+ a_b: Callable[[Any], RuntimeExpr]
102
+ b_c: Callable[[Any], RuntimeExpr]
103
+ b_args: tuple[JustTypeRef, ...]
104
+
105
+ def __call__(self, x: Any) -> RuntimeExpr:
106
+ # if we have A -> B and B[C] -> D then we should use (C,) as the type args
107
+ # when converting from A -> B
108
+ if self.b_args:
109
+ with with_type_args(self.b_args, retrieve_conversion_decls):
110
+ first_res = self.a_b(x)
111
+ else:
112
+ first_res = self.a_b(x)
113
+ return self.b_c(first_res)
114
+
115
+ def __str__(self) -> str:
116
+ return f"{self.b_c} ∘ {self.a_b}"
117
+
118
+
119
+ def convert(source: object, target: type[V]) -> V:
120
+ """
121
+ Convert a source object to a target type.
122
+ """
123
+ assert isinstance(target, RuntimeClass)
124
+ return cast("V", resolve_literal(target.__egg_tp__, source, target.__egg_decls_thunk__))
125
+
126
+
127
+ def convert_to_same_type(source: object, target: RuntimeExpr) -> RuntimeExpr:
128
+ """
129
+ Convert a source object to the same type as the target.
130
+ """
131
+ tp = target.__egg_typed_expr__.tp
132
+ return resolve_literal(tp.to_var(), source, Thunk.value(target.__egg_decls__))
133
+
134
+
135
+ def process_tp(tp: type | RuntimeClass) -> JustTypeRef | type:
136
+ """
137
+ Process a type before converting it, to add it to the global declerations and resolve to a ref.
138
+ """
139
+ if isinstance(tp, RuntimeClass):
140
+ _TO_PROCESS_DECLS.append(tp)
141
+ egg_tp = tp.__egg_tp__
142
+ return egg_tp.to_just()
143
+ return tp
144
+
145
+
146
+ def min_binary_conversion(
147
+ method_name: str, lhs: type | JustTypeRef, rhs: type | JustTypeRef
148
+ ) -> tuple[Callable[[Any], RuntimeExpr], Callable[[Any], RuntimeExpr]] | None:
149
+ """
150
+ Given a binary method and two starting types for the LHS and RHS, return a pair of callable which will convert
151
+ the LHS and RHS to appropriate types which support this method. If no such conversion is possible, return None.
152
+
153
+ It should return the types which minimize the total conversion cost. If one of the types is a Python type, then
154
+ both of them can be converted. However, if both are egglog types, then only one of them can be converted.
155
+ """
156
+ decls = retrieve_conversion_decls()
157
+ # tuple of (cost, convert lhs, convert rhs)
158
+ best_method: tuple[int, Callable[[Any], RuntimeExpr], Callable[[Any], RuntimeExpr]] | None = None
159
+
160
+ possible_lhs = _all_conversions_from(lhs) if isinstance(lhs, type) else [(0, lhs, identity)]
161
+ possible_rhs = _all_conversions_from(rhs) if isinstance(rhs, type) else [(0, rhs, identity)]
162
+ for lhs_cost, lhs_converted_type, lhs_convert in possible_lhs:
163
+ # Start by checking if we have a LHS that matches exactly and a RHS which can be converted
164
+ if (desired_other_type := decls.check_binary_method_with_self_type(method_name, lhs_converted_type)) and (
165
+ converter := _lookup_conversion(rhs, desired_other_type)
166
+ ):
167
+ cost = lhs_cost + converter[0]
168
+ if best_method is None or best_method[0] > cost:
169
+ best_method = (cost, lhs_convert, converter[1])
170
+
171
+ for rhs_cost, rhs_converted_type, rhs_convert in possible_rhs:
172
+ # Next see if it's possible to convert the LHS and keep the RHS as is
173
+ for desired_self_type in decls.check_binary_method_with_other_type(method_name, rhs_converted_type):
174
+ if converter := _lookup_conversion(lhs, desired_self_type):
175
+ cost = rhs_cost + converter[0]
176
+ if best_method is None or best_method[0] > cost:
177
+ best_method = (cost, converter[1], rhs_convert)
178
+ if best_method is None:
179
+ return None
180
+ return best_method[1], best_method[2]
181
+
182
+
183
+ def _all_conversions_from(tp: JustTypeRef | type) -> list[tuple[int, JustTypeRef, Callable[[Any], RuntimeExpr]]]:
184
+ """
185
+ Get all conversions from a type to other types.
186
+
187
+ Returns a list of tuples of (cost, target type, conversion function).
188
+ """
189
+ return [
190
+ (cost, target, fn)
191
+ for (source, target), (cost, fn) in CONVERSIONS.items()
192
+ if (issubclass(tp, source) if isinstance(tp, type) and isinstance(source, type) else source == tp)
193
+ ]
194
+
195
+
196
+ def identity(x: Any) -> Any:
197
+ return x
198
+
199
+
200
+ TYPE_ARGS = ContextVar[tuple[RuntimeClass, ...]]("TYPE_ARGS")
201
+
202
+
203
+ def get_type_args() -> tuple[type, ...]:
204
+ """
205
+ Get the type args for the type being converted.
206
+ """
207
+ return cast("tuple[type, ...]", TYPE_ARGS.get())
208
+
209
+
210
+ @contextmanager
211
+ def with_type_args(args: tuple[JustTypeRef, ...], decls: Callable[[], Declarations]) -> Generator[None, None, None]:
212
+ token = TYPE_ARGS.set(tuple(RuntimeClass(decls, a.to_var()) for a in args))
213
+ try:
214
+ yield
215
+ finally:
216
+ TYPE_ARGS.reset(token)
217
+
218
+
219
+ def resolve_literal(
220
+ tp: TypeOrVarRef,
221
+ arg: object,
222
+ decls: Callable[[], Declarations] = retrieve_conversion_decls,
223
+ tcs: TypeConstraintSolver | None = None,
224
+ cls_ident: Ident | None = None,
225
+ ) -> RuntimeExpr:
226
+ """
227
+ Try to convert an object to a type, raising a ConvertError if it is not possible.
228
+
229
+ If the type has vars in it, they will be tried to be resolved into concrete vars based on the type constraint solver.
230
+
231
+ If it cannot be resolved, we assume that the value passed in will resolve it.
232
+ """
233
+ arg_type = resolve_type(arg)
234
+
235
+ # If we have any type variables, dont bother trying to resolve the literal, just return the arg
236
+ try:
237
+ tp_just = tp.to_just()
238
+ except TypeVarError:
239
+ # If this is a generic arg but passed in a non runtime expression, try to resolve the generic
240
+ # args first based on the existing type constraint solver
241
+ if tcs:
242
+ try:
243
+ tp_just = tcs.substitute_typevars(tp, cls_ident)
244
+ # If we can't resolve the type var yet, then just assume it is the right value
245
+ except TypeConstraintError:
246
+ assert isinstance(arg, RuntimeExpr), f"Expected a runtime expression, got {arg}"
247
+ tp_just = arg.__egg_typed_expr__.tp
248
+ else:
249
+ # If this is a var, it has to be a runtime expession
250
+ assert isinstance(arg, RuntimeExpr), f"Expected a runtime expression, got {arg}"
251
+ return arg
252
+ if tcs:
253
+ tcs.infer_typevars(tp, tp_just, cls_ident)
254
+ if arg_type == tp_just:
255
+ # If the type is an egg type, it has to be a runtime expr
256
+ assert isinstance(arg, RuntimeExpr)
257
+ return arg
258
+ if (conversion := _lookup_conversion(arg_type, tp_just)) is not None:
259
+ with with_type_args(tp_just.args, decls):
260
+ return conversion[1](arg)
261
+ raise ConvertError(f"Cannot convert {arg} of type {arg_type} to {tp_just}")
262
+
263
+
264
+ def _lookup_conversion(lhs: type | JustTypeRef, rhs: JustTypeRef) -> tuple[int, Callable[[Any], RuntimeExpr]] | None:
265
+ """
266
+ Looks up a conversion function for the given types.
267
+
268
+ Also looks up all parent types of the lhs if it is a Python type and looks up more general not paramtrized types for rhs.
269
+ """
270
+ for lhs_type in lhs.__mro__ if isinstance(lhs, type) else [lhs]:
271
+ if (key := (lhs_type, rhs)) in CONVERSIONS:
272
+ return CONVERSIONS[key]
273
+ if rhs.args and (key := (lhs_type, JustTypeRef(rhs.ident))) in CONVERSIONS:
274
+ return CONVERSIONS[key]
275
+ return None
276
+
277
+
278
+ def _debug_print_converers():
279
+ """
280
+ Prints a mapping of all source types to target types that have a conversion function.
281
+ """
282
+ source_to_targets = defaultdict(list)
283
+ for source, target in CONVERSIONS:
284
+ source_to_targets[source].append(target)
285
+
286
+
287
+ def resolve_type(x: object) -> JustTypeRef | type:
288
+ if isinstance(x, RuntimeExpr):
289
+ return x.__egg_typed_expr__.tp
290
+ return type(x)