Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ _This project uses semantic versioning_

## UNRELEASED

- Make shared-subexpression hoisting deterministic across Python processes,
stabilizing generated `let` bindings and serialized e-graph output
[#422](https://github.com/egraphs-good/egglog-python/pull/422).

- Upgrade to Egglog 3 and matching `egglog-experimental` APIs
[#414](https://github.com/egraphs-good/egglog-python/pull/414).
- BREAKING: container rebuilding is now handled by Egglog, so `Map.rebuild()`,
Expand Down
4 changes: 1 addition & 3 deletions python/egglog/egraph_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -1711,9 +1711,7 @@ def _sanitize_egg_ident(input_string: str) -> str:


def _exprs_multiple_parents(typed_expr: TypedExprDecl) -> list[TypedExprDecl]:
"""
Returns all expressions that have multiple parents (a list but semantically just an ordered set).
"""
"""Return multiply-parented expressions in deterministic preorder for stable synthetic let names."""
parent_counts: dict[TypedExprDecl, int] = {}
traversal_order: list[TypedExprDecl] = []
traversed: set[TypedExprDecl] = set()
Expand Down
44 changes: 44 additions & 0 deletions python/tests/test_high_level.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

import importlib
import math
import os
import pathlib
import subprocess
import sys
from collections.abc import Callable, Iterator
from copy import copy
from dataclasses import dataclass
Expand Down Expand Up @@ -936,6 +939,47 @@ def pair(cls, left: DuplicateEdge, right: DuplicateEdge) -> DuplicateEdge: ...
assert sum(line.endswith(" $__expr_2 $__expr_2)") for line in lines) == 1


_DETERMINISTIC_HOISTING_SCRIPT = """
from __future__ import annotations

from egglog import EGraph, Expr, StringLike


class B(Expr):
@classmethod
def var(cls, name: StringLike) -> B: ...

def __and__(self, other: B) -> B: ...

def __or__(self, other: B) -> B: ...


egraph = EGraph(save_egglog_string=True)
x, y = B.var("x"), B.var("y")
shared = x & y
expr = shared | y
for _ in range(24):
expr = (shared & expr) | (expr & (shared | x))
egraph.let("$e", expr)
print(egraph.as_egglog_string, end="")
"""


def test_shared_subexpression_lowering_is_deterministic_across_processes() -> None:
transcripts = [
subprocess.run(
[sys.executable, "-c", _DETERMINISTIC_HOISTING_SCRIPT],
capture_output=True,
text=True,
check=True,
env={**os.environ, "PYTHONHASHSEED": str(seed)},
).stdout
for seed in (1, 2, 3)
]

assert transcripts[1:] == transcripts[:-1]


def test_freeze_omits_synthetic_let_bindings() -> None:
class FreezeLetNum(Expr):
@classmethod
Expand Down
Loading