Skip to content

Support greedy DAG extraction in experimental - #55

Open
saulshanabrook wants to merge 19 commits into
mainfrom
codex/greedy-dag-extractor
Open

Support greedy DAG extraction in experimental#55
saulshanabrook wants to merge 19 commits into
mainfrom
codex/greedy-dag-extractor

Conversation

@saulshanabrook

@saulshanabrook saulshanabrook commented Jun 24, 2026

Copy link
Copy Markdown
Member

Greedy DAG extraction in experimental

Depends on the core support-API PR:

This PR is the experimental half of the greedy-DAG extraction split. Core exposes the extraction support APIs; this PR owns the actual greedy-DAG extractor and the user-facing :extractor greedy-dag command option.

Related context:

Changes

  • Adds a root-aware greedy-DAG extractor in src/greedy_dag_extract.rs.
  • Adds :extractor greedy-dag support for extract, multi-extract, and keep-best; omitting the option still uses tree extraction.
  • Adds an experimental DagCostModel trait and wires the dynamic set-cost model through both tree and greedy-DAG extraction.
  • Re-exports DagCostModel, TreeCostModelFromDag, extract_best_greedy_dag, and extract_variants_greedy_dag.
  • Uses core's Read::enodes_for_eclass indexed lookup to discover normal constructor rows for each reachable (sort, value) root.
  • Returns None for ordinary per-root best-extraction misses, matching the new core ExtractedTerms shape; strict commands such as keep-best and dynamic extract convert that back into ExtractError.
  • Adds local indexed interner/secondary-map helpers used by the greedy-DAG cost sets.
  • Keeps the egglog workspace git dependencies on the core PR branch while the upstream core PR is pending; the final exact upstream commit pin should be set after the core PR merges.
  • Updates the file test harness to validate extracted terms by re-evaluating them and checking that they produce the same (sort, value) as the requested root. For ordinary extract commands, the harness also runs a paired greedy-DAG extraction at the same program point.
  • Adds regression coverage for cycle avoidance, multi-root extraction, variants, dynamic costs, multi-extract, and keep-best.
  • Fixes review edge cases around zero greedy-DAG variants, subsumed constructor rows in keep-best, negative dynamic cost rows, hidden table stats, and repository-local performance log hygiene.

Algorithm Notes

The extractor adapts extraction-gym's worklist-based greedy DAG heuristic to egglog values. It discovers producer rows reachable from the requested roots, keeps a once-paid dependency set per candidate DAG, rejects self-reachable choices, and propagates improvements only to producer rows whose dependencies changed.

This experimental implementation is normal-mode only: it uses exact (sort, value) roots and constructor row outputs, and it does not depend on internal union-find/proof-term canonicalization or view constructors.

The result is still a greedy heuristic, not an optimal DAG extractor. Combined-root extraction intentionally selects one producer per reachable (sort, value) for the requested root set, which matches the practical behavior tested here but does not claim globally optimal per-root choices.

Performance

Measured locally with release builds and stdout redirected away:

  • tests/taylor51.egg: tree 985.3 ms +/- 2.5 ms; greedy-DAG 210.2 ms +/- 0.9 ms; greedy-DAG was 4.69x faster.
  • Poach taylor7.egg adapted to current syntax in a temporary copy: tree 35.355 s +/- 0.124 s; greedy-DAG 2.079 s +/- 0.002 s; greedy-DAG was 17.01x faster.

CodSpeed exec was attempted, but the local macOS arm64 harness was unavailable, so these numbers come from hyperfine.

Validation

  • cargo check
  • cargo test --test integration_test greedy_dag
  • cargo test --test integration_test multi_extract
  • cargo test --test integration_test keep_best
  • cargo test --release --test files
  • make nits
  • git diff --check

@saulshanabrook saulshanabrook changed the title [codex] Support greedy DAG extraction APIs Support greedy DAG extraction in experimental Jun 29, 2026
@saulshanabrook
saulshanabrook marked this pull request as ready for review June 29, 2026 17:01
@saulshanabrook
saulshanabrook requested a review from a team as a code owner June 29, 2026 17:01
@saulshanabrook
saulshanabrook requested review from FTRobbin and removed request for a team June 29, 2026 17:01
@saulshanabrook

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a greedy DAG extractor (greedy-dag) to egglog-experimental that charges shared subterms once using marginal costs. Introduces DagCostModel trait, GreedyDagExtractor algorithm, secondary_map internals, and wires the new extractor into extract, multi-extract, and keep-best commands. Also extends table_stats to cover constructor tables and updates the for-sugar rule evaluation mode.

Changes

Greedy DAG Extractor

Layer / File(s) Summary
Dense interner and secondary-map data structures
src/secondary_map.rs
Introduces typed dense-ID interning (InternId, InternerBuilder, Interner) plus SecondarySet, SecondaryMap, SparseSecondaryMap, and AggregatedSparseSecondaryMap with unit tests.
DagCostModel trait, parsing helpers, and public entry points
src/greedy_dag_extract.rs (lines 1–161, 856–900), src/lib.rs, Cargo.toml
Defines DagCostModel with marginal_enode_cost/marginal_container_cost, a TreeCostModelFromDag adapter, :extractor greedy-dag keyword parsing, and public extract_best/variants_greedy_dag entry points; registers modules and re-exports DagCostModel. Adds fixedbitset and hashbrown dependencies.
Greedy DAG algorithm: data model, discovery, propagation, reconstruction
src/greedy_dag_extract.rs (lines 163–667, 723–854)
Implements GreedyDagExtractor: cost-key and producer-row data model, root-local reachable discovery, fixed-point worklist propagation using AggregatedSparseSecondaryMap for cost merging, and TermDag reconstruction with (Value, sort) caching.
DynamicCostModel and CustomExtract rewired to greedy-DAG APIs
src/set_cost.rs
DynamicCostModel switches from CostModel to BaseCostModel+DagCostModel; CustomExtract::update gains :extractor argument parsing and dispatches to extract_best/variants_greedy_dag or tree variants.
MultiExtract rewired to DagCostModel and greedy-dag option
src/multi_extract.rs
MultiExtract generic bounds change from CostModel to DagCostModel; update parses :extractor option and dispatches between greedy-DAG and tree extraction; MultiExtractOutput pulls termdag from extraction result.
keep-best rewired with function/constructor table reinsertion
src/keep_best.rs
keep-best gains a use_greedy_dag flag; collect_and_extract determines TableKind (function vs constructor), builds roots, calls extract_best_greedy_dag or extract_best_tree; reinsertion evaluates term IDs and writes via state.set or state.add.
table_stats extended to cover constructor tables
src/table_stats.rs
compute_table_stats tries function_entries then falls back to constructor_enodes via a shared visit_row closure; docs updated from "function table" to "table".
for-sugar rule uses RuleEvalMode::Seminaive
src/sugar/for.rs
Rule construction replaces naive: false with eval_mode: RuleEvalMode::Seminaive and adds include_subsumed: false.
Test harness and integration tests
tests/files.rs, tests/integration_test.rs
files.rs test harness runs a paired greedy-DAG extraction for every undecorated extract command and validates sort/value. Integration tests cover shared-subterm preference, set-cost, cycle avoidance, multi-extract, keep-best, variant ranking, and an updated error-message assertion.
Performance experiment log
.agents/logs/2026-06-24-greedy-dag-extractor-perf.md
Documentation-only log of 38 benchmark experiments (Experiments 0–37) covering profiling, optimization candidates, accepted/rejected changes, and final results.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related issues

Possibly related PRs

  • egraphs-good/egglog-experimental#45: Both PRs modify src/table_stats.rs; this PR extends the same compute_table_stats logic introduced by that PR to cover constructor tables via a fallback to constructor_enodes.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding experimental greedy DAG extraction.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description matches the implemented greedy-DAG extractor, related command options, tests, and dependency updates.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/greedy-dag-extractor

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

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

⚠️ Outside diff range comments (1)
.agents/logs/2026-06-24-greedy-dag-extractor-perf.md (1)

1-1123: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Consider whether this log file belongs in the repository.

This 1123-line performance experiment log in .agents/logs/ appears to be AI session output rather than permanent project documentation. While valuable for understanding optimization decisions, consider:

  1. Moving to docs/perf/greedy-dag-extractor.md with sanitized paths and condensed narrative
  2. Keeping only the "Decision" summaries and final validation sections
  3. Removing the raw log if the key decisions are captured in code comments or a shorter ADR

The current form with extensive local paths, ephemeral temp directories, and external script references (../egglog_repro/) is not reproducible for other contributors.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.agents/logs/2026-06-24-greedy-dag-extractor-perf.md around lines 1 - 1123,
The issue is that this `.agents/logs/` file is a long-lived AI experiment log
with ephemeral paths and session-specific output, so it should not live in the
repository as-is. Move the durable takeaways into a concise doc or ADR such as a
performance note for the greedy-DAG extractor, keeping only the relevant
decision summaries and final validation results from sections like “Final
validation” and “Research idea disposition.” Remove the raw experiment trace,
local temp directories, and one-off command history from the tracked tree, and
leave the repository with a shorter, reproducible narrative that points to the
implemented greedy-DAG changes instead of the transient benchmark log.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.agents/logs/2026-06-24-greedy-dag-extractor-perf.md:
- Line 621: The log entry still says "Status: in progress" for the
diff-reduction pass, so update the status in the relevant log entry to reflect
the actual state if the work is finished, or add a clear finalization note if
the entry is meant to stay historical. Use the existing status field in the log
entry and keep the wording consistent with the surrounding progress notes.
- Line 954: The benchmark log contains ephemeral temp directory paths with
random suffixes, which should be normalized for a permanent record. Update the
referenced path strings in the log entry to use a stable placeholder such as
<tmp-dir> or explicitly label them as example paths, and make the same change
consistently wherever the temp path appears in this document so the logs are
reproducible and not tied to one machine-specific directory.
- Line 265: The experiment log still contains local machine paths like
/private/tmp/ and ../egglog_repro/, which should be sanitized or relocated.
Update the affected entries in the markdown log so they use generic, shareable
paths, and if this content is meant to be a persistent performance note rather
than session output, move it out of the .agents/logs/ area into a more
appropriate docs/perf location. Use the existing log sections and the referenced
Config/command entries to find all remaining path leaks.
- Around line 119-120: The log contains machine-specific absolute paths that
will break for other developers; update the notes in the markdown log to use
generic placeholders or documented variables instead. In the affected log
entries, replace references like the worktree and home-directory paths with
repo-agnostic examples, or add a clear note that they are illustrative, and keep
the guidance consistent across the initial observation and all later path
mentions.

In `@Cargo.toml`:
- Around line 20-22: The three git dependencies for egglog, egglog-ast, and
egglog-reports are currently tracking a moving branch, which makes builds
non-reproducible. Update the dependency entries in Cargo.toml to use the same
fixed immutable git revision instead of branch = "codex/greedy-dag-extractor",
keeping the current repository URL and other settings unchanged.

In `@src/greedy_dag_extract.rs`:
- Around line 843-851: The non-eq root fallback in the greedy extraction branch
ignores nvariants == 0 and may still return one result. Update the logic in the
extract path around extract_best_with_sort to check nvariants first and return
an empty Vec immediately when zero variants are requested, before any fallback
to best extraction or the warning path.

In `@src/keep_best.rs`:
- Around line 124-128: The keep-best extraction path currently reinserts
constructor rows for all enodes, including ones already marked subsumed. Update
the logic in egraph.constructor_enodes so the keep-best flow matches greedy-DAG
discovery by filtering out enodes where enode.subsumed is true before pushing
rows into raw_rows. Use the existing constructor_enodes callback and the
enode.subsumed field to locate and apply the fix.

In `@src/lib.rs`:
- Around line 48-50: The greedy-DAG APIs are not exposed to external callers
because `extract_best_greedy_dag` and `extract_variants_greedy_dag` live in the
private `greedy_dag_extract` module. Update `src/lib.rs` to re-export those
functions alongside `DagCostModel`, or change their visibility to `pub(crate)`
if they are meant only for internal command wiring. Use the existing
`greedy_dag_extract` module and the two function names as the entry points to
fix.

In `@src/multi_extract.rs`:
- Around line 44-49: The UserDefinedCommand implementation for MultiExtract is
missing the extra type bounds needed by extract_variants_greedy_dag. Keep the
MultiExtract struct generic as-is, but update the impl UserDefinedCommand for
MultiExtract<C, CM> so C also requires Ord, Eq, Clone, and Debug alongside Cost
+ Send + Sync. This change should be applied in the MultiExtract impl block and
any related bounds in the UserDefinedCommand methods that call
extract_variants_greedy_dag.

In `@src/set_cost.rs`:
- Around line 220-224: The cost conversion in the set-cost extraction path can
panic on negative user-provided values, so replace the assert-based assumption
in the cost-mapping logic with a safe fallback. Update the closure in
set_cost.rs where egraph.value_to_base::<i64>(c) is converted to DefaultCost to
validate the i64 first, then either skip the invalid row or handle it as invalid
input instead of crashing. Keep the fix localized to the cost-table handling in
the extraction flow.

---

Outside diff comments:
In @.agents/logs/2026-06-24-greedy-dag-extractor-perf.md:
- Around line 1-1123: The issue is that this `.agents/logs/` file is a
long-lived AI experiment log with ephemeral paths and session-specific output,
so it should not live in the repository as-is. Move the durable takeaways into a
concise doc or ADR such as a performance note for the greedy-DAG extractor,
keeping only the relevant decision summaries and final validation results from
sections like “Final validation” and “Research idea disposition.” Remove the raw
experiment trace, local temp directories, and one-off command history from the
tracked tree, and leave the repository with a shorter, reproducible narrative
that points to the implemented greedy-DAG changes instead of the transient
benchmark log.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: f837d2e6-c323-4603-ba89-ae4c438dbaaa

📥 Commits

Reviewing files that changed from the base of the PR and between 6525a48 and b1663ca.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • .agents/logs/2026-06-24-greedy-dag-extractor-perf.md
  • Cargo.toml
  • src/greedy_dag_extract.rs
  • src/keep_best.rs
  • src/lib.rs
  • src/multi_extract.rs
  • src/secondary_map.rs
  • src/set_cost.rs
  • src/sugar/for.rs
  • src/table_stats.rs
  • tests/files.rs
  • tests/integration_test.rs
📜 Review details
⚠️ CI failures not shown inline (2)

GitHub Actions: Build / 1_test.txt: Support greedy DAG extraction in experimental

Conclusion: failure

View job details

##[group]Run bail() {
 �[36;1mbail() {�[0m
 �[36;1m  printf '::error::install-action: %s\n' "$*"�[0m

GitHub Actions: Build / test: Support greedy DAG extraction in experimental

Conclusion: failure

View job details

##[group]Run bail() {
 �[36;1mbail() {�[0m
 �[36;1m  printf '::error::install-action: %s\n' "$*"�[0m
🔇 Additional comments (10)
.agents/logs/2026-06-24-greedy-dag-extractor-perf.md (1)

1076-1076: 📐 Maintainability & Code Quality | ⚡ Quick win

Good documentation of checked-in benchmark methodology.

Lines 1104-1108 and 1115-1119 properly document the checked-in tests/greedy-dag-taylor.egg benchmark and its performance characteristics. This is the right approach for reproducible benchmarks—prefer checked-in files over ephemeral local copies. The note about CodSpeed substring filtering (Line 1108) is valuable context for future maintainers.

Also applies to: 1104-1108, 1115-1119

src/table_stats.rs (2)

264-286: 🎯 Functional Correctness

Verify the table-kind probe does not swallow real backend errors.

Line 264 treats any function_entries failure as “try constructor rows instead.” If EGraph::function_entries can also fail for ordinary backend reasons, this path will mask the real error and retry down the wrong branch. Please confirm the egglog API only returns Err here for non-function tables, or branch on a specific error kind instead.


13-15: 🎯 Functional Correctness

Confirm the hidden-table guarantee matches the implementation.

The updated docs say the no-argument form skips hidden tables, but Lines 345-352 only filter !f.is_let_binding(). Unless get_function_names() already omits hidden symbols, print-table-stats will still report them.

Also applies to: 328-329

src/sugar/for.rs (1)

2-2: LGTM!

Also applies to: 46-48

src/secondary_map.rs (1)

438-438: 🎯 Functional Correctness

No change needed: Option::is_none_or is supported by the declared Rust 1.91.0 toolchain.

			> Likely an incorrect or invalid review comment.
tests/files.rs (1)

3-8: LGTM!

Also applies to: 50-59, 126-239

tests/integration_test.rs (1)

56-75: LGTM!

Also applies to: 120-225, 487-502, 545-577, 767-767, 805-814

src/set_cost.rs (1)

1-11: LGTM!

Also applies to: 240-337

src/multi_extract.rs (1)

6-22: LGTM!

Also applies to: 95-124

src/keep_best.rs (1)

3-23: LGTM!

Also applies to: 35-96, 132-167

Comment thread .agents/logs/2026-06-24-greedy-dag-extractor-perf.md Outdated
Comment thread .agents/logs/2026-06-24-greedy-dag-extractor-perf.md Outdated
Comment thread .agents/logs/2026-06-24-greedy-dag-extractor-perf.md Outdated
Comment thread .agents/logs/2026-06-24-greedy-dag-extractor-perf.md Outdated
Comment thread Cargo.toml
Comment on lines +20 to +22
egglog = { git = "https://github.com/saulshanabrook/egg-smol.git", branch = "codex/greedy-dag-extractor", default-features = false }
egglog-ast = { git = "https://github.com/saulshanabrook/egg-smol.git", branch = "codex/greedy-dag-extractor", default-features = false }
egglog-reports = { git = "https://github.com/saulshanabrook/egg-smol.git", branch = "codex/greedy-dag-extractor", default-features = false }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

git ls-remote https://github.com/saulshanabrook/egg-smol.git refs/heads/codex/greedy-dag-extractor

Repository: egraphs-good/egglog-experimental

Length of output: 249


Pin the egglog git dependencies to a fixed commit.
Cargo.toml:20-22 currently tracks branch = "codex/greedy-dag-extractor" for all three crates; switch them to the same immutable rev to keep builds reproducible and avoid unexpected API drift.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Cargo.toml` around lines 20 - 22, The three git dependencies for egglog,
egglog-ast, and egglog-reports are currently tracking a moving branch, which
makes builds non-reproducible. Update the dependency entries in Cargo.toml to
use the same fixed immutable git revision instead of branch =
"codex/greedy-dag-extractor", keeping the current repository URL and other
settings unchanged.

Comment thread src/greedy_dag_extract.rs
Comment thread src/keep_best.rs Outdated
Comment thread src/lib.rs Outdated
Comment thread src/multi_extract.rs
Comment thread src/set_cost.rs Outdated
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.

1 participant