Skip to content

Add polynomials example#397

Open
saulshanabrook wants to merge 56 commits intomainfrom
polynomials
Open

Add polynomials example#397
saulshanabrook wants to merge 56 commits intomainfrom
polynomials

Conversation

@saulshanabrook
Copy link
Member

WIP PR to add example for factoring polynomials efficiently using multisets. Eventually the goal here is to explore how to avoid AC blowup by using containers

@coderabbitai
Copy link

coderabbitai bot commented Jan 28, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added EGraph.freeze() to capture immutable e-graph snapshots for inspection
    • Variadic EGraph constructor now accepts initial actions at creation
    • Expanded multiset operations and polynomial support with new methods
    • Added PyObject class for Python interoperability
    • Enhanced debugging tools: run, stats, function_values, freeze, display, saturate
    • New comprehensive containers and polynomials documentation
  • Bug Fixes

    • Fixed let binding syntax by prefixing with $ for valid egglog output
    • Improved empty container handling in Python-to-egglog conversion
    • Enhanced nested lambda type inference
    • Fixed schedule and rewrite preservation after materialization
  • Documentation

    • Added detailed containers/polynomials guide with examples
    • Expanded debugging and inspection reference
    • Clarified proof-mode bindings availability
  • Dependencies

    • Updated egglog dependencies to multiset-changes branch
    • Added indexmap and tempfile dependencies

Walkthrough

The pull request introduces substantial refactoring and feature enhancements across the egglog-python codebase. Primary changes include migrating egglog dependencies from the fix-fn-bug branch to multiset-changes, replacing the Term type with TermId throughout bindings and Rust code, adding e-graph freezing capability via FrozenEGraph, expanding Python bindings with multiset operations and PyObject support, correcting naming inconsistencies (e.g., DeclerationsLikeDeclarationsLike, ClassTypeVarRefTypeVarRef), introducing new documentation on containers and polynomials, and updating array API program generation with recursive value handling.

Changes

Cohort / File(s) Summary
Dependency Updates
Cargo.toml, .github/workflows/CI.yml, pyproject.toml
Updated egglog dependencies from fix-fn-bug to multiset-changes branch; added indexmap and tempfile; enabled debug symbols in release builds; narrowed pytest invocation in CI; updated Ruff and dev dependencies.
Type System Refactoring (Declarations)
python/egglog/declarations.py, python/egglog/runtime.py, python/tests/test_runtime.py, python/tests/test_type_constraint_solver.py
Renamed ClassTypeVarRef→TypeVarRef, DeclerationsLike→DeclarationsLike, DelayedDeclerations→DelayedDeclarations; added EGraphDecl and DummyDecl; updated type variable and declaration handling throughout type resolution.
Term→TermId Migration (Rust/Bindings)
src/termdag.rs, src/extract.rs, src/py_object_sort.rs, python/egglog/bindings.pyi
Replaced Term with TermId throughout termdag methods (app, lit, var, expr_to_term, term_to_expr, to_string); updated Extractor to return TermId; introduced _TermId type alias in bindings.
E-graph Freezing
src/freeze.rs, src/egraph.rs, src/lib.rs, python/egglog/egraph.py, python/egglog/bindings.pyi
Added FrozenRow, FrozenFunction, FrozenEGraph structs in Rust; exposed freeze() method on EGraph; added Value.ord pyclass attribute; updated bindings with frozen structures and FrozenEGraph export.
Python Bindings Expansion
python/egglog/bindings.pyi
Added Prove, ProveExists, ProveExistsOutput commands; introduced FrozenEGraph/FrozenFunction/FrozenRow; refactored TermDag interface to use _TermId; updated ExtractBest/ExtractVariants to use _TermId.
Multiset and Container Operations
python/egglog/builtins.py
Introduced multiset_flat_map, multiset_fold, swapped variants; added MultiSet methods (and, single, sum_multisets, contains, remove, length, pick, filter, etc.); expanded Vec with union (or) and map; added PyObject class with evaluation support.
E-graph Actions and Construction
python/egglog/egraph.py, python/egglog/__init__.py, python/egglog/deconstruct.py
Made EGraph constructor accept variadic ActionLike arguments; exposed ActionLike in public API; added get_constant_name function; updated _from_termdag to use int instead of _Term; enhanced freeze() and _values_to_expr helpers.
Type Resolution and Conversion
python/egglog/conversion.py, python/egglog/type_constraint_solver.py
Simplified resolve_literal by removing TypeConstraintSolver dependency; refactored TypeConstraintSolver to use _typevar_to_type mapping instead of class-specific storage; added substitute_typevars_try_function for runtime probing.
E-graph State and Translation
python/egglog/egraph_state.py
Added _normalize_global_let_name to prefix let-bindings with $; introduced expr_to_let_counter and unnamed_function_counter for deterministic naming; updated exprs_from_egg to accept list[int] term identifiers.
Documentation and Examples
docs/explanation/2026_02_containers.md, docs/reference/python-integration.md, docs/reference/egglog-translation.md, docs/how-to-guides.md, docs/changelog.md, docs/conf.py
Added comprehensive containers/polynomials documentation; renamed "Visualization" section to "Debugging and Inspection"; expanded python-integration guide with debugging utilities; corrected imports and added changelog entries.
Array API Program Generation
python/egglog/exp/array_api_program_gen.py, python/egglog/exp/array_api_numba.py, python/egglog/exp/array_api_loopnest.py, python/egglog/exp/array_api_jit.py
Extended program generation with program_if helper and recursive value support; updated mean/std/unique to use new parameter patterns; modified jit to construct EGraph and run schedule for evaluation; added loopnest vs parameter.
Program Generation and Visualization
python/egglog/exp/program_gen.py, python/egglog/exp/polynomials.py, python/egglog/pretty.py, python/egglog/visualizer_widget.py, src/conversions.rs
Fixed is_identifer→is_identifier, eval_program_rulseset→eval_program_ruleset; added polynomials.py module with Report/TotalReport and example workflows; enhanced AllDecls to include EGraphDecl and DummyDecl; updated Sort/Function/Constructor command variants in conversions; changed visualizer to save HTML to file instead of embedding.
Test Suite Updates
python/tests/test_high_level.py, python/tests/test_array_api.py, python/tests/test_pretty.py, python/tests/test_runtime.py, python/tests/test_type_constraint_solver.py, python/tests/test_unstable_fn.py, python/tests/test_program_gen.py
Added tests for let-binding prefixing, EGraph constructor, schedule tracking, freeze behavior, runtime class stability, doctest discovery; updated NDArray construction to use NDArray.fn(); refactored tuple/value construction patterns; added polynomial factoring test scaffolding.
Test Snapshots (Array API)
python/tests/__snapshots__/test_array_api/test_jit[lda]*, test_jit[tuple]*, test_program_compile[tuple]*, TestLoopNest.test_index_codegen*
Updated NDArray/TupleInt/Value construction patterns; replaced Vec[Int]/from_vec with direct Vec(); switched to NDArray(RecursiveValue(...)) patterns; updated mean/std/unique_counts/unique_inverse index handling; replaced Slice construction with index-based extraction.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch polynomials

@saulshanabrook saulshanabrook marked this pull request as ready for review March 16, 2026 22:50
Copilot AI review requested due to automatic review settings March 16, 2026 22:50
@saulshanabrook
Copy link
Member Author

@coderabbitai full review

@coderabbitai
Copy link

coderabbitai bot commented Mar 16, 2026

✅ Actions performed

Full review triggered.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a polynomial factoring example (via multiset-based normalization) and expands the egglog Python/Rust bindings to support new debugging (“freeze”) and updated container/array-api/program-generation workflows.

Changes:

  • Add EGraph.freeze() and supporting Rust/Python plumbing to snapshot an e-graph into replayable high-level actions.
  • Introduce a polynomial container example + tests/docs demonstrating factoring without AC blowup.
  • Refactor/extend runtime/type-inference, container helpers, and array-api program generation; update tests/snapshots accordingly.

Reviewed changes

Copilot reviewed 53 out of 57 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
uv.lock Add sympy (and mpmath) to dev dependencies
src/termdag.rs Switch TermDag wrapper APIs to use TermId instead of Term
src/serialize.rs Fix spelling in doc comment (“separate”)
src/py_object_sort.rs Update to new TermId-based TermDag APIs
src/lib.rs Export new freeze pyclasses into module bindings
src/freeze.rs Add Rust-side frozen e-graph structures (FrozenEGraph, etc.)
src/extract.rs Update extractor return types to TermId
src/egraph.rs Record command strings earlier; add freeze; adjust Value pyclass flags
src/conversions.rs Update bindings conversions for newer egglog AST/outputs + prove exists output
python/tests/test_unstable_fn.py Add regression test for distinct lambda naming
python/tests/test_type_constraint_solver.py Update tests for new TypeConstraintSolver API
python/tests/test_runtime.py Add doctest/runtime stability tests; adjust typevar usage
python/tests/test_program_gen.py Fix typo eval_program_ruleset reference
python/tests/test_pretty.py Add freeze pretty-print tests; adjust long-line wrapping expectations
python/tests/test_high_level.py Add tests for let-name prefixing, constructor registration, scheduler updates, etc.
python/tests/test_array_api.py Broad refactors for tuple/value constructors, reshape/lda/jit behavior, add polynomial factoring test
python/tests/snapshots/test_array_api/test_program_compile[tuple][expr].py Update snapshot for new tuple/value constructors
python/tests/snapshots/test_array_api/test_jit[tuple][initial_expr].py Update snapshot for new tuple constructor form
python/tests/snapshots/test_array_api/test_jit[tuple][expr].py Update snapshot for new program-gen output structure
python/tests/snapshots/test_array_api/test_jit[tuple][code].py Remove outdated generated code snapshot
python/tests/snapshots/test_array_api/test_jit[lda][initial_expr].py Update snapshot for numerous array-api lowering changes
python/tests/snapshots/test_array_api/test_jit[lda][expr].py Update snapshot for numerous array-api lowering changes
python/tests/snapshots/test_array_api/test_jit[lda][code].py Update snapshot for new generated numpy code
python/tests/snapshots/test_array_api/TestLoopNest.test_index_codegen[expr].py Update snapshot formatting / tuple constructor changes
python/egglog/visualizer_widget.py Change non-notebook visualization to write standalone HTML
python/egglog/type_constraint_solver.py Redesign solver around TypeVarRef; add callable probing path for UnstableFn
python/egglog/thunk.py Preserve exception chaining/traceback when thunk evaluation fails
python/egglog/runtime.py Runtime typevar ref updates; add attribute caching; improve partial application + type inference plumbing
python/egglog/pretty.py Improve formatting pipeline (AST normalize + black); add EGraphDecl/DummyDecl support
python/egglog/exp/program_gen.py Fix typos and rename is_identifier / eval_program_ruleset
python/egglog/exp/polynomials.py New end-to-end polynomial/multiset example utilities + reports
python/egglog/exp/array_api_program_gen.py Refactor program generation rules; add recursive value printing; update optionals handling
python/egglog/exp/array_api_numba.py Update numba rewrite rules for updated array-api signatures
python/egglog/exp/array_api_loopnest.py Update loopnest rules for new tuple/vec encodings
python/egglog/exp/array_api_jit.py Rewrite JIT to use EGraph.run/extract and improve error notes
python/egglog/egraph_state.py Deterministic naming counters; $-prefix normalization; TermId extraction changes; unnamed function naming
python/egglog/egraph.py Variadic constructor for initial actions; add high-level freeze(); schedule decl thunk refactors
python/egglog/deconstruct.py Add constant name helper; adjust bound type param extraction
python/egglog/declarations.py Introduce EGraphDecl, DummyDecl, TypeVarRef; adjust declaration plumbing and typevar utilities
python/egglog/conversion.py Rework literal resolution (dummy sentinel support); rename debug helper
python/egglog/builtins.py Add/extend multiset/vec/numeric/string builtins; move PyObject definition; update conversions
python/egglog/bindings.pyi Update stubs for TermId-based extraction + freeze + prove commands
python/egglog/init.py Re-export ActionLike for typing
pyproject.toml Add sympy dev dep; adjust ruff ignores
docs/reference/python-integration.md Expand debugging/inspection docs (run/stats/function_values/freeze/display/saturate)
docs/reference/egglog-translation.md Document passing actions to EGraph(...); note proof-mode limitations
docs/how-to-guides.md Clarify parsing/running program strings section
docs/explanation/2026_02_containers.md Add large containers/polynomials writeup and reproducible examples
docs/conf.py Remove INP001 noqa after config update
docs/changelog.md Add unreleased entry summarizing new features and fixes
Cargo.toml Update egglog git deps; add indexmap, tempfile; enable pyo3 indexmap feature
Cargo.lock Update dependency graph for new egglog branch + added crates
AGENTS.md Add repo guidance document for contributors/agents
.github/workflows/CI.yml Narrow CodSpeed snapshot update run to targeted array-api tests
.github/AGENTS.md Remove older agent instructions (replaced by top-level AGENTS.md)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
python/egglog/runtime.py (1)

301-312: ⚠️ Potential issue | 🟠 Major

Keep the bound receiver in partial method calls.

For bound instance methods, RuntimeFunction.__call__ prepends self.__egg_bound__ before building call.args, but Line 309 still slices with len(partial_args) only. UnstableFn(obj.method, x) therefore turns into PartialCallDecl(obj) instead of PartialCallDecl(obj, x) and exposes x again as a remaining parameter.

Suggested fix
             call = (res_typed_expr := res.__egg_typed_expr__).expr
             return_tp = res_typed_expr.tp
             assert isinstance(call, CallDecl), "partial function must be a call"
             # Clip off the remaining arguments
-            n_args = len(partial_args)
-            value = PartialCallDecl(replace(call, args=call.args[:n_args]))
-            remaining_arg_types = [a.tp for a in call.args[n_args:]]
+            bound_arg_count = int(
+                isinstance(fn_arg, RuntimeFunction) and isinstance(fn_arg.__egg_bound__, RuntimeExpr)
+            )
+            kept_arg_count = bound_arg_count + len(partial_args)
+            value = PartialCallDecl(replace(call, args=call.args[:kept_arg_count]))
+            remaining_arg_types = [a.tp for a in call.args[kept_arg_count:]]
             type_ref = JustTypeRef(Ident.builtin("UnstableFn"), (return_tp, *remaining_arg_types))
             return RuntimeExpr.__from_values__(Declarations.create(self, res), TypedExprDecl(type_ref, value))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/egglog/runtime.py` around lines 301 - 312, RuntimeFunction.__call__ is
failing to account for a bound receiver when creating PartialCallDecl: it
prepends self.__egg_bound__ to call.args but still uses n_args =
len(partial_args) to slice the original call.args, causing the bound self to be
dropped and a real partial argument to be misclassified as remaining; fix by
computing n_args as len(partial_args) + (1 if getattr(self, "__egg_bound__",
None) is not None else 0) (or otherwise detecting whether a bound receiver was
prepended) and use that adjusted n_args when slicing call.args and when building
remaining_arg_types and PartialCallDecl so the bound receiver remains included
in the partial call.
🧹 Nitpick comments (9)
AGENTS.md (2)

39-39: Consider whether this specific skill name might become stale.

The reference to $github-actions-rest-logs is environment-specific and could change over time. Per the earlier review comment about removing content that "might change too quickly," consider either:

  • Making this more generic (e.g., "use available GitHub Actions debugging tools")
  • Adding a note that the skill name may vary by environment
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AGENTS.md` at line 39, The line referencing the environment-specific skill
`$github-actions-rest-logs` may become stale; update the sentence so it uses a
generic phrase (e.g., "use available GitHub Actions debugging tools or the REST
API with GITHUB_PAT_TOKEN") or append a short parenthetical note that the exact
skill name may vary by environment. Locate the literal
`$github-actions-rest-logs` in AGENTS.md and replace it with the chosen generic
wording or add the clarification immediately after that token to prevent future
breakage.

43-45: Minor style: Vary sentence structure in the verification list.

Three consecutive sentences start with "Run," which reduces readability slightly. Consider rewording for variety, e.g., "After typing changes, run make mypy" or using a bulleted list format.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AGENTS.md` around lines 43 - 45, In AGENTS.md, the three verification lines
all start with "Run" which is repetitive; reword them to vary sentence structure
and improve readability by making one or two sentences lead with the context
instead (for example: "After typing changes, run `make mypy`", "For touched
modules, run targeted `pytest`", "When docs or public API change, run `make
docs`"), or convert them into a bulleted list—update the lines mentioning `make
mypy`, `pytest`, and `make docs` accordingly.
pyproject.toml (1)

206-209: Keep INP001 scoped to docs/**.

The comment says this exception is only needed for docs, but adding it to the global ignore list disables Ruff's namespace-package check across the whole repo. Move INP001 into the existing docs/** per-file ignore instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pyproject.toml` around lines 206 - 209, Remove "INP001" from the global
ignore list in pyproject.toml and instead add it to the existing per-file-ignore
entry for "docs/**" so the INP001 exception only applies to documentation files;
locate the global ignore array that currently contains "INP001" and the
per-file-ignore mapping for "docs/**" and move the "INP001" token there (leave
other ignores like "PLW0108" untouched).
python/tests/test_array_api.py (1)

458-479: New polynomial factoring test validates key PR functionality.

The test_polynomial_factoring test covers the polynomial factoring feature mentioned in the PR objectives. The test approach of normalizing both extracted and expected expressions before comparison is sound.

Note: The input parameter on line 469 shadows the Python builtin. Consider renaming to input_expr or similar to avoid potential confusion.

♻️ Optional: Rename parameter to avoid shadowing builtin
 `@pytest.mark.parametrize`(
-    ("input", "expected"),
+    ("input_expr", "expected"),
     [
         pytest.param(x * x, x**2, id="exp"),
         ...
     ],
 )
-def test_polynomial_factoring(input: Value, expected: Value):
+def test_polynomial_factoring(input_expr: Value, expected: Value):
     egraph = EGraph()
-    x = egraph.let("x", input)
+    x = egraph.let("x", input_expr)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/tests/test_array_api.py` around lines 458 - 479, Rename the test
parameter that shadows the builtin: change the parametrize tuple and the test
function signature from ("input", "expected") / def
test_polynomial_factoring(input: Value, expected: Value) to ("input_expr",
"expected") / def test_polynomial_factoring(input_expr: Value, expected: Value),
and update all intra-function references (e.g., egraph.let("x", input) ->
egraph.let("x", input_expr)) so the parameter name matches everywhere (keeping
the pytest.param entries intact except for the renamed parameter).
python/egglog/exp/array_api_program_gen.py (1)

560-579: New recursive value program generation support.

The recursive_value_program and vec_recursive_value_program functions enable code generation for nested/recursive value structures, supporting the new NDArray(rv) literal syntax on line 428.

Note: The v: Value parameter in _vec_recursive_value_program (line 575) appears unused. If it's required for the ruleset registration pattern, consider adding a comment; otherwise, it could be removed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/egglog/exp/array_api_program_gen.py` around lines 560 - 579, The
parameter v in the ruleset function _vec_recursive_value_program is unused;
either remove it from the signature or mark it explicitly as unused (e.g.,
rename to _v) or add a comment explaining it's required by
array_api_program_gen_ruleset.register's signature pattern; update the function
signature for _vec_recursive_value_program (and any registration call)
accordingly and add a brief inline comment referencing
vec_recursive_value_program and recursive_value_program so future readers know
why the parameter was kept if you choose not to remove it.
python/egglog/egraph_state.py (1)

482-496: Complex conditional logic for UnstableFn type arguments.

The conditional handling for UnstableFn with len(ref.args) > 1 vs the fallback Unit() literal is intricate. The logic constructs type arguments differently when there are more than one argument versus zero/one.

Consider adding a brief inline comment explaining the expected structure of UnstableFn type arguments (return type position, arg types position) to aid future maintainability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/egglog/egraph_state.py` around lines 482 - 496, Add an inline comment
above the UnstableFn branch (the if ref.ident == Ident.builtin("UnstableFn")
block) explaining the expected structure of ref.args and how type_args are
constructed: document that ref.args[0] is the return type, ref.args[1:] are
parameter types (and when len(ref.args) <= 1 we use a Unit() literal for the
param-list), and that the code builds a bindings.Call with the return type as
the head and parameter types as Var children (referencing bindings.Call,
bindings.Lit, and bindings.Var in this block); keep it short and local to the
type_args construction to improve future maintainability.
python/egglog/declarations.py (1)

388-519: Substantial to_actions implementation for EGraph serialization.

The to_actions cached property converts an EGraphDecl to a list of actions for reconstructing the egraph. The implementation:

  1. Iteratively finds grounded terms for e-classes
  2. Converts all expressions to grounded form
  3. Emits unions, let bindings, sets, and expr actions

The algorithm handles the grounding fixpoint correctly. Note the comment on line 512 has a typo: "remaining call s" (extra spaces).

📝 Fix typo in comment
-        # Now add any remaining call    s that weren't part of any other actions
+        # Now add any remaining calls that weren't part of any other actions
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/egglog/declarations.py` around lines 388 - 519, Fix the typo in the
comment above the final actions.extend block in the to_actions cached_property:
change the comment text "Now add any remaining call    s that weren't part of
any other actions" to "Now add any remaining calls that weren't part of any
other actions" so it reads correctly; the change should be made near the end of
the to_actions method just before the actions.extend(...) that handles
single_e_class_calls and emitted_call_decls.
python/egglog/conversion.py (1)

230-233: Move DUMMY_VALUE guard before resolve_type for clearer control flow.

At Line 231, arg_type is computed even though it’s unused when arg is DUMMY_VALUE (Line 232). Reordering makes this path cheaper and easier to read.

Proposed cleanup
-    tp_just = tp.to_just()
-    arg_type = resolve_type(arg)
-    if arg is DUMMY_VALUE:
+    tp_just = tp.to_just()
+    if arg is DUMMY_VALUE:
         return RuntimeExpr.__from_values__(decls(), TypedExprDecl(tp_just, DummyDecl()))
+    arg_type = resolve_type(arg)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/egglog/conversion.py` around lines 230 - 233, Move the DUMMY_VALUE
early-return guard to before the call to resolve_type to avoid computing
arg_type when it's unused: check "if arg is DUMMY_VALUE" first and return the
RuntimeExpr.__from_values__(decls(), TypedExprDecl(tp.to_just(), DummyDecl()))
there, then compute arg_type = resolve_type(arg) afterwards; reference symbols:
DUMMY_VALUE, resolve_type, tp.to_just()/tp_just, TypedExprDecl,
RuntimeExpr.__from_values__, decls(), DummyDecl().
python/egglog/deconstruct.py (1)

191-193: Remove unreachable InitRef branch from egg_bound guard.

Because Line 185–189 already returns early for InitRef, including InitRef again in Line 192 is dead logic and can be simplified.

Suggested simplification
-        JustTypeRef(call.callable.ident, call.bound_tp_params)
-        if isinstance(call.callable, (ClassMethodRef, MethodRef, InitRef)) and call.bound_tp_params
+        JustTypeRef(call.callable.ident, call.bound_tp_params)
+        if isinstance(call.callable, (ClassMethodRef, MethodRef)) and call.bound_tp_params
         else None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/egglog/deconstruct.py` around lines 191 - 193, The isinstance check in
the egg_bound construction includes InitRef redundantly; since InitRef is
already returned early in the prior branch, remove InitRef from the tuple in the
conditional expression so it reads isinstance(call.callable, (ClassMethodRef,
MethodRef)) (leaving the rest of the expression unchanged), ensuring JustTypeRef
and the None branch behavior remain the same.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Cargo.toml`:
- Line 39: Remove the unused Rust crate dependency declaration for tempfile from
Cargo.toml by deleting the line `tempfile = "3.24.0"` in the [dependencies]
section; then run `cargo check` (or `cargo build`) to ensure no Rust code
references remain and update Cargo.lock if necessary.
- Around line 22-26: Update the git dependencies for the egg-smol fork to pin to
the fixed commit instead of a moving branch: replace branch = "multiset-changes"
with rev = "66b277805587423e92a96b05fc4b2643f2cf4aec" for the egglog,
egglog-ast, egglog-core-relations, egglog-reports, and egglog-bridge entries in
Cargo.toml, and make the identical change in the
[patch.'https://github.com/egraphs-good/egglog'] block so the fork is locked to
the exact commit shown in Cargo.lock.

In `@docs/conf.py`:
- Line 1: The import line for pathlib lost its lint suppression for INP001;
either restore the inline suppression by adding back "# noqa: INP001" on the
"import pathlib" line, or update the project's ruff per-file-ignores by adding
"INP001" to the "docs/**" entry in [tool.ruff.lint.per-file-ignores] so that the
implicit namespace package rule is ignored for docs; pick one approach and apply
it consistently to avoid INP001 lint failures.

In `@docs/explanation/2026_02_containers.md`:
- Line 438: Fix the typo in the sentence "We now have an expression that is
mainly a sum of products, a multivariate polyonimal." by replacing the
misspelled word "polyonimal" with the correct word "polynomial" so the line
reads "We now have an expression that is mainly a sum of products, a
multivariate polynomial."

In `@pyproject.toml`:
- Around line 283-286: Add sympy to the project's optional dev dependencies so
pip installs include it: update the pyproject.toml by adding "sympy>=1.14.0"
under the [project.optional-dependencies].dev table to mirror the existing entry
in [dependency-groups].dev; ensure the version string matches "sympy>=1.14.0"
exactly so pip install .[dev] and .[test] will provide SymPy as expected.

In `@python/egglog/builtins.py`:
- Around line 1209-1233: The py_eval and py_exec stubs declare parameters named
globals and locals which shadow Python builtins; rename these parameters to
globals_ and locals_ in the py_eval and py_exec function signatures and update
their default expressions and docstrings to use globals_/locals_ (and update any
internal references or callers in this module to the new names); if API
compatibility must be preserved, add backwards-compatible aliases or wrapper
stubs that accept globals and locals and forward them to the new signatures;
ensure you update any references in related functions (e.g., documentation or
tests) so the code compiles and behavior is unchanged.

In `@python/egglog/egraph.py`:
- Around line 1402-1407: The current code assumes a single callable ref when
reading self._state.egg_fn_to_callable_refs[name] and when scanning
self._state.cost_callables, which raises ValueError if multiple callables share
the same egg name; change both sites to select the correct callable_ref by
matching the function signature (arity and sorts) instead of unpacking a
singleton: iterate candidates from self._state.egg_fn_to_callable_refs[name] or
the generator over cost_callables and pick the ref whose callable metadata
matches fn.input_sorts and fn.output_sort (or equivalent stored sorts), then use
that matched callable_ref; update the logic in the functions related to freeze()
and _values_to_expr() so they handle multiple refs safely by signature-matching
rather than tuple-unpacking.

In `@python/egglog/exp/array_api_jit.py`:
- Around line 37-41: The exception handler for bindings.EggSmolError currently
calls egraph.extract(fn_program) which may itself raise another EggSmolError;
instead perform the extraction for the diagnostic note in a best-effort way: try
to call egraph.extract(fn_program) inside its own small try/except and capture a
safe string (e.g., the extracted value or a fallback like "<unavailable>") and
then call e.add_note(...) with that safe value; do not let a failing extract
replace the original exception. Reference the symbols EggSmolError, e.add_note,
egraph.extract, and fn_program.as_py_object when updating the handler.

In `@python/egglog/exp/polynomials.py`:
- Around line 197-202: The factoring loop currently compares only the
single-iteration res.run_sec to max_factoring_sec and can both exceed the total
phase cap and fail to append a slow-but-updated result; introduce a cumulative
timer (e.g., cumulative_run_sec = 0.0) before the loop, after each run_example
call add res.run_sec to cumulative_run_sec, append res to factored_reports when
res.updated is true (even if this iteration pushed cumulative_run_sec over the
cap), and break the loop when cumulative_run_sec > max_factoring_sec or when
res.updated is false; adjust checks around
run_example/factoring/distributed_report.extracted/egraph and keep printing the
iteration and res.cost as before.
- Around line 123-148: Both combined_factored and combined_polynomial are only
taking a single register_sec and extract_sec from one element, undercounting
total time; update combined_factored (method combined_factored) to sum
register_sec across all entries in self.factored (e.g., sum(r.register_sec for r
in self.factored)) and sum extract_sec across all entries (e.g.,
sum(r.extract_sec for r in self.factored)), and update combined_polynomial
(property combined_polynomial) to sum register_sec across the contributing
reports (self.polynomial_multisets, self.polynomial_multisets_factored,
self.polynomial) and likewise sum extract_sec across those reports instead of
using only polynomial_multisets.register_sec and polynomial.extract_sec so
Report.total_sec reflects the full aggregated register/extract time.

In `@python/egglog/visualizer_widget.py`:
- Around line 35-52: The code writes raw str(self.egraphs) into HTML/JS and uses
a fixed tmp.html which can lead to XSS and clobbering; change VisualizerWidget
to JSON-serialize self.egraphs (use json.dumps) and embed that JSON string into
the HTML template instead of str(self.egraphs), and write to a uniquely named
temp file (use tempfile.NamedTemporaryFile or tempfile.mkstemp) rather than a
fixed "tmp.html"; update the place where HTML (the HTML constant / MAGIC_STRING
replacement) is created and where file.write_text and webbrowser.open are called
so the file contains safe JSON and is unique per invocation.

In `@src/egraph.rs`:
- Around line 69-73: The code appends cmds_str into self.cmds before executing
the batch, causing unexecuted commands to be recorded if egraph.run_program
(invoked via py.detach(|| self.egraph.run_program(commands))) fails; change the
flow so you call py.detach(...) and only on successful completion append
cmds_str to self.cmds (i.e., move the cmds.push_str(&cmds_str) into the success
path after egraph.run_program returns without error), keeping the info!("Running
commands:\n{}", cmds_str) log where appropriate and referencing self.cmds,
cmds_str, and the py.detach(|| self.egraph.run_program(commands)) call to locate
the change.

In `@src/freeze.rs`:
- Around line 33-35: Update the doc comment for the function from_egraph to
accurately describe its behavior: it constructs a FrozenEGraph from a live
EGraph (taking an &EGraph and producing a FrozenEGraph), rather than converting
a frozen e-graph into reconstruction commands; reference the function name
from_egraph and the types EGraph and FrozenEGraph in the comment so the purpose
is clear.

---

Outside diff comments:
In `@python/egglog/runtime.py`:
- Around line 301-312: RuntimeFunction.__call__ is failing to account for a
bound receiver when creating PartialCallDecl: it prepends self.__egg_bound__ to
call.args but still uses n_args = len(partial_args) to slice the original
call.args, causing the bound self to be dropped and a real partial argument to
be misclassified as remaining; fix by computing n_args as len(partial_args) + (1
if getattr(self, "__egg_bound__", None) is not None else 0) (or otherwise
detecting whether a bound receiver was prepended) and use that adjusted n_args
when slicing call.args and when building remaining_arg_types and PartialCallDecl
so the bound receiver remains included in the partial call.

---

Nitpick comments:
In `@AGENTS.md`:
- Line 39: The line referencing the environment-specific skill
`$github-actions-rest-logs` may become stale; update the sentence so it uses a
generic phrase (e.g., "use available GitHub Actions debugging tools or the REST
API with GITHUB_PAT_TOKEN") or append a short parenthetical note that the exact
skill name may vary by environment. Locate the literal
`$github-actions-rest-logs` in AGENTS.md and replace it with the chosen generic
wording or add the clarification immediately after that token to prevent future
breakage.
- Around line 43-45: In AGENTS.md, the three verification lines all start with
"Run" which is repetitive; reword them to vary sentence structure and improve
readability by making one or two sentences lead with the context instead (for
example: "After typing changes, run `make mypy`", "For touched modules, run
targeted `pytest`", "When docs or public API change, run `make docs`"), or
convert them into a bulleted list—update the lines mentioning `make mypy`,
`pytest`, and `make docs` accordingly.

In `@pyproject.toml`:
- Around line 206-209: Remove "INP001" from the global ignore list in
pyproject.toml and instead add it to the existing per-file-ignore entry for
"docs/**" so the INP001 exception only applies to documentation files; locate
the global ignore array that currently contains "INP001" and the per-file-ignore
mapping for "docs/**" and move the "INP001" token there (leave other ignores
like "PLW0108" untouched).

In `@python/egglog/conversion.py`:
- Around line 230-233: Move the DUMMY_VALUE early-return guard to before the
call to resolve_type to avoid computing arg_type when it's unused: check "if arg
is DUMMY_VALUE" first and return the RuntimeExpr.__from_values__(decls(),
TypedExprDecl(tp.to_just(), DummyDecl())) there, then compute arg_type =
resolve_type(arg) afterwards; reference symbols: DUMMY_VALUE, resolve_type,
tp.to_just()/tp_just, TypedExprDecl, RuntimeExpr.__from_values__, decls(),
DummyDecl().

In `@python/egglog/declarations.py`:
- Around line 388-519: Fix the typo in the comment above the final
actions.extend block in the to_actions cached_property: change the comment text
"Now add any remaining call    s that weren't part of any other actions" to "Now
add any remaining calls that weren't part of any other actions" so it reads
correctly; the change should be made near the end of the to_actions method just
before the actions.extend(...) that handles single_e_class_calls and
emitted_call_decls.

In `@python/egglog/deconstruct.py`:
- Around line 191-193: The isinstance check in the egg_bound construction
includes InitRef redundantly; since InitRef is already returned early in the
prior branch, remove InitRef from the tuple in the conditional expression so it
reads isinstance(call.callable, (ClassMethodRef, MethodRef)) (leaving the rest
of the expression unchanged), ensuring JustTypeRef and the None branch behavior
remain the same.

In `@python/egglog/egraph_state.py`:
- Around line 482-496: Add an inline comment above the UnstableFn branch (the if
ref.ident == Ident.builtin("UnstableFn") block) explaining the expected
structure of ref.args and how type_args are constructed: document that
ref.args[0] is the return type, ref.args[1:] are parameter types (and when
len(ref.args) <= 1 we use a Unit() literal for the param-list), and that the
code builds a bindings.Call with the return type as the head and parameter types
as Var children (referencing bindings.Call, bindings.Lit, and bindings.Var in
this block); keep it short and local to the type_args construction to improve
future maintainability.

In `@python/egglog/exp/array_api_program_gen.py`:
- Around line 560-579: The parameter v in the ruleset function
_vec_recursive_value_program is unused; either remove it from the signature or
mark it explicitly as unused (e.g., rename to _v) or add a comment explaining
it's required by array_api_program_gen_ruleset.register's signature pattern;
update the function signature for _vec_recursive_value_program (and any
registration call) accordingly and add a brief inline comment referencing
vec_recursive_value_program and recursive_value_program so future readers know
why the parameter was kept if you choose not to remove it.

In `@python/tests/test_array_api.py`:
- Around line 458-479: Rename the test parameter that shadows the builtin:
change the parametrize tuple and the test function signature from ("input",
"expected") / def test_polynomial_factoring(input: Value, expected: Value) to
("input_expr", "expected") / def test_polynomial_factoring(input_expr: Value,
expected: Value), and update all intra-function references (e.g.,
egraph.let("x", input) -> egraph.let("x", input_expr)) so the parameter name
matches everywhere (keeping the pytest.param entries intact except for the
renamed parameter).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5c1c44b9-f994-49c2-8186-da02b873a6cb

📥 Commits

Reviewing files that changed from the base of the PR and between 27f70d1 and d1a9f04.

⛔ Files ignored due to path filters (3)
  • Cargo.lock is excluded by !**/*.lock
  • docs/explanation/2026_02_yarn-polynomials.gif is excluded by !**/*.gif
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (54)
  • .github/AGENTS.md
  • .github/workflows/CI.yml
  • AGENTS.md
  • Cargo.toml
  • docs/changelog.md
  • docs/conf.py
  • docs/explanation/2026_02_containers.md
  • docs/how-to-guides.md
  • docs/reference/egglog-translation.md
  • docs/reference/python-integration.md
  • pyproject.toml
  • python/egglog/__init__.py
  • python/egglog/bindings.pyi
  • python/egglog/builtins.py
  • python/egglog/conversion.py
  • python/egglog/declarations.py
  • python/egglog/deconstruct.py
  • python/egglog/egraph.py
  • python/egglog/egraph_state.py
  • python/egglog/exp/array_api.py
  • python/egglog/exp/array_api_jit.py
  • python/egglog/exp/array_api_loopnest.py
  • python/egglog/exp/array_api_numba.py
  • python/egglog/exp/array_api_program_gen.py
  • python/egglog/exp/polynomials.py
  • python/egglog/exp/program_gen.py
  • python/egglog/pretty.py
  • python/egglog/runtime.py
  • python/egglog/thunk.py
  • python/egglog/type_constraint_solver.py
  • python/egglog/visualizer_widget.py
  • python/tests/__snapshots__/test_array_api/TestLoopNest.test_index_codegen[expr].py
  • python/tests/__snapshots__/test_array_api/test_jit[lda][code].py
  • python/tests/__snapshots__/test_array_api/test_jit[lda][expr].py
  • python/tests/__snapshots__/test_array_api/test_jit[lda][initial_expr].py
  • python/tests/__snapshots__/test_array_api/test_jit[tuple][code].py
  • python/tests/__snapshots__/test_array_api/test_jit[tuple][expr].py
  • python/tests/__snapshots__/test_array_api/test_jit[tuple][initial_expr].py
  • python/tests/__snapshots__/test_array_api/test_program_compile[tuple][expr].py
  • python/tests/test_array_api.py
  • python/tests/test_high_level.py
  • python/tests/test_pretty.py
  • python/tests/test_program_gen.py
  • python/tests/test_runtime.py
  • python/tests/test_type_constraint_solver.py
  • python/tests/test_unstable_fn.py
  • src/conversions.rs
  • src/egraph.rs
  • src/extract.rs
  • src/freeze.rs
  • src/lib.rs
  • src/py_object_sort.rs
  • src/serialize.rs
  • src/termdag.rs
💤 Files with no reviewable changes (2)
  • .github/AGENTS.md
  • python/tests/snapshots/test_array_api/test_jit[tuple][code].py

@codspeed-hq
Copy link

codspeed-hq bot commented Mar 17, 2026

Merging this PR will degrade performance by 52.45%

❌ 4 regressed benchmarks
✅ 8 untouched benchmarks
⏩ 2 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation test_jit[add] 536.2 ms 625.5 ms -14.28%
Simulation test_jit[lda] 13.8 s 28.9 s -52.45%
WallTime test_jit[lda] 18.6 s 37.9 s -50.98%
WallTime test_jit[add] 634.1 ms 777.9 ms -18.48%

Comparing polynomials (81546cc) with main (a19aa86)2

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (27f70d1) during the generation of this report, so a19aa86 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants