Replace scalar Dijkstra with measured Pareto label-setting search (#1076)#1084
Merged
Conversation
) Implements M3 (F3b) of the Symbolic Growth Domain & Pareto Search milestone. The scalar Dijkstra in `ReductionGraph` had a path-dependent-cost hole: when two paths reached the same node, only the cheaper-so-far label's size was kept, so a cheaper-but-larger intermediate state could poison downstream choices (issue #788). This replaces it with a generic multi-label Pareto search plus a measured concrete-instance label domain. - New `PathLabel` trait (`extend` + `dominates` + `cost`) and a generic `pareto_search` kernel over per-node antichain bags with predecessor pointers, branch-and-bound, deterministic safety caps (hop cap 16, bag cap 32 with a deterministic tie-break), and a deterministically ordered Pareto front. - `CostLabel`: scalar formula label reproducing Dijkstra behavior for the existing `PathCostFn` cost functions; `find_cheapest_path*` keep their signatures and now run the kernel with it. - `MeasuredLabel` (`src/rules/pareto.rs`): the concrete-instance label. Its `extend` runs a four-part pruning stack in order — (1) symbolic pre-flight guard (evaluated in f64 so a `2^num_vertices` prediction is refused without executing, making OOM structurally impossible), (2) execute + measure the real target size, (3) branch-and-bound, (4) componentwise measured-size dominance (disabled by the `exhaustive` flag; guards 1-3 stay sound). A caught panic from a reduction whose preconditions the instance violates prunes that edge. - `MeasuredPath` carries the constructed reduction chain (via `Rc`) so downstream solve/witness extraction reuses it without re-executing. - `ILPSolver::best_path_to_ilp` now uses `find_measured_best_path_to_name`, ranking ILP variants by real measured size instead of step count / formula. Verification (all green): #788 known-answer test (HC on the prism graph selects the measured optimum), OOM pre-flight guard test (64-vertex HighlyConnectedDeletion refused in <1ms, exponential construction never started), and a hand-built diamond negative control where scalar cost selection commits to P1 while the Pareto search returns the strictly-better-final-size P2. Closes #788. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EgxSbn5gwizTBkC22eyWXR
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## 1075-growth-domain #1084 +/- ##
====================================================
Coverage 98.00% 98.00%
====================================================
Files 1044 1046 +2
Lines 107195 107670 +475
====================================================
+ Hits 105053 105525 +472
- Misses 2142 2145 +3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements M3 (F3b) of the Symbolic Growth Domain & Pareto Search milestone. Independent of the growth domain (measured labels are numeric; the pre-flight guard uses plain
Expr::eval). References #1076.Closes #788.
The bug
ReductionGraph's scalar Dijkstra evaluated each edge's cost at the size accumulated along the path — a path-dependent cost, which violates Dijkstra's assumptions. When two paths reached the same node, only the cheaper-so-far label's size was retained, so a cheaper-but-larger intermediate could poison downstream choices and miss the path with the smallest final target.Design
Pareto label-setting kernel (
pareto_search, generic over a newPathLabeltrait):cost()order (early branch-and-bound bound).(cost, hops, node-name path)tie-break (never iteration-order truncation). Edges visited in deterministic(target-name, target-variant)order.find_cheapest_path*keep their signatures and return the front's best element.Two label domains:
CostLabel— scalar formula label that reproduces Dijkstra behavior for the existingPathCostFnfunctions (used byfind_cheapest_path*).MeasuredLabel— the concrete-instance label. Itsextendruns a four-part pruning stack, in order:f64, so a2^num_verticesprediction is flagged rather than overflowingusize); over budget ⇒Nonewithout executing. This makes OOM structurally impossible during selection.None. A caught panic from a reduction whose preconditions the instance violates prunes that edge.exhaustiveflag disables only this guard (1–3 stay sound).MeasuredPathcarries the constructed reduction chain (Rc), so the winner's downstream solve/witness extraction reuses it with no re-execution. Default size budget10^7, overridable.Behavioral changes
ILPSolver::best_path_to_ilpnow callsReductionGraph::find_measured_best_path_to_name, ranking ILP variants by real measured size instead of step count + formula. It returns aMeasuredPath, andtry_solve_via_reductionsolves/extracts from the already-built chain (no re-reduction). Aggregate-path detection (for theWitnessPathRequirederror) still uses a formula existence check.find_cheapest_path/find_cheapest_path_modenow run the generic kernel withCostLabel(olddijkstradeleted); signatures and observed behavior unchanged.find_all_paths/find_paths_up_to*untouched. Growth domain /canonical.rsuntouched.Verification (exact results)
test_hamiltoniancircuit_to_ilp_measured_optimum_788): HamiltonianCircuit on the prism graph (6 vertices, 9 edges). The literal chain quoted in [Enhancement] ILP path selector over-prioritizes step count over final output size #788 (HC → HP → ConsecutiveOnesSubmatrix → ILP = 60) no longer exists on the current reduction graph. The measured search now selects the current measured optimum HC → LongestCircuit → ILP<bool>, total 232 (num_constraints=127, num_vars=105), ahead of RuralPostman → ILP<i32> (366) and TravelingSalesman → ILP<bool> (768). The test pins this, proving ranking by measured final size. Note: on today's graph the old formula-selector coincidentally also lands on 232 (the LongestCircuit overhead formula is tight), so the genuine behavioral proof of the path-dependent-cost fix is the diamond negative control below.test_oom_preflight_guard_highlyconnecteddeletion): a dense 64-vertexHighlyConnectedDeletionrouted at the2^num_verticesedge (highlyconnecteddeletion_ilp). Guard 1 evaluates2^64 ≫ 10^7and skips; the search returnsNonein ~50 µs (asserted< 1s), never starting the exponential cluster enumeration. Without the guard this OOMs.cargo testfully green — lib 5265 passed, integration 75 passed, CLI 495 passed — including the closed-loop reduce tests routing throughfind_cheapest_path.test_negative_control_diamond_pareto_beats_scalar): a hand-built 4-node diamond (test-onlyReductionGraph::from_test_edges). P1 (S→M→T) has the lower first-edge cost but a larger measured intermediate at M; P2 (S→P→M→T) has a higher first-edge cost but a strictly smaller final measured size. Scalar-cost selection (find_cheapest_path) returns P1; the Pareto search keeps both incomparable routes into M and returns P2 (final size 6). This is the behavioral proof the old Dijkstra could not pass.cargo clippy --all-targets --features ilp-highs -- -D warningsclean, CLI clippy clean,cargo fmt --checkclean.Notes for reviewers
MeasuredLabelexecutes reductions during exploration; a reduction that panics on an infeasible intermediate is treated as an infeasible edge (caught, pruned). A thread-local silencer keeps these expected/recovered panics off stderr without affecting other threads.🤖 Generated with Claude Code
https://claude.ai/code/session_01EgxSbn5gwizTBkC22eyWXR