egglog 6.1.0__cp310-none-win_amd64.whl → 7.0.0__cp310-none-win_amd64.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.

egglog/__init__.py CHANGED
@@ -4,7 +4,7 @@ Package for creating e-graphs in Python.
4
4
 
5
5
  from . import config, ipython_magic # noqa: F401
6
6
  from .builtins import * # noqa: UP029
7
+ from .conversion import convert, converter # noqa: F401
7
8
  from .egraph import *
8
- from .runtime import convert, converter # noqa: F401
9
9
 
10
10
  del ipython_magic
Binary file
egglog/bindings.pyi CHANGED
@@ -29,7 +29,9 @@ class EGraph:
29
29
  fact_directory: str | Path | None = None,
30
30
  seminaive: bool = True,
31
31
  terms_encoding: bool = False,
32
+ record: bool = False,
32
33
  ) -> None: ...
34
+ def commands(self) -> str | None: ...
33
35
  def parse_program(self, __input: str, /) -> list[_Command]: ...
34
36
  def run_program(self, *commands: _Command) -> list[str]: ...
35
37
  def extract_report(self) -> _ExtractReport | None: ...
egglog/builtins.py CHANGED
@@ -7,8 +7,8 @@ from __future__ import annotations
7
7
 
8
8
  from typing import TYPE_CHECKING, Generic, Protocol, TypeAlias, TypeVar, Union
9
9
 
10
+ from .conversion import converter
10
11
  from .egraph import Expr, Unit, function, method
11
- from .runtime import converter
12
12
 
13
13
  if TYPE_CHECKING:
14
14
  from collections.abc import Callable
egglog/conversion.py ADDED
@@ -0,0 +1,172 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING, TypeVar, cast
5
+
6
+ from .declarations import *
7
+ from .pretty import *
8
+ from .runtime import *
9
+ from .thunk import *
10
+
11
+ if TYPE_CHECKING:
12
+ from collections.abc import Callable
13
+
14
+ from .declarations import HasDeclerations
15
+ from .egraph import Expr
16
+
17
+ __all__ = ["convert", "converter", "resolve_literal", "convert_to_same_type"]
18
+ # Mapping from (source type, target type) to and function which takes in the runtimes values of the source and return the target
19
+ CONVERSIONS: dict[tuple[type | JustTypeRef, JustTypeRef], tuple[int, Callable]] = {}
20
+ # Global declerations to store all convertable types so we can query if they have certain methods or not
21
+ # Defer it as a thunk so we can register conversions without triggering type signature loading
22
+ CONVERSIONS_DECLS: Callable[[], Declarations] = Thunk.value(Declarations())
23
+
24
+ T = TypeVar("T")
25
+ V = TypeVar("V", bound="Expr")
26
+
27
+
28
+ class ConvertError(Exception):
29
+ pass
30
+
31
+
32
+ def converter(from_type: type[T], to_type: type[V], fn: Callable[[T], V], cost: int = 1) -> None:
33
+ """
34
+ Register a converter from some type to an egglog type.
35
+ """
36
+ to_type_name = process_tp(to_type)
37
+ if not isinstance(to_type_name, JustTypeRef):
38
+ raise TypeError(f"Expected return type to be a egglog type, got {to_type_name}")
39
+ _register_converter(process_tp(from_type), to_type_name, fn, cost)
40
+
41
+
42
+ def _register_converter(a: type | JustTypeRef, b: JustTypeRef, a_b: Callable, cost: int) -> None:
43
+ """
44
+ Registers a converter from some type to an egglog type, if not already registered.
45
+
46
+ Also adds transitive converters, i.e. if registering A->B and there is already B->C, then A->C will be registered.
47
+ Also, if registering A->B and there is already D->A, then D->B will be registered.
48
+ """
49
+ if a == b:
50
+ return
51
+ if (a, b) in CONVERSIONS and CONVERSIONS[(a, b)][0] <= cost:
52
+ return
53
+ CONVERSIONS[(a, b)] = (cost, a_b)
54
+ for (c, d), (other_cost, c_d) in list(CONVERSIONS.items()):
55
+ if b == c:
56
+ _register_converter(a, d, _ComposedConverter(a_b, c_d), cost + other_cost)
57
+ if a == d:
58
+ _register_converter(c, b, _ComposedConverter(c_d, a_b), cost + other_cost)
59
+
60
+
61
+ @dataclass
62
+ class _ComposedConverter:
63
+ """
64
+ A converter which is composed of multiple converters.
65
+
66
+ _ComposeConverter(a_b, b_c) is equivalent to lambda x: b_c(a_b(x))
67
+
68
+ We use the dataclass instead of the lambda to make it easier to debug.
69
+ """
70
+
71
+ a_b: Callable
72
+ b_c: Callable
73
+
74
+ def __call__(self, x: object) -> object:
75
+ return self.b_c(self.a_b(x))
76
+
77
+ def __str__(self) -> str:
78
+ return f"{self.b_c} ∘ {self.a_b}"
79
+
80
+
81
+ def convert(source: object, target: type[V]) -> V:
82
+ """
83
+ Convert a source object to a target type.
84
+ """
85
+ assert isinstance(target, RuntimeClass)
86
+ return cast(V, resolve_literal(target.__egg_tp__, source))
87
+
88
+
89
+ def convert_to_same_type(source: object, target: RuntimeExpr) -> RuntimeExpr:
90
+ """
91
+ Convert a source object to the same type as the target.
92
+ """
93
+ tp = target.__egg_typed_expr__.tp
94
+ return resolve_literal(tp.to_var(), source)
95
+
96
+
97
+ def process_tp(tp: type | RuntimeClass) -> JustTypeRef | type:
98
+ """
99
+ Process a type before converting it, to add it to the global declerations and resolve to a ref.
100
+ """
101
+ global CONVERSIONS_DECLS
102
+ if isinstance(tp, RuntimeClass):
103
+ CONVERSIONS_DECLS = Thunk.fn(_combine_decls, CONVERSIONS_DECLS, tp)
104
+ return tp.__egg_tp__.to_just()
105
+ return tp
106
+
107
+
108
+ def _combine_decls(d: Callable[[], Declarations], x: HasDeclerations) -> Declarations:
109
+ return Declarations.create(d(), x)
110
+
111
+
112
+ def min_convertable_tp(a: object, b: object, name: str) -> JustTypeRef:
113
+ """
114
+ Returns the minimum convertable type between a and b, that has a method `name`, raising a ConvertError if no such type exists.
115
+ """
116
+ decls = CONVERSIONS_DECLS()
117
+ a_tp = _get_tp(a)
118
+ b_tp = _get_tp(b)
119
+ a_converts_to = {
120
+ to: c for ((from_, to), (c, _)) in CONVERSIONS.items() if from_ == a_tp and decls.has_method(to.name, name)
121
+ }
122
+ b_converts_to = {
123
+ to: c for ((from_, to), (c, _)) in CONVERSIONS.items() if from_ == b_tp and decls.has_method(to.name, name)
124
+ }
125
+ if isinstance(a_tp, JustTypeRef):
126
+ a_converts_to[a_tp] = 0
127
+ if isinstance(b_tp, JustTypeRef):
128
+ b_converts_to[b_tp] = 0
129
+ common = set(a_converts_to) & set(b_converts_to)
130
+ if not common:
131
+ raise ConvertError(f"Cannot convert {a_tp} and {b_tp} to a common type")
132
+ return min(common, key=lambda tp: a_converts_to[tp] + b_converts_to[tp])
133
+
134
+
135
+ def identity(x: object) -> object:
136
+ return x
137
+
138
+
139
+ def resolve_literal(tp: TypeOrVarRef, arg: object) -> RuntimeExpr:
140
+ arg_type = _get_tp(arg)
141
+
142
+ # If we have any type variables, dont bother trying to resolve the literal, just return the arg
143
+ try:
144
+ tp_just = tp.to_just()
145
+ except NotImplementedError:
146
+ # If this is a var, it has to be a runtime exprssions
147
+ assert isinstance(arg, RuntimeExpr)
148
+ return arg
149
+ if arg_type == tp_just:
150
+ # If the type is an egg type, it has to be a runtime expr
151
+ assert isinstance(arg, RuntimeExpr)
152
+ return arg
153
+ # Try all parent types as well, if we are converting from a Python type
154
+ for arg_type_instance in arg_type.__mro__ if isinstance(arg_type, type) else [arg_type]:
155
+ try:
156
+ fn = CONVERSIONS[(cast(JustTypeRef | type, arg_type_instance), tp_just)][1]
157
+ except KeyError:
158
+ continue
159
+ break
160
+ else:
161
+ raise ConvertError(f"Cannot convert {arg_type} to {tp_just}")
162
+ return fn(arg)
163
+
164
+
165
+ def _get_tp(x: object) -> JustTypeRef | type:
166
+ if isinstance(x, RuntimeExpr):
167
+ return x.__egg_typed_expr__.tp
168
+ tp = type(x)
169
+ # If this value has a custom metaclass, let's use that as our index instead of the type
170
+ if type(tp) != type:
171
+ return type(tp)
172
+ return tp