Skip to content

Commit 9b702ca

Browse files
committed
Merged latest into this branch and fixed minor conflicts
2 parents 1aafc1b + ecaf609 commit 9b702ca

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1245
-679
lines changed

CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,10 @@ if(ZLIB AND NOT TARGET ZLIB::ZLIB)
500500
find_package(ZLIB 1.2.3)
501501
endif()
502502

503-
include(CPack)
503+
if(CPack_CMake_INCLUDED EQUAL 0)
504+
include(CPack)
505+
endif()
506+
504507
set(CPACK_PACKAGE_VERSION_MAJOR "${HIGHS_VERSION_MAJOR}")
505508
set(CPACK_PACKAGE_VERSION_MINOR "${HIGHS_VERSION_MINOR}")
506509
set(CPACK_PACKAGE_VERSION_PATCH "${HIGHS_VERSION_PATCH}")

FEATURES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ 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+
1112
The irreducible infeasibility system (IIS) facility now detects infeasibility due to bounds on constraint activity values being incompatible with constraint bounds. A kIisStrategyLight mode for the iis_strategy option has been introduced so that only infeasibility due to incompatible variable/constraint bounds and constraint activity values is checked for. The LP corresponding to any known IIS is now formed and held as a data member of the HighsIis class.
1213

14+
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.
1315

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/TestCAPI.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,6 @@ void fullApi() {
617617

618618
// Define all column names to be different
619619
for (HighsInt iCol = 0; iCol < num_col; iCol++) {
620-
const char suffix = iCol + '0';
621620
char name[5]; // 3 chars prefix, 1 char iCol, 1 char 0-terminator
622621
sprintf(name, "%s%" HIGHSINT_FORMAT "", col_prefix, iCol);
623622
const char* name_p = name;
@@ -650,7 +649,6 @@ void fullApi() {
650649

651650
// Define all row names to be different
652651
for (HighsInt iRow = 0; iRow < num_row; iRow++) {
653-
const char suffix = iRow + '0';
654652
char name[5]; // 3 chars prefix, 1 char iCol, 1 char 0-terminator
655653
sprintf(name, "%s%" HIGHSINT_FORMAT "", row_prefix, iRow);
656654
const char* name_p = name;

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: 118 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

@@ -477,3 +477,119 @@ TEST_CASE("mps-silly-names", "[highs_filereader]") {
477477
HighsStatus return_status = h.readModel(model_file);
478478
REQUIRE(return_status == HighsStatus::kOk);
479479
}
480+
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+
580+
TEST_CASE("lp-duplicate-variable", "[highs_filereader]") {
581+
const std::string test_name = Catch::getResultCapture().getCurrentTestName();
582+
std::string lp_file = test_name + ".lp";
583+
FILE* file = fopen(lp_file.c_str(), "w");
584+
std::string file_content =
585+
"Minimize\n obj: 2 x + y + z\nSubject To\nr0: 2 x + y - x + 0 z >= "
586+
"2\nr1: y + x - y >= 1\nEnd\n";
587+
if (dev_run) printf("Using .lp file\n%s", file_content.c_str());
588+
fprintf(file, "%s", file_content.c_str());
589+
fclose(file);
590+
Highs h;
591+
h.setOptionValue("output_flag", dev_run);
592+
REQUIRE(h.readModel(lp_file) == HighsStatus::kWarning);
593+
594+
std::remove(lp_file.c_str());
595+
}

0 commit comments

Comments
 (0)