egglog 6.1.0__cp311-none-win_amd64.whl → 7.1.0__cp311-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: ...
@@ -492,6 +494,12 @@ class Relation:
492
494
  class PrintOverallStatistics:
493
495
  def __init__(self) -> None: ...
494
496
 
497
+ @final
498
+ class UnstableCombinedRuleset:
499
+ name: str
500
+ rulesets: list[str]
501
+ def __init__(self, name: str, rulesets: list[str]) -> None: ...
502
+
495
503
  _Command: TypeAlias = (
496
504
  SetOption
497
505
  | Datatype
@@ -519,6 +527,7 @@ _Command: TypeAlias = (
519
527
  | CheckProof
520
528
  | Relation
521
529
  | PrintOverallStatistics
530
+ | UnstableCombinedRuleset
522
531
  )
523
532
 
524
533
  def termdag_term_to_expr(termdag: TermDag, term: _Term) -> _Expr: ...
egglog/builtins.py CHANGED
@@ -5,10 +5,14 @@ Builtin sorts and function to egg.
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
- from typing import TYPE_CHECKING, Generic, Protocol, TypeAlias, TypeVar, Union
8
+ from functools import partial
9
+ from typing import TYPE_CHECKING, Generic, Protocol, TypeAlias, TypeVar, Union, overload
9
10
 
11
+ from typing_extensions import TypeVarTuple, Unpack
12
+
13
+ from .conversion import converter
10
14
  from .egraph import Expr, Unit, function, method
11
- from .runtime import converter
15
+ from .runtime import RuntimeFunction
12
16
 
13
17
  if TYPE_CHECKING:
14
18
  from collections.abc import Callable
@@ -31,6 +35,7 @@ __all__ = [
31
35
  "py_eval",
32
36
  "py_exec",
33
37
  "py_eval_fn",
38
+ "UnstableFn",
34
39
  ]
35
40
 
36
41
 
@@ -461,3 +466,38 @@ def py_exec(code: StringLike, globals: object = PyObject.dict(), locals: object
461
466
  """
462
467
  Copies the locals, execs the Python code, and returns the locals with any updates.
463
468
  """
469
+
470
+
471
+ TS = TypeVarTuple("TS")
472
+
473
+ T1 = TypeVar("T1")
474
+ T2 = TypeVar("T2")
475
+ T3 = TypeVar("T3")
476
+
477
+
478
+ class UnstableFn(Expr, Generic[T, Unpack[TS]], builtin=True):
479
+ @overload
480
+ def __init__(self, f: Callable[[Unpack[TS]], T]) -> None: ...
481
+
482
+ @overload
483
+ def __init__(self, f: Callable[[T1, Unpack[TS]], T], _a: T1, /) -> None: ...
484
+
485
+ @overload
486
+ def __init__(self, f: Callable[[T1, T2, Unpack[TS]], T], _a: T1, _b: T2, /) -> None: ...
487
+
488
+ # Removing due to bug in MyPy
489
+ # https://github.com/python/mypy/issues/17212
490
+ # @overload
491
+ # def __init__(self, f: Callable[[T1, T2, T3, Unpack[TS]], T], _a: T1, _b: T2, _c: T3, /) -> None: ...
492
+
493
+ # etc, for partial application
494
+
495
+ @method(egg_fn="unstable-fn")
496
+ def __init__(self, f, *partial) -> None: ...
497
+
498
+ @method(egg_fn="unstable-app")
499
+ def __call__(self, *args: Unpack[TS]) -> T: ...
500
+
501
+
502
+ converter(RuntimeFunction, UnstableFn, UnstableFn)
503
+ converter(partial, UnstableFn, lambda p: UnstableFn(p.func, *p.args))
egglog/conversion.py ADDED
@@ -0,0 +1,177 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING, NewType, 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
+ TypeName = NewType("TypeName", str)
20
+ CONVERSIONS: dict[tuple[type | TypeName, TypeName], tuple[int, Callable]] = {}
21
+ # Global declerations to store all convertable types so we can query if they have certain methods or not
22
+ # Defer it as a thunk so we can register conversions without triggering type signature loading
23
+ CONVERSIONS_DECLS: Callable[[], Declarations] = Thunk.value(Declarations())
24
+
25
+ T = TypeVar("T")
26
+ V = TypeVar("V", bound="Expr")
27
+
28
+
29
+ class ConvertError(Exception):
30
+ pass
31
+
32
+
33
+ def converter(from_type: type[T], to_type: type[V], fn: Callable[[T], V], cost: int = 1) -> None:
34
+ """
35
+ Register a converter from some type to an egglog type.
36
+ """
37
+ to_type_name = process_tp(to_type)
38
+ if not isinstance(to_type_name, str):
39
+ raise TypeError(f"Expected return type to be a egglog type, got {to_type_name}")
40
+ _register_converter(process_tp(from_type), to_type_name, fn, cost)
41
+
42
+
43
+ def _register_converter(a: type | TypeName, b: TypeName, a_b: Callable, cost: int) -> None:
44
+ """
45
+ Registers a converter from some type to an egglog type, if not already registered.
46
+
47
+ Also adds transitive converters, i.e. if registering A->B and there is already B->C, then A->C will be registered.
48
+ Also, if registering A->B and there is already D->A, then D->B will be registered.
49
+ """
50
+ if a == b:
51
+ return
52
+ if (a, b) in CONVERSIONS and CONVERSIONS[(a, b)][0] <= cost:
53
+ return
54
+ CONVERSIONS[(a, b)] = (cost, a_b)
55
+ for (c, d), (other_cost, c_d) in list(CONVERSIONS.items()):
56
+ if b == c:
57
+ _register_converter(a, d, _ComposedConverter(a_b, c_d), cost + other_cost)
58
+ if a == d:
59
+ _register_converter(c, b, _ComposedConverter(c_d, a_b), cost + other_cost)
60
+
61
+
62
+ @dataclass
63
+ class _ComposedConverter:
64
+ """
65
+ A converter which is composed of multiple converters.
66
+
67
+ _ComposeConverter(a_b, b_c) is equivalent to lambda x: b_c(a_b(x))
68
+
69
+ We use the dataclass instead of the lambda to make it easier to debug.
70
+ """
71
+
72
+ a_b: Callable
73
+ b_c: Callable
74
+
75
+ def __call__(self, x: object) -> object:
76
+ return self.b_c(self.a_b(x))
77
+
78
+ def __str__(self) -> str:
79
+ return f"{self.b_c} ∘ {self.a_b}"
80
+
81
+
82
+ def convert(source: object, target: type[V]) -> V:
83
+ """
84
+ Convert a source object to a target type.
85
+ """
86
+ assert isinstance(target, RuntimeClass)
87
+ return cast(V, resolve_literal(target.__egg_tp__, source))
88
+
89
+
90
+ def convert_to_same_type(source: object, target: RuntimeExpr) -> RuntimeExpr:
91
+ """
92
+ Convert a source object to the same type as the target.
93
+ """
94
+ tp = target.__egg_typed_expr__.tp
95
+ return resolve_literal(tp.to_var(), source)
96
+
97
+
98
+ def process_tp(tp: type | RuntimeClass) -> TypeName | type:
99
+ """
100
+ Process a type before converting it, to add it to the global declerations and resolve to a ref.
101
+ """
102
+ global CONVERSIONS_DECLS
103
+ if isinstance(tp, RuntimeClass):
104
+ CONVERSIONS_DECLS = Thunk.fn(_combine_decls, CONVERSIONS_DECLS, tp)
105
+ egg_tp = tp.__egg_tp__
106
+ if egg_tp.args:
107
+ raise TypeError(f"Cannot register a converter for a generic type, got {tp}")
108
+ return TypeName(egg_tp.name)
109
+ return tp
110
+
111
+
112
+ def _combine_decls(d: Callable[[], Declarations], x: HasDeclerations) -> Declarations:
113
+ return Declarations.create(d(), x)
114
+
115
+
116
+ def min_convertable_tp(a: object, b: object, name: str) -> TypeName:
117
+ """
118
+ Returns the minimum convertable type between a and b, that has a method `name`, raising a ConvertError if no such type exists.
119
+ """
120
+ decls = CONVERSIONS_DECLS()
121
+ a_tp = _get_tp(a)
122
+ b_tp = _get_tp(b)
123
+ a_converts_to = {
124
+ to: c for ((from_, to), (c, _)) in CONVERSIONS.items() if from_ == a_tp and decls.has_method(to, name)
125
+ }
126
+ b_converts_to = {
127
+ to: c for ((from_, to), (c, _)) in CONVERSIONS.items() if from_ == b_tp and decls.has_method(to, name)
128
+ }
129
+ if isinstance(a_tp, str):
130
+ a_converts_to[a_tp] = 0
131
+ if isinstance(b_tp, str):
132
+ b_converts_to[b_tp] = 0
133
+ common = set(a_converts_to) & set(b_converts_to)
134
+ if not common:
135
+ raise ConvertError(f"Cannot convert {a_tp} and {b_tp} to a common type")
136
+ return min(common, key=lambda tp: a_converts_to[tp] + b_converts_to[tp])
137
+
138
+
139
+ def identity(x: object) -> object:
140
+ return x
141
+
142
+
143
+ def resolve_literal(tp: TypeOrVarRef, arg: object) -> RuntimeExpr:
144
+ arg_type = _get_tp(arg)
145
+
146
+ # If we have any type variables, dont bother trying to resolve the literal, just return the arg
147
+ try:
148
+ tp_just = tp.to_just()
149
+ except NotImplementedError:
150
+ # If this is a var, it has to be a runtime expession
151
+ assert isinstance(arg, RuntimeExpr)
152
+ return arg
153
+ tp_name = TypeName(tp_just.name)
154
+ if arg_type == tp_name:
155
+ # If the type is an egg type, it has to be a runtime expr
156
+ assert isinstance(arg, RuntimeExpr)
157
+ return arg
158
+ # Try all parent types as well, if we are converting from a Python type
159
+ for arg_type_instance in arg_type.__mro__ if isinstance(arg_type, type) else [arg_type]:
160
+ try:
161
+ fn = CONVERSIONS[(cast(TypeName | type, arg_type_instance), tp_name)][1]
162
+ except KeyError:
163
+ continue
164
+ break
165
+ else:
166
+ raise ConvertError(f"Cannot convert {arg_type} to {tp_name}")
167
+ return fn(arg)
168
+
169
+
170
+ def _get_tp(x: object) -> TypeName | type:
171
+ if isinstance(x, RuntimeExpr):
172
+ return TypeName(x.__egg_typed_expr__.tp.name)
173
+ tp = type(x)
174
+ # If this value has a custom metaclass, let's use that as our index instead of the type
175
+ if type(tp) != type:
176
+ return type(tp)
177
+ return tp