Skip to content

Term-encoding congruence via native :merge (proof mode, ~15% faster)#2

Closed
oflatt-claude wants to merge 4 commits into
port-tuple-outputsfrom
port-native-merge
Closed

Term-encoding congruence via native :merge (proof mode, ~15% faster)#2
oflatt-claude wants to merge 4 commits into
port-tuple-outputsfrom
port-native-merge

Conversation

@oflatt-claude

Copy link
Copy Markdown
Owner

Stacked on saulshanabrook#3 (tuple-output functions) — review/merge that first; this PR's diff is against port-tuple-outputs.

Ports the proof-mode speedup from oflatt/egglog-duckdb: term-encoding congruence closure now runs as a native :merge instead of a generated self-join rule. Contained to the encoding + a general, reusable merge mechanism — no --native-uf, no --fast-rebuild, no dataflow backends, no backend-trait layer, no DefaultVal::Identity, and the union-find stays the existing keyed @UF_S (S S) -> Proof table (path-compression/single-parent rules unchanged).

Result

Proofs on tests/math-microbenchmark.egg ((run 11), release):

wall clock @rebuilding search
before (rule-encoded congruence) ~43.3 s 13.67 s
after (:merge congruence) ~36.7 s 9.60 s

≈15% faster (1.18×); the win is the congruence self-join disappearing from rebuild. Output is equivalent (only a cosmetic fresh-variable name differs). Full tests/files.rs suite: 747/747 across all treatments (normal, term_encoding, proofs, proof_testing, desugar), plus the proof unit/extraction/regression suites.

How it works

  • General merge actions (egglog-bridge). New MergeFn::{Seq, TableInsert, Construct, IfEq} + TableAction::lookup_or_insert_multi (following egglog PR #933): a :merge can now sequence effects, insert a row into another table, mint a value-tuple constructor, and branch. This is the only bridge addition — no proof-specific variants.
  • FD tuple view (encoder). In proof mode a constructor's view becomes the functional-dependency tuple (children) -> (eclass, proof). On an FD conflict (two congruent terms share canonical children) its :merge stages the congruence edge (@UF_S new old) = (Trans new_pf (Sym old_pf)) — the exact edge/proof the rule produced — and keeps the current row. The rebuild's re-set at canonical children is what triggers the merge, so the @congruence_rule is dropped entirely.
  • Self-contained (ports #927's annotation mechanism). The Proof sort carries :internal-proof-names <congr> <trans> <sym>; re-declaring it repopulates the proof-constructor names, and the congruence merge is detected purely by view shape. So a dumped/desugared program rebuilds its own merge in a fresh egraph (fixes the desugar_proof_testing round-trip).

Touch points beyond the above: multi-output-aware serialize, extraction eclass-column helpers, constraint.rs delete arity, typecheck tuple-output/0-input carve-outs for the encoder-minted view, and multi-output subsume.

Not included / follow-ups

  • Canonicalize-at-creation — next PR (born-canonical view rows via default-on-miss primitives).
  • Term-mode (non-proof) congruence still uses rules; this PR targets proof mode (the goal was faster proofs).

Commits

  1. egglog-bridge: general merge-action MergeFn variants
  2. native congruence :merge builder (inert)
  3. encoder: congruence via :merge (FD tuple view)
  4. self-contained via :internal-proof-names (round-trip fix)

🤖 Generated with Claude Code

oflatt and others added 4 commits July 2, 2026 19:31
…/Construct/IfEq)

Add a general "run arbitrary actions" capability to the bridge merge language,
following egglog PR #933: MergeFn::{TableInsert, Seq, Construct, IfEq} plus
TableAction::lookup_or_insert_multi, wired through fill_deps/resolve/run. These
let a function's :merge stage a row into another table, build a value-tuple
constructor term, sequence effects, and branch — enough to express term-encoding
congruence (union edge + proof composition) as a native merge instead of a rule.

Dormant: no encoder emits these yet. Workspace builds clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add EGraph::native_congruence_merge, which builds the term-encoding congruence
merge for a proof-mode constructor view `(children) -> (eclass, proof)` using the
general merge-action variants: on an FD conflict it keeps the old (eclass, proof)
and stages `(@UF_S new_eclass old_eclass) = (Trans new_proof (Sym old_proof))`
into the per-sort UF table — the exact edge/proof the rule-encoded congruence
produces. declare_function calls it, falling back to the ordinary merge lowering.

Inert until the encoder emits 2-output views (next commit): the guard requires a
term-constructor view with two outputs, which nothing produces yet. Builds clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In proof mode each constructor's view becomes a functional-dependency tuple
`(children) -> (eclass, proof)` whose native :merge resolves congruence, instead
of the rule-encoded self-join. On an FD conflict the merge stages the congruence
edge `(@UF_S new old) = (Trans new_pf (Sym old_pf))` into the per-sort UF table
(built from the general merge-action variants) and keeps the current row; the
existing UF/path-compression/rebuild machinery is unchanged. The `@congruence_rule`
is dropped for constructors — the rebuild's re-`set` at canonical children triggers
the merge, subsuming it.

Encoder (proof_encoding.rs): FD view declaration, `update_fd_view`/`query_fd_view`,
FD-aware `add_term_and_view`, `rebuilding_rules_fd`, fact-reader destructuring, and
FD delete/subsume rules. Backend: `native_congruence_merge` builds the merge in
declare_function; serialization, extraction helpers, the delete-arity check, the
typecheck tuple-output/0-input carve-outs, and multi-output `subsume` all updated
to handle the tuple view.

Verified: all `proofs`, `proof_testing`, `term_encoding`, and normal file
treatments pass, plus the proof unit/extraction/regression suites.

KNOWN LIMITATION: the 7 `desugar_proof_testing` round-trip tests fail — the merge
is built from proof_state, so a dumped-and-re-run encoded program (fresh egraph,
no proof_state) loses the Trans/Sym/UF bindings and congruence degrades. Fix is to
carry those names in a view annotation so the program is self-contained.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Port PR #927's annotation mechanism so a desugared proof program re-parses
self-contained. The Proof sort now carries :internal-proof-names <congr> <trans>
<sym>; declaring it repopulates proof_state.proof_names, and native_congruence_merge
is detected purely by view shape (term-constructor + eq-sort first output + a
second proof output) rather than a live proof_state flag. Together with the
existing :internal-uf -> uf_parent round-trip, a dumped encoded program rebuilds
the congruence merge in a fresh egraph.

Threads a proof_ctors field through the Sort command (parse/Display/desugar/
typecheck/declare), mirroring uf/proof_func.

Fixes the 7 desugar_proof_testing failures. Full files suite: 747/747 across all
treatments (normal, term_encoding, proofs, proof_testing, desugar).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@oflatt-claude

Copy link
Copy Markdown
Owner Author

Reopening against saulshanabrook/egglog-encoding (stacked on saulshanabrook#3).

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