Skip to content

[Refactor] Separate model and reduction semantics from solver execution: repository audit and implementation plan #1145

Description

@isPANN

Goal and responsibility boundaries

This package is about problem models, mathematical reductions, and solution mappings. Models and rules must not change their mathematical semantics to accommodate a particular solver's numerical range, tolerances, termination statuses, or search capabilities.

Implement this work under the following contract:

pred solve returns a solution satisfying the problem definition and the requested solve guarantee (including optimality when required), or an explicit non-solution outcome/error. extract_solution and pred extract assume a witness satisfying the reduction's documented premises and map it directly. They do not recheck feasibility, optimality, or solver correctness. Callers supplying witnesses outside that contract receive no mathematical correctness guarantee. Parsing and type conversion remain transport responsibilities. Every witness satisfying the premises must map correctly, including every tied optimum when optimality is required.

Layer Responsible for Not responsible for
Problem / model Mathematical meaning of instances and solutions, feasibility, objective values, correct computation in the declared representation HiGHS tolerances, solver statuses, search size, recovering from solver failure
ReduceTo / ReductionResult Target construction, applicability domain, parameter relationships, mapping target witnesses that satisfy the preconditions into source witnesses Calling a backend, repairing backend solutions, independently checking global optimality
HiGHS adapter Encoding native ILP for the backend, execution, status interpretation, numerical decoding, and checking witnesses against the original ILP Branching on source model names, changing constraints, independently proving global optimality
Solver orchestration Deterministic selection of registered capabilities, executing reduction chains, using conclusions accepted by the adapter, extracting source witnesses and computing requested output values Adding another backend precision policy, silently switching solvers after failure
CLI / MCP Calling public APIs, transporting and presenting results Implementing separate model validation, reductions, or solver conclusions

Optimal / Infeasible are conclusions under the selected backend's contract, not additional formal proofs. The adapter may return a successful optimal solution only when the backend reports optimality and its witness passes validation; timeouts, non-optimal termination, and invalid results must produce explicit errors. Floating-point backend arithmetic alone does not justify adding a certificate system or downgrading all normal results throughout the package.

Incorrect coefficients, overflow, discarded nonzero terms, and incorrect witness mappings remain this library's responsibility. Floating-point models use their declared ordinary floating-point arithmetic; this does not require converting every computation to exact arithmetic. Backend tolerances must not define model semantics.

Audit scope and evidence

Audit baseline: refactor/native-ilp-adapter, commit 59775947c40be6c0a3b842b3935f96fb879f0d95. PR #1147 already introduces the native ILP adapter on this branch. Check whether this work has reached the main branch before implementation; do not create another adapter.

The repository-wide static scan covered:

  • src/models/: 208 Rust files across graph/formula/set/algebraic/misc and shared model code.
  • src/rules/: 286 Rust files, including direct reductions, casts, shared extractors, parameter metadata, the reduction graph, and geometric mappings.
  • 201 files under src/unit_tests/models/, 278 under src/unit_tests/rules/, plus solver and integration tests.
  • 21 files under src/solvers/, 11 under src/topology/, the registry, generated macros, CLI/MCP, example-db, design and paper documentation, skills, and review scripts.
  • The current workspace also contains untracked .agents/skills/, verifier/, and some skills. Their relevant policies were inspected, but these files are not part of the baseline commit. Do not include these directories wholesale in implementation commits.

Method: scan solver calls, floating-point tolerances/conversions, search dimensions, constructor domain restrictions, shared validation APIs, and their callers; then read the relevant production paths in full. Distinguish production methods from example-db builders and tests. This is a repository-wide responsibility-boundary audit, not a claim to have reproved every reduction theorem or run the entire test suite.

Checks actually performed:

  1. A temporary Rust program outside the repository, depending directly on the current source, confirmed:
    • For ILP<bool,f64>, 5e-10*x <= 0 evaluates to Ok(true) at x=1.
    • The same instance has universe_size=1, but the constructed target has 0 constraints.
    • For CVP with B=[[1]] and both the integer target and witness equal to 2^53, zero-distance evaluation fails with InexactFloatConversion.
    • The same CVP instance serializes integer JSON through the existing dynamic serde API, contrary to the documented universal JSON range restriction.
  2. cargo test --lib generic_decision_ilp_respects_maximization_bounds -- --nocapture passed. This test explicitly requires UnresolvedDecision when a triangle has no independent set of size 2, confirming the current behavior.
  3. Enumerating all 16 assignments in the existing QUBO scaling test gave objective values in [-3.3e-8, 6e-9]. Their spread, 3.9e-8, is smaller than the test's absolute tolerance of 1e-7; this branch cannot distinguish the best assignment from the worst.

A. Models contain numerical transport and backend acceptance policies

A1. Floating-point ILP tolerances expand the feasible set — high priority

Locations: ILP model, adapter decoding and validation.

The f64 implementation of ILPCoefficient::satisfies() relaxes comparisons by 1e-9 * max(|lhs|, |rhs|, 1). This affects Problem::evaluate(), brute force, direct extraction, and any future backend, not just the HiGHS interface. from_integer() also ties floating-point model evaluation to the global exact-float conversion gate.

Cause: Model evaluation, numerical encoding, and backend acceptance share the model's comparison/conversion methods.

Changes:

  • Keep the existing ILPCoefficient, LinearConstraint::is_satisfied(), ILP::is_feasible(), and Problem::evaluate() path. Define one consistent model arithmetic/comparison contract there. Do not add evaluate_for_highs, lenient, strict, or caller-dependent switches.
  • Remove backend-style tolerance relaxation from model comparisons. Evaluate and compare f64 expressions using ordinary finite floating-point arithmetic; retain integer semantics for integer expressions. Converting integer variable values into a floating-point domain follows that domain's declared ordinary rounding semantics, not the backend's input-range policy.
  • Handle returned-value integrality and range at the adapter's decoding boundary, then call the same evaluation/witness API on the original ILP. Report InvalidSolution when that model rejects the witness; do not alter constraints or add a permissive model branch.
  • Some floating-point equality instances may consequently report an invalid returned solution. Expose that limitation through an explicit error rather than expanding the model's feasible set to pass tests.

Acceptance: A small constraint-violating assignment is rejected by model evaluation and adapter validation. Extraction has no invalid-witness rejection requirement. Adapter decoding policies are tested in the adapter module. All four native integer/floating-point ILP combinations continue to use one execution entry point.

A2. ExpectedRetrievalCost computes the same mathematical quantity through two conversion paths

Locations: model expected_cost, rule latency_distance and coefficient construction.

The model converts usize latency to i64, then through i64_to_exact_f64; the rule duplicates the latency formula and casts directly with as f64. The same quantity therefore has conflicting representation policies.

Changes: Make the existing latency calculation a pure mathematical method owned by the model and reused by both callers. Convert under the floating-point model's single arithmetic policy. Delete the duplicated formula and backend-style precision gate in the model. Retain finite-probability checks, the input representation convention for probability sums, and non-finite arithmetic checks; these are not HiGHS tolerances.

Acceptance: Explicit assignments on small instances produce corresponding objectives in the source model and constructed target. Do not create a numerical conversion helper for each rule.

B. CVP's integer semantics and existing exact algorithm are restricted by an f64 interface

Locations: CVP model, customized CVP solver, SubsetSum→CVP, CVP→QUBO. Coordinate with #1146.

Findings and causes:

  • ClosestVectorTarget::to_f64() serves both model evaluation and the solver. Integer bases, targets, and witnesses pass through a floating-point gate first.
  • The solver already uses BigRational, but integer basis entries must pass an f64 check before rational conversion, and integer targets detour through f64. Removing one check alone leaves equivalent restrictions elsewhere.
  • Model evaluation uses floating-point accumulation and sqrt(). The SubsetSum rule stores and compares against sqrt(n). Rank checks and the rule's exact elimination also use different intermediate representations.

Plan:

  1. Replace the f64-centric numerical method on the existing ClosestVectorTarget with the mathematical coordinate conversion the model needs. Integers enter integer/rational arithmetic directly; finite f64 coordinates enter the existing rational representation according to their stored values. Remove integer→float→rational round trips.
  2. Use squared distance as the common CVP objective. Provide one model-owned squared_distance() implementation and use it from Problem::evaluate(). Represent squared distance with the existing BigRational dependency and update the Min value type; enable serialization support on that dependency as needed, without adding an arithmetic framework. Squared distance preserves minimizers, but the returned objective API and documentation must explicitly change.
  3. Use the same coordinate semantics in existing sphere enumeration. Remove f64 gates on bases, targets, and candidate coefficients. After removing those gates, retain necessary checked arithmetic for the algorithm's actual i64 candidate updates; a floating-point range gate must not substitute for integer arithmetic checks.
  4. Use the integer squared threshold n in SubsetSum→CVP. Remove sqrt(n) and threshold-protection explanations motivated by floating-point precision. Reuse the model's squared-distance API rather than privately recomputing another distance in the rule.
  5. Retain CVP→QUBO's mathematical finite box and quadratic expansion. If constructed coefficients cannot fit the target's i64 representation, return a reduction error. Fix rank/independent-row selection in the model's existing shared computation path; do not reorder rows in one rule merely to bypass a model validator defect. Mathematically meaningful triangular structure may remain.
  6. Update registration, DynProblem evaluation/serialization, examples, the paper, and typed/dynamic callers. Do not retain old distance evaluation as a hidden compatibility branch.

Acceptance: Ordinary integer and floating-point targets use the same mathematical interface. Zero/nonzero distances, SubsetSum YES/NO thresholds, and CVP→QUBO mappings are correct. Retain the existing large-integer zero-distance failure as one regression for this shared path, without spreading it across other models.

C. Direct witness extraction and a minimal public API

Contract: Extraction maps witnesses satisfying the rule's mathematical premises. It does not establish those premises. State whether a rule requires feasibility or optimality and prove the mapping for every qualifying target witness. A public API may have this precondition. An invalid manually supplied witness is not evidence of a reduction defect.

Confirmed implementation findings:

  • problemreductions-cli/src/dispatch.rs, BundleReplay::extract, evaluates terminal feasibility and rejects input before mapping.
  • src/rules/graph.rs, ReductionChain::extract_solution_any, mixes reverse witness mapping with aggregate evaluation and Or(false) interpretation. Typed and JSON extraction translate its None into a premise error. ExecutedPath::extract_solution separately implements the reverse loop.
  • src/rules/ilp_qubo.rs, ReductionILPToQUBO::extract_solution, evaluates QUBO energy and rejects a missing ILP feasibility certificate before projecting original variables.
  • The solver resolver and compiled ILP pipeline call the same mixed-purpose chain method. Removing its checks without updating those callers would change solve behavior.

Changes:

  1. Keep Problem::evaluate and the typed ReductionResult::extract_solution signatures. Remove validation-only extraction calls, bounds, and helpers. Do not replace them with assertions, certificates, checked/unchecked variants, fallback values, or renamed wrappers.
  2. Remove the feasibility acceptance gate from BundleReplay::extract. Keep file/JSON/type errors. Evaluation required for existing output may remain, with actual evaluation errors propagated, but must not serve as a feasibility or optimality acceptance gate. Do not add final source validation.
  3. Make chain extraction perform only reverse witness mapping. Move source solve-outcome interpretation to the existing solver orchestration, using existing aggregate relationships and retained reduction steps. Update typed solve, resolver, fixed ILP pipelines, and bundle solve together. Do not introduce a mode parameter or duplicate threshold handling in CLI code.
  4. Remove QUBO certificate checking from witness projection only together with the corresponding solve-path handling. Audit other rule extractors for repeated constraint checks and states excluded by their premises. Keep genuine mathematical cases and actual representation failures reachable under the contract. Necessary normalization is part of a mapping, not a fallback.
  5. Verify each affected rule's actual theorem and domain. A valid target optimum that cannot yield the promised source solution is a construction/domain/capability defect, not an excuse for a new extraction guard. Threshold/value-only guarantees must not be advertised as unconditional witness guarantees. Preserve legitimate aggregate solving through existing APIs.
  6. Preserve tests unless they require a removed guard or API. Do not add invalid-witness rejection tests as a universal rule requirement. Test all qualifying target optima on appropriate small instances, source feasibility/objectives, and multi-step composition.

Public API inventory and dispositions

Current method Visibility and actual role Planned disposition
ReductionResult::extract_solution Public typed mathematical mapping implemented by rules Keep name and signature; document input premises and output guarantee
ReductionChain::extract_solution Public typed chain entry Keep; delegate to the single direct reverse mapping implementation
ReductionChain::extract_solution_any pub(crate); currently shared by extraction and solver outcome interpretation Remove this mixed-purpose method and its Option-as-premise protocol. Do not replace it with another suffixed extraction facade. Any unavoidable internal reverse traversal must be private, perform only mapping, and be shared by the existing callers
DynReductionResult::extract_solution_dyn Method on the existing public trait in rules::traits; blanket implementation supplies type erasure, registry/macro code uses the trait Retain only the necessary object-safe bridge to the typed rule implementation. Do not add sibling methods or mathematical checks. Audit registry field types and macro expansion before narrowing visibility; a crate-private re-export alone does not make the underlying public trait private
ReductionChain::extract_solution_json Public transport entry used by the separate CLI crate Keep the existing required JSON boundary for this change; restrict it to deserialize, invoke the same mapping, serialize. Do not create JSON-specific mathematical behavior or additional format variants
ExecutedPath::extract_solution Public path entry with a second reverse traversal implementation Preserve the caller-facing operation; consolidate the duplicated traversal with chain extraction using existing storage/types. Do not introduce a new path interface or rebuild executed reductions

The dynamic solve API uses DynAggregateReductionResult::source_has_solution in place of extract_value_from_solution_dyn. It interprets the mapped optimum using the registered source variant's value_has_solution callback, generated from SolutionAggregate. This replaces the hard-coded Or(false) handling and supports optimization aggregates such as Extremum(None) without restricting general aggregate-only Sum mappings. Problem, ReductionResult, and the typed extract_value signatures remain unchanged. No additional extraction variant is introduced.

The aim is fewer responsibilities and implementations, not cosmetic renaming. Repeated implementations of the same trait method on individual rules are normal polymorphism; parallel suffixed entry points with different semantics are not. Do not remove ExtractionResult or bulk-rename the public API merely to shorten names. Every API removal/visibility change must list migrated in-repository callers and remove the superseded implementation without compatibility aliases.

Examples and acceptance: On the path a-b-c, the maximum independent set {a,c} maps by complement to the minimum vertex cover {b} without revalidation. An arbitrary non-independent set is outside the contract. Typed, JSON, and executed-path extraction have the same mapping semantics. Decision NO belongs to solving; extraction neither derives NO nor validates the caller's claim of optimality.

D. Solver orchestration applies inconsistent policies to the same backend conclusion — high priority

Locations: CompiledIlpPipeline, resolver, typed ILPSolver, CLI bundle. Coordinate with the witness-validation portion of #1141.

Findings:

  • After the adapter accepts a target optimum, the pipeline still returns UnresolvedDecision when the mathematical threshold is not satisfied.
  • Evaluation defines model semantics and supplies requested display values; extraction does not use it as an acceptance gate.
  • Typed and dynamic solve paths must share mathematical threshold interpretation without adding final source-witness validation.
  • The CLI bundle does not reuse the fixed pipeline's aggregate threshold interpretation when converting a successful target solve into a source conclusion.

Changes:

  1. Keep HighsAdapter::solve(&ILP<V,C>) as the only backend execution entry for the four native ILP combinations. Preserve deterministic registration and native terminal paths. Encoding/returned-value range restrictions belong in the adapter.
  2. Read native HiGHS statuses in the existing adapter. Use the already-required HiGHS Rust bindings directly so Infeasible and UnboundedOrInfeasible remain distinct. A zero-objective disambiguation solve is permitted only for UnboundedOrInfeasible; a definite Infeasible result returns directly. Only native Optimal with an accepted witness and zero optimality gap may succeed. Preserve time-limit, interruption, memory/iteration/solution-limit, model-loading, and execution failures as explicit errors; configured time limits must not turn unrelated failures into timeouts. Models/rules must not trigger retries based on numerical magnitude, and general failures must not become infeasibility.
  3. After the adapter accepts an optimum, interpret decision thresholds through existing typed aggregate relationships, with DynAggregateReductionResult::source_has_solution() for registered dynamic solve paths. A missed threshold produces source NO/Infeasible. Delete UnresolvedDecision, its dedicated handling, and tests permitting that behavior.
  4. Keep aggregate solve-outcome interpretation in existing solver orchestration and witness mapping in the reduction layer. Both fixed pipelines and explicit bundles must reuse the same solver-side outcome interpretation and the same direct mappings. Retain existing executed steps and aggregate APIs; no new dispatch layer, parallel extractor, or CLI-specific threshold branch.
  5. Compute source evaluation only when needed for output, propagating actual evaluation errors. Do not add source-feasibility acceptance gates after successful extraction. Do not use evaluate_dyn() as an extraction acceptance gate.
  6. Keep SolveOutcome's Optimal / Infeasible under the existing backend contract, with typed errors for operational failures. Update error enums, exhaustive matches, CLI/MCP documentation, and downstream integration tests. Do not add compatibility wrappers for old errors.

Acceptance: For a triangle, independent-set threshold 1 yields YES and threshold 2 yields NO. Typed/default/explicit ILP/bundle paths agree. Invalid backend witnesses are rejected by the adapter; manually supplied witnesses outside the extraction contract have no mapping-correctness guarantee. Timeouts and backend failures never become NO. Test errors in shared orchestration/adapter code, without manufacturing HiGHS precision failures for every source model.

E. A global numerical gate conflates three different responsibilities

Main callers:

Path Actual responsibility Change belongs in
solvers/ilp/adapter.rs HiGHS input encoding and returned-value decoding Adapter transport contract
models/algebraic/ilp.rs, models/misc/expected_retrieval_cost.rs Floating-point model evaluation Model arithmetic policy in A
models/algebraic/closest_vector_problem.rs, solvers/customized/closest_vector_problem.rs, rules/subsetsum_closestvectorproblem.rs CVP coordinates and distances Common mathematical interface in B
rules/ilp_i64_ilp_f64.rs, qubo_casts.rs, spinglass_casts.rs, maximumsetpacking_casts.rs, closestvectorproblem_casts.rs Explicit numerical variant conversion Mathematical contract of the conversion
topology/kings_subgraph.rs, triangular_subgraph.rs, unit_disk_graph.rs; rules/maximumindependentset_casts.rs Discrete-to-floating-point geometry Representation contract preserving actual adjacency
rules/unitdiskmapping/weighted.rs Mathematical gadget weight construction Gadget and target numerical-domain contracts

Finding: types.rs incorrectly describes 2^53-1 as the largest integer exactly representable by f64. The conservative supported conversion range is valid; document it as an interface limit. Conversely, a lossless scalar conversion does not establish that subsequent floating-point accumulation or backend solving is free of rounding.

Changes:

  • Restrict existing i64_to_exact_f64() to callers that actually need lossless scalar conversion. Keep a simple range check: accept [-(2^53-1), 2^53-1] and reject everything outside it. Correct error messages and constant documentation to describe this supported range. Reuse this helper instead of scattering casts and reverse-conversion branches. Check the boundary once; do not turn this into a precision audit for every rule.
  • Backend input acceptance is defined by the adapter's encoding contract. It must not restrict integer model construction, integer rules, or evaluation that does not use that backend.
  • Remove solver-path dependencies on explicit integer→floating-point variant edges used only for backend execution; the native ILP pipeline already avoids that step. Retain conversions with independent mathematical uses as ordinary explicit rules, documenting their representation domain and formal coefficient/coordinate embedding without promising error-free machine evaluation or HiGHS optimality.
  • Do not build whole-instance precision proofs, coefficient-sum safety thresholds, or optimizer revalidation to preserve cast edges. transform = exact describes parameter relationships, not exact numerical solving.
  • A geometric mapping that changes adjacency is a reduction error. Preserve existing adjacency checks and mathematical gadget weight bounds. Do not remove these as solver-precision policy.
  • Correct docs/src/design.md's claim that CLI/MCP universally reject large integer JSON. No corresponding public transport gate was found, and dynamic serde output was verified. Describe the actual public codecs; do not impose floating-point limits on every Rust model for a particular consumer.

Acceptance: Lossless conversion has one implementation and test location. Integer models/rules operate within their own declared domains independently of HiGHS support. Geometric edges preserve adjacency. The paper distinguishes formal embeddings from machine computation.

F. Search-space representation still constrains models

Locations: IntegerKnapsack, OpenShopScheduling, BruteForceProblem / CartesianIndices, registration macros. Coordinate with #1143.

Findings and causes:

  • IntegerKnapsack's evaluate() calls BruteForceProblem::dimensions() to check multiplicity; its constructor also requires capacity/size+1 to fit usize. Mathematical evaluation depends on the searcher's domain-length representation.
  • OpenShopScheduling's constructor requires the enumeration horizon plus one to be representable. Other models have unchecked products/casts in derived dimensions, such as capacity as usize + 1 in flow dimensions.
  • Shared CartesianIndices first requires the product of all coordinate cardinalities to fit usize, although one assignment may be small.

Changes:

  1. Evaluate IntegerKnapsack directly from mathematical multiplicity, capacity, and objective arithmetic, without calling a solver trait. If an upper bound is shared, let the model own the mathematical bound and the solver read it.
  2. Distinguish bounds required by actual model storage/witness formats from cardinalities needed only for enumeration. Move only the latter out of constructors. Do not mechanically remove real representation constraints, such as PreemptiveScheduling's dense time witness format.
  3. Replace BruteForceProblem::dimensions() directly with num_variables() -> Result<usize, SolveError> and dimension(variable: usize) -> Result<usize, SolveError>. Update registration macros, brute_force_dimensions, CLI inspect, test support, and every implementation together. Use Separate lazy search cardinality from machine-sized storage requirements #1143's single migration, without safe_dimensions or model-specific exceptions.
  4. Terminate CartesianIndices by mixed-radix exhaustion instead of requiring the total search count to fit usize. Actual coordinate-count, mask, or table representation failures still produce typed errors in their owning layer. Do not add computational-difficulty thresholds.
  5. highlyconnecteddeletion_ilp.rs protects 1u64 << n only with a debug assertion. This is a mask representation limit in the construction. Return an explicit ReductionError at that construction boundary; do not narrow HighlyConnectedDeletion's mathematical domain or redirect to another rule.

Acceptance: Direct model construction/evaluation does not call enumeration dimensions. Small-instance solving remains consistent. A short prefix of a large Cartesian product can be iterated; unrepresentable coordinates/tables return errors instead of panicking. Test actual interface boundaries without exhausting huge search spaces.

Scope: The full migration covers the inventoried 199 model implementation files and 436 reference files, including models, registration macros, solver/CLI callers, tests, and documentation. Preserve unrelated inherent methods named dimensions. No compatibility method or hidden bypass remains. TruthTable constructors and serde share row-count and shape validation; native subset-DP solvers report mask/table representation errors through SolveError.

G. Mathematical definitions and parameter metadata need separate fixes

These are not adapter problems. Removing backend policy does not justify deleting their guards.

G1. Incorrect parameter declaration in MaximumSetPacking→ILP

rule:43 declares exact num_constraints = universe_size, but construction at line 68 keeps only constraints for elements appearing in multiple sets. A single set {0} gives 1 != 0.

Changes: Use the existing upper_bound relationship for the whole parameter block (num_vars = num_sets is also a valid upper bound). Preserve construction's omission of unnecessary constraints. Do not add redundant constraints to satisfy metadata or invent a source-model parameter solely for this rule. Update the corresponding paper relationship.

Acceptance: Compare actual Problem::parameters() with the declaration through existing ParameterTransform. One single-set instance and one instance with shared elements are sufficient.

G2. Mathematical domain restrictions must not be removed as backend restrictions

  • maxcut_minimummatrixcover.rs requires nonnegative weights because its target is documented as a nonnegative matrix. However, MinimumMatrixCover::new() only checks matrix shape; construction/serde and documentation need alignment. Do not simply remove the rule's negative-weight check and claim support for the full MaxCut domain.
  • decisionminimumvertexcover_hamiltoniancircuit.rs registers an i64-weight source but requires unit weights at runtime. Express this premise with the existing One mathematical variant, complete its Decision metadata, and update registration/callers for that exact endpoint. Do not choose edges by inspecting weights in the solver.
  • SteinerTreeInGraphs documents a subtree, but its predicate only checks terminal connectivity, allowing cycles and unrelated selected edges; with 0/1 terminals, it accepts any edge selection. SteinerTree already has different acyclicity/whole-selection connectivity checks. The former's ILP rule accepts only positive weights and rejects empty terminals. The fact that a positive-weight optimum can be a tree does not define all model witnesses as trees.

Plan and order:

  1. Keep SteinerTree as the canonical model and remove SteinerTreeInGraphs, including its duplicate ILP rule, exports, registration, examples, tests, and paper entries. The model accepts signed edge weights and a nonempty set of distinct terminals. Selected edges must form one acyclic connected tree containing every terminal. With one terminal, the zero-edge tree is feasible; additional selected edges must form a tree containing that terminal. Empty terminal sets are rejected by the common construction/serde path.
  2. Reuse the existing SteinerTree predicate and construction path. Preserve the existing PrizeCollectingSteinerForest -> SteinerTree caller: when no vertex has a positive prize, it creates exactly one terminal. Cover this case through construction, evaluation, ILP reduction, and source extraction. No duplicate implementation, conversion, or alias remains.
  3. Use the existing vertex-selection, connectivity-flow, and tree-edge-count construction in steinertree_ilp.rs for signed weights and single-terminal trees, with the same construction for all inputs. Do not rely on positive objectives to make an incorrect feasible set happen to yield a correct optimum. Do not force genuinely different problems into an unsuitable shared construction.
  4. Enforce MinimumMatrixCover's nonnegative-matrix definition through its existing common construction path. Keep this MaxCut mapping's mathematical applicability domain explicit. Full signed MaxCut continues to use its existing suitable rules, without rerouting inside this edge.

Coordinate with the remaining mathematical-domain work in #1092. Its old i32/CVP representation is not evidence for the current implementation. G2's mathematical-definition and API-scope decisions are explicit prerequisites for that work, without blocking independent A–F fixes.

G3. PCSF prize gadgets must not bypass component charges

For two adjacent vertices with zero edge cost, prizes (1, 2), beta = 1, and omega = 5, the source optimum is the empty forest with value 3. An auxiliary terminal used as a bridge to the root can bypass the component charge and yield a target optimum whose extracted forest costs 5. This is a mathematical construction defect, independent of the solver.

Changes:

  • Enforce PCSF's documented nonnegative prizes, edge costs, beta, and omega through its existing common constructor, shared by serde and create APIs. SteinerTree itself retains signed weights.
  • Set M = omega + 1; assign inclusion edges weight M and omission edges weight M + beta * prize. Compute these only when a prize gadget exists, with checked native integer arithmetic.
  • Prove every optimal target tree uses exactly one edge at each auxiliary terminal: replacing a double-edge gadget's omission edge with the corresponding root attachment strictly reduces cost.
  • Keep the existing witness interface. The target optimum equals the source optimum plus k*M; extraction maps every optimal target witness to a source optimum. Do not introduce a second evaluation API or perform source optimization inside extraction.
  • Update the canonical witness, paper construction/proof, and tests. Parameter counts remain unchanged. Cover zero prizes, beta = 0, omega = 0, all optimal target witnesses, and actual coefficient-overflow boundaries without backend precision tests in the rule.

Acceptance: The two-vertex example has source optimum 3 and target optimum 15. Independent proof/constructor/adversary checks cover the declared mathematical domain; Rust tests cover the stored representation and public construction paths separately. Existing solver integration remains a separate check.

H. Tests and examples need clearer responsibilities

Findings:

  • The small-scale branch in the QUBO scaling test cannot discriminate between solutions.
  • The adapter precision comparison codifies the model discrepancy of integer rejection versus floating-point tolerance acceptance.
  • assert_bf_vs_ilp() is a solver integration check, not sufficient evidence by itself for every mathematical rule.
  • Direct solve calls located in rule files belong to example-db builders or test helpers. No direct HiGHS invocation was found inside reduce_to() or core extractors. 94 rule files already use shared rule_example_via_*ilp helpers.
  • The untracked local verifier/ uses default pred solve for source and target validation. That provides end-to-end integration evidence; backend timeouts/failures do not automatically refute model/rule theorems.

Changes:

  1. Model tests cover definitions, instance/configuration domains, and direct evaluation. Rule tests cover construction, witness mappings, and parameter relationships, using manual witnesses or small exhaustive enumeration. Enumeration is a legitimate test tool without making models depend on solvers.
  2. Preserve existing tests; redundant tests are not a cleanup target. Keep representative HiGHS round-trip integration tests. Attribute failures to construction, solving, or mathematical mapping; source checks in tests assess the theorem, not a production extraction gate. Do not change mathematical rules to accommodate the backend.
  3. Delete the ineffective tiny-scale QUBO branch and retain the ordinary-scale reference comparison. Replace the adapter's 2^52 model-tolerance comparison with ordinary small-constraint checks. Test decoding/range boundaries centrally in the adapter/conversion helper once.
  4. Retain necessary Knapsack/flow regressions preserving large integer coefficients: these check mathematical construction and do not call HiGHS. Do not turn them into templates for every rule.
  5. Do not relocate all canonical example files. Keep example construction isolated under example-db. Where touched, replace duplicated solving boilerplate with existing example-db builder APIs; do not alter production reduction construction to make an example solvable.
  6. If the local verifier is included, retain its existing recording/replay and explicit failure reporting without creating another verification framework. Document the different evidentiary scope of mathematical oracles and default-backend integration. Remove claims that passing the default solver proves model correctness.

I. Documentation and skills perpetuate the coupling

Locations:

  • verify-reduction type gate: prohibits Max→Min and may STOP for different inner Rust types in Min.
  • add-rule: similar type gates, mandatory exact helpers for every i64→f64 conversion, and blanket boundary-test requirements.
  • review-quality: mechanical thresholds such as at least five vertices and assertion counts.
  • review-structural: fixed test-function counts and a numerical checklist without responsibility boundaries.
  • Related guidance in add-model, fix-rule-issue, and .claude/CLAUDE.md's numerical/testing policies.
  • design.md: global conversion policy, JSON range claims, UnresolvedDecision, and special QUBO tolerance guidance.
  • paper variant-conversion section: mixes mathematical embeddings, Rust helpers, and machine-evaluation guarantees.

Existing counterexamples and stale content: minimumvertexcover_maximumindependentset.rs already implements a Max→Min witness mapping, contradicting the skill's mechanical optimization-direction gate. ReductionResult does not require identical Value types. The skill also describes MinimumHittingSet as Min<usize>, whereas current src/models/set/minimum_hitting_set.rs:135 uses Min<i64>. Do not extend numerical type restrictions based on that stale example.

Changes:

  1. Make .claude/CLAUDE.md and docs/src/design.md the canonical responsibility/arithmetic contract. Skills reference and apply it instead of duplicating a separate numerical policy.
  2. Check actual associated types, mathematical objective relationships, and whether the extractor is implementable in Rust. Different optimization directions can be handled by objective relationships/complement mappings. Different Rust numeric types do not automatically invalidate a witness reduction. Check aggregate mappings against their actual value-conversion contracts.
  3. Choose boundary tests from concrete implementation risks. Remove fixed vertex, assertion, and test-function counts as correctness gates. Retain construction, mathematical counterexample, mapping, and necessary integration checks. No corresponding precision/assertion-count gate was found in scripts/pipeline_checks.py; the findings are primarily in skills. Check the actual review entry points during implementation without adding a counting framework.
  4. Remove guidance requiring each rule to handle solver precision. State failure attribution and adapter responsibilities explicitly. Ordinary input validation, representation errors, and finite floating-point values remain legitimate concerns.
  5. Update affected API/CLI/getting-started documentation, ILP/CVP/conversion paper sections, and examples. The paper describes mathematical definitions and guarantees; implementation documentation explains Rust helpers. Do not present transform=exact as proof of backend accuracy.
  6. Remove requirements for external extraction feasibility/threshold rejection from .claude/CLAUDE.md, docs/src/design.md, and canonical add/review skills. In add-rule, replace “Validate first, then map” and blanket invalid-extraction test requirements with direct mapping under documented premises. In review-structural, review mathematical cases and reachable representation errors, not mandatory defensive error branches. Keep mathematical proof and exhaustive-oracle checks in verify-reduction.
  7. .agents/skills is currently an untracked copy differing from .claude/skills. Maintain one canonical source, configure local entry points to reference it directly, and remove independent policies from superseded copies. Do not commit the whole untracked directory or create a synchronization framework.

Implementation order, shared APIs, and delivery boundaries

Phase Work Call chain to update together Completion condition
1 Responsibility contract and skill gates (I) CLAUDE, design, add/review/verify skills Guidance matches existing witness/aggregate APIs
2 Extraction responsibility boundaries (C) Adapter→chain→typed/dynamic solve; external input→bundle extraction All extraction trusts documented premises; solve owns outcome interpretation; one mapping implementation
3 ILP model, adapter, and solver conclusions (A, D, ILP portion of E) ILPCoefficient→HighsAdapter→CompiledIlpPipeline→resolver/bundle Consistent normal YES/NO; backend restrictions stay out of models/rules
4 CVP and remaining conversions (B, E) CVP model→customized solver→SubsetSum/CVP rules→serialization/paper One implementation of each mathematical quantity; no f64 detours
5 Separate enumeration capability from models (F, with #1143) BruteForceProblem→all implementations→macros→registry→inspect/test support One fallible API without compatibility bypasses
6 Mathematical domains and parameters (G) Relevant models, rules, registration, paper Correct parameter formulas; domains settled before implementation
Alongside every phase Tests and documentation (H, I) Existing tests/docs for changed behavior No accumulated stale tests/interfaces; each phase independently reviewable

Implementation constraints:

  • Consolidate responsibilities through existing APIs. Do not introduce a solver framework, generic adapter layer, runtime difficulty estimator, certificate system, or precision-audit platform.
  • Typed and dynamic mathematical APIs share one implementation. Keep necessary type erasure at the existing registry boundary. No if model_name == ..., strict/lenient, fallback, or second extractor to bypass the contract.
  • Replace old APIs, update every caller, and delete superseded error branches, wrappers, and duplicate checks. No version suffixes or compatibility paths.
  • Retain correct mathematical bounds, including distance bounds and big-M derivations. Remove nonmathematical restrictions or misplaced rationales such as making HiGHS solve more easily. Retain actual arithmetic checks required for signed constructions.
  • Do not delete geometric adjacency validation, ordinary probability-input tolerances, or explicit model representation constraints merely because their descriptions mention precision or range.
  • Coordinate Separate witness feasibility and source solve conclusions at shared boundaries #1141's extraction boundary, Separate CVP integer and floating-point variants under the numeric contract #1146's CVP work, and Separate lazy search cardinality from machine-sized storage requirements #1143's enumeration migration under this contract. Do not downgrade results throughout the library to independently prove backend optimality. Editing this issue does not automatically edit those other issues.
  • Each implementation PR contains only its task's changes. Confirm the exact scope before exceeding 20 changed files or 1,000 added lines. This issue does not authorize committing the local stash, untracked verifier/skills, generated data, or temporary probes wholesale.

Overall acceptance

  • Problem::evaluate() and reduce_to() do not consult backend tolerances, solver statuses, or solver search restrictions.
  • Problem::evaluate() remains unchanged. Typed and external extraction map qualifying witnesses directly without feasibility/optimality gates.
  • HiGHS encoding, returned-value decoding, status interpretation, and original-ILP validation stay at the adapter/orchestration boundary.
  • Every native ILP terminal uses the same adapter; integer pipelines do not depend on float-cast edges.
  • Accepted backend optima produce normal YES/NO through mathematical aggregate mappings; timeouts, invalid returned witnesses, and general backend errors never become NO.
  • CVP objectives, its solver, SubsetSum thresholds, and QUBO mappings follow one mathematical definition, with all public outputs updated.
  • Pure model construction/evaluation does not require a representable enumeration space; search representation failures produce explicit errors in the search implementation.
  • Set-packing parameter declarations match construction; mathematical-domain issues and backend limitations are tracked and fixed separately.
  • Tests do not mask behavior by expanding tolerances or accepting UnresolvedDecision; shared numerical boundaries are not retested in every rule.
  • Documentation matches the direct extraction contract and actual APIs; skills permit valid witness reductions with different objective directions or numerical types.

Validation: run each phase's existing focused tests first, then the repository's actual make check and make mcp-test commands. Run make paper for phases affecting example-db/the paper. HiGHS is currently a regular dependency; do not use the obsolete --features ilp-highs command. Validate new executable behavior under repository coverage requirements; do not manufacture mirror tests for documentation/skill edits. Record actual build/test failures and never report unexecuted checks as passing. Generated paper data, temporary probes, and audit output are not commit artifacts.

Implementation status and validation

The shared extraction/solve changes and ILP -> QUBO projection are implemented locally. Remaining rule-local cleanup in C is pending. Unaffected work retains its recorded status. No implementation commit has been created or pushed. Historical validation below is separate from the current-stage checks.

Completed local implementation scope: ILP -> QUBO and the shared extraction/solve call paths. Other rule-local guards listed in C remain pending.

  • ILP -> QUBO extraction now projects the original variables directly and discards slack variables; it does not evaluate target energy or require a certificate.
  • The private reverse mapping implementation is shared by typed chain, JSON, and executed-path extraction. extract_solution_any is deleted. pred extract no longer uses target feasibility as an acceptance gate.
  • Solver-side complete_chain interprets aggregate outcomes before mapping each step. Both fixed ILP pipelines and bundle completion use it. Source infeasibility is handled for optimization aggregates as well as decision aggregates.
  • The typed core API signatures are preserved. The dynamic aggregate method and generated variant predicate are specified in the API inventory above; the general aggregate-only API retains its supported value types.
  • Canonical design/agent instructions and add/review skill guidance reflect direct extraction. Tests requiring removed extraction rejection branches are updated; unrelated tests remain.
  • The example maximize 3x + 2y, x + y <= 1 produces target witness [true,false,false] and source witness [1,0]. Both brute-force and ILP bundle solves pass. A source-infeasible instance returns Infeasible through the same solve paths, while its QUBO still has an optimum.

Current-stage validation:

  • make check passed formatting, Clippy, workspace tests, and documentation examples (5,818 main-library tests).
  • make mcp-test passed.
  • make coverage passed the unchanged 95% changed-line gate against origin/main: 95.74% across 5,043 measured lines, including the pre-existing working-tree changes. This is not a coverage measurement isolated to this stage.
  • git diff --check passed.

Remaining delivery order:

  1. Complete the other rule-local extractions in C after verifying each rule's actual mathematical premise; fix genuine mapping defects directly.
  2. Review the geometric mapping helpers' construction invariants and remove only redundant wrappers/checks in those paths.
  3. Update affected tests and documentation for those rules and run the required checks for each subsequent change. Do not infer completion of those rules from the shared-path validation above.

API review acceptance:

  • Keep the typed core signatures; no new public extraction variants, wrappers, mode flags, or certificate API.
  • Delete the mixed extract_solution_any entry and its extraction-premise Option protocol.
  • Share direct reverse traversal across existing chain/path/transport callers; preserve only required type-erasure and JSON boundaries.
  • Remove external and rule-local feasibility/optimality acceptance gates; keep transport errors and legitimate mapping computation.
  • Verify every affected solve entry still produces correct solutions or source outcomes without using extraction as a validator.

Enumeration and metadata:

  • All inventoried reference-solver implementations and callers use the fallible coordinate API. Checked integer conversion failures share the existing SolveError through the standard From conversion.
  • ClosestSubstring and MinimumDiscretePlanarInverseKinematics accept valid instances whose configuration products exceed the native integer range. Their complexity expressions use existing primitive parameters and AM–GM bounds instead of storing the product.
  • KthLargestMTuple exposes the total input-element count, and ConsistencyOfDatabaseFrequencyTables exposes the largest attribute domain, replacing overflowing search-product parameters. Registrations, callers, tests, and paper descriptions are updated.
  • SimultaneousIncongruences construction and direct evaluation no longer require a representable search period. Its native-integer lcm_moduli query returns Result<i64, EvaluationError>; enumeration propagates period-representation failures explicitly. Negative witnesses are rejected under the documented nonnegative domain.
  • Proven redundant checks and unused BruteForceProblem implementations on mathematical test fixtures are removed. Small-input regressions cover actual coordinate-count, cardinality, and storage failures without invoking HiGHS.

Strict coverage gate:

  • The repository requirement remains >95% coverage for new code, with no exemptions or new coverage exclusions.
  • make coverage generates workspace LCOV and runs diff-cover with --fail-under 95, comparing committed and uncommitted changes against origin/main. COVERAGE_BASE selects a different review base explicitly.
  • Codecov project and patch thresholds have zero allowance; upload failures are reported as failures.

Recorded verification of the existing working tree (not validation of pending extraction/API changes):

  • make check passed formatting, Clippy, all workspace tests including ignored tests, and documentation examples. The main library suite passed 5,818 tests.

  • make mcp-test passed 53 unit tests and 2 integration tests.

  • make paper passed example/schema exports and Typst compilation.

  • make coverage passed the unchanged changed-line gate against origin/main: 95.93%, with 4,990 measured changed lines and 203 uncovered. No exemptions or new exclusions were added.

  • git diff --check passed.

  • The PCSF construction retains its independent proof/constructor/adversary evidence: 6,901 independently checked instances and 51,724 optimal target witnesses. The two-vertex example returns source optimum 3, target optimum 15, and extracted source optimum 3 through the Steiner and native ILP bundles.

Reports are local artifacts. Temporary probes, coverage data, and unrelated working-tree files are not commit artifacts.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions