-
Notifications
You must be signed in to change notification settings - Fork 0
Add apyds-egg support package with E-Graph implementation #146
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
Changes from all commits
ace3939
878068c
b04b828
871c362
74e91a0
cd34f73
6ff621b
9acd306
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,155 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| from __future__ import annotations | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| __all__ = ["EClassId", "UnionFind", "ENode", "EGraph"] | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| from dataclasses import dataclass | ||||||||||||||||||||||||||||||||||||||||||||||
| from typing import NewType, Callable | ||||||||||||||||||||||||||||||||||||||||||||||
| from collections import defaultdict | ||||||||||||||||||||||||||||||||||||||||||||||
| import apyds | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| EClassId = NewType("EClassId", int) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| class UnionFind: | ||||||||||||||||||||||||||||||||||||||||||||||
| """Union-find data structure for managing disjoint sets.""" | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| def __init__(self) -> None: | ||||||||||||||||||||||||||||||||||||||||||||||
| self.parent: dict[EClassId, EClassId] = {} | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| def find(self, x: EClassId) -> EClassId: | ||||||||||||||||||||||||||||||||||||||||||||||
| """Find the canonical representative of x's set with path compression.""" | ||||||||||||||||||||||||||||||||||||||||||||||
| if x not in self.parent: | ||||||||||||||||||||||||||||||||||||||||||||||
| self.parent[x] = x | ||||||||||||||||||||||||||||||||||||||||||||||
| if self.parent[x] != x: | ||||||||||||||||||||||||||||||||||||||||||||||
| self.parent[x] = self.find(self.parent[x]) | ||||||||||||||||||||||||||||||||||||||||||||||
| return self.parent[x] | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| def union(self, a: EClassId, b: EClassId) -> EClassId: | ||||||||||||||||||||||||||||||||||||||||||||||
| """Union two sets and return the canonical representative.""" | ||||||||||||||||||||||||||||||||||||||||||||||
| ra, rb = self.find(a), self.find(b) | ||||||||||||||||||||||||||||||||||||||||||||||
| if ra != rb: | ||||||||||||||||||||||||||||||||||||||||||||||
| self.parent[rb] = ra | ||||||||||||||||||||||||||||||||||||||||||||||
| return ra | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| @dataclass(frozen=True) | ||||||||||||||||||||||||||||||||||||||||||||||
| class ENode: | ||||||||||||||||||||||||||||||||||||||||||||||
| """Node in the E-Graph with an operator and children.""" | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| op: str | ||||||||||||||||||||||||||||||||||||||||||||||
| children: tuple[EClassId, ...] | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| def canonicalize(self, find: Callable[[EClassId], EClassId]) -> ENode: | ||||||||||||||||||||||||||||||||||||||||||||||
| """Canonicalize children using the find function.""" | ||||||||||||||||||||||||||||||||||||||||||||||
| return ENode(self.op, tuple(find(c) for c in self.children)) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| class EGraph: | ||||||||||||||||||||||||||||||||||||||||||||||
| """E-Graph for representing equivalence classes of terms.""" | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| def __init__(self) -> None: | ||||||||||||||||||||||||||||||||||||||||||||||
| self.uf = UnionFind() | ||||||||||||||||||||||||||||||||||||||||||||||
| self.next_id = 0 | ||||||||||||||||||||||||||||||||||||||||||||||
| self.classes: dict[EClassId, set[ENode]] = {} | ||||||||||||||||||||||||||||||||||||||||||||||
| self.parents: dict[EClassId, set[tuple[ENode, EClassId]]] = defaultdict(set) | ||||||||||||||||||||||||||||||||||||||||||||||
| self.hashcons: dict[ENode, EClassId] = {} | ||||||||||||||||||||||||||||||||||||||||||||||
| self.worklist: set[EClassId] = set() | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| def _fresh_id(self) -> EClassId: | ||||||||||||||||||||||||||||||||||||||||||||||
| """Generate a fresh E-class ID.""" | ||||||||||||||||||||||||||||||||||||||||||||||
| eid = EClassId(self.next_id) | ||||||||||||||||||||||||||||||||||||||||||||||
| self.next_id += 1 | ||||||||||||||||||||||||||||||||||||||||||||||
| return eid | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| def find(self, eclass: EClassId) -> EClassId: | ||||||||||||||||||||||||||||||||||||||||||||||
| """Find the canonical representative of an E-class.""" | ||||||||||||||||||||||||||||||||||||||||||||||
| return self.uf.find(eclass) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| def add(self, term: apyds.Term) -> EClassId: | ||||||||||||||||||||||||||||||||||||||||||||||
| """Add a term to the E-Graph and return its E-class ID. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| Args: | ||||||||||||||||||||||||||||||||||||||||||||||
| term: An apyds.Term to add to the E-Graph. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||||||||||||||||||
| The E-class ID for the added term. | ||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||
| enode = self._term_to_enode(term) | ||||||||||||||||||||||||||||||||||||||||||||||
| return self._add_enode(enode) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| def _term_to_enode(self, term: apyds.Term) -> ENode: | ||||||||||||||||||||||||||||||||||||||||||||||
| """Convert an apyds.Term to an ENode.""" | ||||||||||||||||||||||||||||||||||||||||||||||
| inner = term.term | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(inner, apyds.List): | ||||||||||||||||||||||||||||||||||||||||||||||
| children = [] | ||||||||||||||||||||||||||||||||||||||||||||||
| for i in range(len(inner)): | ||||||||||||||||||||||||||||||||||||||||||||||
| child_term = inner[i] | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+86
to
+87
|
||||||||||||||||||||||||||||||||||||||||||||||
| for i in range(len(inner)): | |
| child_term = inner[i] | |
| for child_term in inner: |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The merge method lacks complete documentation. It should document its parameters (what a and b represent), return value (what the returned EClassId represents), and the important side effect that rebuild() must be called after merge operations to maintain congruence.
| """Merge two E-classes and schedule rebuilding.""" | |
| """Merge two E-classes and schedule rebuilding. | |
| Parameters | |
| ---------- | |
| a : EClassId | |
| The ID of the first E-class to merge. | |
| b : EClassId | |
| The ID of the second E-class to merge. | |
| Returns | |
| ------- | |
| EClassId | |
| The canonical E-class ID representing the union of ``a`` and ``b``. | |
| Notes | |
| ----- | |
| This method updates internal parent/class/parent-links structures and | |
| adds the resulting E-class to the worklist for congruence repair. | |
| Call :meth:`rebuild` after performing merges to restore global | |
| congruence in the E-Graph. | |
| """ |
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,51 @@ | ||||
| [build-system] | ||||
| requires = [ | ||||
| "setuptools~=80.9.0", | ||||
| "setuptools-scm~=9.2.2", | ||||
| ] | ||||
| build-backend = "setuptools.build_meta" | ||||
|
|
||||
| [project] | ||||
| name = "apyds-egg" | ||||
| dynamic = ["version"] | ||||
| dependencies = [ | ||||
| "apyds", | ||||
| ] | ||||
| requires-python = ">=3.11, <3.15" | ||||
| authors = [{ name = "Hao Zhang", email = "hzhangxyz@outlook.com" }] | ||||
| description = "E-Graph implementation for apyds" | ||||
| readme = "README.md" | ||||
|
||||
| readme = "README.md" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The find method lacks documentation about its parameter. It should document what eclass represents (an E-class ID to look up) and clarify that it returns the canonical representative after any merge operations.