Skip to content

Commit cf94a0f

Browse files
authored
Merge pull request #2370 from ERGO-Code/fix-1966
Fix 1966
2 parents b6bd5fb + db935ba commit cf94a0f

File tree

2 files changed

+75
-40
lines changed

2 files changed

+75
-40
lines changed

check/TestIpm.cpp

Lines changed: 70 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,19 @@ TEST_CASE("test-analytic-centre-box", "[highs_ipm]") {
8989
}
9090

9191
TEST_CASE("test-1966", "[highs_ipm]") {
92+
// This is the primal-dual infeasible instance
93+
// ("PrimalAndDualInfeasible") from OR-Tools that exposed the need
94+
// for primal_solution_status and dual_solution_status not to be set
95+
// to kSolutionStatusFeasible when there are no primal/dual
96+
// infeasiblities, but meaningful primal/dual residual errors
97+
//
98+
// Takehome: make sure HiGHS unit tests include what failed in unit
99+
// tests elsewhere!
92100
Highs highs;
93101
highs.setOptionValue("output_flag", dev_run);
94102
const HighsInfo& info = highs.getInfo();
95103
HighsLp lp;
104+
lp.sense_ = ObjSense::kMaximize;
96105
lp.num_col_ = 2;
97106
lp.num_row_ = 2;
98107
lp.col_cost_ = {2, -1};
@@ -103,43 +112,70 @@ TEST_CASE("test-1966", "[highs_ipm]") {
103112
lp.a_matrix_.start_ = {0, 2, 4};
104113
lp.a_matrix_.index_ = {0, 1, 0, 1};
105114
lp.a_matrix_.value_ = {1, -1, 1, -1};
115+
lp.a_matrix_.format_ = MatrixFormat::kRowwise;
106116
highs.passModel(lp);
107-
highs.setOptionValue("solver", kIpmString);
108117
highs.setOptionValue("presolve", kHighsOffString);
109-
110-
if (dev_run) printf("\nWith default residual tolerances\n");
111-
highs.run();
112-
if (dev_run) {
113-
highs.writeSolution("", kSolutionStylePretty);
114-
printf("Num primal infeasibilities = %d\n",
115-
int(info.num_primal_infeasibilities));
116-
printf("Max primal infeasibility = %g\n", info.max_primal_infeasibility);
117-
printf("Sum primal infeasibilities = %g\n",
118-
info.sum_primal_infeasibilities);
119-
printf("Num dual infeasibilities = %d\n",
120-
int(info.num_dual_infeasibilities));
121-
printf("Max dual infeasibility = %g\n", info.max_dual_infeasibility);
122-
printf("Sum dual infeasibilities = %g\n", info.sum_dual_infeasibilities);
123-
}
124-
highs.clearSolver();
125-
126-
if (dev_run) printf("\nWith infinite residual tolerances\n");
127-
highs.setOptionValue("primal_residual_tolerance", 1e30);
128-
highs.setOptionValue("dual_residual_tolerance", 1e30);
129-
highs.run();
130-
if (dev_run) {
131-
highs.writeSolution("", kSolutionStylePretty);
132-
printf("Num primal infeasibilities = %d\n",
133-
int(info.num_primal_infeasibilities));
134-
printf("Max primal infeasibility = %g\n", info.max_primal_infeasibility);
135-
printf("Sum primal infeasibilities = %g\n",
136-
info.sum_primal_infeasibilities);
137-
printf("Num dual infeasibilities = %d\n",
138-
int(info.num_dual_infeasibilities));
139-
printf("Max dual infeasibility = %g\n", info.max_dual_infeasibility);
140-
printf("Sum dual infeasibilities = %g\n", info.sum_dual_infeasibilities);
118+
// if (dev_run) highs.writeModel("");
119+
HighsModelStatus require_model_status = HighsModelStatus::kNotset;
120+
for (int k = 0; k < 2; k++) {
121+
if (k == 0) {
122+
highs.setOptionValue("solver", kIpmString);
123+
if (dev_run) printf("Solving with IPX\n");
124+
require_model_status = HighsModelStatus::kInfeasible;
125+
} else {
126+
highs.setOptionValue("solver", kPdlpString);
127+
if (dev_run) printf("Solving with PDLP\n");
128+
require_model_status = HighsModelStatus::kUnboundedOrInfeasible;
129+
}
130+
highs.run();
131+
REQUIRE(info.primal_solution_status != kSolutionStatusFeasible);
132+
REQUIRE(info.dual_solution_status != kSolutionStatusFeasible);
133+
REQUIRE(highs.getModelStatus() == require_model_status);
134+
if (dev_run) {
135+
// Nice illustration that IPX
136+
//
137+
// * identifies "infeasible"
138+
//
139+
// * gets no primal or dual infeasibilies
140+
//
141+
// * gets primal and dual residual errors
142+
//
143+
// whereas PDLP
144+
//
145+
// * identifies only "infeasible or unbounded"
146+
//
147+
// * gets primal infeasibilies and no primal residual errors
148+
//
149+
// * gets no primal infeasibilies but primal residual errors
150+
//
151+
// highs.writeSolution("", kSolutionStylePretty);
152+
printf("Primal solution status = %d\n", int(info.primal_solution_status));
153+
printf("Dual solution status = %d\n", int(info.dual_solution_status));
154+
printf("Num primal infeasibilities = %d\n",
155+
int(info.num_primal_infeasibilities));
156+
printf("Max primal infeasibility = %g\n",
157+
info.max_primal_infeasibility);
158+
printf("Sum primal infeasibilities = %g\n",
159+
info.sum_primal_infeasibilities);
160+
printf("Num dual infeasibilities = %d\n",
161+
int(info.num_dual_infeasibilities));
162+
printf("Max dual infeasibility = %g\n",
163+
info.max_dual_infeasibility);
164+
printf("Sum dual infeasibilities = %g\n",
165+
info.sum_dual_infeasibilities);
166+
printf("Num primal residual errors = %d\n",
167+
int(info.num_primal_residual_errors));
168+
printf("Max primal residual error = %g\n",
169+
info.max_primal_residual_error);
170+
printf("Num dual residual errors = %d\n",
171+
int(info.num_dual_residual_errors));
172+
printf("Max dual residual error = %g\n",
173+
info.max_dual_residual_error);
174+
printf("Primal-dual objective error = %g\n",
175+
info.primal_dual_objective_error);
176+
}
177+
highs.clearSolver();
141178
}
142-
143179
highs.resetGlobalScheduler(true);
144180
}
145181

highs/lp_data/HighsInterface.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2607,9 +2607,6 @@ HighsStatus Highs::lpKktCheck(const std::string& message) {
26072607
info.num_primal_infeasibilities == 0 &&
26082608
(!get_residuals || info.num_primal_residual_errors == 0))
26092609
model_status_ = HighsModelStatus::kUnbounded;
2610-
if (model_status_ != HighsModelStatus::kOptimal &&
2611-
model_status_ != HighsModelStatus::kUnknown)
2612-
return HighsStatus::kOk;
26132610
bool was_optimal = model_status_ == HighsModelStatus::kOptimal;
26142611
bool kkt_ok = true;
26152612
bool written_optimality_error_header = false;
@@ -2717,8 +2714,10 @@ HighsStatus Highs::lpKktCheck(const std::string& message) {
27172714
assert(info.num_relative_dual_residual_errors == 0);
27182715
}
27192716
}
2720-
// Infeasibility of the primal and dual solutions should have been
2721-
// set in getKktFailures
2717+
// Infeasibility of the primal and dual solutions based on number
2718+
// of primal/dual infeasibilities should have been set in
2719+
// getKktFailures, but qualify this if the residuals are
2720+
// meaningful
27222721
if (info.num_primal_infeasibilities) {
27232722
assert(info.primal_solution_status == kSolutionStatusInfeasible);
27242723
} else {
@@ -2843,7 +2842,7 @@ HighsStatus Highs::lpKktCheck(const std::string& message) {
28432842
" since relative violation of tolerances is %8.3g\n",
28442843
max_tolerance_relative_violation);
28452844
} else if (max_allowed_tolerance_relative_violation > 1 &&
2846-
max_tolerance_relative_violation > 0) {
2845+
max_tolerance_relative_violation > 1) {
28472846
highsLogUser(log_options, HighsLogType::kInfo,
28482847
"Model status is \"Optimal\" since relative violation of "
28492848
"tolerances is no more than %8.3g\n",

0 commit comments

Comments
 (0)