Skip to content

Commit cd50bf7

Browse files
committed
Extracted effective-costs changes from fix-initial-sweep-interrupt
1 parent 939eba3 commit cd50bf7

12 files changed

Lines changed: 603 additions & 203 deletions

check/TestMipSolver.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2033,5 +2033,6 @@ TEST_CASE("dominated-column-double-fixing", "[highs_test_mip_solver]") {
20332033
highs.setOptionValue("output_flag", dev_run);
20342034
REQUIRE(highs.passModel(lp) == HighsStatus::kOk);
20352035
solve(highs, kHighsOffString, HighsModelStatus::kInfeasible);
2036+
highs.setOptionValue("output_flag", dev_run);
20362037
solve(highs, kHighsOnString, HighsModelStatus::kInfeasible);
20372038
}

check/TestPresolve.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,3 +1241,56 @@ TEST_CASE("issue-3140", "[highs_test_presolve]") {
12411241

12421242
highs.resetGlobalScheduler(true);
12431243
}
1244+
1245+
TEST_CASE("presolve-light-no-crossover", "[highs_test_presolve]") {
1246+
Highs h;
1247+
h.setOptionValue("output_flag", dev_run);
1248+
std::string source = "instances"; //"miplib2017";
1249+
std::string model = "adlittle"; //"germanrr"; //"neos-2746589-doon";//
1250+
std::string type = "mps";
1251+
std::string filename =
1252+
(source == "instances" ? std::string(HIGHS_DIR) + "/check/instances/"
1253+
: "/srv/" + source) +
1254+
"/" + model + "." + type + (source == "instances" ? "" : ".gz");
1255+
1256+
REQUIRE(h.readModel(filename) == HighsStatus::kOk);
1257+
const HighsLp& lp = h.getLp();
1258+
1259+
REQUIRE(h.setOptionValue("presolve_light", kHighsOnString) ==
1260+
HighsStatus::kOk);
1261+
const std::string solver = kIpxString; // kHipoString; //
1262+
REQUIRE(h.setOptionValue("solver", solver) == HighsStatus::kOk);
1263+
REQUIRE(h.setOptionValue("run_crossover", kHighsOffString) ==
1264+
HighsStatus::kOk);
1265+
REQUIRE(h.setOptionValue("solve_relaxation", true) == HighsStatus::kOk);
1266+
h.run();
1267+
1268+
REQUIRE(h.getModelStatus() == HighsModelStatus::kOptimal);
1269+
1270+
h.resetGlobalScheduler(true);
1271+
}
1272+
1273+
/*
1274+
TEST_CASE("test-fuzzing", "[highs_test_presolve]") {
1275+
Highs h;
1276+
// h.setOptionValue("output_flag", dev_run);
1277+
h.setOptionValue("presolve_rule_logging", true);
1278+
h.setOptionValue("log_dev_level", 1);
1279+
1280+
const std::string model = "issue-008";
1281+
std::string model_file = std::string(HIGHS_DIR) + "/build/OscarFuzzing/" +
1282+
model + "/" + model + ".mps";
1283+
1284+
REQUIRE(h.readModel(model_file) == HighsStatus::kOk);
1285+
1286+
std::string options_file =
1287+
std::string(HIGHS_DIR) + "/build/OscarFuzzing/" + model + "/options.txt";
1288+
REQUIRE(h.readOptions(options_file) == HighsStatus::kOk);
1289+
1290+
h.writeOptions("", true);
1291+
1292+
h.run();
1293+
1294+
h.resetGlobalScheduler(true);
1295+
}
1296+
*/

check/TestPresolveRules.cpp

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ TEST_CASE("test-col-stuffing", "[highs_test_presolve_rules]") {
2626
Highs h;
2727
h.setOptionValue("output_flag", dev_run);
2828
h.setOptionValue("presolve_rule_test", kPresolveRuleColStuffing);
29+
REQUIRE(h.setOptionValue("presolve_rule_logging", true) == HighsStatus::kOk);
30+
// Initial sweep doesn't yield reductions, but switch it off for clarity
31+
REQUIRE(h.setOptionValue("presolve_rule_off",
32+
1 << kPresolveRuleInitialSweep) == HighsStatus::kOk);
2933
const bool lp0 = true;
3034
const bool lp1 = true;
3135
const bool lp1a = true;
@@ -90,6 +94,67 @@ TEST_CASE("test-col-stuffing", "[highs_test_presolve_rules]") {
9094
h.resetGlobalScheduler(true);
9195
}
9296

97+
/*
98+
TEST_CASE("test-weakly-dominated-col-upper", "[highs_test_presolve_rules]") {
99+
Highs h;
100+
h.setOptionValue("output_flag", dev_run);
101+
REQUIRE(h.setOptionValue("presolve_rule_logging", true) == HighsStatus::kOk);
102+
// LP is
103+
//
104+
// min -y, subject to x+y <= 0, x >= 0; 0 <= x <= 1, y free
105+
//
106+
// Optimal solution is x = 1; y = -1, with x nonbasic with dual -1, and
107+
HighsLp lp;
108+
lp.num_col_ = 2;
109+
lp.num_row_ = 2;
110+
lp.col_lower_ = {-kHighsInf, -kHighsInf};
111+
lp.col_upper_ = {1, kHighsInf};
112+
lp.row_lower_ = {-kHighsInf, 1};
113+
lp.row_upper_ = { 0, kHighsInf};
114+
lp.a_matrix_.format_ = MatrixFormat::kRowwise;
115+
lp.a_matrix_.start_ = {0, 2, 3};
116+
lp.a_matrix_.index_ = {0, 1, 0};
117+
lp.a_matrix_.value_ = {1, 1, 1};
118+
119+
bool maximize_first = true;
120+
std::string sense_string = "";
121+
std::string test_string = "";
122+
123+
for (HighsInt k = 0; k < 2; k++) {
124+
// Passes are minimize c^Tx and maximize -c^Tx according to
125+
// maximize_first
126+
if (maximize_first) {
127+
lp.sense_ = ObjSense::kMaximize;
128+
sense_string = "maximize";
129+
lp.col_cost_ = {0, 1};
130+
} else {
131+
lp.sense_ = ObjSense::kMinimize;
132+
sense_string = "minimize";
133+
lp.col_cost_ = {0, -1};
134+
}
135+
// REQUIRE(h.setOptionValue("presolve_rule_test", 0) == HighsStatus::kOk);
136+
// test_string = "vanilla-presolve-" + sense_string;
137+
// presolveOffOn(test_string, lp, h);
138+
139+
REQUIRE(h.setOptionValue("presolve_rule_test",
140+
kPresolveRuleWeaklyDominatedColUpper) == HighsStatus::kOk);
141+
142+
// test_string = "initial-sweep+test-weakly-dominated-col-upper-" +
143+
sense_string;
144+
// presolveOffOn(test_string, lp, h, 1, 1, 1);
145+
146+
REQUIRE(h.setOptionValue("presolve_rule_off", 1 <<
147+
kPresolveRuleInitialSweep) == HighsStatus::kOk);
148+
149+
test_string = "test-weakly-dominated-col-upper-" + sense_string;
150+
presolveOffOn(test_string, lp, h, 1, 2, 1);
151+
152+
maximize_first = !maximize_first;
153+
}
154+
h.resetGlobalScheduler(true);
155+
}
156+
*/
157+
93158
TEST_CASE("test-parallel-rows-cut-ordering", "[highs_test_presolve_rules]") {
94159
// Rows 0 and 1 are parallel (both [1, 1]). Row 0 is marked as a
95160
// cut. detectParallelRowsAndCols must remove the cut row (0) and
@@ -113,6 +178,7 @@ TEST_CASE("test-parallel-rows-cut-ordering", "[highs_test_presolve_rules]") {
113178

114179
HighsOptions options;
115180
options.presolve_rule_test = kPresolveRuleParallelRowsAndCols;
181+
options.presolve_rule_off = 1 << kPresolveRuleInitialSweep;
116182
options.output_flag = dev_run;
117183

118184
HighsTimer timer;
@@ -137,6 +203,117 @@ TEST_CASE("test-parallel-rows-cut-ordering", "[highs_test_presolve_rules]") {
137203
REQUIRE(!postsolve_stack.isCutRow(0));
138204
}
139205

206+
TEST_CASE("test-effective-costs", "[highs_test_presolve]") {
207+
// Debugging ZeroObjSingletonContinuousCol for germanrr highlighted
208+
// the deficiency in computing the active_cost_norm when the
209+
// objective is f = z, with z = c^Tx and z free. In
210+
// HighsSolution.cpp is the method getEffectiveCosts that
211+
// substitutes all free column singletons into the objective to get
212+
// the "effective costs".
213+
Highs h;
214+
h.setOptionValue("output_flag", dev_run);
215+
bool test_all = true;
216+
bool test_lp0 = test_all;
217+
bool test_lp1 = test_all;
218+
bool test_lp2 = test_all;
219+
220+
if (test_lp0) {
221+
HighsLp lp;
222+
// First LP is
223+
//
224+
// min 4z
225+
//
226+
// -1 <= x + y - 2z <= 1
227+
//
228+
// -1 <= 201x + y <= 1
229+
//
230+
// 0 <= x <= 1, y, z free
231+
//
232+
// where the bounds on the two constraints and non-unit
233+
// coefficients of z in the objective and first contraint give
234+
// code coverage
235+
//
236+
// Aiming to minimize 4z, and bound is given by 2z >= x + y - 1,
237+
// so substitute z = (x+y-1)/2 into the objective to give
238+
//
239+
// min 2x + 2y - 2
240+
//
241+
// y is then minimized with bound is given by y >= -201x - 1, so
242+
// substitute y = -201x - 1 into the objective to give
243+
//
244+
// min 2x +(-402x-2) - 2 = -400x - 4
245+
//
246+
// This function is minimized when x = 1 to give y = -202 and z =
247+
// -101 with objective -404
248+
//
249+
// The optimal dual values are -400 for x, -2 for row 0 and 2 for
250+
// row 1. However, although this example tests code coverage on
251+
// identifying free column singletons and a double free column
252+
// singleton identified in getEffectiveCosts, the dual of -400 for
253+
// the only nonbasic column means that there are no active costs,
254+
// so active_cost_norm is zero (hence absolute and relative dual
255+
// infeasibility measures are identical).
256+
lp.model_name_ = "LP0";
257+
lp.num_col_ = 3;
258+
lp.num_row_ = 2;
259+
lp.col_cost_ = {0, 0, 4};
260+
lp.col_lower_ = {0, -kHighsInf, -kHighsInf};
261+
lp.col_upper_ = {1, kHighsInf, kHighsInf};
262+
lp.a_matrix_.format_ = MatrixFormat::kRowwise;
263+
lp.a_matrix_.start_ = {0, 3, 5};
264+
lp.a_matrix_.index_ = {0, 1, 2, 0, 1};
265+
lp.a_matrix_.value_ = {1, 1, -2, 201, 1};
266+
lp.row_lower_ = {-1, -1};
267+
lp.row_upper_ = {1, 1};
268+
h.passModel(lp);
269+
h.setOptionValue("log_dev_level", 1);
270+
h.setOptionValue("presolve_rule_logging", kHighsOnString);
271+
h.run();
272+
REQUIRE(h.getInfo().active_cost_norm == 0);
273+
}
274+
if (test_lp1) {
275+
HighsLp lp;
276+
// Here's a simpler example that reflects the behaviour observed
277+
// with germanrr, where the cost row of the matrix introduced many
278+
// large costs. Hence the presolved model had a large value for
279+
// active_cost_norm but, after postsolve, the model had
280+
// active_cost_norm = 1.
281+
282+
double cost = 1e5;
283+
double eps = 1e-4;
284+
lp.model_name_ = "LP1";
285+
lp.num_col_ = 3;
286+
lp.num_row_ = 2;
287+
lp.col_cost_ = {0, 0, 1};
288+
lp.col_lower_ = {0, 0, -kHighsInf};
289+
lp.col_upper_ = {1, 1, kHighsInf};
290+
lp.a_matrix_.format_ = MatrixFormat::kRowwise;
291+
lp.a_matrix_.start_ = {0, 3, 5};
292+
lp.a_matrix_.index_ = {0, 1, 2, 0, 1};
293+
lp.a_matrix_.value_ = {cost, cost - eps, 1, 1, 1, 1};
294+
lp.row_lower_ = {0, 1};
295+
lp.row_upper_ = {0, 1};
296+
h.passModel(lp);
297+
298+
h.run();
299+
REQUIRE(h.getInfo().active_cost_norm == cost);
300+
}
301+
if (test_lp2) {
302+
// Finally gas11 has 61 free column singletons: 55 in the first
303+
// pass, and 6 in the second.
304+
const std::string model = "gas11";
305+
std::string model_file =
306+
std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps";
307+
REQUIRE(h.readModel(model_file) == HighsStatus::kWarning);
308+
REQUIRE(h.setOptionValue(kPresolveString, kHighsOffString) ==
309+
HighsStatus::kOk);
310+
HighsStatus return_status = h.run();
311+
REQUIRE(return_status == HighsStatus::kOk);
312+
double active_cost_norm = 2.000000001e+7;
313+
REQUIRE(std::fabs(h.getInfo().active_cost_norm - active_cost_norm) <= 1e-8);
314+
}
315+
}
316+
140317
TEST_CASE("test-fourier-motzkin", "[highs_test_presolve_rules]") {
141318
Highs h;
142319
h.setOptionValue("output_flag", dev_run);

cmake/sources.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ set(highs_sources
417417
presolve/HighsSymmetry.cpp
418418
presolve/HPresolve.cpp
419419
presolve/HPresolveAnalysis.cpp
420+
presolve/HPresolveDebug.cpp
420421
presolve/HPresolveTest.cpp
421422
presolve/ICrash.cpp
422423
presolve/ICrashUtil.cpp

highs/lp_data/HighsInfo.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ void HighsInfo::invalidatePrimalKkt() {
5353
num_complementarity_violations = kHighsIllegalComplementarityCount;
5454
max_complementarity_violation = kHighsIllegalComplementarityViolation;
5555
primal_dual_objective_error = kHighsIllegalComplementarityViolation;
56+
active_bound_norm = kHighsIllegalDoubleMeasure;
5657
}
5758

5859
void HighsInfo::invalidateDualKkt() {
@@ -69,6 +70,7 @@ void HighsInfo::invalidateDualKkt() {
6970
num_complementarity_violations = kHighsIllegalComplementarityCount;
7071
max_complementarity_violation = kHighsIllegalComplementarityViolation;
7172
primal_dual_objective_error = kHighsIllegalComplementarityViolation;
73+
active_cost_norm = kHighsIllegalDoubleMeasure;
7274
}
7375

7476
bool HighsInfo::equal(const HighsInfo& info_) const {
@@ -149,6 +151,8 @@ bool HighsInfo::equal(const HighsInfo& info_) const {
149151
return false;
150152
if (info_.primal_dual_objective_error != this->primal_dual_objective_error)
151153
return false;
154+
if (info_.active_cost_norm != this->active_cost_norm) return false;
155+
if (info_.active_bound_norm != this->active_bound_norm) return false;
152156
return true;
153157
}
154158

highs/lp_data/HighsInfo.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ struct HighsInfoStruct {
129129
double max_complementarity_violation;
130130
double primal_dual_objective_error;
131131
double primal_dual_integral;
132+
double active_cost_norm;
133+
double active_bound_norm;
132134
};
133135

134136
class HighsInfo : public HighsInfoStruct {
@@ -386,6 +388,15 @@ class HighsInfo : public HighsInfoStruct {
386388
new InfoRecordDouble("primal_dual_integral", "Primal-dual integral",
387389
advanced, &primal_dual_integral, 0);
388390
records.push_back(record_double);
391+
392+
record_double = new InfoRecordDouble("active_cost_norm", "Active cost norm",
393+
advanced, &active_cost_norm, 0);
394+
records.push_back(record_double);
395+
396+
record_double =
397+
new InfoRecordDouble("active_bound_norm", "Active bound norm", advanced,
398+
&active_bound_norm, 0);
399+
records.push_back(record_double);
389400
}
390401

391402
public:

0 commit comments

Comments
 (0)