#include #include #include "HCheckConfig.h" #include "Highs.h" #include "catch.hpp" #include "io/FilereaderLp.h" const bool dev_run = false; const double inf = kHighsInf; const double double_equal_tolerance = 1e-5; TEST_CASE("qp-unbounded", "[qpsolver]") { std::string filename; filename = std::string(HIGHS_DIR) + "/check/instances/qpunbounded.lp"; Highs highs; highs.setOptionValue("output_flag", dev_run); REQUIRE(highs.readModel(filename) == HighsStatus::kOk); REQUIRE(highs.run() == HighsStatus::kOk); REQUIRE(highs.getModelStatus() == HighsModelStatus::kUnbounded); } TEST_CASE("qp-infeasible", "[qpsolver]") { std::string filename; filename = std::string(HIGHS_DIR) + "/check/instances/qpinfeasible.lp"; Highs highs; highs.setOptionValue("output_flag", dev_run); REQUIRE(highs.readModel(filename) == HighsStatus::kOk); REQUIRE(highs.run() == HighsStatus::kOk); REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible); } TEST_CASE("qpsolver", "[qpsolver]") { double required_objective_function_value; double required_x0; double required_x1; double required_x2; std::string filename; filename = std::string(HIGHS_DIR) + "/check/instances/qptestnw.lp"; required_objective_function_value = -6.45; required_x0 = 1.4; required_x1 = 1.7; const double required_col_dual0 = 0; const double required_col_dual1 = 0; const double required_row_dual0 = 0.8; const double required_row_dual1 = 0; const double required_row_dual2 = 0; // At the optimal solution g-Qx = [0.8, -1.6] with only constraint 0 // active. It has normal [1, -2], so dual of 0.8 is correct Highs highs; highs.setOptionValue("output_flag", dev_run); const HighsModel& model = highs.getModel(); const HighsInfo& info = highs.getInfo(); const HighsSolution& solution = highs.getSolution(); const double& objective_function_value = info.objective_function_value; HighsStatus return_status = highs.readModel(filename); REQUIRE(return_status == HighsStatus::kOk); return_status = highs.run(); REQUIRE(return_status == HighsStatus::kOk); double alt_objective_function_value = model.objectiveValue(solution.col_value); REQUIRE(fabs(objective_function_value - alt_objective_function_value) < double_equal_tolerance); REQUIRE(fabs(objective_function_value - required_objective_function_value) < double_equal_tolerance); REQUIRE(fabs(solution.col_value[0] - required_x0) < double_equal_tolerance); REQUIRE(fabs(solution.col_value[1] - required_x1) < double_equal_tolerance); REQUIRE(fabs(solution.col_dual[0] - required_col_dual0) < double_equal_tolerance); REQUIRE(fabs(solution.col_dual[1] - required_col_dual1) < double_equal_tolerance); REQUIRE(fabs(solution.row_dual[0] - required_row_dual0) < double_equal_tolerance); REQUIRE(fabs(solution.row_dual[1] - required_row_dual1) < double_equal_tolerance); REQUIRE(fabs(solution.row_dual[1] - required_row_dual1) < double_equal_tolerance); if (dev_run) highs.writeSolution("", 1); // Check with qjh.mps filename = std::string(HIGHS_DIR) + "/check/instances/qjh.mps"; required_objective_function_value = -5.25; required_x0 = 0.5; required_x1 = 5.0; required_x2 = 1.5; return_status = highs.readModel(filename); REQUIRE(return_status == HighsStatus::kOk); return_status = highs.run(); REQUIRE(return_status == HighsStatus::kOk); if (dev_run) printf("Objective = %g\n", objective_function_value); alt_objective_function_value = model.objectiveValue(solution.col_value); REQUIRE(fabs(objective_function_value - alt_objective_function_value) < double_equal_tolerance); REQUIRE(fabs(objective_function_value - required_objective_function_value) < double_equal_tolerance); REQUIRE(fabs(solution.col_value[0] - required_x0) < double_equal_tolerance); REQUIRE(fabs(solution.col_value[1] - required_x1) < double_equal_tolerance); REQUIRE(fabs(solution.col_value[2] - required_x2) < double_equal_tolerance); REQUIRE(return_status == HighsStatus::kOk); // Test writeModel by writing out qjh.mps... filename = "qjh.mps"; highs.writeModel(filename); // ... and reading it in again return_status = highs.readModel(filename); REQUIRE(return_status == HighsStatus::kOk); return_status = highs.run(); REQUIRE(return_status == HighsStatus::kOk); if (dev_run) printf("Objective = %g\n", objective_function_value); alt_objective_function_value = model.objectiveValue(solution.col_value); REQUIRE(fabs(objective_function_value - alt_objective_function_value) < double_equal_tolerance); REQUIRE(fabs(objective_function_value - required_objective_function_value) < double_equal_tolerance); REQUIRE(fabs(solution.col_value[0] - required_x0) < double_equal_tolerance); REQUIRE(fabs(solution.col_value[1] - required_x1) < double_equal_tolerance); REQUIRE(fabs(solution.col_value[2] - required_x2) < double_equal_tolerance); std::remove(filename.c_str()); // Test that attempting to solve MIQP yields error HighsInt num_col = highs.getNumCol(); std::vector integrality; integrality.assign(num_col, HighsVarType::kInteger); REQUIRE(highs.changeColsIntegrality(0, num_col - 1, integrality.data()) == HighsStatus::kOk); return_status = highs.run(); REQUIRE(return_status == HighsStatus::kError); // Test that attempting to solve MIQP relaxation is OK highs.setOptionValue("solve_relaxation", true); return_status = highs.run(); REQUIRE(return_status == HighsStatus::kOk); } TEST_CASE("test-qod", "[qpsolver]") { HighsStatus return_status; HighsModelStatus model_status; double required_objective_function_value; double required_x0; double required_x1; HighsModel local_model; HighsLp& lp = local_model.lp_; HighsHessian& hessian = local_model.hessian_; // Oscar's edge case // // min x^2 + x = x(x + 1) lp.model_name_ = "qod"; lp.num_col_ = 1; lp.num_row_ = 0; lp.col_cost_ = {1.0}; lp.col_lower_ = {-inf}; lp.col_upper_ = {inf}; lp.sense_ = ObjSense::kMinimize; lp.offset_ = 0.25; hessian.dim_ = lp.num_col_; hessian.start_ = {0, 1}; hessian.index_ = {0}; hessian.value_ = {2.0}; Highs highs; highs.setOptionValue("output_flag", dev_run); const HighsModel& model = highs.getModel(); const HighsInfo& info = highs.getInfo(); const HighsSolution& solution = highs.getSolution(); const double& objective_function_value = info.objective_function_value; return_status = highs.passModel(local_model); REQUIRE(return_status == HighsStatus::kOk); if (dev_run) highs.writeModel(""); return_status = highs.run(); REQUIRE(return_status == HighsStatus::kOk); if (dev_run) { printf("One variable unconstrained QP: objective = %g; solution:\n", objective_function_value); highs.writeSolution("", kSolutionStylePretty); } required_objective_function_value = 0; required_x0 = -0.5; double alt_objective_function_value = model.objectiveValue(solution.col_value); REQUIRE(fabs(objective_function_value - alt_objective_function_value) < double_equal_tolerance); REQUIRE(fabs(objective_function_value - required_objective_function_value) < double_equal_tolerance); REQUIRE(fabs(solution.col_value[0] - required_x0) < double_equal_tolerance); // Add a variable x1 with objective x1^2 - x1 // // Add the variable highs.addCol(-1, -inf, inf, 0, NULL, NULL); if (dev_run) highs.writeModel(""); // Can solve the model before the Hessian has been replaced return_status = highs.run(); REQUIRE(return_status == HighsStatus::kOk); model_status = highs.getModelStatus(); REQUIRE(model_status == HighsModelStatus::kUnbounded); // Pass the new Hessian hessian.dim_ = 2; hessian.start_ = {0, 1, 2}; hessian.index_ = {0, 1}; hessian.value_ = {2.0, 2.0}; return_status = highs.passHessian(hessian); REQUIRE(return_status == HighsStatus::kOk); return_status = highs.run(); REQUIRE(return_status == HighsStatus::kOk); if (dev_run) { printf("Two variable unconstrained QP: objective = %g; solution:\n", objective_function_value); highs.writeSolution("", kSolutionStylePretty); } required_objective_function_value = -0.25; required_x1 = 0.5; alt_objective_function_value = model.objectiveValue(solution.col_value); REQUIRE(fabs(objective_function_value - alt_objective_function_value) < double_equal_tolerance); REQUIRE(fabs(objective_function_value - required_objective_function_value) < double_equal_tolerance); REQUIRE(fabs(solution.col_value[0] - required_x0) < double_equal_tolerance); REQUIRE(fabs(solution.col_value[1] - required_x1) < double_equal_tolerance); // Illustrate methods for getting and changing the offset by getting // the current offset, shifting it by the current objective and // checking that the objective value is changed accordingly double offset; return_status = highs.getObjectiveOffset(offset); REQUIRE(return_status == HighsStatus::kOk); double dl_offset = -objective_function_value; offset += dl_offset; return_status = highs.changeObjectiveOffset(offset); required_objective_function_value += dl_offset; REQUIRE(fabs(objective_function_value - required_objective_function_value) < double_equal_tolerance); // Add the constraint 0.5 <= x0 + x1 lp.a_matrix_.index_ = {0, 1}; lp.a_matrix_.value_ = {1, 1}; highs.addRow(0.5, inf, 2, lp.a_matrix_.index_.data(), lp.a_matrix_.value_.data()); if (dev_run) highs.writeModel(""); return_status = highs.run(); REQUIRE(return_status == HighsStatus::kOk); if (dev_run) { printf("Two variable constrained QP: objective = %g; solution:\n", objective_function_value); highs.writeSolution("", kSolutionStylePretty); } required_objective_function_value = 0.125; required_x0 = -0.25; required_x1 = 0.75; alt_objective_function_value = model.objectiveValue(solution.col_value); REQUIRE(fabs(objective_function_value - alt_objective_function_value) < double_equal_tolerance); REQUIRE(fabs(objective_function_value - required_objective_function_value) < double_equal_tolerance); REQUIRE(fabs(solution.col_value[0] - required_x0) < double_equal_tolerance); REQUIRE(fabs(solution.col_value[1] - required_x1) < double_equal_tolerance); } TEST_CASE("test-qjh", "[qpsolver]") { // Test passing/reading and solving the problem qjh // // minimize -x_2 - 3x_3 + (1/2)(2x_1^2 - 2x_1x_3 + 0.2x_2^2 + 2x_3^2) // // subject to x_1 + x_3 <= 2; x>=0 HighsStatus return_status; HighsModelStatus model_status; double required_objective_function_value; HighsModel local_model; HighsLp& lp = local_model.lp_; HighsHessian& hessian = local_model.hessian_; // Start with an unconstrained QP lp.model_name_ = "qjh"; lp.num_col_ = 3; lp.num_row_ = 0; lp.col_cost_ = {0.0, -1.0, -3.0}; lp.col_lower_ = {-inf, -inf, -inf}; lp.col_upper_ = {inf, inf, inf}; lp.sense_ = ObjSense::kMinimize; lp.offset_ = 0; hessian.dim_ = lp.num_col_; // hessian.format_ = HessianFormat::kSquare; // hessian.start_ = {0, 2, 3, 5}; // hessian.index_ = {0, 2, 1, 0, 2}; // hessian.value_ = {2.0, -1.0, 0.2, -1.0, 2.0}; hessian.format_ = HessianFormat::kTriangular; hessian.start_ = {0, 2, 3, 4}; hessian.index_ = {0, 2, 1, 2}; hessian.value_ = {2.0, -1.0, 0.2, 2.0}; Highs highs; highs.setOptionValue("output_flag", dev_run); const HighsInfo& info = highs.getInfo(); const double& objective_function_value = info.objective_function_value; return_status = highs.passModel(local_model); REQUIRE(return_status == HighsStatus::kOk); if (dev_run) highs.writeModel(""); return_status = highs.run(); REQUIRE(return_status == HighsStatus::kOk); required_objective_function_value = -5.50; REQUIRE(fabs(objective_function_value - required_objective_function_value) < double_equal_tolerance); if (dev_run) printf("Objective = %g\n", objective_function_value); if (dev_run) highs.writeSolution("", kSolutionStylePretty); // Now with a constraint lp.num_row_ = 1; lp.col_lower_ = {0.0, 0.0, 0.0}; lp.row_lower_ = {-inf}; lp.row_upper_ = {2}; lp.a_matrix_.start_ = {0, 1, 1, 2}; lp.a_matrix_.index_ = {0, 0}; lp.a_matrix_.value_ = {1.0, 1.0}; lp.a_matrix_.format_ = MatrixFormat::kColwise; return_status = highs.passModel(local_model); REQUIRE(return_status == HighsStatus::kOk); if (dev_run) highs.writeModel(""); return_status = highs.run(); REQUIRE(return_status == HighsStatus::kOk); required_objective_function_value = -5.25; REQUIRE(fabs(objective_function_value - required_objective_function_value) < double_equal_tolerance); if (dev_run) printf("Objective = %g\n", objective_function_value); if (dev_run) highs.writeSolution("", kSolutionStylePretty); // Make the problem infeasible return_status = highs.changeColBounds(0, 3, inf); REQUIRE(return_status == HighsStatus::kOk); return_status = highs.changeColBounds(2, 3, inf); REQUIRE(return_status == HighsStatus::kOk); return_status = highs.run(); REQUIRE(return_status == HighsStatus::kOk); if (dev_run) highs.writeSolution("", kSolutionStylePretty); model_status = highs.getModelStatus(); if (dev_run) printf("Infeasible QP status: %s\n", highs.modelStatusToString(model_status).c_str()); REQUIRE(model_status == HighsModelStatus::kInfeasible); return_status = highs.clearModel(); std::string filename; for (HighsInt test_k = 0; test_k < 2; test_k++) { if (test_k == 0) { filename = std::string(HIGHS_DIR) + "/check/instances/qjh.mps"; } else if (test_k == 1) { filename = std::string(HIGHS_DIR) + "/check/instances/qjh_quadobj.mps"; } else { filename = std::string(HIGHS_DIR) + "/check/instances/qjh_qmatrix.mps"; } return_status = highs.readModel(filename); REQUIRE(return_status == HighsStatus::kOk); return_status = highs.run(); REQUIRE(return_status == HighsStatus::kOk); REQUIRE(fabs(objective_function_value - required_objective_function_value) < double_equal_tolerance); return_status = highs.clearModel(); } } TEST_CASE("test-min-negative-definite", "[qpsolver]") { HighsModel model; model.lp_.model_name_ = "min-negative-definite"; model.lp_.num_col_ = 1; model.lp_.num_row_ = 0; model.lp_.col_cost_ = {0}; model.lp_.col_lower_ = {0}; model.lp_.col_upper_ = {inf}; model.hessian_.dim_ = model.lp_.num_col_; model.hessian_.start_ = {0, 1}; model.hessian_.index_ = {0}; model.hessian_.value_ = {-0.5}; Highs highs; highs.setOptionValue("output_flag", dev_run); // Should load OK REQUIRE(highs.passModel(model) == HighsStatus::kOk); // Run should fail since objective is non-convex REQUIRE(highs.run() == HighsStatus::kError); } TEST_CASE("test-max-negative-definite", "[qpsolver]") { HighsLp lp; HighsHessian hessian; // Construct a QP with negative definite Hessian const double col_lower = 0; const double col_upper = inf; // 10.0; lp.model_name_ = "max-negative-definite"; lp.num_col_ = 3; lp.num_row_ = 1; lp.offset_ = -1.0; lp.col_cost_ = {1.0, 1.0, 2.0}; lp.col_lower_ = {col_lower, col_lower, col_lower}; lp.col_upper_ = {col_upper, col_upper, col_upper}; lp.row_lower_ = {4}; lp.row_upper_ = {inf}; lp.a_matrix_.format_ = MatrixFormat::kRowwise; lp.a_matrix_.start_ = {0, 3}; lp.a_matrix_.index_ = {0, 1, 2}; lp.a_matrix_.value_ = {1.0, 1.0, 1.0}; Highs highs; highs.setOptionValue("output_flag", dev_run); REQUIRE(highs.passModel(lp) == HighsStatus::kOk); hessian.dim_ = lp.num_col_; hessian.start_ = {0, 2, 3, 4}; hessian.index_ = {0, 2, 1, 2}; hessian.value_ = {-2.0, -1, -1.0, -1}; if (dev_run) { printf("\nNegative definite Hessian\n"); hessian.print(); } // Should load OK REQUIRE(highs.passHessian(hessian) == HighsStatus::kOk); // Make the problem a maximization REQUIRE(highs.changeObjectiveSense(ObjSense::kMaximize) == HighsStatus::kOk); REQUIRE(highs.run() == HighsStatus::kOk); if (dev_run) highs.writeSolution("", kSolutionStylePretty); const double required_objective_function_value = 1.25; REQUIRE(fabs(highs.getObjectiveValue() - required_objective_function_value) < double_equal_tolerance); const HighsSolution& solution = highs.getSolution(); REQUIRE(fabs(solution.col_value[0] - 0.0) < double_equal_tolerance); REQUIRE(fabs(solution.col_value[1] - 1.5) < double_equal_tolerance); REQUIRE(fabs(solution.col_value[2] - 2.5) < double_equal_tolerance); REQUIRE(fabs(solution.col_dual[0] + 1.0) < double_equal_tolerance); REQUIRE(fabs(solution.row_dual[0] + 0.5) < double_equal_tolerance); } TEST_CASE("test-semi-definite0", "[qpsolver]") { HighsStatus return_status; HighsModelStatus model_status; double required_objective_function_value; HighsModel local_model; HighsLp& lp = local_model.lp_; HighsHessian& hessian = local_model.hessian_; // Construct a QP with positive semi-definite Hessian const double col_lower = 0; const double col_upper = inf; // 10.0; lp.model_name_ = "semi-definite"; lp.num_col_ = 3; lp.num_row_ = 1; lp.col_cost_ = {1.0, 1.0, 2.0}; lp.col_lower_ = {col_lower, col_lower, col_lower}; lp.col_upper_ = {col_upper, col_upper, col_upper}; lp.row_lower_ = {2}; lp.row_upper_ = {inf}; lp.a_matrix_.format_ = MatrixFormat::kColwise; lp.a_matrix_.start_ = {0, 1, 2, 3}; lp.a_matrix_.index_ = {0, 0, 0}; lp.a_matrix_.value_ = {1.0, 1.0, 1.0}; hessian.dim_ = lp.num_col_; hessian.start_ = {0, 2, 2, 3}; hessian.index_ = {0, 2, 2}; hessian.value_ = {2.0, -1.0, 1.0}; Highs highs; highs.setOptionValue("output_flag", dev_run); return_status = highs.passModel(local_model); REQUIRE(return_status == HighsStatus::kOk); // highs.writeModel("semi-definite.mps"); return_status = highs.run(); REQUIRE(return_status == HighsStatus::kOk); if (dev_run) highs.writeSolution("", kSolutionStylePretty); } TEST_CASE("test-semi-definite1", "[qpsolver]") { HighsStatus return_status; HighsModelStatus model_status; double required_objective_function_value; HighsLp lp; HighsHessian hessian; lp.model_name_ = "semi-definite"; lp.num_col_ = 2; lp.num_row_ = 1; lp.col_cost_ = {-2.0, 0.0}; lp.col_lower_ = {-inf, -inf}; lp.col_upper_ = {inf, inf}; lp.sense_ = ObjSense::kMinimize; lp.offset_ = 0; lp.row_lower_ = {1}; lp.row_upper_ = {1}; lp.a_matrix_.format_ = MatrixFormat::kRowwise; lp.a_matrix_.start_ = {0, 2}; lp.a_matrix_.index_ = {0, 1}; lp.a_matrix_.value_ = {1.0, 1.0}; Highs highs; highs.setOptionValue("output_flag", dev_run); REQUIRE(highs.passModel(lp) == HighsStatus::kOk); hessian.dim_ = lp.num_col_; hessian.start_ = {0, 2, 3}; hessian.index_ = {0, 1, 1}; hessian.value_ = {1.0, -1.0, 1.0}; REQUIRE(highs.passHessian(hessian) == HighsStatus::kOk); REQUIRE(highs.run() == HighsStatus::kOk); if (dev_run) highs.writeSolution("", kSolutionStylePretty); const HighsSolution& solution = highs.getSolution(); const double objective_function_value = highs.getObjectiveValue(); REQUIRE(fabs(objective_function_value + 1.5) < double_equal_tolerance); REQUIRE(fabs(solution.col_value[0] - 1) < double_equal_tolerance); REQUIRE(fabs(solution.col_value[1]) < double_equal_tolerance); } TEST_CASE("test-semi-definite2", "[qpsolver]") { HighsStatus return_status; HighsModelStatus model_status; double required_objective_function_value; HighsLp lp; HighsHessian hessian; lp.model_name_ = "semi-definite"; lp.num_col_ = 2; lp.num_row_ = 1; lp.col_cost_ = {0.0, -1.0}; lp.col_lower_ = {-inf, -inf}; lp.col_upper_ = {inf, inf}; lp.sense_ = ObjSense::kMinimize; lp.offset_ = 0; lp.row_lower_ = {-inf}; lp.row_upper_ = {1}; lp.a_matrix_.format_ = MatrixFormat::kRowwise; lp.a_matrix_.start_ = {0, 2}; lp.a_matrix_.index_ = {0, 1}; lp.a_matrix_.value_ = {1.0, 1.0}; Highs highs; highs.setOptionValue("output_flag", dev_run); REQUIRE(highs.passModel(lp) == HighsStatus::kOk); // Illustrates how final entries of 0 on the diagonal are handled hessian.dim_ = lp.num_col_; hessian.start_ = {0, 1, 1}; hessian.index_ = {0}; hessian.value_ = {1.0}; REQUIRE(highs.passHessian(hessian) == HighsStatus::kOk); REQUIRE(highs.run() == HighsStatus::kOk); if (dev_run) highs.writeSolution("", kSolutionStylePretty); const HighsSolution& solution = highs.getSolution(); const double objective_function_value = highs.getObjectiveValue(); REQUIRE(fabs(objective_function_value + 1.5) < double_equal_tolerance); REQUIRE(fabs(solution.col_value[0] + 1) < double_equal_tolerance); REQUIRE(fabs(solution.col_value[1] - 2) < double_equal_tolerance); } void hessianProduct(const HighsHessian& hessian, const std::vector& arg, std::vector& result) { HighsInt dim = hessian.dim_; assert(HighsInt(arg.size()) == dim); result.resize(dim); for (HighsInt iCol = 0; iCol < dim; iCol++) { double sum = 0; for (HighsInt iEl = hessian.start_[iCol]; iEl < hessian.start_[iCol + 1]; iEl++) sum += hessian.value_[iEl] * arg[hessian.index_[iEl]]; result[iCol] = sum; } } TEST_CASE("test-qp-modification", "[qpsolver]") { // HighsStatus return_status; // HighsModelStatus model_status; // double required_objective_function_value; HighsModel model; HighsLp& lp = model.lp_; HighsHessian& hessian = model.hessian_; lp.num_col_ = 2; lp.num_row_ = 1; lp.col_cost_ = {1.0, -1.0}; lp.col_lower_ = {-inf, -inf}; lp.col_upper_ = {inf, inf}; lp.sense_ = ObjSense::kMinimize; lp.offset_ = 0; lp.row_lower_ = {-inf}; lp.row_upper_ = {1}; lp.a_matrix_.format_ = MatrixFormat::kRowwise; lp.a_matrix_.start_ = {0, 2}; lp.a_matrix_.index_ = {0, 1}; lp.a_matrix_.value_ = {1.0, 1.0}; hessian.dim_ = 1; hessian.start_ = {0, 1}; hessian.value_ = {1.0}; Highs highs; highs.setOptionValue("output_flag", dev_run); const HighsModel& incumbent_model = highs.getModel(); // Cannot have Hessian with index exceeding hessian.dim_-1 hessian.index_ = {1}; REQUIRE(highs.passModel(model) == HighsStatus::kError); // Correct the Hessian index hessian.index_[0] = 0; REQUIRE(highs.passModel(model) == HighsStatus::kOk); if (dev_run) { printf("\nNow solve the QP\n\n"); incumbent_model.hessian_.print(); } highs.run(); if (dev_run) highs.writeSolution("", kSolutionStylePretty); // Add a new variables and ensure that the Hessian dimension is correct std::vector index = {0}; std::vector value = {1}; REQUIRE(highs.addCol(-1, 0, 1, 1, index.data(), value.data()) == HighsStatus::kOk); REQUIRE((incumbent_model.hessian_.dim_ == 0 || incumbent_model.hessian_.dim_ == incumbent_model.lp_.num_col_)); if (dev_run) { printf("\nNow solve the QP after adding new variable\n\n"); incumbent_model.hessian_.print(); } highs.run(); if (dev_run) highs.writeSolution("", kSolutionStylePretty); HighsInt dim = incumbent_model.lp_.num_col_; std::vector arg0; std::vector arg1; std::vector result0; std::vector result1; arg0.resize(dim); HighsRandom random; for (HighsInt iCol = 0; iCol < dim; iCol++) arg0[iCol] = random.fraction(); HighsHessian hessian0 = incumbent_model.hessian_; arg1 = arg0; // Deleting column 1 removes no nonzeros from the Hessian HighsInt delete_col = 1; REQUIRE(highs.deleteCols(delete_col, delete_col) == HighsStatus::kOk); REQUIRE((incumbent_model.hessian_.dim_ == 0 || incumbent_model.hessian_.dim_ == incumbent_model.lp_.num_col_)); if (dev_run) { printf("\nNow solve the QP after deleting column 1\n\n"); incumbent_model.hessian_.print(); } highs.run(); if (dev_run) highs.writeSolution("", kSolutionStylePretty); dim--; for (HighsInt iCol = delete_col; iCol < dim; iCol++) arg1[iCol] = arg1[iCol + 1]; arg0[delete_col] = 0; hessianProduct(hessian0, arg0, result0); for (HighsInt iCol = delete_col; iCol < dim; iCol++) result0[iCol] = result0[iCol + 1]; arg1.resize(dim); hessianProduct(incumbent_model.hessian_, arg1, result1); for (HighsInt iCol = 0; iCol < dim; iCol++) REQUIRE(result0[iCol] == result1[iCol]); // Deleting column 0 removes only nonzero from the Hessian, so problem is an // LP delete_col = 0; REQUIRE(highs.deleteCols(delete_col, delete_col) == HighsStatus::kOk); REQUIRE((incumbent_model.hessian_.dim_ == 0 || incumbent_model.hessian_.dim_ == incumbent_model.lp_.num_col_)); if (dev_run) { printf("\nNow solve the LP after deleting column 0\n\n"); incumbent_model.hessian_.print(); } highs.run(); if (dev_run) highs.writeSolution("", kSolutionStylePretty); } TEST_CASE("test-qp-delete-col", "[qpsolver]") { HighsModel model; HighsLp& lp = model.lp_; HighsHessian& hessian = model.hessian_; lp.num_col_ = 5; lp.num_row_ = 1; lp.col_cost_ = {-2, -1, 0, 1, 2}; lp.col_lower_ = {0, 0, 0, 0, 0}; lp.col_upper_ = {inf, inf, inf, inf, inf}; lp.sense_ = ObjSense::kMinimize; lp.offset_ = 0; lp.row_lower_ = {-inf}; lp.row_upper_ = {1}; 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_ = {1, 1, 1, 1, 1}; hessian.dim_ = 5; hessian.start_ = {0, 4, 7, 10, 11, 12}; hessian.index_ = {0, 1, 3, 4, 1, 2, 4, 2, 3, 4, 3, 4}; hessian.value_ = {11, 21, 41, 51, 22, 32, 52, 33, 43, 53, 44, 55}; Highs highs; highs.setOptionValue("output_flag", dev_run); const HighsModel& incumbent_model = highs.getModel(); REQUIRE(highs.passModel(model) == HighsStatus::kOk); if (dev_run) incumbent_model.hessian_.print(); HighsInt dim = incumbent_model.lp_.num_col_; std::vector arg0; std::vector arg1; std::vector result0; std::vector result1; arg0.resize(dim); HighsRandom random; for (HighsInt iCol = 0; iCol < dim; iCol++) arg0[iCol] = random.fraction(); HighsHessian hessian0 = incumbent_model.hessian_; arg1 = arg0; std::vector set = {1, 3}; REQUIRE(highs.deleteCols(2, set.data()) == HighsStatus::kOk); if (dev_run) incumbent_model.hessian_.print(); arg1[1] = arg1[2]; arg1[2] = arg1[4]; arg0[1] = 0; arg0[3] = 0; hessianProduct(hessian0, arg0, result0); result0[1] = result0[2]; result0[2] = result0[4]; dim = 3; arg1.resize(dim); hessianProduct(incumbent_model.hessian_, arg1, result1); for (HighsInt iCol = 0; iCol < dim; iCol++) REQUIRE(result0[iCol] == result1[iCol]); dim = 100; lp.clear(); lp.num_col_ = dim; lp.num_row_ = 1; lp.col_cost_.resize(dim); lp.col_lower_.resize(dim); lp.col_upper_.resize(dim); lp.a_matrix_.index_.resize(dim); lp.a_matrix_.value_.resize(dim); for (HighsInt iCol = 0; iCol < dim; iCol++) { lp.col_cost_[iCol] = double(iCol); lp.col_lower_[iCol] = 0; lp.col_upper_[iCol] = inf; lp.a_matrix_.index_[iCol] = iCol; lp.a_matrix_.value_[iCol] = 1; } lp.a_matrix_.start_.push_back(dim); lp.sense_ = ObjSense::kMinimize; lp.offset_ = 0; lp.row_lower_ = {-inf}; lp.row_upper_ = {1}; lp.a_matrix_.format_ = MatrixFormat::kRowwise; hessian.clear(); hessian.dim_ = dim; std::vector hessian_col(dim); for (HighsInt iCol = 0; iCol < dim; iCol++) { hessian_col.assign(dim, 0); HighsInt kmax = std::max(HighsInt(1), (dim - iCol) / 3); hessian_col[iCol] = dim * iCol + iCol; if (iCol < dim - 1) { for (HighsInt k = 0; k < kmax; k++) { HighsInt iRow = iCol + 1 + random.integer(dim - iCol - 1); assert(iRow >= 0); assert(iRow < dim); hessian_col[iRow] = dim * iCol + iRow; } } for (HighsInt iRow = iCol; iRow < dim; iRow++) { if (hessian_col[iRow]) { hessian.index_.push_back(iRow); hessian.value_.push_back(hessian_col[iRow]); } } hessian.start_.push_back(HighsInt(hessian.index_.size())); } REQUIRE(highs.passModel(model) == HighsStatus::kOk); if (dev_run && dim < 20) incumbent_model.hessian_.print(); arg0.resize(dim); for (HighsInt iCol = 0; iCol < dim; iCol++) arg0[iCol] = random.fraction(); hessian0 = incumbent_model.hessian_; arg1 = arg0; std::vector mask; mask.assign(dim, 0); HighsInt kmax = std::max(HighsInt(1), dim / 3); for (HighsInt k = 0; k < kmax; k++) { HighsInt iRow = random.integer(dim); assert(iRow >= 0); assert(iRow < dim); mask[iRow] = 1; } highs.deleteCols(mask.data()); if (dev_run && dim < 20) incumbent_model.hessian_.print(); for (HighsInt iCol = 0; iCol < dim; iCol++) { HighsInt iRow = mask[iCol]; if (iRow < 0) { arg0[iCol] = 0; } else { arg1[iRow] = arg1[iCol]; } } hessianProduct(hessian0, arg0, result0); for (HighsInt iCol = 0; iCol < dim; iCol++) { HighsInt iRow = mask[iCol]; if (iRow >= 0) result0[iRow] = result0[iCol]; } dim = incumbent_model.hessian_.dim_; arg1.resize(dim); hessianProduct(incumbent_model.hessian_, arg1, result1); for (HighsInt iCol = 0; iCol < dim; iCol++) { REQUIRE(result0[iCol] == result1[iCol]); } } TEST_CASE("test-qp-hot-start", "[qpsolver]") { // Test hot start HighsStatus return_status; Highs highs; highs.setOptionValue("output_flag", dev_run); const HighsInfo& info = highs.getInfo(); for (HighsInt k = 0; k < 2; k++) { if (dev_run) printf( "\n" "===================\n" "Hot start test %d\n" "===================\n", int(k)); if (k == 1) { const std::string filename = std::string(HIGHS_DIR) + "/check/instances/primal1.mps"; REQUIRE(highs.readModel(filename) == HighsStatus::kOk); } else if (k == 2) { const std::string filename = std::string(HIGHS_DIR) + "/check/instances/qptestnw.lp"; REQUIRE(highs.readModel(filename) == HighsStatus::kOk); } else { HighsModel model; model.lp_.num_col_ = 2; model.lp_.num_row_ = 1; model.lp_.col_cost_ = {-2, -2}; model.lp_.col_lower_ = {-inf, -inf}; model.lp_.col_upper_ = {inf, inf}; model.lp_.row_lower_ = {1}; model.lp_.row_upper_ = {inf}; model.lp_.a_matrix_.format_ = MatrixFormat::kRowwise; model.lp_.a_matrix_.start_ = {0, 2}; model.lp_.a_matrix_.index_ = {0, 1}; model.lp_.a_matrix_.value_ = {1, 1}; model.hessian_.dim_ = 2; model.hessian_.start_ = {0, 1, 2}; model.hessian_.index_ = {0, 1}; model.hessian_.value_ = {2, 2}; REQUIRE(highs.passModel(model) == HighsStatus::kOk); } return_status = highs.run(); REQUIRE(return_status == HighsStatus::kOk); if (dev_run) highs.writeSolution("", 1); HighsBasis basis = highs.getBasis(); HighsSolution solution = highs.getSolution(); if (dev_run) printf("Saved basis has validity = %d\n", basis.valid); if (dev_run) printf( "================\n" "Hot start re-run\n" "================\n"); return_status = highs.run(); REQUIRE(return_status == HighsStatus::kOk); REQUIRE(info.qp_iteration_count == 0); if (dev_run) printf( "===========================\n" "Hot start using saved basis\n" "===========================\n"); highs.setBasis(basis); return_status = highs.run(); REQUIRE(return_status == HighsStatus::kOk); REQUIRE(info.qp_iteration_count == 0); // QP Hot start needs a saved solution as well as a basis after // clearSolver() if (dev_run) printf( "==============================================================\n" "Hot start using saved basis and solution after clearing solver\n" "==============================================================\n"); highs.clearSolver(); highs.setSolution(solution); highs.setBasis(basis); return_status = highs.run(); REQUIRE(return_status == HighsStatus::kOk); REQUIRE(info.qp_iteration_count == 0); /* if (dev_run) printf("=================================================\n" "Hot start using saved basis after clearing solver\n" "=================================================\n"); highs.clearSolver(); highs.setBasis(basis); return_status = highs.run(); REQUIRE(return_status == HighsStatus::kOk); REQUIRE(info.qp_iteration_count == 0); */ // QP Hot start needs a saved solution as well as a basis after // clearSolver() if (dev_run) printf( "==============================================================\n" "Hot start using alien basis and solution after clearing solver\n" "==============================================================\n"); highs.clearSolver(); highs.setSolution(solution); basis.alien = true; highs.setBasis(basis); return_status = highs.run(); REQUIRE(return_status == HighsStatus::kOk); REQUIRE(info.qp_iteration_count == 0); } } TEST_CASE("test-qp-terminations", "[qpsolver]") { Highs highs; highs.setOptionValue("output_flag", dev_run); const HighsInfo& info = highs.getInfo(); std::string filename = std::string(HIGHS_DIR) + "/check/instances/qptestnw.lp"; REQUIRE(highs.readModel(filename) == HighsStatus::kOk); highs.setOptionValue("qp_iteration_limit", 1); REQUIRE(highs.run() == HighsStatus::kWarning); REQUIRE(highs.getModelStatus() == HighsModelStatus::kIterationLimit); highs.clearSolver(); highs.setOptionValue("qp_iteration_limit", kHighsIInf); highs.setOptionValue("time_limit", 0); REQUIRE(highs.run() == HighsStatus::kWarning); REQUIRE(highs.getModelStatus() == HighsModelStatus::kTimeLimit); highs.setOptionValue("time_limit", kHighsInf); filename = std::string(HIGHS_DIR) + "/check/instances/primal1.mps"; REQUIRE(highs.readModel(filename) == HighsStatus::kOk); highs.setOptionValue("qp_nullspace_limit", 1); REQUIRE(highs.run() == HighsStatus::kError); REQUIRE(highs.getModelStatus() == HighsModelStatus::kSolveError); highs.setOptionValue("qp_nullspace_limit", 4000); }