Skip to content

Commit

Permalink
Merge branch 'latest' into fix-1956-1977-1981
Browse files Browse the repository at this point in the history
  • Loading branch information
jajhall committed Oct 26, 2024
2 parents e89230b + 537423d commit f9abad4
Show file tree
Hide file tree
Showing 10 changed files with 427 additions and 10 deletions.
116 changes: 115 additions & 1 deletion check/TestLpSolvers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#include "Highs.h"
#include "catch.hpp"

const bool dev_run = false;
const bool dev_run = true; // false;

struct IterationCount {
HighsInt simplex;
Expand Down Expand Up @@ -497,3 +497,117 @@ TEST_CASE("dual-objective", "[highs_lp_solver]") {
testDualObjective("etamacro");
testDualObjective("stair");
}

void testStandardForm(const HighsLp& lp) {
Highs highs;
highs.setOptionValue("output_flag", dev_run);
HighsInt sense = HighsInt(lp.sense_);
highs.passModel(lp);
highs.run();
// highs.writeSolution("", kSolutionStylePretty);
double required_objective_function_value =
highs.getInfo().objective_function_value;

HighsInt num_col;
HighsInt num_row;
HighsInt num_nz;
double offset;
REQUIRE(highs.getStandardFormLp(num_col, num_row, num_nz, offset) ==
HighsStatus::kOk);

std::vector<double> cost(num_col);
std::vector<double> rhs(num_row);
std::vector<HighsInt> start(num_col + 1);
std::vector<HighsInt> index(num_nz);
std::vector<double> value(num_nz);
REQUIRE(highs.getStandardFormLp(num_col, num_row, num_nz, offset, cost.data(),
rhs.data(), start.data(), index.data(),
value.data()) == HighsStatus::kOk);

HighsLp standard_form_lp;
standard_form_lp.num_col_ = num_col;
standard_form_lp.num_row_ = num_row;
standard_form_lp.offset_ = offset;
standard_form_lp.col_cost_ = cost;
standard_form_lp.col_lower_.assign(num_col, 0);
standard_form_lp.col_upper_.assign(num_col, kHighsInf);
standard_form_lp.row_lower_ = rhs;
standard_form_lp.row_upper_ = rhs;
standard_form_lp.a_matrix_.start_ = start;
standard_form_lp.a_matrix_.index_ = index;
standard_form_lp.a_matrix_.value_ = value;
REQUIRE(highs.passModel(standard_form_lp) == HighsStatus::kOk);
// highs.writeModel("");
REQUIRE(highs.run() == HighsStatus::kOk);
REQUIRE(highs.getModelStatus() == HighsModelStatus::kOptimal);
highs.writeSolution("", kSolutionStylePretty);
double objective_function_value =
sense * highs.getInfo().objective_function_value;
double objective_difference =
std::fabs(objective_function_value - required_objective_function_value) /
std::max(1.0, std::fabs(required_objective_function_value));
REQUIRE(objective_difference < 1e-10);
const bool look_at_presolved_lp = false;
if (look_at_presolved_lp) {
// Strange that presolve doesn't convert the constraints
//
// Ax-s = b; s >= 0 into Ax >= b
REQUIRE(highs.passModel(standard_form_lp) == HighsStatus::kOk);
REQUIRE(highs.presolve() == HighsStatus::kOk);
HighsLp presolved_lp = highs.getPresolvedLp();
REQUIRE(highs.passModel(presolved_lp) == HighsStatus::kOk);
highs.writeModel("");
}
}

void testStandardFormModel(const std::string model) {
const std::string model_file =
std::string(HIGHS_DIR) + "/check/instances/" + model + ".mps";
;
Highs highs;
highs.setOptionValue("output_flag", dev_run);
highs.readModel(model_file);
HighsLp lp = highs.getLp();
testStandardForm(lp);
}

TEST_CASE("standard-form-mps", "[highs_lp_solver]") {
testStandardFormModel("avgas");
testStandardFormModel("afiro");
}

TEST_CASE("standard-form-lp", "[highs_lp_solver]") {
HighsLp lp;
lp.offset_ = -0.5;
lp.num_col_ = 4;
lp.num_row_ = 3;
lp.col_cost_ = {1, 1, 1, -1};
lp.col_lower_ = {1, -kHighsInf, -kHighsInf, -1};
lp.col_upper_ = {kHighsInf, kHighsInf, 2, 3};
lp.row_lower_ = {0, 1, -kHighsInf};
lp.row_upper_ = {4, kHighsInf, 4};
lp.a_matrix_.start_ = {0, 2, 4, 6, 8};
lp.a_matrix_.index_ = {0, 2, 0, 1, 1, 2, 0, 2};
lp.a_matrix_.value_ = {1, 1, 1, 1, 1, 1, 1, 1};

testStandardForm(lp);
Highs highs;
highs.setOptionValue("output_flag", dev_run);

std::vector<HighsInt> index;
std::vector<double> value;
// Add a fixed column and a fixed row, and maximize
highs.passModel(lp);
index = {0, 1, 2};
value = {-1, 1, -1};
REQUIRE(highs.addCol(-2.0, 1.0, 1.0, 3, index.data(), value.data()) ==
HighsStatus::kOk);
index = {0, 1, 2, 3};
value = {-2, -1, 1, 3};
REQUIRE(highs.addRow(1.0, 1.0, 4, index.data(), value.data()) ==
HighsStatus::kOk);
REQUIRE(highs.changeObjectiveSense(ObjSense::kMaximize) == HighsStatus::kOk);
printf(
"\nNow test by adding a fixed column and a fixed row, and maximizing\n");
testStandardForm(highs.getLp());
}
21 changes: 21 additions & 0 deletions src/Highs.h
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,17 @@ class Highs {
* Methods for model output
*/

/**
* @brief Identify and the standard form of the HighsLp instance in
* HiGHS
*/
HighsStatus getStandardFormLp(HighsInt& num_col, HighsInt& num_row,
HighsInt& num_nz, double& offset,
double* cost = nullptr, double* rhs = nullptr,
HighsInt* start = nullptr,
HighsInt* index = nullptr,
double* value = nullptr);

/**
* @brief Return a const reference to the presolved HighsLp instance in HiGHS
*/
Expand Down Expand Up @@ -1391,6 +1402,12 @@ class Highs {
HighsPresolveStatus::kNotPresolved;
HighsModelStatus model_status_ = HighsModelStatus::kNotset;

bool standard_form_valid_;
double standard_form_offset_;
std::vector<double> standard_form_cost_;
std::vector<double> standard_form_rhs_;
HighsSparseMatrix standard_form_matrix_;

HEkk ekk_instance_;

HighsPresolveLog presolve_log_;
Expand Down Expand Up @@ -1443,6 +1460,9 @@ class Highs {
// Clears the presolved model and its status
void clearPresolve();
//
// Clears the standard form LP
void clearStandardFormLp();
//
// Methods to clear solver data for users in Highs class members
// before (possibly) updating them with data from trying to solve
// the incumbent model.
Expand Down Expand Up @@ -1484,6 +1504,7 @@ class Highs {
void reportSolvedLpQpStats();

// Interface methods
HighsStatus formStandardFormLp();
HighsStatus basisForSolution();
HighsStatus addColsInterface(
HighsInt ext_num_new_col, const double* ext_col_cost,
Expand Down
60 changes: 60 additions & 0 deletions src/lp_data/Highs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ HighsStatus Highs::clearModel() {
HighsStatus Highs::clearSolver() {
HighsStatus return_status = HighsStatus::kOk;
clearPresolve();
clearStandardFormLp();
invalidateUserSolverData();
return returnFromHighs(return_status);
}
Expand Down Expand Up @@ -1639,6 +1640,37 @@ HighsStatus Highs::run() {
return returnFromRun(return_status, undo_mods);
}

HighsStatus Highs::getStandardFormLp(HighsInt& num_col, HighsInt& num_row,
HighsInt& num_nz, double& offset,
double* cost, double* rhs, HighsInt* start,
HighsInt* index, double* value) {
if (!this->standard_form_valid_) {
HighsStatus status = formStandardFormLp();
assert(status == HighsStatus::kOk);
}
num_col = this->standard_form_cost_.size();
num_row = this->standard_form_rhs_.size();
num_nz = this->standard_form_matrix_.start_[num_col];
offset = this->standard_form_offset_;
for (HighsInt iCol = 0; iCol < num_col; iCol++) {
if (cost) cost[iCol] = this->standard_form_cost_[iCol];
if (start) start[iCol] = this->standard_form_matrix_.start_[iCol];
if (index || value) {
for (HighsInt iEl = this->standard_form_matrix_.start_[iCol];
iEl < this->standard_form_matrix_.start_[iCol + 1]; iEl++) {
if (index) index[iEl] = this->standard_form_matrix_.index_[iEl];
if (value) value[iEl] = this->standard_form_matrix_.value_[iEl];
}
}
}
if (start) start[num_col] = this->standard_form_matrix_.start_[num_col];
if (rhs) {
for (HighsInt iRow = 0; iRow < num_row; iRow++)
rhs[iRow] = this->standard_form_rhs_[iRow];
}
return HighsStatus::kOk;
}

HighsStatus Highs::getDualRay(bool& has_dual_ray, double* dual_ray_value) {
has_dual_ray = false;
return getDualRayInterface(has_dual_ray, dual_ray_value);
Expand Down Expand Up @@ -2362,6 +2394,7 @@ HighsStatus Highs::addCols(const HighsInt num_new_col, const double* costs,
this->logHeader();
HighsStatus return_status = HighsStatus::kOk;
clearPresolve();
clearStandardFormLp();
return_status = interpretCallStatus(
options_.log_options,
addColsInterface(num_new_col, costs, lower_bounds, upper_bounds,
Expand Down Expand Up @@ -2400,6 +2433,7 @@ HighsStatus Highs::addRows(const HighsInt num_new_row,
this->logHeader();
HighsStatus return_status = HighsStatus::kOk;
clearPresolve();
clearStandardFormLp();
return_status = interpretCallStatus(
options_.log_options,
addRowsInterface(num_new_row, lower_bounds, upper_bounds, num_new_nz,
Expand All @@ -2415,6 +2449,7 @@ HighsStatus Highs::changeObjectiveSense(const ObjSense sense) {
model_.lp_.sense_ = sense;
// Nontrivial change
clearPresolve();
clearStandardFormLp();
invalidateModelStatusSolutionAndInfo();
}
return returnFromHighs(HighsStatus::kOk);
Expand Down Expand Up @@ -2544,6 +2579,7 @@ HighsStatus Highs::changeColCost(const HighsInt col, const double cost) {
HighsStatus Highs::changeColsCost(const HighsInt from_col,
const HighsInt to_col, const double* cost) {
clearPresolve();
clearStandardFormLp();
HighsIndexCollection index_collection;
const HighsInt create_error =
create(index_collection, from_col, to_col, model_.lp_.num_col_);
Expand All @@ -2570,6 +2606,7 @@ HighsStatus Highs::changeColsCost(const HighsInt num_set_entries,
if (doubleUserDataNotNull(options_.log_options, cost, "column costs"))
return HighsStatus::kError;
clearPresolve();
clearStandardFormLp();
// Ensure that the set and data are in ascending order
std::vector<double> local_cost{cost, cost + num_set_entries};
std::vector<HighsInt> local_set{set, set + num_set_entries};
Expand All @@ -2593,6 +2630,7 @@ HighsStatus Highs::changeColsCost(const HighsInt num_set_entries,

HighsStatus Highs::changeColsCost(const HighsInt* mask, const double* cost) {
clearPresolve();
clearStandardFormLp();
HighsIndexCollection index_collection;
const bool create_error = create(index_collection, mask, model_.lp_.num_col_);
assert(!create_error);
Expand All @@ -2614,6 +2652,7 @@ HighsStatus Highs::changeColsBounds(const HighsInt from_col,
const HighsInt to_col, const double* lower,
const double* upper) {
clearPresolve();
clearStandardFormLp();
HighsIndexCollection index_collection;
const HighsInt create_error =
create(index_collection, from_col, to_col, model_.lp_.num_col_);
Expand Down Expand Up @@ -2648,6 +2687,7 @@ HighsStatus Highs::changeColsBounds(const HighsInt num_set_entries,
null_data;
if (null_data) return HighsStatus::kError;
clearPresolve();
clearStandardFormLp();
// Ensure that the set and data are in ascending order
std::vector<double> local_lower{lower, lower + num_set_entries};
std::vector<double> local_upper{upper, upper + num_set_entries};
Expand All @@ -2673,6 +2713,7 @@ HighsStatus Highs::changeColsBounds(const HighsInt num_set_entries,
HighsStatus Highs::changeColsBounds(const HighsInt* mask, const double* lower,
const double* upper) {
clearPresolve();
clearStandardFormLp();
HighsIndexCollection index_collection;
const bool create_error = create(index_collection, mask, model_.lp_.num_col_);
assert(!create_error);
Expand All @@ -2695,6 +2736,7 @@ HighsStatus Highs::changeRowsBounds(const HighsInt from_row,
const HighsInt to_row, const double* lower,
const double* upper) {
clearPresolve();
clearStandardFormLp();
HighsIndexCollection index_collection;
const HighsInt create_error =
create(index_collection, from_row, to_row, model_.lp_.num_row_);
Expand Down Expand Up @@ -2729,6 +2771,7 @@ HighsStatus Highs::changeRowsBounds(const HighsInt num_set_entries,
null_data;
if (null_data) return HighsStatus::kError;
clearPresolve();
clearStandardFormLp();
// Ensure that the set and data are in ascending order
std::vector<double> local_lower{lower, lower + num_set_entries};
std::vector<double> local_upper{upper, upper + num_set_entries};
Expand All @@ -2754,6 +2797,7 @@ HighsStatus Highs::changeRowsBounds(const HighsInt num_set_entries,
HighsStatus Highs::changeRowsBounds(const HighsInt* mask, const double* lower,
const double* upper) {
clearPresolve();
clearStandardFormLp();
HighsIndexCollection index_collection;
const bool create_error = create(index_collection, mask, model_.lp_.num_row_);
assert(!create_error);
Expand Down Expand Up @@ -3052,6 +3096,7 @@ HighsStatus Highs::getCoeff(const HighsInt row, const HighsInt col,

HighsStatus Highs::deleteCols(const HighsInt from_col, const HighsInt to_col) {
clearPresolve();
clearStandardFormLp();
HighsIndexCollection index_collection;
const HighsInt create_error =
create(index_collection, from_col, to_col, model_.lp_.num_col_);
Expand All @@ -3070,6 +3115,7 @@ HighsStatus Highs::deleteCols(const HighsInt num_set_entries,
const HighsInt* set) {
if (num_set_entries == 0) return HighsStatus::kOk;
clearPresolve();
clearStandardFormLp();
HighsIndexCollection index_collection;
const HighsInt create_error =
create(index_collection, num_set_entries, set, model_.lp_.num_col_);
Expand All @@ -3083,6 +3129,7 @@ HighsStatus Highs::deleteCols(const HighsInt num_set_entries,

HighsStatus Highs::deleteCols(HighsInt* mask) {
clearPresolve();
clearStandardFormLp();
const HighsInt original_num_col = model_.lp_.num_col_;
HighsIndexCollection index_collection;
const bool create_error = create(index_collection, mask, original_num_col);
Expand All @@ -3096,6 +3143,7 @@ HighsStatus Highs::deleteCols(HighsInt* mask) {

HighsStatus Highs::deleteRows(const HighsInt from_row, const HighsInt to_row) {
clearPresolve();
clearStandardFormLp();
HighsIndexCollection index_collection;
const HighsInt create_error =
create(index_collection, from_row, to_row, model_.lp_.num_row_);
Expand All @@ -3114,6 +3162,7 @@ HighsStatus Highs::deleteRows(const HighsInt num_set_entries,
const HighsInt* set) {
if (num_set_entries == 0) return HighsStatus::kOk;
clearPresolve();
clearStandardFormLp();
HighsIndexCollection index_collection;
const HighsInt create_error =
create(index_collection, num_set_entries, set, model_.lp_.num_row_);
Expand All @@ -3127,6 +3176,7 @@ HighsStatus Highs::deleteRows(const HighsInt num_set_entries,

HighsStatus Highs::deleteRows(HighsInt* mask) {
clearPresolve();
clearStandardFormLp();
const HighsInt original_num_row = model_.lp_.num_row_;
HighsIndexCollection index_collection;
const bool create_error = create(index_collection, mask, original_num_row);
Expand All @@ -3141,6 +3191,7 @@ HighsStatus Highs::deleteRows(HighsInt* mask) {
HighsStatus Highs::scaleCol(const HighsInt col, const double scale_value) {
HighsStatus return_status = HighsStatus::kOk;
clearPresolve();
clearStandardFormLp();
HighsStatus call_status = scaleColInterface(col, scale_value);
return_status = interpretCallStatus(options_.log_options, call_status,
return_status, "scaleCol");
Expand All @@ -3151,6 +3202,7 @@ HighsStatus Highs::scaleCol(const HighsInt col, const double scale_value) {
HighsStatus Highs::scaleRow(const HighsInt row, const double scale_value) {
HighsStatus return_status = HighsStatus::kOk;
clearPresolve();
clearStandardFormLp();
HighsStatus call_status = scaleRowInterface(row, scale_value);
return_status = interpretCallStatus(options_.log_options, call_status,
return_status, "scaleRow");
Expand Down Expand Up @@ -3446,6 +3498,14 @@ void Highs::clearPresolve() {
presolve_.clear();
}

void Highs::clearStandardFormLp() {
standard_form_valid_ = false;
standard_form_offset_ = 0;
standard_form_cost_.clear();
standard_form_rhs_.clear();
standard_form_matrix_.clear();
}

void Highs::invalidateUserSolverData() {
invalidateModelStatus();
invalidateSolution();
Expand Down
Loading

0 comments on commit f9abad4

Please sign in to comment.