Skip to content

Commit 8873bf5

Browse files
authored
Merge pull request #3081 from ERGO-Code/fix-col-stuffing
Fix column stuffing
2 parents 50a0824 + 665302f commit 8873bf5

17 files changed

Lines changed: 1007 additions & 18 deletions

BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,10 @@ TEST_NAMES = [
225225
"TestLpValidation",
226226
"TestMipSolver",
227227
"TestPresolve",
228+
"TestPresolveRules",
228229
"TestQpSolver",
229230
"TestRanging",
231+
"TestRunData",
230232
"TestRays",
231233
"TestSemiVariables",
232234
"TestSetup",

check/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,11 @@ if ((NOT FAST_BUILD OR ALL_TESTS) AND NOT (BUILD_EXTRA_UNIT_ONLY))
7171
TestPdlp.cpp
7272
TestPdlpHi.cpp
7373
TestPresolve.cpp
74+
TestPresolveRules.cpp
7475
TestQpSolver.cpp
7576
TestRanging.cpp
7677
TestRays.cpp
78+
TestRunData.cpp
7779
TestSemiVariables.cpp
7880
TestSetup.cpp
7981
TestSort.cpp

check/TestPresolveRules.cpp

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#include <numeric>
2+
3+
#include "HCheckConfig.h"
4+
#include "Highs.h"
5+
#include "catch.hpp"
6+
7+
const bool dev_run = false;
8+
9+
void presolveOffOn(const std::string& message, const HighsLp& lp, Highs& h,
10+
const HighsInt require_presolved_model_num_col = 0,
11+
const HighsInt require_presolved_model_num_row = 0,
12+
const HighsInt require_presolved_model_num_nz = 0);
13+
14+
TEST_CASE("test-col-stuffing", "[highs_test_presolve_rules]") {
15+
HighsLp lp;
16+
17+
Highs h;
18+
h.setOptionValue("output_flag", dev_run);
19+
h.setOptionValue("presolve_rule_test", kPresolveRuleColStuffing);
20+
const bool lp0 = true;
21+
const bool lp1 = true;
22+
const bool lp1a = true;
23+
const bool lp1b = true;
24+
25+
if (lp0) {
26+
lp.num_col_ = 3;
27+
lp.num_row_ = 1;
28+
lp.sense_ = ObjSense::kMaximize;
29+
lp.col_cost_ = {1.8, 0.9, 1};
30+
lp.col_lower_.assign(lp.num_col_, 0);
31+
lp.col_upper_.assign(lp.num_col_, 1);
32+
lp.row_lower_ = {-kHighsInf};
33+
lp.row_upper_ = {4};
34+
lp.a_matrix_.format_ = MatrixFormat::kRowwise;
35+
lp.a_matrix_.start_ = {0, lp.num_col_};
36+
lp.a_matrix_.index_.resize(lp.num_col_);
37+
std::iota(lp.a_matrix_.index_.begin(), lp.a_matrix_.index_.end(), 0);
38+
lp.a_matrix_.value_ = {3, 2, 2};
39+
40+
for (int k = 0; k < 2; k++) {
41+
if (dev_run) printf("\n3-variable knapsack: %s\n", k == 0 ? "LP" : "IP");
42+
REQUIRE(h.passModel(lp) == HighsStatus::kOk);
43+
h.setOptionValue("presolve_rule_test", kPresolveRuleColStuffing);
44+
h.run();
45+
if (dev_run) h.writeSolution("", 1);
46+
lp.integrality_.assign(lp.num_col_, HighsVarType::kInteger);
47+
}
48+
lp.clear();
49+
}
50+
51+
lp.num_col_ = 2;
52+
lp.num_row_ = 1;
53+
lp.sense_ = ObjSense::kMinimize;
54+
lp.col_lower_.assign(lp.num_col_, 0);
55+
lp.col_upper_.assign(lp.num_col_, 1);
56+
lp.row_lower_ = {2.0};
57+
lp.row_upper_ = {kHighsInf};
58+
lp.a_matrix_.format_ = MatrixFormat::kRowwise;
59+
lp.a_matrix_.start_ = {0, lp.num_col_};
60+
lp.a_matrix_.index_.resize(lp.num_col_);
61+
std::iota(lp.a_matrix_.index_.begin(), lp.a_matrix_.index_.end(), 0);
62+
if (lp1) {
63+
lp.col_cost_.assign(lp.num_col_, 1);
64+
lp.a_matrix_.value_.assign(lp.num_col_, 1);
65+
presolveOffOn("Capturing neos-787933 issue", lp, h);
66+
}
67+
if (lp1a) {
68+
lp.col_cost_ = {2, 1};
69+
lp.a_matrix_.value_.assign(lp.num_col_, 1);
70+
presolveOffOn("Variant A neos-787933 issue", lp, h);
71+
}
72+
if (lp1b) {
73+
lp.col_cost_ = {-2, -1};
74+
lp.a_matrix_.value_.assign(lp.num_col_, 1);
75+
presolveOffOn("Variant B neos-787933 issue", lp, h);
76+
}
77+
lp.clear();
78+
79+
h.resetGlobalScheduler(true);
80+
}
81+
82+
void presolveOffOn(const std::string& message, const HighsLp& lp, Highs& h,
83+
const HighsInt require_presolved_model_num_col,
84+
const HighsInt require_presolved_model_num_row,
85+
const HighsInt require_presolved_model_num_nz) {
86+
const HighsRunData& run_data = h.getRunData();
87+
bool presolve_on = false;
88+
// If the model reduces to empty, then the output from different
89+
// solvers cannot be tested
90+
const bool reduce_to_empty = require_presolved_model_num_col == 0 &&
91+
require_presolved_model_num_row == 0;
92+
const HighsInt to_k = reduce_to_empty ? 2 : 5;
93+
for (int k = 0; k < to_k; k++) {
94+
std::string solver = kSimplexString;
95+
std::string run_crossover = kHighsOnString;
96+
bool basis_postsolve = true;
97+
if (k == 0) {
98+
// Presolve off - to get the optimal solution to debug
99+
// presolve
100+
presolve_on = false;
101+
} else {
102+
presolve_on = true;
103+
if (k == 1) {
104+
solver = kSimplexString;
105+
} else if (k == 2) {
106+
solver = kIpmString;
107+
} else if (k == 3) {
108+
solver = kIpmString;
109+
run_crossover = kHighsOffString;
110+
basis_postsolve = false;
111+
} else {
112+
solver = kHiPdlpString;
113+
basis_postsolve = false;
114+
}
115+
}
116+
std::string presolve = presolve_on ? kHighsOnString : kHighsOffString;
117+
h.setOptionValue(kPresolveString, presolve);
118+
h.setOptionValue(kRunCrossoverString, run_crossover);
119+
h.setOptionValue(kSolverString, solver);
120+
if (dev_run)
121+
printf(
122+
"\n============\n%s: presolve = %s; solver = %s%s\n============\n\n",
123+
message.c_str(), presolve.c_str(), solver.c_str(),
124+
solver == kIpmString ? ("; run_crossover = " + run_crossover).c_str()
125+
: "");
126+
REQUIRE(h.passModel(lp) == HighsStatus::kOk);
127+
h.run();
128+
if (dev_run) h.writeSolution("", 1);
129+
if (presolve_on) {
130+
// Ensure that the model is reduced to empty
131+
REQUIRE(run_data.presolved_model_num_col ==
132+
require_presolved_model_num_col);
133+
REQUIRE(run_data.presolved_model_num_row ==
134+
require_presolved_model_num_row);
135+
REQUIRE(run_data.presolved_model_num_nz ==
136+
require_presolved_model_num_nz);
137+
// Ensure that dual postsolve is correct
138+
REQUIRE(h.getModelStatus() == HighsModelStatus::kOptimal);
139+
REQUIRE(h.getInfo().num_primal_infeasibilities == 0);
140+
REQUIRE(h.getInfo().num_dual_infeasibilities == 0);
141+
REQUIRE(h.getInfo().simplex_iteration_count == 0);
142+
// Ensure that any basis postsolve is correct
143+
if (basis_postsolve)
144+
REQUIRE(run_data.num_simplex_iterations_after_postsolve == 0);
145+
}
146+
}
147+
}

check/TestRunData.cpp

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#include <cstdio>
2+
3+
#include "HCheckConfig.h"
4+
#include "Highs.h"
5+
#include "catch.hpp"
6+
#include "io/HMPSIO.h"
7+
8+
const bool dev_run = false;
9+
10+
TEST_CASE("run-data-md", "[highs_run_data]") {
11+
Highs h;
12+
h.setOptionValue("output_flag", dev_run);
13+
// Use this name so that it can be copied to docs and provides code
14+
// coverage
15+
const std::string run_data_file = "HighsRunData.md";
16+
REQUIRE(h.writeRunData(run_data_file) == HighsStatus::kOk);
17+
if (!dev_run) std::remove(run_data_file.c_str());
18+
}
19+
20+
TEST_CASE("highs-run-data", "[highs_run_data]") {
21+
const std::string test_name = Catch::getResultCapture().getCurrentTestName();
22+
const std::string highs_run_data_file = test_name + ".run_data";
23+
24+
Highs h;
25+
if (!dev_run) h.setOptionValue("output_flag", false);
26+
const HighsRunData& highs_run_data = h.getRunData();
27+
28+
auto testRunData = [&](const std::string& filename) {
29+
HighsStatus return_status = h.readModel(filename);
30+
REQUIRE(return_status == HighsStatus::kOk);
31+
32+
// Cannot write run_data since not valid before run()
33+
return_status = h.writeRunData("");
34+
REQUIRE(return_status == HighsStatus::kWarning);
35+
36+
HighsRunDataType highs_run_data_type;
37+
return_status = h.getRunDataType("presolved_num_col", highs_run_data_type);
38+
REQUIRE(return_status == HighsStatus::kError);
39+
return_status =
40+
h.getRunDataType("presolved_model_num_col", highs_run_data_type);
41+
REQUIRE(return_status == HighsStatus::kOk);
42+
REQUIRE(highs_run_data_type == HighsRunDataType::kInt);
43+
44+
return_status = h.getRunDataType("presolving_time", highs_run_data_type);
45+
REQUIRE(return_status == HighsStatus::kError);
46+
return_status = h.getRunDataType("presolve_time", highs_run_data_type);
47+
REQUIRE(return_status == HighsStatus::kOk);
48+
REQUIRE(highs_run_data_type == HighsRunDataType::kDouble);
49+
50+
// Run data not valid before run()
51+
HighsInt presolved_model_num_col;
52+
return_status =
53+
h.getRunDataValue("presolved_model_num_col", presolved_model_num_col);
54+
REQUIRE(return_status == HighsStatus::kWarning);
55+
56+
return_status = h.run();
57+
REQUIRE(return_status == HighsStatus::kOk);
58+
59+
if (dev_run) {
60+
return_status = h.writeRunData("");
61+
REQUIRE(return_status == HighsStatus::kOk);
62+
}
63+
64+
return_status = h.writeRunData(highs_run_data_file);
65+
REQUIRE(return_status == HighsStatus::kOk);
66+
67+
// Wrong name for objective
68+
return_status =
69+
h.getRunDataValue("presolved_num_col", presolved_model_num_col);
70+
REQUIRE(return_status == HighsStatus::kError);
71+
72+
// Right name for objective
73+
return_status =
74+
h.getRunDataValue("presolved_model_num_col", presolved_model_num_col);
75+
REQUIRE(return_status == HighsStatus::kOk);
76+
77+
if (dev_run)
78+
printf("From getRunDataValue: presolved_model_num_col = %d\n",
79+
int(presolved_model_num_col));
80+
81+
double presolve_time;
82+
// Wrong name for simplex iteration count
83+
return_status = h.getRunDataValue("presolving_time", presolve_time);
84+
REQUIRE(return_status == HighsStatus::kError);
85+
86+
// Right name for presolve time
87+
return_status = h.getRunDataValue("presolve_time", presolve_time);
88+
REQUIRE(return_status == HighsStatus::kOk);
89+
90+
const HighsModelStatus model_status = h.getModelStatus();
91+
if (dev_run) {
92+
printf("From getModelStatus: model_status = %s\n",
93+
h.modelStatusToString(model_status).c_str());
94+
printf("From getRunData: presolved_model_num_col = %d\n",
95+
int(highs_run_data.presolved_model_num_col));
96+
printf("From getRunData: presolved_model_num_row = %d\n",
97+
int(highs_run_data.presolved_model_num_row));
98+
printf("From getRunData: presolved_model_num_nz = %d\n",
99+
int(highs_run_data.presolved_model_num_nz));
100+
if (!h.getLp().isMip())
101+
printf(
102+
"From getRunData: num_simplex_iterations_after_postsolve = %d\n",
103+
int(highs_run_data.num_simplex_iterations_after_postsolve));
104+
printf("From getRunData: presolve_time = %g\n",
105+
highs_run_data.presolve_time);
106+
printf("From getRunData: solve_time = %g\n",
107+
highs_run_data.solve_time);
108+
printf("From getRunData: postsolve_time = %g\n",
109+
highs_run_data.postsolve_time);
110+
}
111+
REQUIRE(highs_run_data.presolved_model_num_col >= 0);
112+
REQUIRE(highs_run_data.presolved_model_num_row >= 0);
113+
REQUIRE(highs_run_data.presolved_model_num_nz >= 0);
114+
if (!h.getLp().isMip())
115+
REQUIRE(highs_run_data.num_simplex_iterations_after_postsolve == 0);
116+
REQUIRE(highs_run_data.presolve_time >= 0);
117+
REQUIRE(highs_run_data.solve_time >= 0);
118+
REQUIRE(highs_run_data.postsolve_time >= 0);
119+
};
120+
121+
std::string filename;
122+
filename = std::string(HIGHS_DIR) + "/check/instances/adlittle.mps";
123+
testRunData(filename);
124+
125+
filename = std::string(HIGHS_DIR) + "/check/instances/egout-ac.mps";
126+
testRunData(filename);
127+
128+
// Doesn't work for MIPs yet, but wait until profiling is merged in
129+
// to avoid conflicts
130+
//
131+
// filename = std::string(HIGHS_DIR) + "/check/instances/flugpl.mps";
132+
// testRunData(filename);
133+
134+
if (!dev_run) std::remove(highs_run_data_file.c_str());
135+
136+
h.resetGlobalScheduler(true);
137+
}

check/meson.build

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,11 @@ test_array = [
5555
['test_TestOptions', 'TestOptions.cpp'],
5656
['test_TestPdlp', 'TestPdlp.cpp'],
5757
['test_TestPresolve', 'TestPresolve.cpp'],
58+
['test_TestPresolveRules', 'TestPresolveRules.cpp'],
5859
['test_TestQpSolver', 'TestQpSolver.cpp'],
5960
['test_TestRanging', 'TestRanging.cpp'],
6061
['test_TestRays', 'TestRays.cpp'],
62+
['test_TestRunData', 'TestRunData.cpp'],
6163
['test_TestSemiVariables', 'TestSemiVariables.cpp'],
6264
['test_TestSetup', 'TestSetup.cpp'],
6365
['test_TestSort', 'TestSort.cpp'],

cmake/sources.cmake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ set(highs_sources
364364
lp_data/HighsModelUtils.cpp
365365
lp_data/HighsOptions.cpp
366366
lp_data/HighsRanging.cpp
367+
lp_data/HighsRunData.cpp
367368
lp_data/HighsSolution.cpp
368369
lp_data/HighsSolutionDebug.cpp
369370
lp_data/HighsSolve.cpp
@@ -410,6 +411,7 @@ set(highs_sources
410411
presolve/HighsSymmetry.cpp
411412
presolve/HPresolve.cpp
412413
presolve/HPresolveAnalysis.cpp
414+
presolve/HPresolveTest.cpp
413415
presolve/ICrash.cpp
414416
presolve/ICrashUtil.cpp
415417
presolve/ICrashX.cpp
@@ -492,6 +494,7 @@ set(highs_headers
492494
lp_data/HighsModelUtils.h
493495
lp_data/HighsOptions.h
494496
lp_data/HighsRanging.h
497+
lp_data/HighsRunData.h
495498
lp_data/HighsSolution.h
496499
lp_data/HighsSolutionDebug.h
497500
lp_data/HighsSolve.h

0 commit comments

Comments
 (0)