Skip to content

Commit a88e154

Browse files
Do not discard a MIP solution when its repair LP fails to solve
When a solution is feasible for the presolved model but violates the original model by more than mip_feasibility_tolerance, HighsMipSolverData repairs it by fixing the integer columns and re-solving the LP in the original space. The repaired solution is accepted only if that LP returns a feasible primal solution, so a failure of the solver is treated exactly like a solution that is genuinely bad: the solution is discarded and kHighsInf is returned, so it is not used for bounding. When the discarded solution is the optimum, the search reports a worse incumbent as optimal at a zero gap. The repair LP is solved in the original space with the dual simplex, which on a badly scaled model can fail the ratio test with "excessive dual values" (or its primal counterpart) and return kSolveError. On a 12710 x 23091 MIP with an objective coefficient range of 2.4e13, 17 of 34 repair LPs failed this way and the optimum was among the solutions discarded, leaving an objective 20.67 worse reported as optimal. Retry such a solve once without presolve; the same LP then solves to optimality, as it also does with the primal simplex or with IPM. Also recompute the row activities from the repaired column values. The feasibility test is given solution.row_value and trusts it, but the values returned by the LP solver need only satisfy the LP to its own tolerance, so they can differ from the quad-precision recomputation used by the check that is later applied to the returned solution. Without this a repaired solution can pass here and then fail that check, turning a reported optimum into a solve error. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent bc30ccf commit a88e154

1 file changed

Lines changed: 18 additions & 0 deletions

File tree

highs/mip/HighsMipSolverData.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <sstream>
1212

1313
#include "../extern/pdqsort/pdqsort.h"
14+
#include "lp_data/HighsLpUtils.h"
1415
#include "lp_data/HighsModelUtils.h"
1516
#include "mip/HighsPseudocost.h"
1617
#include "mip/HighsRedcostFixing.h"
@@ -1239,11 +1240,28 @@ double HighsMipSolverData::transformNewIntegerFeasibleSolution(
12391240
// HiPO or IPX to solve an LP without a basis, use simplex
12401241
tmpSolver.setOptionValue("solver", kSimplexString);
12411242
tmpSolver.optimizeLp();
1243+
// The repair LP is solved in the original space, which for a badly scaled
1244+
// model can defeat the dual simplex ratio test ("excessive dual values")
1245+
// or its primal counterpart. That is a failure of the solver, not a
1246+
// statement about the solution, so retry once without presolve rather than
1247+
// discarding a solution that may be the incumbent - or the optimum.
1248+
if (tmpSolver.getModelStatus() == HighsModelStatus::kSolveError) {
1249+
tmpSolver.setOptionValue("presolve", kHighsOffString);
1250+
tmpSolver.optimizeLp();
1251+
}
12421252
this->total_repair_lp_iterations +=
12431253
tmpSolver.getInfo().simplex_iteration_count;
12441254
if (tmpSolver.getInfo().primal_solution_status == kSolutionStatusFeasible) {
12451255
this->total_repair_lp_feasible++;
12461256
solution = tmpSolver.getSolution();
1257+
// Recompute the row activities from the repaired column values. The
1258+
// feasibility test below is given solution.row_value and trusts it, but
1259+
// the values returned by the LP solver need only satisfy the LP to its
1260+
// own tolerance, so they can differ from a quad-precision recomputation -
1261+
// which is what the check applied to the returned solution uses. Without
1262+
// this, a repaired solution can pass here and fail that later check.
1263+
calculateRowValuesQuad(*mipsolver.orig_model_, solution.col_value,
1264+
solution.row_value);
12471265
allow_try_again = false;
12481266
goto try_again;
12491267
}

0 commit comments

Comments
 (0)