Skip to content

Commit 080579b

Browse files
authored
Merge pull request #2559 from ERGO-Code/fix-2557
Added `Highs::getFixedLp(HighsLp& lp)`
2 parents e040dd4 + fd4b6f5 commit 080579b

File tree

12 files changed

+467
-19
lines changed

12 files changed

+467
-19
lines changed

check/TestBasis.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,13 +335,15 @@ TEST_CASE("Basis-read", "[highs_basis_data]") {
335335
HighsBasisStatus status_before = HighsBasisStatus::kNonbasic;
336336
HighsBasisStatus status_after = HighsBasisStatus::kBasic;
337337
Highs h1;
338+
h1.setOptionValue("output_flag", dev_run);
338339
const HighsBasis& basis1 = h1.getBasis();
339340
h1.passModel(lp);
340341
REQUIRE(basis1.col_status[0] == status_before);
341342
h1.run();
342343
REQUIRE(basis1.col_status[0] == status_after);
343344

344345
Highs h2;
346+
h2.setOptionValue("output_flag", dev_run);
345347
const HighsBasis& basis2 = h2.getBasis();
346348
h2.passModel(lp);
347349
REQUIRE(basis2.col_status[0] == status_before);

check/TestCAPI.c

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1820,7 +1820,6 @@ void testGetModel() {
18201820
assert(ck_sense == sense);
18211821

18221822
double* ck_col_cost = (double*)malloc(sizeof(double) * ck_num_col);
1823-
;
18241823
double* ck_col_lower = (double*)malloc(sizeof(double) * ck_num_col);
18251824
double* ck_col_upper = (double*)malloc(sizeof(double) * ck_num_col);
18261825
double* ck_row_lower = (double*)malloc(sizeof(double) * ck_num_row);
@@ -2242,6 +2241,115 @@ void testIis() {
22422241
Highs_destroy(highs);
22432242
}
22442243

2244+
void testFixedLp() {
2245+
// The use of Highs_getFixedLp is illustrated for the MIP
2246+
//
2247+
// Min f = -3x_0 - 2x_1 - x_2
2248+
// s.t. x_0 + x_1 + x_2 <= 7
2249+
// 4x_0 + 2x_1 + x_2 = 12
2250+
// x_0 >=0; x_1 >= 0; x_2 binary
2251+
2252+
const HighsInt num_col = 3;
2253+
const HighsInt num_row = 2;
2254+
const HighsInt num_nz = 6;
2255+
HighsInt a_format = kHighsMatrixFormatColwise;
2256+
HighsInt sense = kHighsObjSenseMinimize;
2257+
double offset = 0;
2258+
2259+
// Define the column costs, lower bounds and upper bounds
2260+
double col_cost[3] = {-3.0, -2.0, -1.0};
2261+
double col_lower[3] = {0.0, 0.0, 0.0};
2262+
double col_upper[3] = {1.0e30, 1.0e30, 1.0};
2263+
// Define the row lower bounds and upper bounds
2264+
double row_lower[2] = {-1.0e30, 12.0};
2265+
double row_upper[2] = {7.0, 12.0};
2266+
// Define the constraint matrix column-wise
2267+
HighsInt a_start[3] = {0, 2, 4};
2268+
HighsInt a_index[6] = {0, 1, 0, 1, 0, 1};
2269+
double a_value[6] = {1.0, 4.0, 1.0, 2.0, 1.0, 1.0};
2270+
HighsInt integrality[3] = {kHighsVarTypeContinuous, kHighsVarTypeContinuous,
2271+
kHighsVarTypeInteger};
2272+
2273+
void* highs = Highs_create();
2274+
Highs_setBoolOptionValue(highs, "output_flag", dev_run);
2275+
Highs_setStringOptionValue(highs, "presolve", "off");
2276+
HighsInt return_status =
2277+
Highs_passMip(highs, num_col, num_row, num_nz, a_format, sense, offset,
2278+
col_cost, col_lower, col_upper, row_lower, row_upper,
2279+
a_start, a_index, a_value, integrality);
2280+
assert(return_status == kHighsStatusOk);
2281+
return_status = Highs_run(highs);
2282+
double mip_objective_function_value;
2283+
return_status = Highs_getDoubleInfoValue(highs, "objective_function_value",
2284+
&mip_objective_function_value);
2285+
assert(return_status == kHighsStatusOk);
2286+
2287+
double* col_value = (double*)malloc(sizeof(double) * num_col);
2288+
return_status = Highs_getSolution(highs, col_value, NULL, NULL, NULL);
2289+
assert(return_status == kHighsStatusOk);
2290+
2291+
HighsInt fixed_lp_num_col;
2292+
HighsInt fixed_lp_num_row;
2293+
HighsInt fixed_lp_num_nz;
2294+
HighsInt fixed_lp_sense;
2295+
double fixed_lp_offset;
2296+
Highs_getFixedLp(highs, kHighsMatrixFormatColwise, &fixed_lp_num_col, &fixed_lp_num_row,
2297+
&fixed_lp_num_nz, &fixed_lp_sense, &fixed_lp_offset, NULL, NULL, NULL, NULL, NULL,
2298+
NULL, NULL, NULL);
2299+
2300+
assert(fixed_lp_num_col == num_col);
2301+
assert(fixed_lp_num_row == num_row);
2302+
assert(fixed_lp_num_nz == num_nz);
2303+
assert(fixed_lp_sense == sense);
2304+
2305+
double* fixed_lp_col_cost = (double*)malloc(sizeof(double) * fixed_lp_num_col);
2306+
double* fixed_lp_col_lower = (double*)malloc(sizeof(double) * fixed_lp_num_col);
2307+
double* fixed_lp_col_upper = (double*)malloc(sizeof(double) * fixed_lp_num_col);
2308+
double* fixed_lp_row_lower = (double*)malloc(sizeof(double) * fixed_lp_num_row);
2309+
double* fixed_lp_row_upper = (double*)malloc(sizeof(double) * fixed_lp_num_row);
2310+
HighsInt* fixed_lp_a_start = (HighsInt*)malloc(sizeof(HighsInt) * fixed_lp_num_col);
2311+
HighsInt* fixed_lp_a_index = (HighsInt*)malloc(sizeof(HighsInt) * fixed_lp_num_nz);
2312+
double* fixed_lp_a_value = (double*)malloc(sizeof(double) * num_nz);
2313+
2314+
// Get the arrays
2315+
Highs_getFixedLp(highs, kHighsMatrixFormatColwise, &fixed_lp_num_col, &fixed_lp_num_row,
2316+
&fixed_lp_num_nz, &fixed_lp_sense, &fixed_lp_offset, fixed_lp_col_cost, fixed_lp_col_lower,
2317+
fixed_lp_col_upper, fixed_lp_row_lower, fixed_lp_row_upper, fixed_lp_a_start, fixed_lp_a_index,
2318+
fixed_lp_a_value);
2319+
2320+
return_status = Highs_passLp(highs,
2321+
fixed_lp_num_col, fixed_lp_num_row, fixed_lp_num_nz,
2322+
kHighsMatrixFormatColwise,
2323+
fixed_lp_sense, fixed_lp_offset,
2324+
fixed_lp_col_cost, fixed_lp_col_lower, fixed_lp_col_upper,
2325+
fixed_lp_row_lower, fixed_lp_row_upper,
2326+
fixed_lp_a_start, fixed_lp_a_index, fixed_lp_a_value);
2327+
assert(return_status == kHighsStatusOk);
2328+
2329+
return_status = Highs_setSolution(highs, col_value, NULL, NULL, NULL);
2330+
assert(return_status == kHighsStatusOk);
2331+
2332+
return_status = Highs_run(highs);
2333+
double objective_function_value;
2334+
return_status = Highs_getDoubleInfoValue(highs, "objective_function_value",
2335+
&objective_function_value);
2336+
assert(return_status == kHighsStatusOk);
2337+
assert(objective_function_value == mip_objective_function_value);
2338+
2339+
2340+
free(col_value);
2341+
free(fixed_lp_col_cost);
2342+
free(fixed_lp_col_lower);
2343+
free(fixed_lp_col_upper);
2344+
free(fixed_lp_row_lower);
2345+
free(fixed_lp_row_upper);
2346+
free(fixed_lp_a_start);
2347+
free(fixed_lp_a_index);
2348+
free(fixed_lp_a_value);
2349+
2350+
Highs_destroy(highs);
2351+
}
2352+
22452353
int main() {
22462354
minimalApiIllegalLp();
22472355
testCallback();
@@ -2265,7 +2373,8 @@ int main() {
22652373
testQpIndefiniteFailure();
22662374
testDualRayTwice();
22672375
testDeleteRowResolveWithBasis();
2268-
testIis();
2376+
testIis();
2377+
testFixedLp();
22692378
return 0;
22702379
}
22712380
// testSetSolution();

check/TestIpm.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ TEST_CASE("test-2527", "[highs_ipm]") {
205205
std::string filename =
206206
std::string(HIGHS_DIR) + "/check/instances/primal1.mps";
207207
Highs h;
208-
// h.setOptionValue("output_flag", dev_run);
208+
h.setOptionValue("output_flag", dev_run);
209209
REQUIRE(h.readModel(filename) == HighsStatus::kOk);
210210
HighsLp lp = h.getLp();
211211
lp.col_cost_.assign(lp.num_col_, 0);

check/TestMipSolver.cpp

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,3 +1035,157 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") {
10351035
"found\n");
10361036
solve(highs, kHighsOffString, require_model_status, optimal_objective);
10371037
}
1038+
1039+
TEST_CASE("get-fixed-lp", "[highs_test_mip_solver]") {
1040+
std::string model = "avgas";
1041+
std::string model_file =
1042+
std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps";
1043+
HighsLp fixed_lp;
1044+
Highs h;
1045+
h.setOptionValue("output_flag", dev_run);
1046+
REQUIRE(h.readModel(model_file) == HighsStatus::kOk);
1047+
REQUIRE(h.getFixedLp(fixed_lp) == HighsStatus::kError);
1048+
1049+
model = "flugpl";
1050+
model_file = std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps";
1051+
REQUIRE(h.readModel(model_file) == HighsStatus::kOk);
1052+
REQUIRE(h.getFixedLp(fixed_lp) == HighsStatus::kError);
1053+
1054+
REQUIRE(h.run() == HighsStatus::kOk);
1055+
double mip_optimal_objective = h.getInfo().objective_function_value;
1056+
HighsSolution solution = h.getSolution();
1057+
1058+
// Transform the incumbent MIP into the fixed LP
1059+
HighsLp mip = h.getLp();
1060+
std::vector<HighsInt> col_set;
1061+
std::vector<double> fixed_value;
1062+
for (HighsInt iCol = 0; iCol < mip.num_col_; iCol++) {
1063+
if (mip.integrality_[iCol] == HighsVarType::kInteger) {
1064+
col_set.push_back(iCol);
1065+
fixed_value.push_back(solution.col_value[iCol]);
1066+
}
1067+
}
1068+
h.clearIntegrality();
1069+
HighsInt num_set_entries = col_set.size();
1070+
h.changeColsBounds(num_set_entries, col_set.data(), fixed_value.data(),
1071+
fixed_value.data());
1072+
h.setOptionValue("presolve", kHighsOffString);
1073+
REQUIRE(h.run() == HighsStatus::kOk);
1074+
1075+
REQUIRE(h.getInfo().objective_function_value == mip_optimal_objective);
1076+
// In calling changeColsBounds, the incumbent solution was always
1077+
// cleared, so there was no information from which to construct an
1078+
// advanced basis. Hence simplex starts from a logical basis and
1079+
// requires a positive number of iterations (#2556)
1080+
//
1081+
// Before code to retain solution if changing the bounds and
1082+
// solution remains feasible
1083+
//
1084+
// REQUIRE(h.getInfo().simplex_iteration_count > 0);
1085+
REQUIRE(h.getInfo().simplex_iteration_count == 0);
1086+
1087+
// Now, passing the MIP solution, there is information from which to
1088+
// construct an advanced basis. In the case of flugpl, this is
1089+
// optimal, so no simplex iterations are required
1090+
h.clearSolver();
1091+
h.setSolution(solution);
1092+
REQUIRE(h.run() == HighsStatus::kOk);
1093+
1094+
REQUIRE(h.getInfo().objective_function_value == mip_optimal_objective);
1095+
REQUIRE(h.getInfo().simplex_iteration_count == 0);
1096+
1097+
// Now re-load the MIP, re-solve, and get the fixed LP
1098+
REQUIRE(h.passModel(mip) == HighsStatus::kOk);
1099+
REQUIRE(h.run() == HighsStatus::kOk);
1100+
REQUIRE(h.getInfo().objective_function_value == mip_optimal_objective);
1101+
1102+
REQUIRE(h.getFixedLp(fixed_lp) == HighsStatus::kOk);
1103+
1104+
REQUIRE(h.passModel(fixed_lp) == HighsStatus::kOk);
1105+
REQUIRE(h.run() == HighsStatus::kOk);
1106+
1107+
REQUIRE(h.getInfo().objective_function_value == mip_optimal_objective);
1108+
1109+
// Now run from saved solution (without presolve)
1110+
h.clearSolver();
1111+
h.setSolution(solution);
1112+
REQUIRE(h.run() == HighsStatus::kOk);
1113+
1114+
REQUIRE(h.getInfo().objective_function_value == mip_optimal_objective);
1115+
REQUIRE(h.getInfo().simplex_iteration_count == 0);
1116+
1117+
REQUIRE(h.readModel(model_file) == HighsStatus::kOk);
1118+
// Perturb one of the integer variables for code coverage of
1119+
// warning: makes fixed LP of flugpl infeasible
1120+
std::vector<HighsVarType> integrality = h.getLp().integrality_;
1121+
for (HighsInt iCol = 0; iCol < fixed_lp.num_col_; iCol++) {
1122+
if (integrality[iCol] != HighsVarType::kContinuous) {
1123+
solution.col_value[iCol] -= 0.01;
1124+
break;
1125+
}
1126+
}
1127+
1128+
REQUIRE(h.run() == HighsStatus::kOk);
1129+
h.setSolution(solution);
1130+
1131+
REQUIRE(h.getFixedLp(fixed_lp) == HighsStatus::kWarning);
1132+
1133+
REQUIRE(h.passModel(fixed_lp) == HighsStatus::kOk);
1134+
REQUIRE(h.run() == HighsStatus::kOk);
1135+
1136+
REQUIRE(h.getModelStatus() == HighsModelStatus::kInfeasible);
1137+
1138+
h.resetGlobalScheduler(true);
1139+
}
1140+
1141+
TEST_CASE("get-fixed-lp-semi", "[highs_test_mip_solver]") {
1142+
HighsLp lp;
1143+
lp.num_col_ = 4;
1144+
lp.num_row_ = 2;
1145+
lp.col_cost_ = {1, 3, 1, 2};
1146+
lp.col_lower_ = {0, 0, 1, 1};
1147+
lp.col_upper_ = {1, 1, 3, 5};
1148+
lp.integrality_ = {HighsVarType::kContinuous, HighsVarType::kInteger,
1149+
HighsVarType::kSemiContinuous, HighsVarType::kSemiInteger};
1150+
lp.row_lower_ = {4, 10};
1151+
lp.row_upper_ = {kHighsInf, kHighsInf};
1152+
lp.a_matrix_.start_ = {0, 2, 4, 6, 8};
1153+
lp.a_matrix_.index_ = {0, 1, 0, 1, 0, 1, 0, 1, 0, 1};
1154+
lp.a_matrix_.value_ = {1, 1, 1, 2, 1, 3, 1, 4, 5, 1};
1155+
Highs h;
1156+
h.setOptionValue("output_flag", dev_run);
1157+
h.setOptionValue("presolve", kHighsOffString);
1158+
h.passModel(lp);
1159+
h.run();
1160+
double mip_optimal_objective = h.getInfo().objective_function_value;
1161+
HighsSolution solution = h.getSolution();
1162+
HighsLp fixed_lp;
1163+
REQUIRE(h.getFixedLp(fixed_lp) == HighsStatus::kOk);
1164+
1165+
REQUIRE(h.passModel(fixed_lp) == HighsStatus::kOk);
1166+
REQUIRE(h.run() == HighsStatus::kOk);
1167+
1168+
REQUIRE(h.getInfo().objective_function_value == mip_optimal_objective);
1169+
}
1170+
1171+
TEST_CASE("row-fixed-lp", "[highs_test_mip_solver]") {
1172+
std::string model = "flugpl";
1173+
std::string model_file =
1174+
std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps";
1175+
Highs h;
1176+
// h.setOptionValue("output_flag", dev_run);
1177+
REQUIRE(h.readModel(model_file) == HighsStatus::kOk);
1178+
REQUIRE(h.run() == HighsStatus::kOk);
1179+
double mip_optimal_objective = h.getInfo().objective_function_value;
1180+
HighsSolution solution = h.getSolution();
1181+
1182+
HighsLp lp = h.getLp();
1183+
h.clearIntegrality();
1184+
h.changeRowsBounds(0, lp.num_row_ - 1, solution.row_value.data(),
1185+
solution.row_value.data());
1186+
h.setOptionValue("presolve", kHighsOffString);
1187+
REQUIRE(h.run() == HighsStatus::kOk);
1188+
REQUIRE(h.getInfo().objective_function_value <= mip_optimal_objective);
1189+
1190+
h.resetGlobalScheduler(true);
1191+
}

highs/Highs.h

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -468,12 +468,11 @@ class Highs {
468468
}
469469

470470
/**
471-
* @brief Return a const pointer to the original row indices for the
472-
* presolved model
471+
* @brief Return an LP associated with a MIP and its solution, with
472+
* each integer variable fixed to the value it takes in the MIP
473+
* solution. If no solution is available, an error is returned.
473474
*/
474-
const HighsInt* getPresolveOrigRowsIndex() const {
475-
return presolve_.data_.postSolveStack.getOrigRowsIndex();
476-
}
475+
HighsStatus getFixedLp(HighsLp& lp) const;
477476

478477
/**
479478
* @brief Return a const reference to the incumbent LP
@@ -1584,6 +1583,9 @@ class Highs {
15841583
// Invalidates the model status, solution_ and info_
15851584
void invalidateModelStatusSolutionAndInfo();
15861585
//
1586+
// Invalidates the model status and info_
1587+
void invalidateModelStatusAndInfo();
1588+
//
15871589
// Sets model status to HighsModelStatus::kNotset
15881590
void invalidateModelStatus();
15891591
//
@@ -1652,6 +1654,8 @@ class Highs {
16521654
const HighsVarType* usr_inegrality);
16531655
HighsStatus changeCostsInterface(HighsIndexCollection& index_collection,
16541656
const double* usr_col_cost);
1657+
1658+
bool feasibleWrtBounds(const bool columns = true) const;
16551659
HighsStatus changeColBoundsInterface(HighsIndexCollection& index_collection,
16561660
const double* usr_col_lower,
16571661
const double* usr_col_upper);

highs/highs_bindings.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,12 @@ highs_getReducedColumnSparse(Highs* h, HighsInt col) {
405405
py::cast(solution_index));
406406
}
407407

408+
std::tuple<HighsStatus, HighsLp> highs_getFixedLp(Highs* h) {
409+
HighsLp lp;
410+
HighsStatus status = h->getFixedLp(lp);
411+
return std::make_tuple(status, lp);
412+
}
413+
408414
std::tuple<HighsStatus, bool> highs_getDualRayExist(Highs* h) {
409415
bool has_dual_ray;
410416
HighsStatus status = h->getDualRay(has_dual_ray);
@@ -1037,6 +1043,7 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) {
10371043
.value("kError", HighsLogType::kError);
10381044
py::enum_<IisStrategy>(m, "IisStrategy", py::module_local())
10391045
.value("kIisStrategyMin", IisStrategy::kIisStrategyMin)
1046+
.value("kIisStrategyLight", IisStrategy::kIisStrategyLight)
10401047
.value("kIisStrategyFromLpRowPriority",
10411048
IisStrategy::kIisStrategyFromLpRowPriority)
10421049
.value("kIisStrategyFromLpColPriority",
@@ -1411,6 +1418,7 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) {
14111418
.def("getReducedRowSparse", &highs_getReducedRowSparse)
14121419
.def("getReducedColumn", &highs_getReducedColumn)
14131420
.def("getReducedColumnSparse", &highs_getReducedColumnSparse)
1421+
.def("getFixedLp", &highs_getFixedLp)
14141422
.def("getDualRayExist", &highs_getDualRayExist)
14151423
.def("getDualRay", &highs_getDualRay)
14161424
.def("getDualUnboundednessDirectionExist",
@@ -1445,6 +1453,7 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) {
14451453

14461454
.def("writeModel", &Highs::writeModel)
14471455
.def("writePresolvedModel", &Highs::writePresolvedModel)
1456+
.def("writeIisModel", &Highs::writeIisModel)
14481457
.def("crossover", &Highs::crossover)
14491458
.def("changeObjectiveSense", &Highs::changeObjectiveSense)
14501459
.def("changeObjectiveOffset", &Highs::changeObjectiveOffset)

0 commit comments

Comments
 (0)