Skip to content

Commit bf92e1c

Browse files
committed
Merge branch 'latest' into mip-race
2 parents ffe29d9 + a9fbcb4 commit bf92e1c

24 files changed

+907
-500
lines changed

FEATURES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ Now handling correctly the case where an infeasible MIP has a feasible relaxatio
88

99
Fixed minor bug exposed by [#2441](https://github.com/ERGO-Code/HiGHS/issues/2441) in Highs::setSolution() for a sparse user solution when the moidel is empty, and only clearing the dual data before solving with modified objective in Highs::multiobjectiveSolve() so that user-supplied solution is not cleared.
1010

11+
Prompted by [#2463](https://github.com/ERGO-Code/HiGHS/issues/2463), the HiGHS solution and basis files now match data to any column and row names in the model, only assuming that the data are aligned with column and row indices if there are no names in the model. This requires a new version (v2) of the HiGHS basis file. Basis files from v1 are still read, but deprecated. Now, when writing out a model, basis or solution, column and row names are added to the model - previously they were created temporarily and inconsistentyly on the fly. If the model has existing names, then distinctive names are created to replace any blank names, but names with spaces or duplicate names yield an error status return.
1112

check/TestBasis.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,16 @@ TEST_CASE("Basis-file", "[highs_basis_file]") {
5656
f << "HiGHS v1" << std::endl;
5757
f << "None" << std::endl;
5858
f.close();
59-
return_status = highs.readBasis(invalid_basis_file);
60-
REQUIRE(return_status == HighsStatus::kOk);
59+
// HiGHS v1 basis file is deprecated, but read, so warning is
60+
// returned
61+
REQUIRE(highs.readBasis(invalid_basis_file) == HighsStatus::kWarning);
62+
63+
// Write and read a file for an invalid basis
64+
f.open(invalid_basis_file, std::ios::out);
65+
f << "HiGHS_basis_file v2" << std::endl;
66+
f << "None" << std::endl;
67+
f.close();
68+
REQUIRE(highs.readBasis(invalid_basis_file) == HighsStatus::kOk);
6169

6270
// Write and read a file for incompatible number of columns
6371
f.open(invalid_basis_file, std::ios::out);

check/TestCheckSolution.cpp

Lines changed: 163 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,9 @@ TEST_CASE("check-set-mip-solution", "[highs_check_solution]") {
9090
if (dev_run) printf("Num nodes = %d\n", int(scratch_num_nodes));
9191

9292
std::string solution_file = test_name + model + ".sol";
93-
if (dev_run) return_status = highs.writeSolution("");
94-
return_status = highs.writeSolution(solution_file);
95-
REQUIRE(return_status == HighsStatus::kOk);
93+
if (dev_run) REQUIRE(highs.writeSolution("") == HighsStatus::kOk);
94+
;
95+
REQUIRE(highs.writeSolution(solution_file) == HighsStatus::kOk);
9696

9797
highs.clear();
9898

@@ -459,6 +459,118 @@ TEST_CASE("read-miplib-solution", "[highs_check_solution]") {
459459
h.resetGlobalScheduler(true);
460460
}
461461

462+
TEST_CASE("read-lp-file-solution", "[highs_check_solution]") {
463+
const std::string test_name = Catch::getResultCapture().getCurrentTestName();
464+
const std::string model_file_name = test_name + ".lp";
465+
const std::string solution_file_name = test_name + ".sol";
466+
const bool with_names = false;
467+
HighsLp lp;
468+
lp.num_col_ = 3;
469+
lp.num_row_ = 2;
470+
lp.col_cost_ = {0, 1, 1};
471+
lp.col_lower_ = {0, 10, 0};
472+
lp.col_upper_ = {kHighsInf, kHighsInf, kHighsInf};
473+
if (with_names) lp.col_names_ = {"x", "y", "z"};
474+
lp.row_lower_ = {1, -kHighsInf};
475+
lp.row_upper_ = {kHighsInf, 2};
476+
if (with_names) lp.row_names_ = {"r-lo", "r-up"};
477+
lp.a_matrix_.start_ = {0, 2, 2, 4};
478+
lp.a_matrix_.index_ = {0, 1, 0, 1};
479+
lp.a_matrix_.value_ = {1, 1, 1, 1};
480+
lp.integrality_ = {HighsVarType::kContinuous, HighsVarType::kContinuous,
481+
HighsVarType::kInteger};
482+
Highs h;
483+
h.setOptionValue("output_flag", dev_run);
484+
REQUIRE(h.passModel(lp) == HighsStatus::kOk);
485+
h.run();
486+
h.writeModel(model_file_name);
487+
h.writeSolution(solution_file_name);
488+
489+
h.readModel(model_file_name);
490+
h.writeModel("");
491+
h.readSolution(solution_file_name);
492+
h.run();
493+
494+
std::remove(model_file_name.c_str());
495+
std::remove(solution_file_name.c_str());
496+
497+
h.resetGlobalScheduler(true);
498+
}
499+
500+
TEST_CASE("read-lp-file-basis", "[highs_check_solution]") {
501+
const std::string test_name = Catch::getResultCapture().getCurrentTestName();
502+
const std::string model_file_name = test_name + ".lp";
503+
const std::string basis_file_name = test_name + ".bas";
504+
const bool with_names = false;
505+
HighsLp lp;
506+
lp.num_col_ = 3;
507+
lp.num_row_ = 2;
508+
lp.col_cost_ = {0, 1, 1};
509+
lp.col_lower_ = {0, 10, 0};
510+
lp.col_upper_ = {kHighsInf, kHighsInf, kHighsInf};
511+
if (with_names) lp.col_names_ = {"x", "y", "z"};
512+
lp.row_lower_ = {1, -kHighsInf};
513+
lp.row_upper_ = {kHighsInf, 2};
514+
if (with_names) lp.row_names_ = {"r-lo", "r-up"};
515+
lp.a_matrix_.start_ = {0, 2, 2, 4};
516+
lp.a_matrix_.index_ = {0, 1, 0, 1};
517+
lp.a_matrix_.value_ = {1, 1, 1, 1};
518+
Highs h;
519+
h.setOptionValue("output_flag", dev_run);
520+
REQUIRE(h.passModel(lp) == HighsStatus::kOk);
521+
h.run();
522+
// Optimally x - basic; y - lower; z - lower
523+
h.writeModel(model_file_name);
524+
h.writeSolution("", 1);
525+
h.writeBasis("");
526+
h.writeBasis(basis_file_name);
527+
528+
h.readModel(model_file_name);
529+
// Variables now ordered y; z; x
530+
h.writeModel("");
531+
h.readBasis(basis_file_name);
532+
// Old read basis yields initial basis: y - basic; z - lower; x -
533+
// lower, using basis for original ordering with new ordering. Not
534+
// optimal - in fact basis matrix B = [0] is singular!
535+
h.run();
536+
REQUIRE(h.getInfo().simplex_iteration_count == 0);
537+
538+
std::remove(model_file_name.c_str());
539+
std::remove(basis_file_name.c_str());
540+
541+
h.resetGlobalScheduler(true);
542+
}
543+
544+
TEST_CASE("read-lp-file-rgn", "[highs_check_solution]") {
545+
const std::string test_name = Catch::getResultCapture().getCurrentTestName();
546+
const std::string filename =
547+
std::string(HIGHS_DIR) + "/check/instances/rgn.mps";
548+
const std::string model_file_name = test_name + ".lp";
549+
const std::string solution_file_name = test_name + ".sol";
550+
Highs h;
551+
h.setOptionValue("output_flag", dev_run);
552+
REQUIRE(h.readModel(filename) == HighsStatus::kOk);
553+
REQUIRE(h.run() == HighsStatus::kOk);
554+
REQUIRE(h.writeSolution(solution_file_name) == HighsStatus::kOk);
555+
REQUIRE(h.writeModel(model_file_name) == HighsStatus::kOk);
556+
557+
REQUIRE(h.readModel(model_file_name) == HighsStatus::kOk);
558+
REQUIRE(h.readSolution(solution_file_name) == HighsStatus::kOk);
559+
bool valid;
560+
bool integral;
561+
bool feasible;
562+
REQUIRE(h.assessPrimalSolution(valid, integral, feasible) ==
563+
HighsStatus::kOk);
564+
REQUIRE(valid);
565+
REQUIRE(integral);
566+
REQUIRE(feasible);
567+
568+
std::remove(model_file_name.c_str());
569+
std::remove(solution_file_name.c_str());
570+
571+
h.resetGlobalScheduler(true);
572+
}
573+
462574
void runWriteReadCheckSolution(Highs& highs, const std::string& test_name,
463575
const std::string& model,
464576
const HighsModelStatus require_model_status,
@@ -478,9 +590,15 @@ void runWriteReadCheckSolution(Highs& highs, const std::string& test_name,
478590
if (dev_run)
479591
printf("Writing solution in style %d to %s\n", int(write_solution_style),
480592
solution_file.c_str());
481-
if (dev_run) return_status = highs.writeSolution("", write_solution_style);
482-
return_status = highs.writeSolution(solution_file, write_solution_style);
483-
REQUIRE(return_status == HighsStatus::kOk);
593+
// For models without names, Highs::writeSolution will return
594+
// HighsStatus::kWarning
595+
HighsStatus require_status = highs.getLp().col_names_.size()
596+
? HighsStatus::kOk
597+
: HighsStatus::kWarning;
598+
REQUIRE(highs.writeSolution(solution_file, write_solution_style) ==
599+
require_status);
600+
if (dev_run)
601+
REQUIRE(highs.writeSolution("", write_solution_style) == HighsStatus::kOk);
484602

485603
const bool& value_valid = highs.getSolution().value_valid;
486604
bool valid, integral, feasible;
@@ -581,3 +699,42 @@ void runSetLpSolution(const std::string model) {
581699

582700
highs.resetGlobalScheduler(true);
583701
}
702+
703+
TEST_CASE("miplib-sol-file", "[highs_filereader]") {
704+
const std::string test_name = Catch::getResultCapture().getCurrentTestName();
705+
std::string sol_file = test_name + ".sol";
706+
std::string lp_file = test_name + ".lp";
707+
FILE* file = fopen(lp_file.c_str(), "w");
708+
std::string file_content =
709+
"Minimize\n obj: 2 sel_2 + sel_3\nSubject To\nr0: sel_0 - sel_1 + sel_4 "
710+
">= "
711+
"2\nEnd\n";
712+
if (dev_run) printf("Using .lp file\n%s", file_content.c_str());
713+
fprintf(file, "%s", file_content.c_str());
714+
fclose(file);
715+
Highs h;
716+
h.setOptionValue("output_flag", dev_run);
717+
REQUIRE(h.readModel(lp_file) == HighsStatus::kOk);
718+
719+
file = fopen(sol_file.c_str(), "w");
720+
file_content =
721+
"=obj= 203672547.1\nsel_0 1\nsel_1 0\nsel_2 0\nsel_3 0\nsel_4 1\n";
722+
if (dev_run) printf("Using .sol file\n%s", file_content.c_str());
723+
fprintf(file, "%s", file_content.c_str());
724+
fclose(file);
725+
REQUIRE(h.readSolution(sol_file) == HighsStatus::kOk);
726+
727+
std::vector<double> solution = h.getSolution().col_value;
728+
REQUIRE(solution[0] == 0);
729+
REQUIRE(solution[1] == 0);
730+
REQUIRE(solution[2] == 1);
731+
REQUIRE(solution[3] == 0);
732+
REQUIRE(solution[4] == 1);
733+
734+
REQUIRE(h.run() == HighsStatus::kOk);
735+
736+
std::remove(lp_file.c_str());
737+
std::remove(sol_file.c_str());
738+
739+
h.resetGlobalScheduler(true);
740+
}

check/TestFilereader.cpp

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ TEST_CASE("writeLocalModel", "[highs_filereader]") {
382382
h.setOptionValue("output_flag", dev_run);
383383
HighsModel model;
384384
HighsLp& lp = model.lp_;
385-
;
385+
386386
lp.num_col_ = 2;
387387
lp.num_row_ = 3;
388388
lp.col_cost_ = {8, 10};
@@ -403,7 +403,7 @@ TEST_CASE("writeLocalModel", "[highs_filereader]") {
403403
// Model has no dimensions for a_matrix_, but these are set in
404404
// writeLocalModel.
405405
if (dev_run) printf("\nModel with no column or row names\n");
406-
REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kWarning);
406+
REQUIRE(h.writeLocalModel(model, write_model_file) == HighsStatus::kOk);
407407
lp.col_names_ = {"C0", "C1"};
408408
lp.row_names_ = {"R0", "R1", "R2"};
409409

@@ -478,6 +478,105 @@ TEST_CASE("mps-silly-names", "[highs_filereader]") {
478478
REQUIRE(return_status == HighsStatus::kOk);
479479
}
480480

481+
TEST_CASE("handle-blank-space-names", "[highs_filereader]") {
482+
HighsLp lp;
483+
lp.num_col_ = 2;
484+
lp.num_row_ = 2;
485+
lp.col_cost_ = {8, 10};
486+
lp.col_lower_ = {0, 0};
487+
lp.col_upper_ = {inf, inf};
488+
lp.row_lower_ = {7, 12};
489+
lp.row_upper_ = {inf, inf};
490+
lp.a_matrix_.start_ = {0, 2, 4};
491+
lp.a_matrix_.index_ = {0, 1, 0, 1};
492+
lp.a_matrix_.value_ = {1, 2, 2, 4};
493+
Highs h;
494+
h.setOptionValue("output_flag", dev_run);
495+
REQUIRE(h.passModel(lp) == HighsStatus::kOk);
496+
h.run();
497+
// Names will be created when writing the solution
498+
REQUIRE(h.writeSolution("", 1) == HighsStatus::kWarning);
499+
REQUIRE(h.writeModel("") == HighsStatus::kOk);
500+
501+
lp.col_names_ = {"Column", "Column"};
502+
lp.row_names_ = {"Row0", "Row1"};
503+
REQUIRE(h.passModel(lp) == HighsStatus::kOk);
504+
REQUIRE(h.writeModel("") == HighsStatus::kError);
505+
506+
lp.col_names_ = {"Column0", ""};
507+
REQUIRE(h.passModel(lp) == HighsStatus::kOk);
508+
h.run();
509+
REQUIRE(h.writeSolution("", kSolutionStyleOldRaw) == HighsStatus::kWarning);
510+
REQUIRE(h.writeModel("") == HighsStatus::kOk);
511+
512+
std::vector<HighsInt> index = {0, 1};
513+
std::vector<double> value = {2, 3};
514+
REQUIRE(h.addRow(5, inf, 2, index.data(), value.data()) == HighsStatus::kOk);
515+
h.run();
516+
REQUIRE(h.writeBasis("") == HighsStatus::kWarning);
517+
REQUIRE(h.writeSolution("", kSolutionStylePretty) == HighsStatus::kOk);
518+
REQUIRE(h.writeModel("") == HighsStatus::kOk);
519+
520+
lp.row_names_[1] = "Row 1";
521+
REQUIRE(h.passModel(lp) == HighsStatus::kOk);
522+
h.run();
523+
REQUIRE(h.writeSolution("", 1) == HighsStatus::kError);
524+
REQUIRE(h.writeModel("") == HighsStatus::kError);
525+
526+
h.resetGlobalScheduler(true);
527+
}
528+
529+
TEST_CASE("read-highs-lp-file0", "[highs_filereader]") {
530+
// Identified when fixing #2463 that LP file reader cannot handle
531+
// case where row names are numeric constants
532+
const std::string test_name = Catch::getResultCapture().getCurrentTestName();
533+
const std::string model_file_name0 = test_name + "-0.lp";
534+
const std::string model_file_name1 = test_name + "-1.lp";
535+
HighsLp lp;
536+
lp.num_col_ = 1;
537+
lp.num_row_ = 3;
538+
lp.col_cost_ = {8};
539+
lp.col_lower_ = {0};
540+
lp.col_upper_ = {inf};
541+
lp.row_lower_ = {-21, 31, 43};
542+
lp.row_upper_ = {inf, inf, inf};
543+
lp.a_matrix_.start_ = {0, 3};
544+
lp.a_matrix_.index_ = {0, 1, 2};
545+
lp.a_matrix_.value_ = {2, -3, 7};
546+
lp.col_names_ = {"col"};
547+
lp.row_names_ = {"row", "55", "9.9"};
548+
Highs h;
549+
h.setOptionValue("output_flag", dev_run);
550+
REQUIRE(h.passModel(lp) == HighsStatus::kOk);
551+
// Create a .lp file for this model - that has numeric constants as
552+
// row names
553+
REQUIRE(h.writeModel(model_file_name0) == HighsStatus::kOk);
554+
// Make sure that the .lp file for this model can be read OK
555+
REQUIRE(h.readModel(model_file_name0) == HighsStatus::kOk);
556+
REQUIRE(h.writeModel(model_file_name1) == HighsStatus::kOk);
557+
558+
std::remove(model_file_name0.c_str());
559+
std::remove(model_file_name1.c_str());
560+
561+
h.resetGlobalScheduler(true);
562+
}
563+
564+
TEST_CASE("read-highs-lp-file1", "[highs_filereader]") {
565+
const std::string test_name = Catch::getResultCapture().getCurrentTestName();
566+
std::string filename;
567+
filename = std::string(HIGHS_DIR) + "/check/instances/rgn.mps";
568+
std::string model_file_name = test_name + ".lp";
569+
Highs h;
570+
h.setOptionValue("output_flag", dev_run);
571+
REQUIRE(h.readModel(filename) == HighsStatus::kOk);
572+
REQUIRE(h.writeModel(model_file_name) == HighsStatus::kOk);
573+
REQUIRE(h.readModel(model_file_name) == HighsStatus::kOk);
574+
575+
std::remove(model_file_name.c_str());
576+
577+
h.resetGlobalScheduler(true);
578+
}
579+
481580
TEST_CASE("lp-duplicate-variable", "[highs_filereader]") {
482581
const std::string test_name = Catch::getResultCapture().getCurrentTestName();
483582
std::string lp_file = test_name + ".lp";

check/TestNames.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,7 @@ TEST_CASE("highs-names", "[highs_names]") {
111111
local_lp.col_names_.clear();
112112
local_lp.row_names_.clear();
113113
highs.passModel(local_lp);
114-
REQUIRE(highs.writeSolution(solution_file, 1) == HighsStatus::kOk);
115-
116-
// Cannot get name of column or row 0
117-
REQUIRE(highs.getColName(0, name) == HighsStatus::kError);
118-
REQUIRE(highs.getRowName(0, name) == HighsStatus::kError);
114+
REQUIRE(highs.writeSolution(solution_file, 1) == HighsStatus::kWarning);
119115

120116
std::remove(solution_file.c_str());
121117
}

check/TestQpSolver.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1121,7 +1121,8 @@ TEST_CASE("rowless-qp", "[qpsolver]") {
11211121
REQUIRE(highs.setOptionValue("qp_regularization_value", 0) ==
11221122
HighsStatus::kOk);
11231123
REQUIRE(highs.run() == HighsStatus::kOk);
1124-
REQUIRE(highs.writeSolution("", kSolutionStylePretty) == HighsStatus::kOk);
1124+
REQUIRE(highs.writeSolution("", kSolutionStylePretty) ==
1125+
HighsStatus::kWarning);
11251126

11261127
const double required_objective_function_value = -2.25;
11271128

0 commit comments

Comments
 (0)