Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Supportcase22 #2020

Merged
merged 3 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading