#include "HCheckConfig.h"
#include "Highs.h"
#include "SpecialLps.h"
#include "catch.hpp"
#include "lp_data/HConst.h"
using std::min;
const bool dev_run = false;
HighsStatus quietRun(Highs& highs);
void colCostColumnHeader();
void colBoundcolumnHeader();
void rowBoundColumnHeader();
void assessNewBounds(double& lower, double& upper);
bool modelStatusOk(Highs& highs);
void testRanging(Highs& highs);
TEST_CASE("Ranging-min", "[highs_test_ranging]") {
Highs highs;
if (!dev_run) highs.setOptionValue("output_flag", false);
HighsLp lp;
HighsModelStatus require_model_status;
double optimal_objective;
const bool from_file = true;
if (from_file) {
std::string model_file =
std::string(HIGHS_DIR) + "/check/instances/adlittle.mps";
REQUIRE(highs.readModel(model_file) == HighsStatus::kOk);
} else {
SpecialLps special_lps;
special_lps.blendingLp(lp, require_model_status, optimal_objective);
highs.passModel(lp);
}
testRanging(highs);
}
TEST_CASE("Ranging-max", "[highs_test_ranging]") {
Highs highs;
if (!dev_run) highs.setOptionValue("output_flag", false);
HighsLp lp;
HighsModelStatus require_model_status;
double optimal_objective;
const bool from_file = true;
if (from_file) {
std::string model_file =
std::string(HIGHS_DIR) + "/check/instances/afiro.mps";
REQUIRE(highs.readModel(model_file) == HighsStatus::kOk);
REQUIRE(highs.changeObjectiveSense(ObjSense::kMaximize) ==
HighsStatus::kOk);
} else {
SpecialLps special_lps;
special_lps.blendingMaxLp(lp, require_model_status, optimal_objective);
highs.passModel(lp);
}
testRanging(highs);
}
HighsStatus quietRun(Highs& highs) {
highs.setOptionValue("output_flag", false);
HighsStatus call_status = highs.run();
if (dev_run) highs.setOptionValue("output_flag", true);
return call_status;
highs.resetGlobalScheduler(true);
}
void colCostColumnHeader() {
if (dev_run)
printf("Col %12s %12s %12s %12s %12s %12s %12s %12s %12s %12s\n", "cost",
"dual", "cost^", "object^", "verify^", "error^", "cost_", "object_",
"verify_", "error_");
}
void colBoundcolumnHeader() {
if (dev_run)
printf("Col %12s %12s %12s %12s %12s %12s %12s %12s %12s %12s %12s %12s\n",
"lower", "upper", "value", "dual", "bound^", "object^", "verify^",
"error^", "bound_", "object_", "verify_", "error_");
}
void rowBoundColumnHeader() {
if (dev_run)
printf("Row %12s %12s %12s %12s %12s %12s %12s %12s %12s %12s %12s %12s\n",
"lower", "upper", "value", "dual", "bound^", "object^", "verify^",
"error^", "bound_", "object_", "verify_", "error_");
}
void assessNewBounds(double& lower, double& upper) {
double difference = lower - upper;
if (difference > 0) {
if (dev_run)
printf("New bounds [%g, %g] inconsistent with difference %g\n", lower,
upper, difference);
if (difference > 1e-10) assert(lower < upper);
double average = (lower + upper) * 0.5;
lower = average;
upper = average;
}
}
bool modelStatusOk(Highs& highs) {
if (highs.getModelStatus() == HighsModelStatus::kOptimal) return true;
return false;
}
void testRanging(Highs& highs) {
HighsLp lp;
double optimal_objective;
REQUIRE(highs.setBasis() == HighsStatus::kOk);
highs.run();
REQUIRE(modelStatusOk(highs));
optimal_objective = highs.getObjectiveValue();
HighsRanging ranging;
REQUIRE(highs.getRanging(ranging) == HighsStatus::kOk);
HighsBasis basis = highs.getBasis();
assert(basis.valid);
HighsSolution solution = highs.getSolution();
vector<HighsBasisStatus>& col_status = basis.col_status;
vector<HighsBasisStatus>& row_status = basis.row_status;
vector<double>& col_value = solution.col_value;
vector<double>& col_dual = solution.col_dual;
vector<double>& row_value = solution.row_value;
vector<double>& row_dual = solution.row_dual;
lp = highs.getLp();
HighsInt numRow = lp.num_row_;
HighsInt numCol = lp.num_col_;
const double relative_error_tolerance = 1e-10;
const double relative_error_denominator = max(1.0, fabs(optimal_objective));
const double initial_error_report_threshold = relative_error_tolerance / 1e6;
double error_report_threshold;
HighsInt num_relative_error = 0;
double sum_relative_error = 0;
double max_relative_error = 0;
HighsInt num_lines_printed;
double max_col_cost_relative_error = 0;
HighsInt max_col_cost_relative_error_col = -1;
double max_col_bound_relative_error = 0;
HighsInt max_col_bound_relative_error_col = -1;
double max_row_bound_relative_error = 0;
HighsInt max_row_bound_relative_error_row = -1;
const HighsInt small_dim = 10;
if (dev_run) printf(" --- Testing cost ranging ---\n");
bool small_numCol = numCol < small_dim;
error_report_threshold = initial_error_report_threshold;
num_lines_printed = 0;
const bool test_all = true;
const bool test_all_col_cost = test_all;
const bool test_all_col_bound = test_all;
const bool test_all_row_bound = test_all;
HighsInt test_col_cost = min(HighsInt{0}, numCol - 1);
HighsInt test_col_bound = min(HighsInt{0}, numCol - 1);
HighsInt test_row_bound = min(HighsInt{0}, numRow - 1);
HighsInt from_i;
HighsInt to_i;
if (test_all_col_cost) {
from_i = 0;
to_i = numCol;
} else {
from_i = test_col_cost;
to_i = from_i + 1;
}
for (HighsInt i = from_i; i < to_i; i++) {
double col_cost_up_value = ranging.col_cost_up.value_[i];
double col_cost_up_objective = ranging.col_cost_up.objective_[i];
double col_cost_dn_value = ranging.col_cost_dn.value_[i];
double col_cost_dn_objective = ranging.col_cost_dn.objective_[i];
double cost = lp.col_cost_[i];
double solved_up = 0;
double solved_dn = 0;
double error;
if (col_cost_up_value < kHighsInf) {
highs.changeColCost(i, col_cost_up_value);
highs.setBasis(basis);
if (test_all_col_cost) {
quietRun(highs);
} else {
highs.run();
}
REQUIRE(modelStatusOk(highs));
solved_up = highs.getObjectiveValue();
highs.changeColCost(i, cost);
error = fabs(solved_up - col_cost_up_objective);
} else {
solved_up = col_cost_up_objective;
error = 0;
}
double relative_up_error = error / relative_error_denominator;
if (relative_up_error >= relative_error_tolerance) {
if (dev_run)
printf("Col %" HIGHSINT_FORMAT
": %g = relative_up_error >= relative_error_tolerance = %g | "
"%g %g %g\n",
i, relative_up_error, relative_error_tolerance, solved_up,
col_cost_up_objective, error);
num_relative_error++;
if (dev_run) REQUIRE(relative_up_error < relative_error_tolerance);
}
max_relative_error = max(max_relative_error, relative_up_error);
sum_relative_error += relative_up_error;
if (col_cost_dn_value > -kHighsInf) {
highs.changeColCost(i, col_cost_dn_value);
highs.setBasis(basis);
if (test_all_col_cost) {
quietRun(highs);
} else {
highs.run();
}
REQUIRE(modelStatusOk(highs));
solved_dn = highs.getObjectiveValue();
highs.changeColCost(i, cost);
error = fabs(solved_dn - col_cost_dn_objective);
} else {
solved_dn = col_cost_dn_objective;
error = 0;
}
double relative_dn_error = error / relative_error_denominator;
if (relative_dn_error >= relative_error_tolerance) {
if (dev_run)
printf("Col %" HIGHSINT_FORMAT
": %g = relative_dn_error >= relative_error_tolerance = %g | "
"%g %g %g\n",
i, relative_dn_error, relative_error_tolerance, solved_dn,
col_cost_dn_objective, error);
num_relative_error++;
if (dev_run) REQUIRE(relative_up_error < relative_error_tolerance);
}
max_relative_error = max(max_relative_error, relative_dn_error);
sum_relative_error += relative_dn_error;
double relative_error = max(relative_up_error, relative_dn_error);
if (relative_error > max_col_cost_relative_error) {
max_col_cost_relative_error = relative_error;
max_col_cost_relative_error_col = i;
}
if (small_numCol || relative_error > error_report_threshold) {
if (num_lines_printed % 50 == 0) colCostColumnHeader();
if (dev_run)
printf("%3" HIGHSINT_FORMAT
" %12g %12g %12g %12g %12g %12g %12g %12g %12g %12g\n",
i, cost, col_dual[i], col_cost_up_value, col_cost_up_objective,
solved_up, relative_up_error, col_cost_dn_value,
col_cost_dn_objective, solved_dn, relative_dn_error);
error_report_threshold = 10 * error_report_threshold;
num_lines_printed++;
}
}
if (dev_run) printf(" --- Testing column bounds ranging ---\n");
error_report_threshold = initial_error_report_threshold;
num_lines_printed = 0;
if (test_all_col_bound) {
from_i = 0;
to_i = numCol;
} else {
from_i = test_col_bound;
to_i = from_i + 1;
}
for (HighsInt i = from_i; i < to_i; i++) {
double col_bound_up_value = ranging.col_bound_up.value_[i];
double col_bound_up_objective = ranging.col_bound_up.objective_[i];
double col_bound_dn_value = ranging.col_bound_dn.value_[i];
double col_bound_dn_objective = ranging.col_bound_dn.objective_[i];
double lower = lp.col_lower_[i];
double upper = lp.col_upper_[i];
double new_lower;
double new_upper;
double solved_up = 0;
double solved_dn = 0;
double error;
if (col_bound_up_value < kHighsInf) {
assert(col_status[i] != HighsBasisStatus::kZero);
new_lower = lower;
new_upper = upper;
if (col_status[i] != HighsBasisStatus::kBasic) {
if (lower == upper) {
new_lower = col_bound_up_value;
new_upper = col_bound_up_value;
} else if (col_status[i] == HighsBasisStatus::kLower) {
new_lower = col_bound_up_value;
} else {
new_upper = col_bound_up_value;
}
} else {
new_lower = col_bound_up_value;
}
assessNewBounds(new_lower, new_upper);
highs.changeColBounds(i, new_lower, new_upper);
highs.setBasis(basis);
if (test_all_col_bound) {
quietRun(highs);
} else {
highs.run();
}
REQUIRE(modelStatusOk(highs));
solved_up = highs.getObjectiveValue();
highs.changeColBounds(i, lower, upper);
error = fabs(solved_up - col_bound_up_objective);
} else {
solved_up = col_bound_up_objective;
error = 0;
}
double relative_up_error = error / relative_error_denominator;
if (relative_up_error >= relative_error_tolerance) {
if (dev_run)
printf("Col %" HIGHSINT_FORMAT
": %g = relative_up_error >= relative_error_tolerance = %g | "
"%g %g %g\n",
i, relative_up_error, relative_error_tolerance, solved_up,
col_bound_up_objective, error);
num_relative_error++;
if (dev_run) REQUIRE(relative_up_error < relative_error_tolerance);
}
max_relative_error = max(max_relative_error, relative_up_error);
sum_relative_error += relative_up_error;
if (col_bound_dn_value > -kHighsInf) {
assert(col_status[i] != HighsBasisStatus::kZero);
new_lower = lower;
new_upper = upper;
if (col_status[i] != HighsBasisStatus::kBasic) {
if (lower == upper) {
new_lower = col_bound_dn_value;
new_upper = col_bound_dn_value;
} else if (col_status[i] == HighsBasisStatus::kLower) {
new_lower = col_bound_dn_value;
} else {
new_upper = col_bound_dn_value;
}
} else {
new_upper = col_bound_dn_value;
}
assessNewBounds(new_lower, new_upper);
highs.changeColBounds(i, new_lower, new_upper);
highs.setBasis(basis);
if (test_all_col_bound) {
quietRun(highs);
} else {
highs.run();
}
REQUIRE(modelStatusOk(highs));
solved_dn = highs.getObjectiveValue();
highs.changeColBounds(i, lower, upper);
error = fabs(solved_dn - col_bound_dn_objective);
} else {
solved_dn = col_bound_dn_objective;
error = 0;
}
double relative_dn_error = error / relative_error_denominator;
if (relative_dn_error >= relative_error_tolerance) {
if (dev_run)
printf("Col %" HIGHSINT_FORMAT
": %g = relative_dn_error >= relative_error_tolerance = %g | "
"%g %g %g\n",
i, relative_dn_error, relative_error_tolerance, solved_dn,
col_bound_dn_objective, error);
num_relative_error++;
if (dev_run) REQUIRE(relative_up_error < relative_error_tolerance);
}
max_relative_error = max(max_relative_error, relative_dn_error);
sum_relative_error += relative_dn_error;
double relative_error = max(relative_up_error, relative_dn_error);
if (relative_error > max_col_bound_relative_error) {
max_col_bound_relative_error = relative_error;
max_col_bound_relative_error_col = i;
}
if (small_numCol || relative_error > error_report_threshold) {
if (num_lines_printed % 50 == 0) colBoundcolumnHeader();
if (dev_run)
printf("%3" HIGHSINT_FORMAT
" %12g %12g %12g %12g %12g %12g %12g %12g %12g %12g %12g %12g\n",
i, lower, upper, col_value[i], col_dual[i], col_bound_up_value,
col_bound_up_objective, solved_up, relative_up_error,
col_bound_dn_value, col_bound_dn_objective, solved_dn,
relative_dn_error);
error_report_threshold = 10 * error_report_threshold;
num_lines_printed++;
}
}
if (dev_run) printf(" --- Testing row bounds ranging ---\n");
bool small_numRow = numRow < small_dim;
error_report_threshold = initial_error_report_threshold;
num_lines_printed = 0;
if (test_all_row_bound) {
from_i = 0;
to_i = numRow;
} else {
from_i = test_row_bound;
to_i = from_i + 1;
}
for (HighsInt i = from_i; i < to_i; i++) {
double row_bound_up_value = ranging.row_bound_up.value_[i];
double row_bound_up_objective = ranging.row_bound_up.objective_[i];
double row_bound_dn_value = ranging.row_bound_dn.value_[i];
double row_bound_dn_objective = ranging.row_bound_dn.objective_[i];
double lower = lp.row_lower_[i];
double upper = lp.row_upper_[i];
double new_lower = lower;
double new_upper = upper;
double solved_up = 0;
double solved_dn = 0;
double error;
if (row_bound_up_value < kHighsInf) {
assert(row_status[i] != HighsBasisStatus::kZero);
new_lower = lower;
new_upper = upper;
if (row_status[i] != HighsBasisStatus::kBasic) {
if (lower == upper) {
new_lower = row_bound_up_value;
new_upper = row_bound_up_value;
} else if (row_status[i] == HighsBasisStatus::kLower) {
new_lower = row_bound_up_value;
} else {
new_upper = row_bound_up_value;
}
} else {
new_lower = row_bound_up_value;
}
assessNewBounds(new_lower, new_upper);
highs.changeRowBounds(i, new_lower, new_upper);
highs.setBasis(basis);
if (test_all_row_bound) {
quietRun(highs);
} else {
highs.run();
}
REQUIRE(modelStatusOk(highs));
solved_up = highs.getObjectiveValue();
highs.changeRowBounds(i, lower, upper);
error = fabs(solved_up - row_bound_up_objective);
} else {
solved_up = row_bound_up_objective;
error = 0;
}
double relative_up_error = error / relative_error_denominator;
if (relative_up_error >= relative_error_tolerance) {
if (dev_run)
printf("Row %" HIGHSINT_FORMAT
": %g = relative_up_error >= relative_error_tolerance = %g | "
"%g %g %g\n",
i, relative_up_error, relative_error_tolerance, solved_up,
row_bound_up_objective, error);
num_relative_error++;
if (dev_run) REQUIRE(relative_up_error < relative_error_tolerance);
}
max_relative_error = max(max_relative_error, relative_up_error);
sum_relative_error += relative_up_error;
if (row_bound_dn_value > -kHighsInf) {
assert(row_status[i] != HighsBasisStatus::kZero);
new_lower = lower;
new_upper = upper;
if (row_status[i] != HighsBasisStatus::kBasic) {
if (lower == upper) {
new_lower = row_bound_dn_value;
new_upper = row_bound_dn_value;
} else if (row_status[i] == HighsBasisStatus::kLower) {
new_lower = row_bound_dn_value;
} else {
new_upper = row_bound_dn_value;
}
} else {
new_upper = row_bound_dn_value;
}
assessNewBounds(new_lower, new_upper);
highs.changeRowBounds(i, new_lower, new_upper);
highs.setBasis(basis);
if (test_all_row_bound) {
quietRun(highs);
} else {
highs.run();
}
REQUIRE(modelStatusOk(highs));
solved_dn = highs.getObjectiveValue();
highs.changeRowBounds(i, lower, upper);
error = fabs(solved_dn - row_bound_dn_objective);
} else {
solved_dn = row_bound_dn_objective;
error = 0;
}
double relative_dn_error = error / relative_error_denominator;
if (relative_dn_error >= relative_error_tolerance) {
if (dev_run)
printf("Row %" HIGHSINT_FORMAT
": %g = relative_dn_error >= relative_error_tolerance = %g | "
"%g %g %g\n",
i, relative_dn_error, relative_error_tolerance, solved_dn,
row_bound_dn_objective, error);
num_relative_error++;
if (dev_run) REQUIRE(relative_up_error < relative_error_tolerance);
}
max_relative_error = max(max_relative_error, relative_dn_error);
sum_relative_error += relative_dn_error;
double relative_error = max(relative_up_error, relative_dn_error);
if (relative_error > max_row_bound_relative_error) {
max_row_bound_relative_error = relative_error;
max_row_bound_relative_error_row = i;
}
if (small_numRow || relative_error > error_report_threshold) {
if (num_lines_printed % 50 == 0) rowBoundColumnHeader();
if (dev_run)
printf("%3" HIGHSINT_FORMAT
" %12g %12g %12g %12g %12g %12g %12g %12g %12g %12g %12g %12g\n",
i, lower, upper, row_value[i], row_dual[i], row_bound_up_value,
row_bound_up_objective, solved_up, relative_up_error,
row_bound_dn_value, row_bound_dn_objective, solved_dn,
relative_dn_error);
error_report_threshold = 10 * error_report_threshold;
num_lines_printed++;
}
}
if (dev_run) {
if (max_col_cost_relative_error_col >= 0)
printf(
"Max col cost relative objective error = %g in col %" HIGHSINT_FORMAT
"\n",
max_col_cost_relative_error, max_col_cost_relative_error_col);
if (max_col_bound_relative_error_col >= 0)
printf(
"Max col bound relative objective error = %g in col %" HIGHSINT_FORMAT
"\n",
max_col_bound_relative_error, max_col_bound_relative_error_col);
if (max_row_bound_relative_error_row >= 0)
printf(
"Max row bound relative objective error = %g in row %" HIGHSINT_FORMAT
"\n",
max_row_bound_relative_error, max_row_bound_relative_error_row);
printf("Num / max / sum relative objective error = %" HIGHSINT_FORMAT
" / %g / %g\n",
num_relative_error, max_relative_error, sum_relative_error);
}
REQUIRE(num_relative_error < 10);
REQUIRE(max_relative_error < relative_error_tolerance);
highs.resetGlobalScheduler(true);
}