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.
- egglog/__init__.py +13 -0
- egglog/bindings.cpython-313t-powerpc64-linux-gnu.so +0 -0
- egglog/bindings.pyi +887 -0
- egglog/builtins.py +1144 -0
- egglog/config.py +8 -0
- egglog/conversion.py +290 -0
- egglog/declarations.py +964 -0
- egglog/deconstruct.py +176 -0
- egglog/egraph.py +2247 -0
- egglog/egraph_state.py +978 -0
- egglog/examples/README.rst +5 -0
- egglog/examples/__init__.py +3 -0
- egglog/examples/bignum.py +32 -0
- egglog/examples/bool.py +38 -0
- egglog/examples/eqsat_basic.py +44 -0
- egglog/examples/fib.py +28 -0
- egglog/examples/higher_order_functions.py +42 -0
- egglog/examples/jointree.py +64 -0
- egglog/examples/lambda_.py +287 -0
- egglog/examples/matrix.py +175 -0
- egglog/examples/multiset.py +60 -0
- egglog/examples/ndarrays.py +144 -0
- egglog/examples/resolution.py +84 -0
- egglog/examples/schedule_demo.py +34 -0
- egglog/exp/MoA.ipynb +617 -0
- egglog/exp/__init__.py +3 -0
- egglog/exp/any_expr.py +947 -0
- egglog/exp/any_expr_example.ipynb +408 -0
- egglog/exp/array_api.py +2019 -0
- egglog/exp/array_api_jit.py +51 -0
- egglog/exp/array_api_loopnest.py +74 -0
- egglog/exp/array_api_numba.py +69 -0
- egglog/exp/array_api_program_gen.py +510 -0
- egglog/exp/program_gen.py +427 -0
- egglog/exp/siu_examples.py +32 -0
- egglog/ipython_magic.py +41 -0
- egglog/pretty.py +566 -0
- egglog/py.typed +0 -0
- egglog/runtime.py +888 -0
- egglog/thunk.py +97 -0
- egglog/type_constraint_solver.py +111 -0
- egglog/visualizer.css +1 -0
- egglog/visualizer.js +35798 -0
- egglog/visualizer_widget.py +39 -0
- egglog-12.0.0.dist-info/METADATA +93 -0
- egglog-12.0.0.dist-info/RECORD +48 -0
- egglog-12.0.0.dist-info/WHEEL +5 -0
- egglog-12.0.0.dist-info/licenses/LICENSE +21 -0
egglog/config.py
ADDED
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)
|