#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; } 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; // if (highs.getModelStatus(true) == 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& col_status = basis.col_status; vector& row_status = basis.row_status; vector& col_value = solution.col_value; vector& col_dual = solution.col_dual; vector& row_value = solution.row_value; vector& 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; // Col cost up ranging 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; // Col cost down ranging 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; // Col bound up ranging if (col_bound_up_value < kHighsInf) { // Free cols should not have a finite col_bound_up_value assert(col_status[i] != HighsBasisStatus::kZero); new_lower = lower; new_upper = upper; if (col_status[i] != HighsBasisStatus::kBasic) { // Nonbasic 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; // Col bound down ranging if (col_bound_dn_value > -kHighsInf) { // Free cols should not have a finite col_bound_dn_value assert(col_status[i] != HighsBasisStatus::kZero); new_lower = lower; new_upper = upper; if (col_status[i] != HighsBasisStatus::kBasic) { // Nonbasic 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; // Row bound up ranging if (row_bound_up_value < kHighsInf) { // Free rows should not have a finite row_bound_up_value assert(row_status[i] != HighsBasisStatus::kZero); new_lower = lower; new_upper = upper; if (row_status[i] != HighsBasisStatus::kBasic) { // Nonbasic 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; // Row bound down ranging if (row_bound_dn_value > -kHighsInf) { // Free rows should not have a finite row_bound_dn_value assert(row_status[i] != HighsBasisStatus::kZero); new_lower = lower; new_upper = upper; if (row_status[i] != HighsBasisStatus::kBasic) { // Nonbasic 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); }