Solver Interface Testing and Conformance
Purpose
This spec defines the conformance test suite for the SolverInterface trait and its 10 methods (load_model, add_rows, set_row_bounds, set_col_bounds, solve, solve_with_basis, reset, get_basis, statistics, name), as specified in Solver Interface Trait. The suite verifies that both reference solver backends (HiGHS and CLP) satisfy the same behavioral contract, produce equivalent solutions within tolerance, and correctly implement the dual normalization convention from Solver Abstraction SS8. All test cases use a shared LP fixture with 3 variables and 2 structural constraints, hand-computable optimal solutions, and explicit CSC/CSR data so that expected primal values, dual values, objective values, and basis statuses can be verified by manual calculation.
Test cases reference the method contracts from Solver Interface Trait SS2, the LP layout convention from Solver Abstraction SS2, the dual normalization convention from Solver Abstraction SS8, and the basis storage convention from Solver Abstraction SS9.
Note: CLP test variants are forward-looking — the CLP solver backend is not yet implemented. Only HiGHS test variants are currently runnable. CLP variants document the intended conformance surface for when additional backends are added.
SS1. Conformance Test Suite
Test naming convention: test_solver_{variant}_{method}_{scenario} where {variant} is highs or clp, {method} is the SolverInterface method under test, and {scenario} describes the test case.
Shared test fixture: Unless otherwise noted, tests use the following LP with 3 variables and 2 structural constraints, designed to follow the LP layout convention from Solver Abstraction SS2.
SS1.1 Shared LP Fixture
Problem description: A minimal hydrothermal stage problem with one reservoir (state variable), one future cost variable (theta), and one thermal generator (decision variable). Demand is 14 MW, hydro productivity is 2 MW/(m3/s), and the incoming state (reservoir volume) is fixed at 6 hm3.
subject to:
with column bounds:
LP layout (per Solver Abstraction SS2):
| Index | Column | Description | Lower Bound | Upper Bound | Objective |
|---|---|---|---|---|---|
| 0 | State (volume) | 0.0 | 10.0 | 0.0 | |
| 1 | Future cost theta | 0.0 | 1.0 | ||
| 2 | Thermal gen | 0.0 | 8.0 | 50.0 |
| Index | Row | Type | Lower Bound | Upper Bound |
|---|---|---|---|---|
| 0 | State-fixing | Equality | 6.0 | 6.0 |
| 1 | Power balance | Equality | 14.0 | 14.0 |
Layout parameters: n_state = 1 (column 0), n_dual_relevant = 1 (row 0), num_rows = 2.
Constraint matrix in CSC format:
The constraint matrix is:
CSC arrays (column-major):
| Array | Values | Description |
|---|---|---|
col_starts | [0, 2, 2, 3] | Column 0 has 2 entries, column 1 has 0, column 2 has 1 |
row_indices | [0, 1, 1] | Column 0 appears in rows 0 and 1; column 2 in row 1 |
values | [1.0, 2.0, 1.0] | Corresponding non-zero values |
num_nz | 3 | Total non-zeros |
Hand-computed optimal solution (no cuts):
From Row 0: . From Row 1: . Minimizing objective with no cuts: (at lower bound).
| Quantity | Value |
|---|---|
| 6.0 | |
| 0.0 | |
| 2.0 | |
| 100.0 |
Hand-computed dual values:
Sensitivity analysis for the equality constraints:
- (state-fixing): Increasing RHS from 6 to . From Row 1: . Objective change: . Therefore .
- (power balance): Increasing RHS from 14 to . From Row 1: . Objective change: . Therefore .
Canonical sign convention (Solver Abstraction SS8): A positive dual on a constraint means increasing RHS increases the objective. For equality constraints, the dual is unrestricted. The values above follow the canonical convention: (increasing the state-fixing RHS decreases the objective through reduced thermal need) and (increasing demand increases thermal cost).
Hand-computed basis:
With 3 columns and 2 rows, there are 2 basic variables. and are basic (between bounds or uniquely determined). is at its lower bound.
| Element | Status | Rationale |
|---|---|---|
| Col 0 | Basic | , uniquely determined |
| Col 1 | AtLower | , at lower bound |
| Col 2 | Basic | , between bounds [0, 8] |
| Row 0 | AtLower | Equality: slack = 0 = lower bound |
| Row 1 | AtLower | Equality: slack = 0 = lower bound |
SS1.2 Benders Cut Fixture
Two Benders cuts are provided for add_rows tests. Each cut has the form , which in LP row form is (coefficients on the theta column and state column).
Cut 1:
Row form: . With : .
Cut 2:
Row form: . With : .
RowBatch CSR data (both cuts):
| Array | Values | Description |
|---|---|---|
num_rows | 2 | Two cuts |
row_starts | [0, 2, 4] | Each cut has 2 non-zeros (state col + theta col) |
col_indices | [0, 1, 0, 1] | Cut 1: cols 0, 1; Cut 2: cols 0, 1 |
values | [-5.0, 1.0, 3.0, 1.0] | Cut 1: -5x0 + 1x1; Cut 2: 3x0 + 1x1 |
row_lower | [20.0, 80.0] | Intercepts (alpha values) |
row_upper | [, ] | Cuts are constraints |
Hand-computed solution with both cuts:
The binding constraint is Cut 2: . Cut 1 is non-binding: .
| Quantity | Value |
|---|---|
| 6.0 | |
| 62.0 | |
| 2.0 | |
| 162.0 |
Dual values with both cuts:
- (state-fixing): Increase RHS from 6 to . Row 1: (cost ). Cut 2: (cost ). Total: .
- (power balance): Same as before, .
- (Cut 1, non-binding): .
- (Cut 2, binding): Increase RHS from 80 to . . Objective change: . Therefore .
SS1.3 Patched RHS Fixture
For set_row_bounds tests, the state-fixing constraint (Row 0) RHS is changed from 6.0 to 4.0, simulating a different incoming state .
Hand-computed solution after RHS patch (with both cuts active):
- . Row 1: . Cut 1: . Cut 2: . Binding: Cut 2, .
| Quantity | Value |
|---|---|
| 4.0 | |
| 68.0 | |
| 6.0 | |
| 368.0 |
SS1.4 load_model Conformance
| Test Name | Input Scenario | Expected Observable Behavior | Variant |
|---|---|---|---|
test_solver_highs_load_model_and_solve | Load shared fixture StageTemplate (SS1.1). Solve without cuts. | Solve returns Ok. Objective = 100.0 within 1e-8 relative tolerance. Primal: , , within 1e-8 absolute tolerance. | HiGHS |
test_solver_clp_load_model_and_solve | Load shared fixture StageTemplate (SS1.1). Solve without cuts. | Solve returns Ok. Objective = 100.0 within 1e-8 relative tolerance. Primal: , , within 1e-8 absolute tolerance. | CLP |
test_solver_highs_load_model_replaces_previous | Load shared fixture. Solve (objective = 100.0). Load a different StageTemplate with objective coefficients [0, 1, 25] (half thermal cost). Solve again. | Second solve returns Ok. Objective = 50.0 (= 0 + 0 + 25*2). Loading a new model fully replaces the previous one per Solver Interface Trait SS2.1. | HiGHS |
test_solver_clp_load_model_replaces_previous | Same as above. | Second solve returns Ok. Objective = 50.0. Model fully replaced. | CLP |
SS1.5 add_rows Conformance
| Test Name | Input Scenario | Expected Observable Behavior | Variant |
|---|---|---|---|
test_solver_highs_add_rows_tightens | Load shared fixture. Add both cuts from SS1.2 via add_rows. Solve. | Objective = 162.0. Primal: , , . The cuts tighten the objective from 100.0 to 162.0. | HiGHS |
test_solver_clp_add_rows_tightens | Same as above. | Objective = 162.0. Primal: , , . | CLP |
test_solver_highs_add_rows_single_cut | Load shared fixture. Add only Cut 1 () via add_rows with a 1-row RowBatch. Solve. | Objective = 150.0 (= 0 + 50 + 100). Primal: , , . | HiGHS |
test_solver_clp_add_rows_single_cut | Same as above. | Objective = 150.0. Primal: , , . | CLP |
SS1.6 set_row_bounds Conformance
| Test Name | Input Scenario | Expected Observable Behavior | Variant |
|---|---|---|---|
test_solver_highs_set_row_bounds_state_change | Load shared fixture. Add both cuts. Patch Row 0 RHS from 6.0 to 4.0 (both lower and upper bounds). Solve. | Objective = 368.0. Primal: , , (per SS1.3). | HiGHS |
test_solver_clp_set_row_bounds_state_change | Same as above. | Objective = 368.0. Primal: , , . | CLP |
SS1.6a set_col_bounds Conformance
Test naming convention: test_solver_{variant}_set_col_bounds_{scenario} where {variant} is highs or clp and {scenario} describes the column-bound patching test case.
These tests verify the set_col_bounds method contract from Solver Interface Trait SS2.3a. Each test starts from the shared LP fixture (SS1.1) and applies column bound updates via three parallel slices: indices, lower, and upper (SoA parameter style). The method is infallible (panics on invalid column index) and preserves the solver basis.
| Test Name | Input Scenario | Expected Observable Behavior | Variant |
|---|---|---|---|
test_solver_highs_set_col_bounds_basic | Load shared fixture. Add both cuts (SS1.2). Update column 2 (, thermal gen) bounds from to via set_col_bounds(&[2], &[0.0], &[3.0]). Solve. | Solve returns Ok. Objective = 162.0. Primal: , , . The tighter upper bound () does not bind because the optimal . Solution is unchanged from the unpatched case (SS1.2). Non-updated column bounds (, ) are unchanged. Row bounds are unchanged. | HiGHS |
test_solver_clp_set_col_bounds_basic | Same as above. | Same: objective = 162.0, primal = . Column bound update does not bind. | CLP |
test_solver_highs_set_col_bounds_tightens | Load shared fixture (no cuts). Update column 1 (, future cost theta) bounds from to via set_col_bounds(&[1], &[10.0], &[f64::INFINITY]). Solve. | Solve returns Ok. Objective = 110.0. Primal: , , . Without the bound update, (at original lower bound 0). With the tighter lower bound , the theta variable is pushed to its new lower bound . Objective: , up from 100.0. Non-updated column bounds (, ) and all row bounds are unchanged. | HiGHS |
test_solver_clp_set_col_bounds_tightens | Same as above. | Same: objective = 110.0, primal = . Tighter lower bound on is active. | CLP |
test_solver_highs_set_col_bounds_repatch | Load shared fixture (no cuts). Solve (objective = 100.0, ). Update column 1 bounds to via set_col_bounds(&[1], &[10.0], &[f64::INFINITY]). Solve (objective = 110.0, ). Re-update column 1 bounds back to via set_col_bounds(&[1], &[0.0], &[f64::INFINITY]). Solve. | Third solve returns Ok. Objective = 100.0. Primal: , , . Re-applying column bound updates restores the original feasible region. The solver correctly applies each successive column bound update before the next solve. Basis is preserved across updates. | HiGHS |
test_solver_clp_set_col_bounds_repatch | Same as above. | Same: three solves produce objectives 100.0, 110.0, 100.0. Column bounds are correctly re-applied at each step. | CLP |
SS1.7 solve Conformance
| Test Name | Input Scenario | Expected Observable Behavior | Variant |
|---|---|---|---|
test_solver_highs_solve_dual_values | Load shared fixture. Solve without cuts. | Duals: , within 1e-6 absolute tolerance. Dual sign follows canonical convention per Solver Abstraction SS8. | HiGHS |
test_solver_clp_solve_dual_values | Same as above. | Duals: , within 1e-6. Sign normalized to canonical convention by CLP implementation. | CLP |
test_solver_highs_solve_dual_values_with_cuts | Load shared fixture. Add both cuts. Solve. | Duals: , , (Cut 1, non-binding), (Cut 2, binding). All within 1e-6 (per SS1.2). | HiGHS |
test_solver_clp_solve_dual_values_with_cuts | Same as above. | Same dual values as HiGHS within 1e-6. Both backends produce identical normalized duals. | CLP |
test_solver_highs_solve_reduced_costs | Load shared fixture. Solve without cuts. | Reduced cost of is 1.0 (at lower bound, positive reduced cost confirms non-basic optimality). LpSolution.reduced_costs has length 3. | HiGHS |
test_solver_clp_solve_reduced_costs | Same as above. | Same reduced costs as HiGHS within 1e-6. | CLP |
test_solver_highs_solve_iterations_reported | Load shared fixture. Solve. | LpSolution.iterations >= 1. LpSolution.solve_time_seconds >= 0.0. Iterations and timing are non-negative. | HiGHS |
test_solver_clp_solve_iterations_reported | Same as above. | LpSolution.iterations >= 1. LpSolution.solve_time_seconds >= 0.0. | CLP |
SS1.8 solve_with_basis Conformance
| Test Name | Input Scenario | Expected Observable Behavior | Variant |
|---|---|---|---|
test_solver_highs_solve_with_basis_warm_start | Load shared fixture. Cold solve (record iterations as ). Extract basis via get_basis. Reset. Load same fixture again. Call solve_with_basis with the cached basis (record ). | Warm-start solve returns Ok with same objective (100.0) and same primal values. . Warm-starting from the optimal basis of the same LP should require 0-1 iterations. | HiGHS |
test_solver_clp_solve_with_basis_warm_start | Same as above. | Same: objective = 100.0, . | CLP |
test_solver_highs_solve_with_basis_cut_extension | Load shared fixture. Solve. Extract basis (2 row statuses). Load same fixture. Add both cuts (now 4 rows). Extend basis: structural row statuses from cache, new dynamic constraint rows initialized as Basic. Call solve_with_basis. | Solve returns Ok. Objective = 162.0 (cuts active). The basis extension per Solver Abstraction SS2.3 allows warm-starting even when cuts are added. | HiGHS |
test_solver_clp_solve_with_basis_cut_extension | Same as above. | Same: objective = 162.0. | CLP |
SS1.9 reset Conformance
| Test Name | Input Scenario | Expected Observable Behavior | Variant |
|---|---|---|---|
test_solver_highs_reset_clears_state | Load shared fixture. Solve. Call reset. Attempt to solve without loading a model. | After reset, solver holds no model. Attempting to solve without a prior load_model is a precondition violation (panic or error, implementation-defined). State is clean per Solver Interface Trait SS2.6. | HiGHS |
test_solver_clp_reset_clears_state | Same as above. | Same behavior: no model after reset. | CLP |
test_solver_highs_reset_preserves_statistics | Load shared fixture. Solve twice (2 solves). Record statistics().solve_count. Call reset. Check statistics().solve_count. | solve_count after reset equals solve_count before reset (= 2). Statistics are preserved across reset per Solver Interface Trait SS2.6. | HiGHS |
test_solver_clp_reset_preserves_statistics | Same as above. | Same: solve_count = 2 is preserved after reset. | CLP |
SS1.10 get_basis Conformance
| Test Name | Input Scenario | Expected Observable Behavior | Variant |
|---|---|---|---|
test_solver_highs_get_basis_dimensions | Load shared fixture. Solve. Call get_basis. | basis.col_status.len() == 3 (3 columns). basis.row_status.len() == 2 (2 static rows, no dynamic constraints). Basis dimensions match loaded LP per Solver Interface Trait SS2.7. | HiGHS |
test_solver_clp_get_basis_dimensions | Same as above. | Same dimensions: 3 column statuses, 2 row statuses. | CLP |
test_solver_highs_get_basis_roundtrip | Load shared fixture. Solve. Extract basis via get_basis. Reset. Load same fixture. Call solve_with_basis with the extracted basis. | Solve returns Ok with 0 or 1 simplex iterations. The basis round-trips through extract-reload without information loss. | HiGHS |
test_solver_clp_get_basis_roundtrip | Same as above. | Same: 0 or 1 iterations on basis reload. | CLP |
test_solver_highs_get_basis_with_cuts | Load shared fixture. Add both cuts (4 total rows). Solve. Call get_basis. | basis.col_status.len() == 3. basis.row_status.len() == 4 (2 structural + 2 dynamic constraint rows). Basis includes dynamic constraint row statuses per Solver Abstraction SS9. | HiGHS |
test_solver_clp_get_basis_with_cuts | Same as above. | Same dimensions: 3 column statuses, 4 row statuses. | CLP |
SS1.11 statistics Conformance
| Test Name | Input Scenario | Expected Observable Behavior | Variant |
|---|---|---|---|
test_solver_highs_statistics_initial | Construct a fresh solver instance. Call statistics before any solves. | solve_count = 0, success_count = 0, failure_count = 0, total_iterations = 0, retry_count = 0, total_solve_time_seconds = 0.0. All counters start at zero. | HiGHS |
test_solver_clp_statistics_initial | Same as above. | Same: all counters zero. | CLP |
test_solver_highs_statistics_increment | Load shared fixture. Solve 3 times (all successful). Call statistics. | solve_count = 3. success_count = 3. failure_count = 0. total_iterations >= 3 (at least 1 iteration per solve). total_solve_time_seconds > 0.0. Counters accumulate monotonically. | HiGHS |
test_solver_clp_statistics_increment | Same as above. | Same: solve_count = 3, success_count = 3, failure_count = 0. | CLP |
SS1.12 name Conformance
| Test Name | Input Scenario | Expected Observable Behavior | Variant |
|---|---|---|---|
test_solver_highs_name_returns_identifier | Construct a HiGHS solver instance. Call name. | Returns "highs". Non-empty &'static str. Value is constant across calls. | HiGHS |
test_solver_clp_name_returns_identifier | Construct a CLP solver instance. Call name. | Returns "clp". Non-empty &'static str. Value is constant across calls. | CLP |
SS2. Cross-Solver Equivalence Tests
These tests verify that both solver backends produce equivalent results when given the same LP input. Equivalence is the central claim of the solver abstraction: the SDDP algorithm produces the same policy regardless of which backend is active. Each test loads the same LP into both HiGHS and CLP, solves, and compares results.
Tolerances:
| Quantity | Tolerance | Type | Rationale |
|---|---|---|---|
| Objective value | Relative | Both solvers minimize the same LP | |
| Primal values | Absolute | State transfer requires tight agreement | |
| Dual values | Absolute | Duals feed cut coefficients; moderate tolerance sufficient | |
| Warm-start iters | Ratio | Implementation-specific iteration counts may differ |
| Test Name | Input Scenario | Expected Observable Behavior |
|---|---|---|
test_solver_cross_solve_objective_agreement | Load shared fixture into both HiGHS and CLP. Solve both (no cuts). | Relative objective difference . Both return 100.0. |
test_solver_cross_solve_primal_agreement | Same LP, both solvers. | for all . Both return . |
test_solver_cross_solve_dual_agreement | Same LP, both solvers. No cuts. | for all . Both return after normalization per Solver Abstraction SS8. |
test_solver_cross_add_cuts_objective_agreement | Load shared fixture into both. Add both cuts (SS1.2). Solve. | Relative objective difference . Both return 162.0. |
test_solver_cross_add_cuts_dual_agreement | Load shared fixture into both. Add both cuts. Solve. | for all . Both return . |
test_solver_cross_patch_rhs_objective_agreement | Load shared fixture into both. Add both cuts. Patch Row 0 RHS to 4.0. Solve. | Relative objective difference . Both return 368.0. |
test_solver_cross_warm_start_iteration_comparison | Load shared fixture into both. Cold solve. Extract basis. Reset. Reload. Warm-start solve. Compare iteration counts. | Both backends achieve reduced iterations with warm start. and (within 2x). |
SS3. Error Path Tests
These tests verify that solver backends correctly identify and report infeasible and unbounded LPs with the appropriate SolverError variants from Solver Interface Trait SS3.
SS3.1 Infeasible LP
LP construction: A 1-variable LP with contradictory bounds.
subject to no constraints, with column bounds (lower bound > upper bound, infeasible).
StageTemplate data:
| Field | Value |
|---|---|
num_cols | 1 |
num_rows | 0 |
num_nz | 0 |
col_starts | [0, 0] |
row_indices | [] |
values | [] |
col_lower | [5.0] |
col_upper | [3.0] |
objective | [1.0] |
row_lower | [] |
row_upper | [] |
n_state | 1 |
n_dual_relevant | 0 |
| Test Name | Input Scenario | Expected Observable Behavior | Variant |
|---|---|---|---|
test_solver_highs_solve_infeasible | Load infeasible LP (SS3.1). Solve. | Returns Err(SolverError::Infeasible). The LP has no feasible point because cannot simultaneously satisfy and . | HiGHS |
test_solver_clp_solve_infeasible | Same as above. | Returns Err(SolverError::Infeasible). | CLP |
SS3.2 Unbounded LP
LP construction: A 1-variable LP with no lower bound and a minimization objective that drives the variable to .
subject to no constraints, with column bounds .
StageTemplate data:
| Field | Value |
|---|---|
num_cols | 1 |
num_rows | 0 |
num_nz | 0 |
col_starts | [0, 0] |
row_indices | [] |
values | [] |
col_lower | [] |
col_upper | [] |
objective | [-1.0] |
row_lower | [] |
row_upper | [] |
n_state | 1 |
n_dual_relevant | 0 |
| Test Name | Input Scenario | Expected Observable Behavior | Variant |
|---|---|---|---|
test_solver_highs_solve_unbounded | Load unbounded LP (SS3.2). Solve. | Returns Err(SolverError::Unbounded). The objective is unbounded below because has no upper bound. | HiGHS |
test_solver_clp_solve_unbounded | Same as above. | Returns Err(SolverError::Unbounded). | CLP |
SS4. LP Lifecycle Tests
These tests verify the full operational lifecycle of a solver instance: load, add cuts, patch, solve, extract basis, warm-start solve, reset, and reload. The lifecycle test ensures that the methods compose correctly in the sequence prescribed by Solver Abstraction SS11.2.
| Test Name | Lifecycle Steps | Expected Observable Behavior at Each Step | Variant |
|---|---|---|---|
test_solver_highs_lifecycle_full_cycle | (1) Construct solver. (2) load_model with shared fixture. (3) solve cold. (4) add_rows with both cuts. (5) solve cold. (6) get_basis. (7) set_row_bounds Row 0 RHS to 4.0. (8) solve_with_basis using basis from step 6. (9) Verify statistics. (10) reset. (11) load_model with shared fixture (original RHS=6). (12) solve cold. | (1) Fresh instance. (2) Model loaded. (3) Objective = 100.0, primal = (6, 0, 2). (4) Two cuts appended at rows 2-3. (5) Objective = 162.0, primal = (6, 62, 2). (6) Basis has 3 col statuses + 4 row statuses. (7) Row 0 RHS changed to 4.0. (8) Objective = 368.0, primal = (4, 68, 6); warm-start used. (9) solve_count >= 3, success_count >= 3, failure_count = 0, total_solve_time_seconds > 0. (10) Clean state; statistics preserved. (11) Fresh model loaded (no cuts). (12) Objective = 100.0, primal = (6, 0, 2); confirms clean state after reset. | HiGHS |
test_solver_clp_lifecycle_full_cycle | Same steps as above. | Same expected behavior at each step. | CLP |
test_solver_highs_lifecycle_repeated_patch_solve | (1) load_model with shared fixture. (2) add_rows with both cuts. (3) solve with RHS=6 (objective=162.0). (4) set_row_bounds Row 0 to 4.0. (5) solve (objective=368.0). (6) set_row_bounds Row 0 to 8.0. (7) solve (objective=?). | (3) Objective = 162.0. (5) Objective = 368.0. (7) With : Row 1 gives , but , so is infeasible for equality. This means the equality power balance cannot be satisfied with and when demand is 14 and productivity is 2, because . The LP becomes infeasible: returns Err(SolverError::Infeasible { .. }). | HiGHS |
test_solver_clp_lifecycle_repeated_patch_solve | Same steps as above. | Same: step 7 returns Err(SolverError::Infeasible { .. }). | CLP |
SS5. Dual Normalization Verification
These tests specifically target the dual normalization contract from Solver Abstraction SS8 and Solver Interface Trait SS7. The canonical sign convention states that a positive dual on a constraint means increasing the RHS increases the objective (). Both backends must produce identical normalized dual values regardless of their native sign convention.
Verification approach: The shared fixture has two equality constraints with hand-computed dual values derived from sensitivity analysis. The cut coefficient formula requires correct dual signs for the cut-relevant rows [0, n_dual_relevant).
| Test Name | Input Scenario | Expected Observable Behavior |
|---|---|---|
test_solver_highs_dual_normalization_dual_relevant_row | Load shared fixture. Solve. Extract dual for Row 0 (dual-relevant, state-fixing). | within 1e-6. The state-fixing row dual propagates as the cut coefficient for the state variable: . A sign error here would produce divergent cuts. |
test_solver_clp_dual_normalization_dual_relevant_row | Same as above. | within 1e-6. Same normalized value as HiGHS regardless of CLP’s native dual convention. |
test_solver_highs_dual_normalization_sensitivity_check | Load shared fixture. Solve to get with RHS_0 = 6. Patch Row 0 RHS to 6.01. Solve again to get . | Finite-difference check: within 1e-2 tolerance. The dual accurately predicts the objective sensitivity to RHS perturbation. |
test_solver_clp_dual_normalization_sensitivity_check | Same as above. | Same finite-difference result within 1e-2. Both backends’ duals predict the correct sensitivity. |
test_solver_highs_dual_normalization_with_binding_cut | Load shared fixture. Add both cuts. Solve. Extract dual for Cut 2 (Row 3, binding). | within 1e-6. The binding cut dual is positive, confirming that tightening the cut RHS (increasing ) increases the objective. |
test_solver_clp_dual_normalization_with_binding_cut | Same as above. | within 1e-6. Same normalized value. |
Cross-References
- Solver Interface Trait – Trait definition (SS1), method contracts for
load_model(SS2.1),add_rows(SS2.2),set_row_bounds(SS2.3),set_col_bounds(SS2.3a),solve(SS2.4),solve_with_basis(SS2.5),reset(SS2.6),get_basis(SS2.7),statistics(SS2.8),name(SS2.9),SolverErrorenum (SS3),LpSolutiontype (SS4.1),Basistype (SS4.2),SolverStatisticstype (SS4.3),StageTemplatetype (SS4.4),RowBatchtype (SS4.5), dual normalization contract (SS7) - Solver Abstraction – LP layout convention (SS2), column layout (SS2.1), row layout with dual-extraction region (SS2.2), basis persistence with cut boundary (SS2.3), solver interface contract (SS4), cut pool design (SS5), error categories (SS6), retry logic contract (SS7), dual normalization canonical convention (SS8), basis storage (SS9), compile-time solver selection (SS10), stage template and rebuild strategy (SS11)
- HiGHS Implementation – HiGHS-specific API mapping, retry strategy, dual sign convention before normalization
- CLP Implementation – CLP-specific API mapping, C++ wrapper strategy, native dual sign convention
- Backend Testing – Conformance test suite structure, parameterized-by-backend pattern, interchangeability verification approach
- Risk Measure Testing – Sibling conformance test spec: shared fixture design, requirements tables, variant equivalence tests
- Cut Selection Testing – Sibling conformance test spec: hand-computable fixtures, cross-variant comparison
- Training Loop – Forward pass (SS4) and backward pass (SS6) that drive the load-patch-solve-basis lifecycle tested in SS4
- Cut Management Implementation – Cut pool activity bitmap (SS1.1), CSR assembly for
addRows(SS1) that produces theRowBatchinput tested in SS1.5 - LP Formulation – Constraint structure defining which row duals are cut-relevant; the fixture Row 0 models the state-linking constraint type
- Binary Formats – Cut pool memory layout (SS3.4) and CSC/CSR format conventions used in the fixture data
- Solver Workspaces – Thread-local solver infrastructure (SS1), per-stage basis cache (SS1.5) that the lifecycle tests in SS4 exercise