Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make rulesets delayed (and refactor) #130

Merged
merged 50 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
a841079
Tmp state before reverting back to delayed decls
saulshanabrook Mar 10, 2024
c91d5d2
tmp
saulshanabrook Mar 17, 2024
9bc912e
tmp again
saulshanabrook Mar 20, 2024
3ef2383
tmp
saulshanabrook Mar 21, 2024
0b0342f
Almost there?
saulshanabrook Mar 23, 2024
7779c46
Defer schedule decls until needed
saulshanabrook Mar 23, 2024
8853944
Update tests
saulshanabrook Mar 25, 2024
7dbca60
Fix infinite recursion
saulshanabrook Mar 25, 2024
42e1df3
Fix duplicate property name
saulshanabrook Mar 25, 2024
e3bc2ae
Fix union order
saulshanabrook Mar 25, 2024
2905131
Move runtime objects to use delayed args so that constants are deferred
saulshanabrook Mar 25, 2024
2c7b8ac
Fix frame handling for registration
saulshanabrook Mar 25, 2024
766a048
Fix init classmethod handling
saulshanabrook Mar 25, 2024
8703588
Fix passing self as arg
saulshanabrook Mar 25, 2024
f73d7d2
Fix emitting callable ref
saulshanabrook Mar 25, 2024
5af1a26
Fix class variable lookup
saulshanabrook Mar 25, 2024
01b4d99
Remove unused code
saulshanabrook Mar 25, 2024
158c4a6
Don't verify bc makes it eager
saulshanabrook Mar 25, 2024
3a205d0
Add on merge
saulshanabrook Mar 25, 2024
ffff155
always declare parametrized types
saulshanabrook Mar 25, 2024
76f1b24
fix type error
saulshanabrook Mar 25, 2024
103c872
Fix copying state
saulshanabrook Mar 25, 2024
0d72efb
Fix copying egg fns
saulshanabrook Mar 25, 2024
141cd2c
Start adding pretty tests
saulshanabrook Mar 25, 2024
40df71c
Add some more tests for prettifying
saulshanabrook Mar 26, 2024
7b458ca
Fix mutations
saulshanabrook Mar 26, 2024
dab6d6b
fix pretty setitem/del tests
saulshanabrook Mar 26, 2024
62f54a3
Fix printing method
saulshanabrook Mar 26, 2024
91d2129
Finish pretty tests
saulshanabrook Mar 26, 2024
f841673
Remove comment
saulshanabrook Mar 27, 2024
d53378d
Fix eval decls
saulshanabrook Mar 27, 2024
ae4dbd6
Emit generic type definitions
saulshanabrook Mar 27, 2024
39e31e1
Fix extracting generic class methods
saulshanabrook Mar 27, 2024
e1a686d
Fix frame for ruleset registration
saulshanabrook Mar 27, 2024
3c6d9e0
Register rulesets before running
saulshanabrook Mar 27, 2024
b52db65
Make empty ruleset the default
saulshanabrook Mar 27, 2024
79a18b3
Have has/eq of lits based on type of lits as well as values
saulshanabrook Mar 27, 2024
f593a46
Include unit in != decls
saulshanabrook Mar 27, 2024
eabe57b
Fix runtime test
saulshanabrook Mar 27, 2024
c941440
Clarify siu example
saulshanabrook Mar 27, 2024
c4975be
Merge main into refactor
saulshanabrook Mar 27, 2024
ca17596
Make compatible with Python < 3.11
saulshanabrook Mar 27, 2024
1ff3445
Try making float extraction stable by specifying costs
saulshanabrook Mar 27, 2024
1da2786
Make tuple compatible with lower python versions
saulshanabrook Mar 27, 2024
582f211
Fix f str quote
saulshanabrook Mar 27, 2024
b94432d
Python 3.10 compat
saulshanabrook Mar 27, 2024
5e4b384
Change benchmarking to use same e-graph for all ops so more accuratel…
saulshanabrook Mar 27, 2024
7acc23e
Run benchmarks each in own isolated egraph
saulshanabrook Mar 27, 2024
0cc4baf
Move performance critical parts to own lines to improve perf
saulshanabrook Mar 28, 2024
2b25475
Lookup things eagerly for improved performance
saulshanabrook Mar 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ _This project uses semantic versioning_

## UNRELEASED

- Defers adding rules in functions until they are used, so that you can use types that are not present yet.
- Removes ability to set custom default ruleset for egraph. Either just use the empty default ruleset or explicitly set it for every run
- Automatically mark Python builtin operators as preserved if they must return a real Python value
- Properly pretty print all items (rewrites, actions, exprs, etc) so that expressions are de-duplicated and state is handled correctly.

## 6.1.0 (2024-03-06)

- Upgrade [egglog](https://github.com/egraphs-good/egglog/compare/4cc011f6b48029dd72104a38a2ca0c7657846e0b...0113af1d6476b75d4319591cc3d675f96a71cdc5)
Expand Down
10 changes: 10 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,13 @@ filterwarnings = [
"ignore::numba.core.errors.NumbaPerformanceWarning",
"ignore::pytest_benchmark.logger.PytestBenchmarkWarning",
]

[tool.coverage.report]
exclude_also = [
"def __repr__",
"raise NotImplementedError",
"if TYPE_CHECKING:",
"class .*\\bProtocol\\):",
"@(abc\\.)?abstractmethod",
"assert_never\\(",
]
2 changes: 1 addition & 1 deletion python/egglog/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from . import config, ipython_magic # noqa: F401
from .builtins import * # noqa: UP029
from .conversion import convert, converter # noqa: F401
from .egraph import *
from .runtime import convert, converter # noqa: F401

del ipython_magic
2 changes: 2 additions & 0 deletions python/egglog/bindings.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ class EGraph:
fact_directory: str | Path | None = None,
seminaive: bool = True,
terms_encoding: bool = False,
record: bool = False,
) -> None: ...
def commands(self) -> str | None: ...
def parse_program(self, __input: str, /) -> list[_Command]: ...
def run_program(self, *commands: _Command) -> list[str]: ...
def extract_report(self) -> _ExtractReport | None: ...
Expand Down
2 changes: 1 addition & 1 deletion python/egglog/builtins.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

from typing import TYPE_CHECKING, Generic, Protocol, TypeAlias, TypeVar, Union

from .conversion import converter
from .egraph import Expr, Unit, function, method
from .runtime import converter

if TYPE_CHECKING:
from collections.abc import Callable
Expand Down
172 changes: 172 additions & 0 deletions python/egglog/conversion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING, TypeVar, cast

from .declarations import *
from .pretty import *
from .runtime import *
from .thunk import *

if TYPE_CHECKING:
from collections.abc import Callable

from .declarations import HasDeclerations
from .egraph import Expr

__all__ = ["convert", "converter", "resolve_literal", "convert_to_same_type"]
# Mapping from (source type, target type) to and function which takes in the runtimes values of the source and return the target
CONVERSIONS: dict[tuple[type | JustTypeRef, JustTypeRef], tuple[int, Callable]] = {}
# Global declerations to store all convertable types so we can query if they have certain methods or not
# Defer it as a thunk so we can register conversions without triggering type signature loading
CONVERSIONS_DECLS: Callable[[], Declarations] = Thunk.value(Declarations())

T = TypeVar("T")
V = TypeVar("V", bound="Expr")


class ConvertError(Exception):
pass


def converter(from_type: type[T], to_type: type[V], fn: Callable[[T], V], cost: int = 1) -> None:
"""
Register a converter from some type to an egglog type.
"""
to_type_name = process_tp(to_type)
if not isinstance(to_type_name, JustTypeRef):
raise TypeError(f"Expected return type to be a egglog type, got {to_type_name}")
_register_converter(process_tp(from_type), to_type_name, fn, cost)


def _register_converter(a: type | JustTypeRef, b: JustTypeRef, a_b: Callable, cost: int) -> None:
"""
Registers a converter from some type to an egglog type, if not already registered.

Also adds transitive converters, i.e. if registering A->B and there is already B->C, then A->C will be registered.
Also, if registering A->B and there is already D->A, then D->B will be registered.
"""
if a == b:
return
if (a, b) in CONVERSIONS and CONVERSIONS[(a, b)][0] <= cost:
return
CONVERSIONS[(a, b)] = (cost, a_b)
for (c, d), (other_cost, c_d) in list(CONVERSIONS.items()):
if b == c:
_register_converter(a, d, _ComposedConverter(a_b, c_d), cost + other_cost)
if a == d:
_register_converter(c, b, _ComposedConverter(c_d, a_b), cost + other_cost)


@dataclass
class _ComposedConverter:
"""
A converter which is composed of multiple converters.

_ComposeConverter(a_b, b_c) is equivalent to lambda x: b_c(a_b(x))

We use the dataclass instead of the lambda to make it easier to debug.
"""

a_b: Callable
b_c: Callable

def __call__(self, x: object) -> object:
return self.b_c(self.a_b(x))

def __str__(self) -> str:
return f"{self.b_c} ∘ {self.a_b}"


def convert(source: object, target: type[V]) -> V:
"""
Convert a source object to a target type.
"""
assert isinstance(target, RuntimeClass)
return cast(V, resolve_literal(target.__egg_tp__, source))


def convert_to_same_type(source: object, target: RuntimeExpr) -> RuntimeExpr:
"""
Convert a source object to the same type as the target.
"""
tp = target.__egg_typed_expr__.tp
return resolve_literal(tp.to_var(), source)


def process_tp(tp: type | RuntimeClass) -> JustTypeRef | type:
"""
Process a type before converting it, to add it to the global declerations and resolve to a ref.
"""
global CONVERSIONS_DECLS
if isinstance(tp, RuntimeClass):
CONVERSIONS_DECLS = Thunk.fn(_combine_decls, CONVERSIONS_DECLS, tp)
return tp.__egg_tp__.to_just()
return tp


def _combine_decls(d: Callable[[], Declarations], x: HasDeclerations) -> Declarations:
return Declarations.create(d(), x)


def min_convertable_tp(a: object, b: object, name: str) -> JustTypeRef:
"""
Returns the minimum convertable type between a and b, that has a method `name`, raising a ConvertError if no such type exists.
"""
decls = CONVERSIONS_DECLS()
a_tp = _get_tp(a)
b_tp = _get_tp(b)
a_converts_to = {
to: c for ((from_, to), (c, _)) in CONVERSIONS.items() if from_ == a_tp and decls.has_method(to.name, name)
}
b_converts_to = {
to: c for ((from_, to), (c, _)) in CONVERSIONS.items() if from_ == b_tp and decls.has_method(to.name, name)
}
if isinstance(a_tp, JustTypeRef):
a_converts_to[a_tp] = 0
if isinstance(b_tp, JustTypeRef):
b_converts_to[b_tp] = 0
common = set(a_converts_to) & set(b_converts_to)
if not common:
raise ConvertError(f"Cannot convert {a_tp} and {b_tp} to a common type")
return min(common, key=lambda tp: a_converts_to[tp] + b_converts_to[tp])


def identity(x: object) -> object:
return x


def resolve_literal(tp: TypeOrVarRef, arg: object) -> RuntimeExpr:
arg_type = _get_tp(arg)

# If we have any type variables, dont bother trying to resolve the literal, just return the arg
try:
tp_just = tp.to_just()
except NotImplementedError:
# If this is a var, it has to be a runtime exprssions
assert isinstance(arg, RuntimeExpr)
return arg
if arg_type == tp_just:
# If the type is an egg type, it has to be a runtime expr
assert isinstance(arg, RuntimeExpr)
return arg
# Try all parent types as well, if we are converting from a Python type
for arg_type_instance in arg_type.__mro__ if isinstance(arg_type, type) else [arg_type]:
try:
fn = CONVERSIONS[(cast(JustTypeRef | type, arg_type_instance), tp_just)][1]
except KeyError:
continue
break
else:
raise ConvertError(f"Cannot convert {arg_type} to {tp_just}")
return fn(arg)


def _get_tp(x: object) -> JustTypeRef | type:
if isinstance(x, RuntimeExpr):
return x.__egg_typed_expr__.tp
tp = type(x)
# If this value has a custom metaclass, let's use that as our index instead of the type
if type(tp) != type:
return type(tp)
return tp
Loading
Loading