Skip to content

Make solve deterministic with registered solver backends#1091

Open
isPANN wants to merge 3 commits into
mainfrom
codex/solver-backend-registry
Open

Make solve deterministic with registered solver backends#1091
isPANN wants to merge 3 commits into
mainfrom
codex/solver-backend-registry

Conversation

@isPANN

@isPANN isPANN commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

This PR makes solve deterministic by separating registered solver execution from exploratory reduction-path search.

1. Changed — solve no longer searches for a reduction path

Previously, solving a concrete problem could invoke reduction-path search at runtime. The selected route could therefore change when unrelated rules, search heuristics, or graph traversal order changed.

Now solve only executes a workflow registered for the exact source variant. Path discovery remains available through the separate path command.

Before: solve(instance) -> search reduction graph -> choose discovered path -> solve
After:  solve(instance) -> look up exact registration -> execute fixed workflow

This makes repeated calls operationally deterministic: the same exact variant selects the same workflow and reduction chain.

2. Added — an exact-variant solver registry

Registrations are keyed by the complete variant, not only by problem name. This prevents a capability registered for one representation from leaking into another representation.

For example, these are different registry keys:

MaximumIndependentSet<SimpleGraph, One>
MaximumIndependentSet<SimpleGraph, i32>
MaximumIndependentSet<UnitDiskGraph, i32>

A pipeline registered for the first key does not automatically claim support for either of the other two.

3. Added — seven native solver registrations

Existing dedicated algorithms are now registered as native solvers with stable implementation metadata. Native is a real source-problem solver: it does not mean “find a reduction path.”

Examples:

TimetableDesign
  -> native implementation: timetable-required-assignments

RootedTreeArrangement<SimpleGraph>
  -> native implementation: rooted-tree-arrangement

Native registrations derive their source variant from Problem::variant, so the variant is not duplicated manually beside the problem type.

4. Added — 151 fixed reduction-to-ILP workflows

The previously exported executable solve routes are registered as fixed pipelines. A pipeline may contain one or many reductions and may terminate at either directly supported ILP variant: ILP<bool> or ILP<i32>.

Boolean-terminal example:

MaximumIndependentSet<SimpleGraph, One>
  -> MaximumIndependentSet<SimpleGraph, i32>
  -> MaximumSetPacking<i32>
  -> ILP<bool>
  -> ILP backend
  -> extract MaximumIndependentSet solution

Integer-terminal example:

RootedTreeArrangement<SimpleGraph>
  -> RootedTreeStorageAssignment
  -> ILP<i32>
  -> ILP backend
  -> extract RootedTreeArrangement solution

The second pipeline stops at ILP<i32>. It does not continue through the available ILP<i32> -> ILP<bool> reduction merely to normalize the terminal type. Registry validation rejects a pipeline that continues after reaching the first directly supported ILP node.

Direct ILP inputs require no reduction:

ILP<bool> -> ILP backend
ILP<i32>  -> ILP backend

5. Changed — backend selection and fallback have fixed semantics

Default selection is:

registered native solver
  else registered reduction-to-ILP workflow
  else brute force

Concrete examples:

Source variant Available workflow(s) Default
TimetableDesign native, brute force native
MaximumClique<SimpleGraph, i32> reduce to ILP<bool>, brute force reduce to ILP
RootedTreeArrangement<SimpleGraph> native, reduce to ILP<i32>, brute force native
MaxCut<SimpleGraph, i32> brute force brute force
ILP<i32> ILP backend, brute force ILP backend

--solver ilp explicitly selects the registered reduction-to-ILP workflow. For RootedTreeArrangement, this means reduce through RootedTreeStorageAssignment to ILP<i32>; it does not mean the source problem itself is an ILP.

Fallback only selects an available workflow. It does not occur after execution begins. For example, if the selected ILP workflow reports an infeasible instance, solve returns the ILP no-solution result instead of silently rerunning brute force. --solver ilp also fails when no pipeline is registered rather than searching for one.

6. Added — structured execution metadata

CLI and MCP results now identify the workflow that actually executed.

Native example:

{
  "kind": "native",
  "implementation": "rooted-tree-arrangement"
}

Reduction-to-ILP example:

{
  "kind": "ilp",
  "reduction_path": [
    "RootedTreeArrangement<SimpleGraph>",
    "RootedTreeStorageAssignment",
    "ILP<i32>"
  ]
}

Here kind: "ilp" identifies the final backend; reduction_path identifies how the source reached that backend. Direct inputs use ["ILP<bool>"] or ["ILP<i32>"].

Brute-force example:

{ "kind": "brute-force" }

The execution object is returned under the top-level solver field alongside problem, evaluation, and the optional solution. Different backends must return an optimal source-problem solution, but this PR does not require them to choose the same lexicographically canonical witness.

7. Added — exact solver capabilities in inspect

inspect exposes the same registry information without executing the problem. For RootedTreeArrangement<SimpleGraph>, the relevant output has this shape:

{
  "solvers": ["native", "ilp", "brute-force"],
  "default_solver": "native",
  "solver_capabilities": {
    "native": {
      "implementation": "rooted-tree-arrangement"
    },
    "ilp": {
      "reduction_path": [
        "RootedTreeArrangement<SimpleGraph>",
        "RootedTreeStorageAssignment",
        "ILP<i32>"
      ]
    },
    "brute_force": true
  }
}

Library, CLI, MCP, and reduction-bundle solving all read from the same resolver rather than maintaining separate capability rules.

8. Removed — search-based and ambiguous solver interfaces

Runtime path discovery and CustomizedSolver are removed from solver dispatch. The public override accepts only ilp and brute-force; auto, customized, native, and internal implementation IDs are rejected.

Native is not an override because a registered native solver is already the deterministic default. This avoids exposing implementation IDs such as rooted-tree-arrangement as long-term CLI API.

The old output field is also removed:

{ "reduced_to": "ILP" }

It could not represent native or brute-force execution and did not say whether the source reduced to ILP<bool> or ILP<i32>. The structured solver object in item 6 replaces it.

9. Changed — shared dispatch code and shorter execution paths

CLI and MCP now share solver-selector parsing, capability rendering, and solve-result JSON rendering. Fixed pipeline edges are indexed once by exact source/target key instead of rescanning every registered reduction for every step.

Brute-force witness lookup now stops at the first optimal witness instead of collecting every optimal witness. An explicit brute-force request also returns before initializing the native/ILP registry.

The focused two-bit test demonstrates the traversal change when the first configuration is an optimal witness:

Before: 4 aggregate evaluations
      + 4 repeated aggregate evaluations
      + 4 evaluations while collecting all optimal witnesses
      = 12 evaluations

After:  4 aggregate evaluations
      + 1 evaluation to find the first optimal witness
      = 5 evaluations

10. Added — registry validation and behavioral coverage

Registry construction now has direct tests for:

  • unknown exact variants;
  • empty pipelines;
  • terminal targets other than ILP<bool> or ILP<i32>;
  • missing or ambiguous exact reduction edges;
  • pipelines that continue after the first supported ILP node.

Behavioral tests cover native-only, direct reduction to ILP, multi-hop reduction to ILP, native plus optional ILP workflow, brute-force-only, and direct ILP<bool>/ILP<i32> cases. They also lock the three solver.kind JSON forms, repeated-call determinism, explicit override behavior, and the rule that an infeasible selected ILP workflow does not fall back.

Verification

  • cargo fmt --all -- --check
  • cargo clippy --all-targets --features ilp-highs -- -D warnings
  • cargo clippy -p problemreductions-cli --all-targets --features highs -- -D warnings
  • cargo test -p problemreductions solvers:: --features ilp-highs — 81 passed
  • cargo test --test solver_routes_fixture --features 'ilp-highs example-db' — all 239 historical variants replayed (local-only fixture, intentionally not committed)
  • make check — 5,402 library tests, 75 integration tests, 322 CLI tests, 17 pred-sym tests, and all doc tests passed
  • architecture guard confirms that solver dispatch contains neither runtime path-search calls nor the removed CustomizedSolver

Closes #1090

@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.69484% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.97%. Comparing base (cb54ea0) to head (ce27fc7).

Files with missing lines Patch % Lines
src/solvers/registry.rs 95.73% 9 Missing ⚠️
src/solvers/ilp/solver.rs 86.20% 4 Missing ⚠️
src/unit_tests/solvers/brute_force.rs 83.33% 3 Missing ⚠️
src/unit_tests/solvers/registry.rs 98.70% 3 Missing ⚠️
src/solvers/resolver.rs 98.71% 1 Missing ⚠️
src/unit_tests/solvers/resolver.rs 99.31% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1091      +/-   ##
==========================================
- Coverage   98.00%   97.97%   -0.04%     
==========================================
  Files        1044     1048       +4     
  Lines      107366   107945     +579     
==========================================
+ Hits       105228   105755     +527     
- Misses       2138     2190      +52     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@isPANN
isPANN marked this pull request as draft July 21, 2026 06:22
@isPANN
isPANN marked this pull request as ready for review July 21, 2026 06:33
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.

Make pred solve deterministic through registered solver pipelines

1 participant