From 43c5d631349f66f3b2a2d4bbe797dc42545aa73b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Li?= Date: Wed, 10 Jul 2024 15:14:12 +0200 Subject: [PATCH] [LL] ExtractNewVariables and ExtractNewConstraints modif : bloc -> unit --- ortools/linear_solver/knitro_interface.cc | 140 ++++------- .../linear_solver/knitro_interface_test.cc | 221 +++++++++++++++--- 2 files changed, 232 insertions(+), 129 deletions(-) diff --git a/ortools/linear_solver/knitro_interface.cc b/ortools/linear_solver/knitro_interface.cc index 9aae28c1ca1..2bf097f7001 100644 --- a/ortools/linear_solver/knitro_interface.cc +++ b/ortools/linear_solver/knitro_interface.cc @@ -680,58 +680,36 @@ void KnitroInterface::AddVariable(MPVariable* var) { } void KnitroInterface::ExtractNewVariables() { - // std::cout << "Extracting Variables " << std::endl; int const total_num_vars = solver_->variables_.size(); if (total_num_vars > last_variable_index_) { int const len = total_num_vars - last_variable_index_; - // for init and def basic properties - std::unique_ptr idx_vars(new int[len]); - std::unique_ptr lb(new double[len]); - std::unique_ptr ub(new double[len]); - std::unique_ptr types(new int[len]); - // lambda fn to destroy the array of names - auto deleter = [len](char** ptr) { - for (int i = 0; i < len; ++i) { - delete[] ptr[i]; - } - delete[] ptr; - }; - std::unique_ptr names(new char*[len], deleter); - // for priority properties - std::unique_ptr prior(new int[len]); - std::unique_ptr prior_idx(new int[len]); - int prior_nb = 0; - // std::cout << "Variables' Containers created" << std::endl; - // Define new variables - for (int j = 0, var_index = last_variable_index_; j < len; - ++j, ++var_index) { + // Create new variables + CHECK_STATUS(KN_add_vars(kc_, len, NULL)); + for (int var_index = last_variable_index_; var_index < total_num_vars; + ++var_index) { MPVariable* const var = solver_->variables_[var_index]; DCHECK(!variable_is_extracted(var_index)); set_variable_as_extracted(var_index, true); - idx_vars[j] = var_index; - lb[j] = redefine_infinity_double(var->lb()); - ub[j] = redefine_infinity_double(var->ub()); - // Def buffer size at 256 for variables' name - names[j] = new char[256]; - strcpy(names[j], var->name().c_str()); - types[j] = - (mip_ && var->integer()) ? KN_VARTYPE_INTEGER : KN_VARTYPE_CONTINUOUS; + // Defines the bounds and type of var + CHECK_STATUS(KN_set_var_lobnd(kc_, var_index, + redefine_infinity_double(var->lb()))); + CHECK_STATUS(KN_set_var_upbnd(kc_, var_index, + redefine_infinity_double(var->ub()))); + CHECK_STATUS(KN_set_var_type(kc_, var_index, + (mip_ && var->integer()) + ? KN_VARTYPE_INTEGER + : KN_VARTYPE_CONTINUOUS)); + // Creates char * value to name the var + std::unique_ptr name(new char[256]); + strcpy(name.get(), var->name().c_str()); + CHECK_STATUS(KN_set_var_name(kc_, var_index, name.get())); + // Branching priority if (var->integer() && (var->branching_priority() != 0)) { - prior_idx[prior_nb] = var_index; - prior[prior_nb] = var->branching_priority(); - prior_nb++; + KN_set_mip_branching_priority(kc_, var_index, + var->branching_priority()); } } - // std::cout << "Variables' Containers filled" << std::endl; - CHECK_STATUS(KN_add_vars(kc_, len, NULL)); - CHECK_STATUS(KN_set_var_lobnds(kc_, len, idx_vars.get(), lb.get())); - CHECK_STATUS(KN_set_var_upbnds(kc_, len, idx_vars.get(), ub.get())); - CHECK_STATUS(KN_set_var_types(kc_, len, idx_vars.get(), types.get())); - // CHECK_STATUS(KN_set_var_names(kc_, len, idx_vars.get(), names.get())); - CHECK_STATUS(KN_set_mip_branching_priorities(kc_, prior_nb, prior_idx.get(), - prior.get())); - // std::cout << "Variables added to the Knitro Context" << std::endl; - // Add new variables to existing constraints. + // Adds new variables to existing constraints. for (int i = 0; i < last_constraint_index_; i++) { MPConstraint* const ct = solver_->constraints_[i]; for (const auto& entry : ct->coefficients_) { @@ -745,77 +723,51 @@ void KnitroInterface::ExtractNewVariables() { } } } - // std::cout << "Extracting Variables End" << std::endl; } void KnitroInterface::ExtractNewConstraints() { - // std::cout << "Extracting Constraints " << std::endl; int const total_num_cons = solver_->constraints_.size(); int const total_num_vars = solver_->variables_.size(); - // std::cout << "Sizes extracted" << std::endl; if (total_num_cons > last_constraint_index_) { int const len = total_num_cons - last_constraint_index_; - std::unique_ptr idx_cons(new int[len]); - std::unique_ptr lb(new double[len]); - std::unique_ptr ub(new double[len]); - std::unique_ptr lin_idx_cons(new int[len * total_num_vars]); - std::unique_ptr lin_idx_vars(new int[len * total_num_vars]); - std::unique_ptr lin_coefs(new double[len * total_num_vars]); - // std::cout << "Creating Char deleter lambda exp" << std::endl; - // lambda fn to destroy the array of names - auto deleter = [len](char** ptr) { - for (int i = 0; i < len; ++i) { - delete[] ptr[i]; - } - delete[] ptr; - }; - std::unique_ptr names(new char*[len], deleter); - // std::cout << "Constraints' Containers created" << std::endl; - int idx_lin_term = 0; - // Define new constraints - for (int j = 0, con_index = last_constraint_index_; j < len; - ++j, ++con_index) { + // Create new constraints + CHECK_STATUS(KN_add_cons(kc_, len, NULL)); + // Counts the number of non zero linear term in case of update Knitro model + int nb_lin_term = 0; + for (int con_index = last_constraint_index_; con_index < total_num_cons; + ++con_index) { MPConstraint* const ct = solver_->constraints_[con_index]; DCHECK(!constraint_is_extracted(con_index)); set_constraint_as_extracted(con_index, true); - idx_cons[j] = con_index; - lb[j] = redefine_infinity_double(ct->lb()); - ub[j] = redefine_infinity_double(ct->ub()); - for (int i = 0; i < total_num_vars; ++i) { - lin_idx_cons[idx_lin_term] = con_index; - lin_idx_vars[idx_lin_term] = i; - lin_coefs[idx_lin_term] = ct->GetCoefficient(solver_->variables_[i]); - idx_lin_term++; - } - // Def buffer size at 256 for variables' name - names[j] = new char[256]; - strcpy(names[j], ct->name().c_str()); - } - // std::cout << "Constraints' Containers filled" << std::endl; - CHECK_STATUS(KN_add_cons(kc_, len, NULL)); - CHECK_STATUS(KN_set_con_lobnds(kc_, len, idx_cons.get(), lb.get())); - CHECK_STATUS(KN_set_con_upbnds(kc_, len, idx_cons.get(), ub.get())); - - // CHECK_STATUS(KN_set_con_names(kc_, len, idx_cons.get(), names.get())); - if (idx_lin_term) { + // Define the bounds of the constraint CHECK_STATUS( - KN_add_con_linear_struct(kc_, idx_lin_term, lin_idx_cons.get(), - lin_idx_vars.get(), lin_coefs.get())); - KN_update(kc_); + KN_set_con_lobnd(kc_, con_index, redefine_infinity_double(ct->lb()))); + CHECK_STATUS( + KN_set_con_upbnd(kc_, con_index, redefine_infinity_double(ct->ub()))); + // Add linear term one by one + for (int i = 0; i < total_num_vars; i++) { + double value = ct->GetCoefficient(solver_->variables_[i]); + if (value) { + CHECK_STATUS(KN_add_con_linear_term(kc_, con_index, i, value)); + nb_lin_term++; + } + } + // Creates char* to name the constraint + std::unique_ptr name(new char[256]); + strcpy(name.get(), ct->name().c_str()); + CHECK_STATUS(KN_set_con_name(kc_, con_index, name.get())); } - // std::cout << "Constraints added to the Knitro Context" << std::endl; + // if a new linear term is added, the Knitro model must be updated + if (nb_lin_term) CHECK_STATUS(KN_update(kc_)); } - // std::cout << "Extracting Constraints End" << std::endl; } void KnitroInterface::ExtractObjective() { - // std::cout << "Extracting Objective Function " << std::endl; int const len = solver_->variables_.size(); if (len) { std::unique_ptr ind(new int[len]); std::unique_ptr val(new double[len]); - // std::cout << "Objective's Containers created" << std::endl; for (int j = 0; j < len; ++j) { ind[j] = j; val[j] = 0.0; @@ -830,7 +782,6 @@ void KnitroInterface::ExtractObjective() { val[idx] = coeff.second; } } - // std::cout << "Objective's Containers filled" << std::endl; // if a init solve occured, remove prev coef to add the new ones if (!no_obj_) { CHECK_STATUS(KN_chg_obj_linear_struct(kc_, len, ind.get(), val.get())); @@ -843,7 +794,6 @@ void KnitroInterface::ExtractObjective() { CHECK_STATUS(KN_update(kc_)); no_obj_ = false; } - // std::cout << "Objective added into the Knitro Context" << std::endl; // Extra check on the optimization direction SetOptimizationDirection(maximize_); diff --git a/ortools/linear_solver/knitro_interface_test.cc b/ortools/linear_solver/knitro_interface_test.cc index 2f64c274e47..797e2d72700 100644 --- a/ortools/linear_solver/knitro_interface_test.cc +++ b/ortools/linear_solver/knitro_interface_test.cc @@ -1,5 +1,6 @@ // ADD HEADER #include +#include #include #include @@ -488,16 +489,10 @@ TEST(KnitroInterface, AddSolutionHintToOptimizer) { /** Unit Test of the method SetSolverSpecificParametersAsString()*/ TEST(KnitroInterface, SetSolverSpecificParametersAsString) { UNITTEST_INIT_MIP(); - std::ofstream param_file; - param_file.open("knitro_interface__test_param.opt"); - param_file << "feastol 1e-08\n"; - param_file << "linsolver_scaling always"; - param_file.close(); EXPECT_TRUE(solver.SetSolverSpecificParametersAsString("")); - EXPECT_FALSE( - solver.SetSolverSpecificParametersAsString("not_a_param_file.opt")); + EXPECT_FALSE(solver.SetSolverSpecificParametersAsString("blabla 5 ")); solver.SetSolverSpecificParametersAsString( - "knitro_interface__test_param.opt"); + "KN_PARAM_FEASTOL 1e-8 KN_PARAM_LINSOLVER_SCALING 1 "); solver.Solve(); int value; getter.Int_Param(KN_PARAM_LINSOLVER_SCALING, &value); @@ -876,12 +871,7 @@ TEST(KnitroInterface, FeasibleSolLP) { objective->SetMaximization(); // setting max iter limit - std::ofstream param_file; - param_file.open("knitro_interface_feasible_test.opt"); - param_file << "maxit 4\n"; - param_file.close(); - solver.SetSolverSpecificParametersAsString( - "knitro_interface_feasible_test.opt"); + solver.SetSolverSpecificParametersAsString("KN_PARAM_MAXIT 4 "); const MPSolver::ResultStatus result_status = solver.Solve(); EXPECT_EQ(result_status, MPSolver::ResultStatus::FEASIBLE); @@ -1061,12 +1051,7 @@ TEST(KnitroInterface, BranchingPriorityChangedForVariable) { obj->SetCoefficient(y, .86); obj->SetMaximization(); - std::ofstream param_file; - param_file.open("knitro_interface_bpcfv.opt"); - param_file << "act_lpalg 1\n"; - param_file << "mip_lpalg 3\n"; - param_file.close(); - solver.SetSolverSpecificParametersAsString("knitro_interface_bpcfv.opt"); + solver.SetSolverSpecificParametersAsString("KN_PARAM_ACT_LPALG 1 KN_PARAM_MIP_LPALG 3 "); // Prioritize branching on x x->SetBranchingPriority(10); @@ -1392,6 +1377,184 @@ TEST(KnitroInterface, AddVarToExistingConstraint) { EXPECT_NEAR(y->solution_value(), 1, ERROR_RATE); } +/** + * Since GLOP do not implement Write method + * + */ +void write_readible_problem_model(MPSolver& solver, char* file_name) { + FILE* file; + file = fopen(file_name, "w"); + + // writing variables + int nb_var = solver.NumVariables(); + fprintf(file, "nb variables : %i\n", nb_var); + for (int i = 0; i < nb_var; i++) { + fprintf(file, "%4.0f %4.0f\n", solver.variable(i)->ub(), + solver.variable(i)->lb()); + } + fprintf(file, "\n"); + + // writing A matrix + int nb_con = solver.NumConstraints(); + fprintf(file, "A : %i x %i\n", nb_con, nb_var); + for (int j = 0; j < nb_con; j++) { + MPConstraint* ct = solver.constraint(j); + for (int i = 0; i < nb_var; i++) { + fprintf(file, "%3.0f ", ct->GetCoefficient(solver.variable(i))); + } + fprintf(file, "\n"); + } + fprintf(file, "\n"); + + // writing cT vector + MPObjective* const obj = solver.MutableObjective(); + for (int i = 0; i < nb_var; i++) + fprintf(file, "%3.0f ", obj->GetCoefficient(solver.variable(i))); + + fclose(file); +} + +/** + * Solve a random generated LP + * max cT.x + * st. Ax <= b + * u <= x <= v + * With + * x \in R^n + * Matrix A a randomly generated invertible lower triangular matrix + * u and v the lower and upper bound of x respectively + */ +TEST(KnitroInterface, RandomLP) { + srand(time(NULL)); + + // Create a LP problem with Knitro solver + UNITTEST_INIT_LP(); + double infinity = solver.infinity(); + const int nb_var = 100; + std::vector vars; + for (int i = 0; i < nb_var; i++) + vars.push_back(solver.MakeNumVar(-rand() % 1000, rand() % 1000, + "x_" + std::to_string(i))); + std::vector cons; + for (int j = 0; j < nb_var; j++) { + cons.push_back(solver.MakeRowConstraint(-infinity, rand() % 2001 - 1000, + "c_" + std::to_string(j))); + for (int i = 0; i <= j; i++) { + cons.back()->SetCoefficient( + vars[i], (i == j) ? rand() % 100 + 1 : rand() % 199 - 99); + } + } + MPObjective* const objective = solver.MutableObjective(); + for (int i = 0; i < nb_var; i++) { + objective->SetCoefficient(vars[i], rand() % 199 - 99); + } + objective->SetMaximization(); + time_t start_time; + time(&start_time); + MPSolver::ResultStatus kc_status = solver.Solve(); + printf("KNITRO solving time = %ld\n", time(NULL) - start_time); + write_readible_problem_model(solver, "lp_problem_knitro.mps"); + + // Create the same problem with GLOP solver + MPSolver solverbis("GLOP_LP", MPSolver::GLOP_LINEAR_PROGRAMMING); + for (int i = 0; i < nb_var; i++) + solverbis.MakeNumVar(vars[i]->lb(), vars[i]->ub(), vars[i]->name()); + for (int j = 0; j < nb_var; j++) { + MPConstraint* ct = solverbis.MakeRowConstraint(cons[j]->lb(), cons[j]->ub(), + cons[j]->name()); + for (int i = 0; i <= j; i++) { + ct->SetCoefficient(solverbis.variable(i), + cons[j]->GetCoefficient(vars[i])); + } + } + MPObjective* const objectivebis = solverbis.MutableObjective(); + for (int i = 0; i < nb_var; i++) { + objectivebis->SetCoefficient(solverbis.variable(i), + objective->GetCoefficient(vars[i])); + } + objectivebis->SetMaximization(); + time(&start_time); + MPSolver::ResultStatus glop_status = solverbis.Solve(); + printf("GLOP solving time = %ld\n", time(NULL) - start_time); + write_readible_problem_model(solverbis, "lp_problem_glop.mps"); + + EXPECT_EQ(kc_status, glop_status); + if (glop_status == MPSolver::ResultStatus::OPTIMAL) { + EXPECT_NEAR(objective->Value(), objectivebis->Value(), 1e-3); + } +} + +/** + * Solve a random generated MIP + * max cT.x + * st. Ax <= b + * u <= x <= v + * With + * x \in Z^n + * Matrix A a randomly generated invertible lower triangular matrix + * u and v the lower and upper bound of x respectively + */ +TEST(KnitroInterface, RandomMIP) { + srand(time(NULL)); + + // Create a LP problem with Knitro solver + UNITTEST_INIT_MIP(); + + double infinity = solver.infinity(); + const int nb_var = 30; + std::vector vars; + for (int i = 0; i < nb_var; i++) + vars.push_back(solver.MakeIntVar(-rand() % 1000, rand() % 1000, + "x_" + std::to_string(i))); + std::vector cons; + for (int j = 0; j < nb_var; j++) { + cons.push_back(solver.MakeRowConstraint(-infinity, rand() % 2001 - 1000, + "c_" + std::to_string(j))); + for (int i = 0; i <= j; i++) { + cons.back()->SetCoefficient( + vars[i], (i == j) ? rand() % 100 + 1 : rand() % 199 - 99); + } + } + MPObjective* const objective = solver.MutableObjective(); + for (int i = 0; i < nb_var; i++) { + objective->SetCoefficient(vars[i], rand() % 199 - 99); + } + objective->SetMaximization(); + time_t start_time; + time(&start_time); + MPSolver::ResultStatus kc_status = solver.Solve(); + printf("KNITRO solving time = %ld\n", time(NULL) - start_time); + write_readible_problem_model(solver, "mip_problem_knitro.mps"); + + // Create the same problem with SCIP solver + MPSolver solverbis("SCIP_MIP", MPSolver::SCIP_MIXED_INTEGER_PROGRAMMING); + for (int i = 0; i < nb_var; i++) + solverbis.MakeIntVar(vars[i]->lb(), vars[i]->ub(), vars[i]->name()); + for (int j = 0; j < nb_var; j++) { + MPConstraint* ct = solverbis.MakeRowConstraint(cons[j]->lb(), cons[j]->ub(), + cons[j]->name()); + for (int i = 0; i <= j; i++) { + ct->SetCoefficient(solverbis.variable(i), + cons[j]->GetCoefficient(vars[i])); + } + } + MPObjective* const objectivebis = solverbis.MutableObjective(); + for (int i = 0; i < nb_var; i++) { + objectivebis->SetCoefficient(solverbis.variable(i), + objective->GetCoefficient(vars[i])); + } + objectivebis->SetMaximization(); + time(&start_time); + MPSolver::ResultStatus scip_status = solverbis.Solve(); + printf("SCIP solving time = %ld\n", time(NULL) - start_time); + write_readible_problem_model(solverbis, "mip_problem_scip.mps"); + + EXPECT_EQ(kc_status, scip_status); + if (scip_status == MPSolver::ResultStatus::OPTIMAL) { + EXPECT_NEAR(objective->Value(), objectivebis->Value(), 1e-3); + } +} + /*-------------------- Callback --------------------*/ /** @@ -1709,16 +1872,6 @@ TEST(KnitroInterface, LazyConstraint) { // Solve // /////////// - // std::ofstream param_file; - // param_file.open("knitro_interface_lazy_constraint_callback_settings.opt"); - // // add the following line to allow debug printf display in KNMPCallback - // // param_file << "mip_numthreads 1\n"; - // // add the following line to allow summary display of Knitro solver - // param_file << "outlev 1\n"; - // param_file.close(); - // solver.SetSolverSpecificParametersAsString("knitro_interface_lazy_constraint_callback_settings.opt"); - - // Solve. solver.Solve(); /////////////////////////////// @@ -1751,11 +1904,11 @@ int main(int argc, char** argv) { } else { if (!RUN_ALL_TESTS()) { remove("knitro_interface_test_LP_model"); - remove("knitro_interface__test_param.opt"); - remove("knitro_interface_bpcfv.opt"); - remove("knitro_interface_feasible_test.opt"); remove("knitro_interface_test_empty"); - remove("knitro_interface_lazy_constraint_callback_settings.opt"); + remove("lp_problem_knitro.mps"); + remove("lp_problem_glop.mps"); + remove("mip_problem_knitro.mps"); + remove("mip_problem_scip.mps"); return EXIT_SUCCESS; } return EXIT_FAILURE;