Skip to content

Commit

Permalink
Merge pull request #2020 from ERGO-Code/supportcase22
Browse files Browse the repository at this point in the history
Supportcase22
  • Loading branch information
jajhall authored Nov 1, 2024
2 parents 788d4ba + 3202278 commit 0268034
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 56 deletions.
49 changes: 48 additions & 1 deletion check/TestCheckSolution.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <cstdio>
//#include <cstdio>
#include <iostream>

#include "HCheckConfig.h"
#include "Highs.h"
Expand Down Expand Up @@ -395,6 +396,52 @@ TEST_CASE("check-set-illegal-solution", "[highs_check_solution]") {
REQUIRE(highs.setSolution(solution) == HighsStatus::kOk);
}

TEST_CASE("read-miplib-solution", "[highs_check_solution]") {
HighsLp lp;
lp.num_col_ = 5;
lp.num_row_ = 1;
lp.sense_ = ObjSense::kMaximize;
lp.col_cost_ = {8, 5, 3, 11, 7};
lp.col_lower_.assign(lp.num_col_, 0);
lp.col_upper_.assign(lp.num_col_, 1);
lp.integrality_.assign(lp.num_col_, HighsVarType::kInteger);
lp.row_lower_ = {-kHighsInf};
lp.row_upper_ = {11};
lp.a_matrix_.format_ = MatrixFormat::kRowwise;
lp.a_matrix_.start_ = {0, 5};
lp.a_matrix_.index_ = {0, 1, 2, 3, 4};
lp.a_matrix_.value_ = {4, 3, 1, 5, 4};
Highs h;
h.setOptionValue("output_flag", dev_run);
h.setOptionValue("presolve", kHighsOffString);
REQUIRE(h.passModel(lp) == HighsStatus::kOk);
REQUIRE(h.run() == HighsStatus::kOk);
// REQUIRE(h.writeSolution("", kSolutionStylePretty) == HighsStatus::kOk);
const std::vector<double>& col_value = h.getSolution().col_value;
std::string miplib_sol_file = "miplib.sol";
FILE* file = fopen(miplib_sol_file.c_str(), "w");
REQUIRE(file != 0);
fprintf(file, "=obj= 22\n");
for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) {
std::string col_name = "c" + std::to_string(int(iCol));
lp.col_names_.push_back(col_name);
if (std::fabs(col_value[iCol]) < 1e-2) continue;
std::string line = col_name + " 1\n";
fprintf(file, "%s", line.c_str());
}
fclose(file);
// Can't read file yet, as model has no column names
REQUIRE(h.readSolution(miplib_sol_file) == HighsStatus::kError);

// Pass model again now that column names have been defined

REQUIRE(h.passModel(lp) == HighsStatus::kOk);
// REQUIRE(h.writeModel("miplib.mps") == HighsStatus::kOk);
REQUIRE(h.readSolution(miplib_sol_file) == HighsStatus::kOk);
REQUIRE(h.run() == HighsStatus::kOk);
std::remove(miplib_sol_file.c_str());
}

void runWriteReadCheckSolution(Highs& highs, const std::string model,
const HighsModelStatus require_model_status,
const HighsInt write_solution_style) {
Expand Down
4 changes: 2 additions & 2 deletions src/lp_data/Highs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3283,7 +3283,7 @@ HighsStatus Highs::readSolution(const std::string& filename,

HighsStatus Highs::assessPrimalSolution(bool& valid, bool& integral,
bool& feasible) const {
return assessLpPrimalSolution(options_, model_.lp_, solution_, valid,
return assessLpPrimalSolution("", options_, model_.lp_, solution_, valid,
integral, feasible);
}

Expand Down Expand Up @@ -3568,7 +3568,7 @@ HighsStatus Highs::completeSolutionFromDiscreteAssignment() {
bool valid, integral, feasible;
// Determine whether this solution is integer feasible
HighsStatus return_status = assessLpPrimalSolution(
options_, lp, solution_, valid, integral, feasible);
"", options_, lp, solution_, valid, integral, feasible);
assert(return_status != HighsStatus::kError);
assert(valid);
// If the current solution is integer feasible, then it can be
Expand Down
147 changes: 98 additions & 49 deletions src/lp_data/HighsLpUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2028,7 +2028,7 @@ void analyseLp(const HighsLogOptions& log_options, const HighsLp& lp) {
}

HighsStatus readSolutionFile(const std::string filename,
const HighsOptions& options, const HighsLp& lp,
const HighsOptions& options, HighsLp& lp,
HighsBasis& basis, HighsSolution& solution,
const HighsInt style) {
const HighsLogOptions& log_options = options.log_options;
Expand All @@ -2047,6 +2047,7 @@ HighsStatus readSolutionFile(const std::string filename,
}
std::string keyword;
std::string name;
double value;
HighsInt num_col;
HighsInt num_row;
const HighsInt lp_num_col = lp.num_col_;
Expand All @@ -2063,50 +2064,89 @@ HighsStatus readSolutionFile(const std::string filename,
read_basis.col_status.resize(lp_num_col);
read_basis.row_status.resize(lp_num_row);
std::string section_name;
if (!readSolutionFileIgnoreLineOk(in_file))
return readSolutionFileErrorReturn(in_file); // Model status
if (!readSolutionFileIgnoreLineOk(in_file))
return readSolutionFileErrorReturn(in_file); // Optimal
if (!readSolutionFileIgnoreLineOk(in_file))
return readSolutionFileErrorReturn(in_file); //
if (!readSolutionFileIgnoreLineOk(in_file))
return readSolutionFileErrorReturn(in_file); // # Primal solution values
if (!readSolutionFileKeywordLineOk(keyword, in_file))
return readSolutionFileErrorReturn(in_file);
// Read in the primal solution values: return warning if there is none
if (keyword == "None")
return readSolutionFileReturn(HighsStatus::kWarning, solution, basis,
read_solution, read_basis, in_file);
// If there are primal solution values then keyword is the status
// and the next line is objective
if (!readSolutionFileIgnoreLineOk(in_file))
return readSolutionFileErrorReturn(in_file); // EOL
if (!readSolutionFileIgnoreLineOk(in_file))
return readSolutionFileErrorReturn(in_file); // Objective
// Next line should be "Columns" and correct number
if (!readSolutionFileHashKeywordIntLineOk(keyword, num_col, in_file))
return readSolutionFileErrorReturn(in_file);
assert(keyword == "Columns");
// The default style parameter is kSolutionStyleRaw, and this still
// allows sparse files to be read. Recognise the latter from num_col
// <= 0. Doesn't matter if num_col = 0, since there's nothing to
// read either way
const bool sparse = num_col <= 0;
if (style == kSolutionStyleSparse) assert(sparse);
if (sparse) {
num_col = -num_col;
assert(num_col <= lp_num_col);
} else {
if (num_col != lp_num_col) {
if (!readSolutionFileIdIgnoreLineOk(section_name, in_file))
return readSolutionFileErrorReturn(
in_file); // Model (status) or =obj= (value)
const bool miplib_sol = section_name == "=obj=";
if (miplib_sol) {
// A MIPLIB solution file has nonzero solution values for a subset
// of the variables identified by name, so there must be column
// names
if (!lp.col_names_.size()) {
highsLogUser(log_options, HighsLogType::kError,
"readSolutionFile: Solution file is for %" HIGHSINT_FORMAT
" columns, not %" HIGHSINT_FORMAT "\n",
num_col, lp_num_col);
"readSolutionFile: Cannot read a MIPLIB solution file "
"without column names in the model\n");
return HighsStatus::kError;
}
// Ensure that the col name hash table has been formed
if (!lp.col_hash_.name2index.size()) lp.col_hash_.form(lp.col_names_);
}
bool sparse = false;
if (!miplib_sol) {
if (!readSolutionFileIgnoreLineOk(in_file))
return readSolutionFileErrorReturn(in_file); // Optimal
if (!readSolutionFileIgnoreLineOk(in_file))
return readSolutionFileErrorReturn(in_file); //
if (!readSolutionFileIgnoreLineOk(in_file))
return readSolutionFileErrorReturn(in_file); // # Primal solution values
if (!readSolutionFileKeywordLineOk(keyword, in_file))
return readSolutionFileErrorReturn(in_file);
// Read in the primal solution values: return warning if there is none
if (keyword == "None")
return readSolutionFileReturn(HighsStatus::kWarning, solution, basis,
read_solution, read_basis, in_file);
// If there are primal solution values then keyword is the status
// and the next line is objective
if (!readSolutionFileIgnoreLineOk(in_file))
return readSolutionFileErrorReturn(in_file); // EOL
if (!readSolutionFileIgnoreLineOk(in_file))
return readSolutionFileErrorReturn(in_file); // Objective
// Next line should be "Columns" and correct number
if (!readSolutionFileHashKeywordIntLineOk(keyword, num_col, in_file))
return readSolutionFileErrorReturn(in_file);
assert(keyword == "Columns");
// The default style parameter is kSolutionStyleRaw, and this still
// allows sparse files to be read. Recognise the latter from num_col
// <= 0. Doesn't matter if num_col = 0, since there's nothing to
// read either way
sparse = num_col <= 0;
if (style == kSolutionStyleSparse) assert(sparse);
if (sparse) {
num_col = -num_col;
assert(num_col <= lp_num_col);
} else {
if (num_col != lp_num_col) {
highsLogUser(log_options, HighsLogType::kError,
"readSolutionFile: Solution file is for %" HIGHSINT_FORMAT
" columns, not %" HIGHSINT_FORMAT "\n",
num_col, lp_num_col);
return readSolutionFileErrorReturn(in_file);
}
}
}
double value;
if (sparse) {
if (miplib_sol) {
HighsInt num_value = 0;
read_solution.col_value.assign(lp_num_col, 0);
for (;;) {
// Only false return is for encountering EOF
if (!readSolutionFileIdDoubleLineOk(name, value, in_file)) break;
auto search = lp.col_hash_.name2index.find(name);
if (search == lp.col_hash_.name2index.end()) {
highsLogUser(log_options, HighsLogType::kError,
"readSolutionFile: name %s is not found\n", name.c_str());
return HighsStatus::kError;
} else if (search->second == kHashIsDuplicate) {
highsLogUser(log_options, HighsLogType::kError,
"readSolutionFile: name %s is duplicated\n", name.c_str());
return HighsStatus::kError;
}
HighsInt iCol = search->second;
assert(lp.col_names_[iCol] == name);
read_solution.col_value[iCol] = value;
num_value++;
if (in_file.eof()) break;
}
} else if (sparse) {
read_solution.col_value.assign(lp_num_col, 0);
HighsInt iCol;
for (HighsInt iX = 0; iX < num_col; iX++) {
Expand All @@ -2116,7 +2156,7 @@ HighsStatus readSolutionFile(const std::string filename,
}
} else {
for (HighsInt iCol = 0; iCol < num_col; iCol++) {
if (!readSolutionFileIdDoubleLineOk(value, in_file))
if (!readSolutionFileIdDoubleLineOk(name, value, in_file))
return readSolutionFileErrorReturn(in_file);
read_solution.col_value[iCol] = value;
}
Expand Down Expand Up @@ -2148,7 +2188,7 @@ HighsStatus readSolutionFile(const std::string filename,
// next.
const bool num_row_ok = num_row == lp_num_row;
for (HighsInt iRow = 0; iRow < num_row; iRow++) {
if (!readSolutionFileIdDoubleLineOk(value, in_file))
if (!readSolutionFileIdDoubleLineOk(name, value, in_file))
return readSolutionFileErrorReturn(in_file);
if (num_row_ok) read_solution.row_value[iRow] = value;
}
Expand Down Expand Up @@ -2191,7 +2231,7 @@ HighsStatus readSolutionFile(const std::string filename,
assert(keyword == "Columns");
double dual;
for (HighsInt iCol = 0; iCol < num_col; iCol++) {
if (!readSolutionFileIdDoubleLineOk(dual, in_file))
if (!readSolutionFileIdDoubleLineOk(name, dual, in_file))
return readSolutionFileErrorReturn(in_file);
read_solution.col_dual[iCol] = dual;
}
Expand All @@ -2202,7 +2242,7 @@ HighsStatus readSolutionFile(const std::string filename,
read_solution, read_basis, in_file);
assert(keyword == "Rows");
for (HighsInt iRow = 0; iRow < num_row; iRow++) {
if (!readSolutionFileIdDoubleLineOk(dual, in_file))
if (!readSolutionFileIdDoubleLineOk(name, dual, in_file))
return readSolutionFileErrorReturn(in_file);
read_solution.row_dual[iRow] = dual;
}
Expand Down Expand Up @@ -2272,8 +2312,15 @@ bool readSolutionFileHashKeywordIntLineOk(std::string& keyword, HighsInt& value,
return true;
}

bool readSolutionFileIdDoubleLineOk(double& value, std::ifstream& in_file) {
std::string id;
bool readSolutionFileIdIgnoreLineOk(std::string& id, std::ifstream& in_file) {
if (in_file.eof()) return false;
in_file >> id; // Id
in_file.ignore(kMaxLineLength, '\n');
return true;
}

bool readSolutionFileIdDoubleLineOk(std::string& id, double& value,
std::ifstream& in_file) {
if (in_file.eof()) return false;
in_file >> id; // Id
if (in_file.eof()) return false;
Expand Down Expand Up @@ -2325,7 +2372,8 @@ void assessColPrimalSolution(const HighsOptions& options, const double primal,

// Determine validity, primal feasibility and (when relevant) integer
// feasibility of a solution
HighsStatus assessLpPrimalSolution(const HighsOptions& options,
HighsStatus assessLpPrimalSolution(const std::string message,
const HighsOptions& options,
const HighsLp& lp,
const HighsSolution& solution, bool& valid,
bool& integral, bool& feasible) {
Expand All @@ -2350,7 +2398,8 @@ HighsStatus assessLpPrimalSolution(const HighsOptions& options,
lp.isMip() ? options.mip_feasibility_tolerance
: options.primal_feasibility_tolerance;
highsLogUser(options.log_options, HighsLogType::kInfo,
"Assessing feasibility of %s tolerance of %11.4g\n",
"%sAssessing feasibility of %s tolerance of %11.4g\n",
message.c_str(),
lp.isMip() ? "MIP using primal feasibility and integrality"
: "LP using primal feasibility",
kPrimalFeasibilityTolerance);
Expand Down
9 changes: 6 additions & 3 deletions src/lp_data/HighsLpUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ void getLpMatrixCoefficient(const HighsLp& lp, const HighsInt row,
void analyseLp(const HighsLogOptions& log_options, const HighsLp& lp);

HighsStatus readSolutionFile(const std::string filename,
const HighsOptions& options, const HighsLp& lp,
const HighsOptions& options, HighsLp& lp,
HighsBasis& basis, HighsSolution& solution,
const HighsInt style);

Expand All @@ -216,7 +216,9 @@ bool readSolutionFileKeywordLineOk(std::string& keyword,
std::ifstream& in_file);
bool readSolutionFileHashKeywordIntLineOk(std::string& keyword, HighsInt& value,
std::ifstream& in_file);
bool readSolutionFileIdDoubleLineOk(double& value, std::ifstream& in_file);
bool readSolutionFileIdIgnoreLineOk(std::string& id, std::ifstream& in_file);
bool readSolutionFileIdDoubleLineOk(std::string& id, double& value,
std::ifstream& in_file);
bool readSolutionFileIdDoubleIntLineOk(double& value, HighsInt& index,
std::ifstream& in_file);

Expand All @@ -225,7 +227,8 @@ void assessColPrimalSolution(const HighsOptions& options, const double primal,
const HighsVarType type, double& col_infeasibility,
double& integer_infeasibility);

HighsStatus assessLpPrimalSolution(const HighsOptions& options,
HighsStatus assessLpPrimalSolution(const std::string message,
const HighsOptions& options,
const HighsLp& lp,
const HighsSolution& solution, bool& valid,
bool& integral, bool& feasible);
Expand Down
3 changes: 2 additions & 1 deletion src/mip/HighsMipSolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ HighsMipSolver::HighsMipSolver(HighsCallback& callback,
// so validate using assert
#ifndef NDEBUG
bool valid, integral, feasible;
assessLpPrimalSolution(options, lp, solution, valid, integral, feasible);
assessLpPrimalSolution("For debugging: ", options, lp, solution, valid,
integral, feasible);
assert(valid);
#endif
bound_violation_ = 0;
Expand Down

0 comments on commit 0268034

Please sign in to comment.