From 69dc22f35d3b98754ca61fd419c72694aeca7a55 Mon Sep 17 00:00:00 2001 From: Guillaume Chatelet Date: Fri, 12 Dec 2025 09:33:50 +0100 Subject: [PATCH] Update linear solver build (#4945) --- examples/cpp/integer_programming.cc | 68 +++--- examples/cpp/linear_programming.cc | 64 +++-- ...trawberry_fields_with_column_generation.cc | 23 +- ortools/algorithms/BUILD.bazel | 33 +-- ortools/algorithms/knapsack_solver.cc | 11 +- ortools/algorithms/knapsack_solver.h | 10 +- ortools/algorithms/python/BUILD.bazel | 29 --- ortools/algorithms/python/knapsack_solver.cc | 4 - ortools/linear_solver/BUILD.bazel | 77 +++--- ortools/linear_solver/CMakeLists.txt | 70 +++++- ortools/linear_solver/bop_interface.cc | 23 +- ortools/linear_solver/cbc_interface.cc | 19 +- ortools/linear_solver/clp_interface.cc | 17 +- ortools/linear_solver/cplex_interface.cc | 30 ++- ortools/linear_solver/glop_interface.cc | 20 +- ortools/linear_solver/glpk_interface.cc | 25 +- ortools/linear_solver/gurobi_interface.cc | 24 +- ortools/linear_solver/highs_interface.cc | 26 ++- ortools/linear_solver/knapsack_interface.cc | 16 +- ortools/linear_solver/linear_solver.cc | 221 +++++++----------- ortools/linear_solver/linear_solver.h | 89 ++++++- ortools/linear_solver/pdlp_interface.cc | 20 +- .../linear_solver/proto_solver/CMakeLists.txt | 56 +++-- .../proto_solver/scip_proto_solver.cc | 4 - ortools/linear_solver/sat_interface.cc | 17 +- ortools/linear_solver/scip_callback.cc | 5 +- ortools/linear_solver/scip_interface.cc | 17 +- ortools/linear_solver/wrappers/CMakeLists.txt | 6 +- ortools/linear_solver/xpress_interface.cc | 26 ++- ortools/pdlp/CMakeLists.txt | 34 ++- 30 files changed, 595 insertions(+), 489 deletions(-) diff --git a/examples/cpp/integer_programming.cc b/examples/cpp/integer_programming.cc index 3b36bdbdcd..f94cdf738c 100644 --- a/examples/cpp/integer_programming.cc +++ b/examples/cpp/integer_programming.cc @@ -14,6 +14,7 @@ // Integer programming example that shows how to use the API. #include +#include #include #include @@ -26,47 +27,40 @@ #include "ortools/linear_solver/linear_solver.h" namespace operations_research { -void RunIntegerProgrammingExample(absl::string_view solver_id) { +void RunIntegerProgrammingExample(const std::string& solver_id) { LOG(INFO) << "---- Integer programming example with " << solver_id << " ----"; - MPSolver::OptimizationProblemType problem_type; - if (!MPSolver::ParseSolverType(solver_id, &problem_type)) { - LOG(INFO) << "Solver id " << solver_id << " not recognized"; + std::unique_ptr solver(MPSolver::CreateSolver(solver_id)); + if (!solver) { + LOG(INFO) << "Unable to create solver : " << solver_id; return; } - if (!MPSolver::SupportsProblemType(problem_type)) { - LOG(INFO) << "Supports for solver " << solver_id << " not linked in."; - return; - } - - MPSolver solver("IntegerProgrammingExample", problem_type); - - const double infinity = solver.infinity(); + const double infinity = solver->infinity(); // x and y are integer non-negative variables. - MPVariable* const x = solver.MakeIntVar(0.0, infinity, "x"); - MPVariable* const y = solver.MakeIntVar(0.0, infinity, "y"); + MPVariable* const x = solver->MakeIntVar(0.0, infinity, "x"); + MPVariable* const y = solver->MakeIntVar(0.0, infinity, "y"); // Maximize x + 10 * y. - MPObjective* const objective = solver.MutableObjective(); + MPObjective* const objective = solver->MutableObjective(); objective->SetCoefficient(x, 1); objective->SetCoefficient(y, 10); objective->SetMaximization(); // x + 7 * y <= 17.5. - MPConstraint* const c0 = solver.MakeRowConstraint(-infinity, 17.5); + MPConstraint* const c0 = solver->MakeRowConstraint(-infinity, 17.5); c0->SetCoefficient(x, 1); c0->SetCoefficient(y, 7); // x <= 3.5 - MPConstraint* const c1 = solver.MakeRowConstraint(-infinity, 3.5); + MPConstraint* const c1 = solver->MakeRowConstraint(-infinity, 3.5); c1->SetCoefficient(x, 1); c1->SetCoefficient(y, 0); - LOG(INFO) << "Number of variables = " << solver.NumVariables(); - LOG(INFO) << "Number of constraints = " << solver.NumConstraints(); + LOG(INFO) << "Number of variables = " << solver->NumVariables(); + LOG(INFO) << "Number of constraints = " << solver->NumConstraints(); - const MPSolver::ResultStatus result_status = solver.Solve(); + const MPSolver::ResultStatus result_status = solver->Solve(); // Check that the problem has an optimal solution. if (result_status != MPSolver::OPTIMAL) { LOG(FATAL) << "The problem does not have an optimal solution!"; @@ -77,21 +71,33 @@ void RunIntegerProgrammingExample(absl::string_view solver_id) { LOG(INFO) << "Optimal objective value = " << objective->Value(); LOG(INFO) << ""; LOG(INFO) << "Advanced usage:"; - LOG(INFO) << "Problem solved in " << solver.wall_time() << " milliseconds"; - LOG(INFO) << "Problem solved in " << solver.iterations() << " iterations"; - LOG(INFO) << "Problem solved in " << solver.nodes() + LOG(INFO) << "Problem solved in " << solver->wall_time() << " milliseconds"; + LOG(INFO) << "Problem solved in " << solver->iterations() << " iterations"; + LOG(INFO) << "Problem solved in " << solver->nodes() << " branch-and-bound nodes"; } void RunAllExamples() { - RunIntegerProgrammingExample("CBC"); - RunIntegerProgrammingExample("SAT"); - RunIntegerProgrammingExample("SCIP"); - RunIntegerProgrammingExample("GUROBI"); - RunIntegerProgrammingExample("GLPK"); - RunIntegerProgrammingExample("CPLEX"); - RunIntegerProgrammingExample("XPRESS"); - RunIntegerProgrammingExample("HIGHS"); + std::vector supported_problem_types = + MPSolverInterfaceFactoryRepository::GetInstance() + ->ListAllRegisteredProblemTypes(); + for (MPSolver::OptimizationProblemType type : supported_problem_types) { + const std::string type_name = MPModelRequest::SolverType_Name( + static_cast(type)); + if (!SolverTypeIsMip(type)) continue; + if (absl::StrContains(type_name, "KNAPSACK")) continue; + if (absl::StrContains(type_name, "BOP")) continue; + if (absl::StrContains(type_name, "HIGHS")) continue; +// ASAN issues a warning in CBC code which cannot be avoided for now: +// AddressSanitizer: float-cast-overflow +// third_party/cbc/Cgl/src/CglPreProcess/CglPreProcess.cpp:1717:36 +#ifdef ADDRESS_SANITIZER + if (type_name.find("CBC") != std::string::npos) { + continue; + } +#endif + RunIntegerProgrammingExample(type_name); + } } } // namespace operations_research diff --git a/examples/cpp/linear_programming.cc b/examples/cpp/linear_programming.cc index c1962f8df1..3c676ce7e3 100644 --- a/examples/cpp/linear_programming.cc +++ b/examples/cpp/linear_programming.cc @@ -14,6 +14,7 @@ // Linear programming example that shows how to use the API. #include +#include #include #include @@ -22,71 +23,64 @@ #include "absl/log/log.h" #include "absl/strings/match.h" #include "absl/strings/string_view.h" -#include "ortools/base/commandlineflags.h" #include "ortools/base/init_google.h" #include "ortools/linear_solver/linear_solver.h" #include "ortools/linear_solver/linear_solver.pb.h" namespace operations_research { -void RunLinearProgrammingExample(absl::string_view solver_id) { +void RunLinearProgrammingExample(const std::string& solver_id) { LOG(INFO) << "---- Linear programming example with " << solver_id << " ----"; - MPSolver::OptimizationProblemType problem_type; - if (!MPSolver::ParseSolverType(solver_id, &problem_type)) { - LOG(INFO) << "Solver id " << solver_id << " not recognized"; + + std::unique_ptr solver(MPSolver::CreateSolver(solver_id)); + if (!solver) { + LOG(INFO) << "Unable to create solver : " << solver_id; return; } - if (!MPSolver::SupportsProblemType(problem_type)) { - LOG(INFO) << "Supports for solver " << solver_id << " not linked in."; - return; - } - - MPSolver solver("IntegerProgrammingExample", problem_type); - - const double infinity = solver.infinity(); + const double infinity = solver->infinity(); // x1, x2 and x3 are continuous non-negative variables. - MPVariable* const x1 = solver.MakeNumVar(0.0, infinity, "x1"); - MPVariable* const x2 = solver.MakeNumVar(0.0, infinity, "x2"); - MPVariable* const x3 = solver.MakeNumVar(0.0, infinity, "x3"); + MPVariable* const x1 = solver->MakeNumVar(0.0, infinity, "x1"); + MPVariable* const x2 = solver->MakeNumVar(0.0, infinity, "x2"); + MPVariable* const x3 = solver->MakeNumVar(0.0, infinity, "x3"); // Maximize 10 * x1 + 6 * x2 + 4 * x3. - MPObjective* const objective = solver.MutableObjective(); + MPObjective* const objective = solver->MutableObjective(); objective->SetCoefficient(x1, 10); objective->SetCoefficient(x2, 6); objective->SetCoefficient(x3, 4); objective->SetMaximization(); // x1 + x2 + x3 <= 100. - MPConstraint* const c0 = solver.MakeRowConstraint(-infinity, 100.0); + MPConstraint* const c0 = solver->MakeRowConstraint(-infinity, 100.0); c0->SetCoefficient(x1, 1); c0->SetCoefficient(x2, 1); c0->SetCoefficient(x3, 1); // 10 * x1 + 4 * x2 + 5 * x3 <= 600. - MPConstraint* const c1 = solver.MakeRowConstraint(-infinity, 600.0); + MPConstraint* const c1 = solver->MakeRowConstraint(-infinity, 600.0); c1->SetCoefficient(x1, 10); c1->SetCoefficient(x2, 4); c1->SetCoefficient(x3, 5); // 2 * x1 + 2 * x2 + 6 * x3 <= 300. - MPConstraint* const c2 = solver.MakeRowConstraint(-infinity, 300.0); + MPConstraint* const c2 = solver->MakeRowConstraint(-infinity, 300.0); c2->SetCoefficient(x1, 2); c2->SetCoefficient(x2, 2); c2->SetCoefficient(x3, 6); // TODO(user): Change example to show = and >= constraints. - LOG(INFO) << "Number of variables = " << solver.NumVariables(); - LOG(INFO) << "Number of constraints = " << solver.NumConstraints(); + LOG(INFO) << "Number of variables = " << solver->NumVariables(); + LOG(INFO) << "Number of constraints = " << solver->NumConstraints(); - const MPSolver::ResultStatus result_status = solver.Solve(); + const MPSolver::ResultStatus result_status = solver->Solve(); // Check that the problem has an optimal solution. if (result_status != MPSolver::OPTIMAL) { LOG(FATAL) << "The problem does not have an optimal solution!"; } - LOG(INFO) << "Problem solved in " << solver.wall_time() << " milliseconds"; + LOG(INFO) << "Problem solved in " << solver->wall_time() << " milliseconds"; // The objective value of the solution. LOG(INFO) << "Optimal objective value = " << objective->Value(); @@ -97,11 +91,11 @@ void RunLinearProgrammingExample(absl::string_view solver_id) { LOG(INFO) << "x3 = " << x3->solution_value(); LOG(INFO) << "Advanced usage:"; - LOG(INFO) << "Problem solved in " << solver.iterations() << " iterations"; + LOG(INFO) << "Problem solved in " << solver->iterations() << " iterations"; LOG(INFO) << "x1: reduced cost = " << x1->reduced_cost(); LOG(INFO) << "x2: reduced cost = " << x2->reduced_cost(); LOG(INFO) << "x3: reduced cost = " << x3->reduced_cost(); - const std::vector activities = solver.ComputeConstraintActivities(); + const std::vector activities = solver->ComputeConstraintActivities(); LOG(INFO) << "c0: dual value = " << c0->dual_value() << " activity = " << activities[c0->index()]; LOG(INFO) << "c1: dual value = " << c1->dual_value() @@ -111,14 +105,16 @@ void RunLinearProgrammingExample(absl::string_view solver_id) { } void RunAllExamples() { - RunLinearProgrammingExample("GLOP"); - RunLinearProgrammingExample("CLP"); - RunLinearProgrammingExample("GUROBI_LP"); - RunLinearProgrammingExample("CPLEX_LP"); - RunLinearProgrammingExample("GLPK_LP"); - RunLinearProgrammingExample("XPRESS_LP"); - RunLinearProgrammingExample("PDLP"); - RunLinearProgrammingExample("HIGHS_LP"); + std::vector supported_problem_types = + MPSolverInterfaceFactoryRepository::GetInstance() + ->ListAllRegisteredProblemTypes(); + for (MPSolver::OptimizationProblemType type : supported_problem_types) { + const std::string type_name = MPModelRequest::SolverType_Name( + static_cast(type)); + if (!absl::StrContains(type_name, "LINEAR_PROGRAMMING")) continue; + if (absl::StrContains(type_name, "HIGHS")) continue; + RunLinearProgrammingExample(type_name); + } } } // namespace operations_research diff --git a/examples/cpp/strawberry_fields_with_column_generation.cc b/examples/cpp/strawberry_fields_with_column_generation.cc index 4e9a995320..62e70195a1 100644 --- a/examples/cpp/strawberry_fields_with_column_generation.cc +++ b/examples/cpp/strawberry_fields_with_column_generation.cc @@ -610,36 +610,29 @@ int main(int argc, char** argv) { operations_research::MPSolver::OptimizationProblemType solver_type; bool found = false; -#if defined(USE_CLP) - if (absl::GetFlag(FLAGS_colgen_solver) == "clp") { + const std::string solver_name = absl::GetFlag(FLAGS_colgen_solver); + if (solver_name == "clp") { solver_type = operations_research::MPSolver::CLP_LINEAR_PROGRAMMING; found = true; } -#endif // USE_CLP - if (absl::GetFlag(FLAGS_colgen_solver) == "glop") { + if (solver_name == "glop") { solver_type = operations_research::MPSolver::GLOP_LINEAR_PROGRAMMING; found = true; } -#if defined(USE_XPRESS) - if (absl::GetFlag(FLAGS_colgen_solver) == "xpress") { + if (solver_name == "xpress") { solver_type = operations_research::MPSolver::XPRESS_LINEAR_PROGRAMMING; - // solver_type = operations_research::MPSolver::CPLEX_LINEAR_PROGRAMMING; found = true; } -#endif -#if defined(USE_CPLEX) - if (absl::GetFlag(FLAGS_colgen_solver) == "cplex") { + if (solver_name == "cplex") { solver_type = operations_research::MPSolver::CPLEX_LINEAR_PROGRAMMING; found = true; } -#endif if (!found) { - LOG(ERROR) << "Unknown solver " << absl::GetFlag(FLAGS_colgen_solver); - return 1; + LOG(ERROR) << "Unknown solver " << solver_name; + return EXIT_FAILURE; } - LOG(INFO) << "Chosen solver: " << absl::GetFlag(FLAGS_colgen_solver) - << std::endl; + LOG(INFO) << "Chosen solver: " << solver_name << std::endl; if (absl::GetFlag(FLAGS_colgen_instance) == -1) { for (int i = 0; i < operations_research::kInstanceCount; ++i) { diff --git a/ortools/algorithms/BUILD.bazel b/ortools/algorithms/BUILD.bazel index 74ff54de2c..0fbf62f2ce 100644 --- a/ortools/algorithms/BUILD.bazel +++ b/ortools/algorithms/BUILD.bazel @@ -11,36 +11,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") load("@rules_cc//cc:cc_library.bzl", "cc_library") load("@rules_cc//cc:cc_test.bzl", "cc_test") package(default_visibility = ["//visibility:public"]) -# Description: -# Home of algorithms used in OR solvers - -# OSS solvers -bool_flag( - name = "with_cbc", - build_setting_default = False, -) - -config_setting( - name = "use_cbc", - flag_values = {":with_cbc": "true"}, -) - -bool_flag( - name = "with_scip", - build_setting_default = True, -) - -config_setting( - name = "use_scip", - flag_values = {":with_scip": "true"}, -) - cc_library( name = "binary_search", srcs = [], @@ -190,14 +165,8 @@ cc_library( name = "knapsack_solver_lib", srcs = ["knapsack_solver.cc"], hdrs = ["knapsack_solver.h"], - copts = [] + select({ - ":use_cbc": ["-DUSE_CBC"], - "//conditions:default": [], - }) + select({ - ":use_scip": ["-DUSE_SCIP"], - "//conditions:default": [], - }), deps = [ + "@abseil-cpp//absl/log", "@abseil-cpp//absl/log:check", "@abseil-cpp//absl/strings", "@abseil-cpp//absl/time", diff --git a/ortools/algorithms/knapsack_solver.cc b/ortools/algorithms/knapsack_solver.cc index 9c5e9682b8..6a3aed918c 100644 --- a/ortools/algorithms/knapsack_solver.cc +++ b/ortools/algorithms/knapsack_solver.cc @@ -23,6 +23,7 @@ #include #include "absl/log/check.h" +#include "absl/log/log.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" #include "ortools/base/stl_util.h" @@ -1071,7 +1072,7 @@ class KnapsackDivideAndConquerSolver : public BaseKnapsackSolver { } private: - // 'DP 2' computes solution 'z' for 0 up to capacitiy + // 'DP 2' computes solution 'z' for 0 up to capacity void SolveSubProblem(bool first_storage, int64_t capacity, int start_item, int end_item); @@ -1411,30 +1412,22 @@ KnapsackSolver::KnapsackSolver(SolverType solver_type, case KNAPSACK_DIVIDE_AND_CONQUER_SOLVER: solver_ = std::make_unique(solver_name); break; -#if defined(USE_CBC) case KNAPSACK_MULTIDIMENSION_CBC_MIP_SOLVER: solver_ = std::make_unique( MPSolver::CBC_MIXED_INTEGER_PROGRAMMING, solver_name); break; -#endif // USE_CBC -#if defined(USE_SCIP) case KNAPSACK_MULTIDIMENSION_SCIP_MIP_SOLVER: solver_ = std::make_unique( MPSolver::SCIP_MIXED_INTEGER_PROGRAMMING, solver_name); break; -#endif // USE_SCIP -#if defined(USE_XPRESS) case KNAPSACK_MULTIDIMENSION_XPRESS_MIP_SOLVER: solver_ = std::make_unique( MPSolver::XPRESS_MIXED_INTEGER_PROGRAMMING, solver_name); break; -#endif -#if defined(USE_CPLEX) case KNAPSACK_MULTIDIMENSION_CPLEX_MIP_SOLVER: solver_ = std::make_unique( MPSolver::CPLEX_MIXED_INTEGER_PROGRAMMING, solver_name); break; -#endif case KNAPSACK_MULTIDIMENSION_CP_SAT_SOLVER: solver_ = std::make_unique(solver_name); break; diff --git a/ortools/algorithms/knapsack_solver.h b/ortools/algorithms/knapsack_solver.h index db3157009b..b07689f36d 100644 --- a/ortools/algorithms/knapsack_solver.h +++ b/ortools/algorithms/knapsack_solver.h @@ -130,14 +130,12 @@ class KnapsackSolver { */ KNAPSACK_DYNAMIC_PROGRAMMING_SOLVER = 2, -#if defined(USE_CBC) /** CBC Based Solver * * This solver can deal with both large number of items and several * dimensions. This solver is based on Integer Programming solver CBC. */ KNAPSACK_MULTIDIMENSION_CBC_MIP_SOLVER = 3, -#endif // USE_CBC /** Generic Solver. * @@ -146,32 +144,27 @@ class KnapsackSolver { */ KNAPSACK_MULTIDIMENSION_BRANCH_AND_BOUND_SOLVER = 5, -#if defined(USE_SCIP) /** SCIP based solver * * This solver can deal with both large number of items and several * dimensions. This solver is based on Integer Programming solver SCIP. */ KNAPSACK_MULTIDIMENSION_SCIP_MIP_SOLVER = 6, -#endif // USE_SCIP -#if defined(USE_XPRESS) /** XPRESS based solver * * This solver can deal with both large number of items and several * dimensions. This solver is based on Integer Programming solver XPRESS. */ KNAPSACK_MULTIDIMENSION_XPRESS_MIP_SOLVER = 7, -#endif -#if defined(USE_CPLEX) /** CPLEX based solver * * This solver can deal with both large number of items and several * dimensions. This solver is based on Integer Programming solver CPLEX. */ KNAPSACK_MULTIDIMENSION_CPLEX_MIP_SOLVER = 8, -#endif + /** Divide and Conquer approach for single dimension problems * * Limited to one dimension, this solver is based on a divide and conquer @@ -180,6 +173,7 @@ class KnapsackSolver { * space complexity is O(capacity + number_of_items). */ KNAPSACK_DIVIDE_AND_CONQUER_SOLVER = 9, + /** CP-SAT based solver * * This solver can deal with both large number of items and several diff --git a/ortools/algorithms/python/BUILD.bazel b/ortools/algorithms/python/BUILD.bazel index 20442a7c24..ec48e1d1dd 100644 --- a/ortools/algorithms/python/BUILD.bazel +++ b/ortools/algorithms/python/BUILD.bazel @@ -13,33 +13,11 @@ # Description: python wrapping of the libraries in ../ -load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") load("@pip_deps//:requirements.bzl", "requirement") load("@pybind11_bazel//:build_defs.bzl", "pybind_extension") load("@rules_cc//cc:cc_library.bzl", "cc_library") load("@rules_python//python:py_test.bzl", "py_test") -# OSS solvers -bool_flag( - name = "with_cbc", - build_setting_default = False, -) - -config_setting( - name = "use_cbc", - flag_values = {":with_cbc": "true"}, -) - -bool_flag( - name = "with_scip", - build_setting_default = True, -) - -config_setting( - name = "use_scip", - flag_values = {":with_scip": "true"}, -) - # knapsack_solver cc_library( name = "knapsack_solver_doc", @@ -50,13 +28,6 @@ cc_library( pybind_extension( name = "knapsack_solver", srcs = ["knapsack_solver.cc"], - copts = select({ - ":use_cbc": ["-DUSE_CBC"], - "//conditions:default": [], - }) + select({ - ":use_scip": ["-DUSE_SCIP"], - "//conditions:default": [], - }), visibility = ["//visibility:public"], deps = [ ":knapsack_solver_doc", diff --git a/ortools/algorithms/python/knapsack_solver.cc b/ortools/algorithms/python/knapsack_solver.cc index 03ef0fbb2f..d9586b6142 100644 --- a/ortools/algorithms/python/knapsack_solver.cc +++ b/ortools/algorithms/python/knapsack_solver.cc @@ -67,19 +67,15 @@ PYBIND11_MODULE(knapsack_solver, m) { KnapsackSolver::SolverType::KNAPSACK_DYNAMIC_PROGRAMMING_SOLVER, DOC(operations_research, KnapsackSolver, SolverType, KNAPSACK_DYNAMIC_PROGRAMMING_SOLVER)) -#if defined(USE_CBC) .value("KNAPSACK_MULTIDIMENSION_CBC_MIP_SOLVER", KnapsackSolver::SolverType::KNAPSACK_MULTIDIMENSION_CBC_MIP_SOLVER, DOC(operations_research, KnapsackSolver, SolverType, KNAPSACK_MULTIDIMENSION_CBC_MIP_SOLVER)) -#endif // USE_CBC -#if defined(USE_SCIP) .value( "KNAPSACK_MULTIDIMENSION_SCIP_MIP_SOLVER", KnapsackSolver::SolverType::KNAPSACK_MULTIDIMENSION_SCIP_MIP_SOLVER, DOC(operations_research, KnapsackSolver, SolverType, KNAPSACK_MULTIDIMENSION_SCIP_MIP_SOLVER)) -#endif // USE_SCIP .value("KNAPSACK_DIVIDE_AND_CONQUER_SOLVER", KnapsackSolver::SolverType::KNAPSACK_DIVIDE_AND_CONQUER_SOLVER, DOC(operations_research, KnapsackSolver, SolverType, diff --git a/ortools/linear_solver/BUILD.bazel b/ortools/linear_solver/BUILD.bazel index fef4f97f31..871c25c4f0 100644 --- a/ortools/linear_solver/BUILD.bazel +++ b/ortools/linear_solver/BUILD.bazel @@ -73,6 +73,16 @@ config_setting( flag_values = {":with_glop": "true"}, ) +bool_flag( + name = "with_gurobi", + build_setting_default = True, +) + +config_setting( + name = "use_gurobi", + flag_values = {":with_gurobi": "true"}, +) + bool_flag( name = "with_glpk", build_setting_default = False, @@ -124,6 +134,16 @@ config_setting( flag_values = {":with_cplex": "true"}, ) +bool_flag( + name = "with_xpress", + build_setting_default = True, +) + +config_setting( + name = "use_xpress", + flag_values = {":with_xpress": "true"}, +) + # Linear solver proto, used for (efficient!) model storage. proto_library( name = "linear_solver_proto", @@ -150,13 +170,10 @@ py_proto_library( cc_library( name = "linear_solver", srcs = [ - "gurobi_interface.cc", - "gurobi_util.cc", "linear_expr.cc", "linear_solver.cc", "linear_solver_callback.cc", "sat_interface.cc", - "xpress_interface.cc", ] + select({ ":use_bop": ["bop_interface.cc"], "//conditions:default": [], @@ -175,6 +192,12 @@ cc_library( }) + select({ ":use_glpk": ["glpk_interface.cc"], "//conditions:default": [], + }) + select({ + ":use_gurobi": [ + "gurobi_interface.cc", + "gurobi_util.cc", + ], + "//conditions:default": [], }) + select({ ":use_highs": ["highs_interface.cc"], "//conditions:default": [], @@ -190,15 +213,20 @@ cc_library( }) + select({ ":use_cplex": ["cplex_interface.cc"], "//conditions:default": [], + }) + select({ + ":use_xpress": ["xpress_interface.cc"], + "//conditions:default": [], }), hdrs = [ - "gurobi_util.h", "linear_expr.h", "linear_solver.h", "linear_solver_callback.h", ] + select({ ":use_glop": ["glop_utils.h"], "//conditions:default": [], + }) + select({ + ":use_gurobi": ["gurobi_util.h"], + "//conditions:default": [], }) + select({ ":use_scip": [ "scip_callback.h", @@ -206,34 +234,6 @@ cc_library( ], "//conditions:default": [], }), - copts = [] + select({ - ":use_bop": ["-DUSE_BOP"], - "//conditions:default": [], - }) + select({ - ":use_cbc": ["-DUSE_CBC"], - "//conditions:default": [], - }) + select({ - ":use_clp": ["-DUSE_CLP"], - "//conditions:default": [], - }) + select({ - ":use_glop": ["-DUSE_GLOP"], - "//conditions:default": [], - }) + select({ - ":use_glpk": ["-DUSE_GLPK"], - "//conditions:default": [], - }) + select({ - ":use_highs": ["-DUSE_HIGHS"], - "//conditions:default": [], - }) + select({ - ":use_pdlp": ["-DUSE_PDLP"], - "//conditions:default": [], - }) + select({ - ":use_scip": ["-DUSE_SCIP"], - "//conditions:default": [], - }) + select({ - ":use_cplex": ["-DUSE_CPLEX"], - "//conditions:default": [], - }), deps = [ ":linear_solver_cc_proto", ":model_exporter", @@ -246,15 +246,12 @@ cc_library( "//ortools/base:status_macros", "//ortools/base:stl_util", "//ortools/base:timer", - "//ortools/linear_solver/proto_solver:gurobi_proto_solver", "//ortools/linear_solver/proto_solver:sat_proto_solver", "//ortools/port:file", "//ortools/port:proto_utils", "//ortools/sat:cp_model_cc_proto", "//ortools/sat:cp_model_solver", "//ortools/sat:lp_utils", - "//ortools/third_party_solvers:gurobi_environment", - "//ortools/third_party_solvers:xpress_environment", "//ortools/util:fp_utils", "//ortools/util:lazy_mutable_copy", "@abseil-cpp//absl/status", @@ -281,6 +278,12 @@ cc_library( "@glpk", ], "//conditions:default": [], + }) + select({ + ":use_gurobi": [ + "//ortools/linear_solver/proto_solver:gurobi_proto_solver", + "//ortools/third_party_solvers:gurobi_environment", + ], + "//conditions:default": [], }) + select({ ":use_pdlp": [ "//ortools/linear_solver/proto_solver:pdlp_proto_solver", @@ -302,7 +305,11 @@ cc_library( "@highs", ], "//conditions:default": [], + }) + select({ + ":use_xpress": ["//ortools/third_party_solvers:xpress_environment"], + "//conditions:default": [], }), + alwayslink = 1, # Important! Library is used via dependency injection. ) cc_library( diff --git a/ortools/linear_solver/CMakeLists.txt b/ortools/linear_solver/CMakeLists.txt index 3263a8d0c2..7f997d2ac2 100644 --- a/ortools/linear_solver/CMakeLists.txt +++ b/ortools/linear_solver/CMakeLists.txt @@ -11,24 +11,73 @@ # See the License for the specific language governing permissions and # limitations under the License. -file(GLOB _SRCS "*.h" "*.cc") -list(REMOVE_ITEM _SRCS - ${CMAKE_CURRENT_SOURCE_DIR}/solve.cc +list(APPEND _SRCS + linear_expr.cc + linear_expr.h + linear_solver_callback.cc + linear_solver_callback.h + linear_solver.cc + linear_solver.h + model_exporter_swig_helper.h + model_exporter.cc + model_exporter.h + model_validator.cc + model_validator.h + solve_mp_model.cc + solve_mp_model.h + sat_interface.cc ) -list(FILTER _SRCS EXCLUDE REGEX "/model_exporter_main\\.cc") -list(FILTER _SRCS EXCLUDE REGEX ".*/.*_test.cc") + +if(USE_BOP) + list(APPEND _SRCS bop_interface.cc) +endif() + +if(USE_COINOR) + list(APPEND _SRCS cbc_interface.cc clp_interface.cc) +endif() + +if(USE_CPLEX) + list(APPEND _SRCS cplex_interface.cc) +endif() + +if(USE_GLOP) + list(APPEND _SRCS glop_interface.cc glop_utils.cc glop_utils.h) +endif() + +if(USE_GUROBI) + list(APPEND _SRCS gurobi_util.h gurobi_interface.cc gurobi_util.cc) +endif() + +if(USE_GLPK) + list(APPEND _SRCS glpk_interface.cc) +endif() + +if(USE_HIGHS) + list(APPEND _SRCS highs_interface.cc) +endif() + +if(USE_PDLP) + list(APPEND _SRCS pdlp_interface.cc) +endif() + if(USE_SCIP) - list(APPEND _SRCS ${LPI_GLOP_SRC}) + list(APPEND _SRCS + scip_callback.cc + scip_callback.h + scip_helper_macros.h + scip_interface.cc + ${LPI_GLOP_SRC}) +endif() + +if(USE_XPRESS) + list(APPEND _SRCS xpress_interface.cc) endif() set(NAME ${PROJECT_NAME}_linear_solver) # Will be merge in libortools.so -#add_library(${NAME} STATIC ${_SRCS}) add_library(${NAME} OBJECT ${_SRCS}) -set_target_properties(${NAME} PROPERTIES - POSITION_INDEPENDENT_CODE ON - ) +set_target_properties(${NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) if(MSVC AND BUILD_SHARED_LIBS) target_compile_definitions(${NAME} PUBLIC "OR_BUILD_DLL") target_compile_definitions(${NAME} PRIVATE "OR_EXPORT") @@ -50,7 +99,6 @@ target_link_libraries(${NAME} PRIVATE $<$:Eigen3::Eigen> $<$:SCIP::libscip> ${PROJECT_NAMESPACE}::ortools_proto) -#add_library(${PROJECT_NAMESPACE}::linear_solver ALIAS ${NAME}) # solve add_executable(solve) diff --git a/ortools/linear_solver/bop_interface.cc b/ortools/linear_solver/bop_interface.cc index 7ac8a49119..d50fab1d2e 100644 --- a/ortools/linear_solver/bop_interface.cc +++ b/ortools/linear_solver/bop_interface.cc @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if defined(USE_BOP) - #include #include #include @@ -21,11 +19,6 @@ #include #include "absl/base/attributes.h" -#include "google/protobuf/text_format.h" -#include "ortools/base/commandlineflags.h" -#include "ortools/base/file.h" -#include "ortools/base/hash.h" -#include "ortools/base/helpers.h" #include "ortools/base/logging.h" #include "ortools/bop/bop_parameters.pb.h" #include "ortools/bop/integral_solver.h" @@ -387,10 +380,16 @@ void BopInterface::NonIncrementalChange() { sync_status_ = MUST_RELOAD; } -// Register BOP in the global linear solver factory. -MPSolverInterface* BuildBopInterface(MPSolver* const solver) { - return new BopInterface(solver); -} +namespace { + +// See MpSolverInterfaceFactoryRepository for details. +const void* const kRegisterBop ABSL_ATTRIBUTE_UNUSED = [] { + MPSolverInterfaceFactoryRepository::GetInstance()->Register( + [](MPSolver* const solver) { return new BopInterface(solver); }, + MPSolver::BOP_INTEGER_PROGRAMMING); + return nullptr; +}(); + +} // namespace } // namespace operations_research -#endif // #if defined(USE_BOP) diff --git a/ortools/linear_solver/cbc_interface.cc b/ortools/linear_solver/cbc_interface.cc index 34c0d2b57d..0f02f9acac 100644 --- a/ortools/linear_solver/cbc_interface.cc +++ b/ortools/linear_solver/cbc_interface.cc @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if defined(USE_CBC) - #include #include #include @@ -23,8 +21,6 @@ #include "absl/base/attributes.h" #include "absl/status/status.h" #include "absl/strings/str_format.h" -#include "ortools/base/commandlineflags.h" -#include "ortools/base/hash.h" #include "ortools/base/logging.h" #include "ortools/base/timer.h" #include "ortools/linear_solver/linear_solver.h" @@ -529,9 +525,16 @@ void CBCInterface::SetLpAlgorithm(int value) { SetUnsupportedIntegerParam(MPSolverParameters::LP_ALGORITHM); } -MPSolverInterface* BuildCBCInterface(MPSolver* const solver) { - return new CBCInterface(solver); -} +namespace { + +// See MpSolverInterfaceFactoryRepository for details. +const void* const kRegisterCBC ABSL_ATTRIBUTE_UNUSED = [] { + MPSolverInterfaceFactoryRepository::GetInstance()->Register( + [](MPSolver* const solver) { return new CBCInterface(solver); }, + MPSolver::CBC_MIXED_INTEGER_PROGRAMMING); + return nullptr; +}(); + +} // namespace } // namespace operations_research -#endif // #if defined(USE_CBC) diff --git a/ortools/linear_solver/clp_interface.cc b/ortools/linear_solver/clp_interface.cc index bce74dbbc9..1fac9f1e78 100644 --- a/ortools/linear_solver/clp_interface.cc +++ b/ortools/linear_solver/clp_interface.cc @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if defined(USE_CLP) || defined(USE_CBC) - #include #include #include @@ -617,9 +615,16 @@ void CLPInterface::SetLpAlgorithm(int value) { } } -MPSolverInterface* BuildCLPInterface(MPSolver* const solver) { - return new CLPInterface(solver); -} +namespace { + +// See MpSolverInterfaceFactoryRepository for details. +const void* const kRegisterCLP ABSL_ATTRIBUTE_UNUSED = [] { + MPSolverInterfaceFactoryRepository::GetInstance()->Register( + [](MPSolver* const solver) { return new CLPInterface(solver); }, + MPSolver::CLP_LINEAR_PROGRAMMING); + return nullptr; +}(); + +} // namespace } // namespace operations_research -#endif // #if defined(USE_CBC) || defined(USE_CLP) diff --git a/ortools/linear_solver/cplex_interface.cc b/ortools/linear_solver/cplex_interface.cc index 137d8fe82c..1f45e01b1f 100644 --- a/ortools/linear_solver/cplex_interface.cc +++ b/ortools/linear_solver/cplex_interface.cc @@ -1,4 +1,4 @@ -// Copyright 2014 IBM Corporation +// Copyright 2010-2025 Google LLC // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -12,8 +12,6 @@ // limitations under the License. // Initial version of this code was written by Daniel Junglas (IBM) -#if defined(USE_CPLEX) - #include #include #include @@ -1282,11 +1280,6 @@ MPSolver::ResultStatus CplexInterface::Solve(MPSolverParameters const& param) { sync_status_ = SOLUTION_SYNCHRONIZED; return result_status_; } - -MPSolverInterface* BuildCplexInterface(bool mip, MPSolver* const solver) { - return new CplexInterface(solver, mip); -} - bool CplexInterface::SetSolverSpecificParametersAsString( const std::string& parameters) { if (parameters.empty()) return true; @@ -1329,5 +1322,24 @@ bool CplexInterface::SetSolverSpecificParametersAsString( return true; } +namespace { + +// See MpSolverInterfaceFactoryRepository for details. +const void* const kRegisterCplex ABSL_ATTRIBUTE_UNUSED = [] { + MPSolverInterfaceFactoryRepository::GetInstance()->Register( + [](MPSolver* const solver) { return new CplexInterface(solver, false); }, + MPSolver::CPLEX_LINEAR_PROGRAMMING); + return nullptr; +}(); + +// See MpSolverInterfaceFactoryRepository for details. +const void* const kRegisterCplexMip ABSL_ATTRIBUTE_UNUSED = [] { + MPSolverInterfaceFactoryRepository::GetInstance()->Register( + [](MPSolver* const solver) { return new CplexInterface(solver, true); }, + MPSolver::CPLEX_MIXED_INTEGER_PROGRAMMING); + return nullptr; +}(); + +} // namespace + } // namespace operations_research -#endif // #if defined(USE_CPLEX) diff --git a/ortools/linear_solver/glop_interface.cc b/ortools/linear_solver/glop_interface.cc index af1fc693f8..b812fd43a1 100644 --- a/ortools/linear_solver/glop_interface.cc +++ b/ortools/linear_solver/glop_interface.cc @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if defined(USE_GLOP) - #include #include #include @@ -35,8 +33,6 @@ #include "ortools/util/time_limit.h" namespace operations_research { -namespace {} // Anonymous namespace - class GLOPInterface : public MPSolverInterface { public: explicit GLOPInterface(MPSolver* solver); @@ -437,10 +433,16 @@ void GLOPInterface::NonIncrementalChange() { sync_status_ = MUST_RELOAD; } -// Register GLOP in the global linear solver factory. -MPSolverInterface* BuildGLOPInterface(MPSolver* const solver) { - return new GLOPInterface(solver); -} +namespace { + +// See MpSolverInterfaceFactoryRepository for details. +const void* const kRegisterGlop ABSL_ATTRIBUTE_UNUSED = [] { + MPSolverInterfaceFactoryRepository::GetInstance()->Register( + [](MPSolver* const solver) { return new GLOPInterface(solver); }, + MPSolver::GLOP_LINEAR_PROGRAMMING); + return nullptr; +}(); + +} // namespace } // namespace operations_research -#endif // #if defined(USE_GLOP) diff --git a/ortools/linear_solver/glpk_interface.cc b/ortools/linear_solver/glpk_interface.cc index cc4c71a58a..b785c36bc9 100644 --- a/ortools/linear_solver/glpk_interface.cc +++ b/ortools/linear_solver/glpk_interface.cc @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if defined(USE_GLPK) - #include #include #include @@ -957,9 +955,24 @@ void GLPKInterface::SetLpAlgorithm(int value) { } } -MPSolverInterface* BuildGLPKInterface(bool mip, MPSolver* const solver) { - return new GLPKInterface(solver, mip); -} +namespace { + +// See MpSolverInterfaceFactoryRepository for details. +const void* const kRegisterGLPKLP ABSL_ATTRIBUTE_UNUSED = [] { + MPSolverInterfaceFactoryRepository::GetInstance()->Register( + [](MPSolver* solver) { return new GLPKInterface(solver, false); }, + MPSolver::GLPK_LINEAR_PROGRAMMING); + return nullptr; +}(); + +// See MpSolverInterfaceFactoryRepository for details. +const void* const kRegisterGLPKMIP ABSL_ATTRIBUTE_UNUSED = [] { + MPSolverInterfaceFactoryRepository::GetInstance()->Register( + [](MPSolver* solver) { return new GLPKInterface(solver, true); }, + MPSolver::GLPK_MIXED_INTEGER_PROGRAMMING); + return nullptr; +}(); + +} // namespace } // namespace operations_research -#endif // #if defined(USE_GLPK) diff --git a/ortools/linear_solver/gurobi_interface.cc b/ortools/linear_solver/gurobi_interface.cc index 4c8e2e20c1..af035792cc 100644 --- a/ortools/linear_solver/gurobi_interface.cc +++ b/ortools/linear_solver/gurobi_interface.cc @@ -1407,12 +1407,28 @@ void GurobiInterface::Write(const std::string& filename) { } } -MPSolverInterface* BuildGurobiInterface(bool mip, MPSolver* const solver) { - return new GurobiInterface(solver, mip); -} - void GurobiInterface::SetCallback(MPCallback* mp_callback) { callback_ = mp_callback; } +namespace { + +// See MpSolverInterfaceFactoryRepository for details. +const void* const kRegisterGurobiLp ABSL_ATTRIBUTE_UNUSED = [] { + MPSolverInterfaceFactoryRepository::GetInstance()->Register( + [](MPSolver* solver) { return new GurobiInterface(solver, false); }, + MPSolver::GUROBI_LINEAR_PROGRAMMING); + return nullptr; +}(); + +// See MpSolverInterfaceFactoryRepository for details. +const void* const kRegisterGurobiMip ABSL_ATTRIBUTE_UNUSED = [] { + MPSolverInterfaceFactoryRepository::GetInstance()->Register( + [](MPSolver* solver) { return new GurobiInterface(solver, true); }, + MPSolver::GUROBI_MIXED_INTEGER_PROGRAMMING); + return nullptr; +}(); + +} // namespace + } // namespace operations_research diff --git a/ortools/linear_solver/highs_interface.cc b/ortools/linear_solver/highs_interface.cc index d780825e5b..5ff4bc366b 100644 --- a/ortools/linear_solver/highs_interface.cc +++ b/ortools/linear_solver/highs_interface.cc @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if defined(USE_HIGHS) - #include #include #include @@ -288,10 +286,24 @@ void HighsInterface::NonIncrementalChange() { sync_status_ = MUST_RELOAD; } -// Register PDLP in the global linear solver factory. -MPSolverInterface* BuildHighsInterface(bool mip, MPSolver* const solver) { - return new HighsInterface(solver, mip); -} +namespace { + +// See MpSolverInterfaceFactoryRepository for details. +const void* const kRegisterHighsLp ABSL_ATTRIBUTE_UNUSED = [] { + MPSolverInterfaceFactoryRepository::GetInstance()->Register( + [](MPSolver* solver) { return new HighsInterface(solver, false); }, + MPSolver::HIGHS_LINEAR_PROGRAMMING); + return nullptr; +}(); + +// See MpSolverInterfaceFactoryRepository for details. +const void* const kRegisterHighsMip ABSL_ATTRIBUTE_UNUSED = [] { + MPSolverInterfaceFactoryRepository::GetInstance()->Register( + [](MPSolver* solver) { return new HighsInterface(solver, true); }, + MPSolver::HIGHS_MIXED_INTEGER_PROGRAMMING); + return nullptr; +}(); + +} // namespace } // namespace operations_research -#endif // #if defined(USE_HIGHS) diff --git a/ortools/linear_solver/knapsack_interface.cc b/ortools/linear_solver/knapsack_interface.cc index 0bb8ef212c..bd8efa5b2d 100644 --- a/ortools/linear_solver/knapsack_interface.cc +++ b/ortools/linear_solver/knapsack_interface.cc @@ -24,7 +24,6 @@ #include #include "absl/base/attributes.h" -#include "absl/memory/memory.h" #include "ortools/algorithms/knapsack_solver.h" #include "ortools/linear_solver/linear_solver.h" #include "ortools/util/fp_utils.h" @@ -358,9 +357,16 @@ double KnapsackInterface::GetVariableValueFromSolution( : 0.0; } -// Register Knapsack solver in the global linear solver factory. -MPSolverInterface* BuildKnapsackInterface(MPSolver* const solver) { - return new KnapsackInterface(solver); -} +namespace { + +// See MpSolverInterfaceFactoryRepository for details. +const void* const kRegisterKnapsack ABSL_ATTRIBUTE_UNUSED = [] { + MPSolverInterfaceFactoryRepository::GetInstance()->Register( + [](MPSolver* const solver) { return new KnapsackInterface(solver); }, + MPSolver::KNAPSACK_MIXED_INTEGER_PROGRAMMING); + return nullptr; +}(); + +} // namespace } // namespace operations_research diff --git a/ortools/linear_solver/linear_solver.cc b/ortools/linear_solver/linear_solver.cc index db5fce17f9..f7e299fdf4 100644 --- a/ortools/linear_solver/linear_solver.cc +++ b/ortools/linear_solver/linear_solver.cc @@ -374,100 +374,15 @@ bool MPSolver::SetSolverSpecificParametersAsString( // ----- Solver ----- -#if defined(USE_BOP) -extern MPSolverInterface* BuildBopInterface(MPSolver* const solver); -#endif -#if defined(USE_CBC) -extern MPSolverInterface* BuildCBCInterface(MPSolver* const solver); -#endif -#if defined(USE_CLP) || defined(USE_CBC) -extern MPSolverInterface* BuildCLPInterface(MPSolver* const solver); -#endif -#if defined(USE_GLOP) -extern MPSolverInterface* BuildGLOPInterface(MPSolver* const solver); -#endif -#if defined(USE_GLPK) -extern MPSolverInterface* BuildGLPKInterface(bool mip, MPSolver* const solver); -#endif -#if defined(USE_HIGHS) -extern MPSolverInterface* BuildHighsInterface(bool mip, MPSolver* const solver); -#endif -#if defined(USE_PDLP) -extern MPSolverInterface* BuildPdlpInterface(MPSolver* const solver); -#endif -extern MPSolverInterface* BuildSatInterface(MPSolver* const solver); -#if defined(USE_SCIP) -extern MPSolverInterface* BuildSCIPInterface(MPSolver* const solver); -#endif -extern MPSolverInterface* BuildGurobiInterface(bool mip, - MPSolver* const solver); -#if defined(USE_CPLEX) -extern MPSolverInterface* BuildCplexInterface(bool mip, MPSolver* const solver); -#endif -extern MPSolverInterface* BuildXpressInterface(bool mip, - MPSolver* const solver); - namespace { MPSolverInterface* BuildSolverInterface(MPSolver* const solver) { DCHECK(solver != nullptr); - switch (solver->ProblemType()) { -#if defined(USE_BOP) - case MPSolver::BOP_INTEGER_PROGRAMMING: - return BuildBopInterface(solver); -#endif -#if defined(USE_CBC) - case MPSolver::CBC_MIXED_INTEGER_PROGRAMMING: - return BuildCBCInterface(solver); -#endif -#if defined(USE_CLP) || defined(USE_CBC) - case MPSolver::CLP_LINEAR_PROGRAMMING: - return BuildCLPInterface(solver); -#endif -#if defined(USE_GLOP) - case MPSolver::GLOP_LINEAR_PROGRAMMING: - return BuildGLOPInterface(solver); -#endif -#if defined(USE_GLPK) - case MPSolver::GLPK_LINEAR_PROGRAMMING: - return BuildGLPKInterface(false, solver); - case MPSolver::GLPK_MIXED_INTEGER_PROGRAMMING: - return BuildGLPKInterface(true, solver); -#endif -#if defined(USE_HIGHS) - case MPSolver::HIGHS_LINEAR_PROGRAMMING: - return BuildHighsInterface(false, solver); - case MPSolver::HIGHS_MIXED_INTEGER_PROGRAMMING: - return BuildHighsInterface(true, solver); -#endif -#if defined(USE_PDLP) - case MPSolver::PDLP_LINEAR_PROGRAMMING: - return BuildPdlpInterface(solver); -#endif - case MPSolver::SAT_INTEGER_PROGRAMMING: - return BuildSatInterface(solver); -#if defined(USE_SCIP) - case MPSolver::SCIP_MIXED_INTEGER_PROGRAMMING: - return BuildSCIPInterface(solver); -#endif - case MPSolver::GUROBI_LINEAR_PROGRAMMING: - return BuildGurobiInterface(false, solver); - case MPSolver::GUROBI_MIXED_INTEGER_PROGRAMMING: - return BuildGurobiInterface(true, solver); -#if defined(USE_CPLEX) - case MPSolver::CPLEX_LINEAR_PROGRAMMING: - return BuildCplexInterface(false, solver); - case MPSolver::CPLEX_MIXED_INTEGER_PROGRAMMING: - return BuildCplexInterface(true, solver); -#endif - case MPSolver::XPRESS_MIXED_INTEGER_PROGRAMMING: - return BuildXpressInterface(true, solver); - case MPSolver::XPRESS_LINEAR_PROGRAMMING: - return BuildXpressInterface(false, solver); - default: - // TODO(user): Revert to the best *available* interface. - LOG(FATAL) << "Linear solver not recognized."; - } - return nullptr; + MPSolverInterface* interface = + MPSolverInterfaceFactoryRepository::GetInstance()->Create(solver); + QCHECK(interface != nullptr) + << "Unsupported problem type: '" << solver->ProblemType() + << "'. Did you forget to link the library for this solver?"; + return interface; } } // namespace @@ -502,53 +417,21 @@ extern bool XpressIsCorrectlyInstalled(); // static bool MPSolver::SupportsProblemType(OptimizationProblemType problem_type) { -#ifdef USE_BOP - if (problem_type == BOP_INTEGER_PROGRAMMING) return true; -#endif -#ifdef USE_CBC - if (problem_type == CBC_MIXED_INTEGER_PROGRAMMING) return true; -#endif -#ifdef USE_CLP - if (problem_type == CLP_LINEAR_PROGRAMMING) return true; -#endif -#ifdef USE_GLOP - if (problem_type == GLOP_LINEAR_PROGRAMMING) return true; -#endif -#ifdef USE_GLPK - if (problem_type == GLPK_LINEAR_PROGRAMMING || - problem_type == GLPK_MIXED_INTEGER_PROGRAMMING) { - return true; + if (!MPSolverInterfaceFactoryRepository::GetInstance()->Supports( + problem_type)) { + return false; } -#endif -#ifdef USE_HIGHS - if (problem_type == HIGHS_MIXED_INTEGER_PROGRAMMING || - problem_type == HIGHS_LINEAR_PROGRAMMING) { - return true; + switch (problem_type) { + case GUROBI_LINEAR_PROGRAMMING: + case GUROBI_MIXED_INTEGER_PROGRAMMING: + return GurobiIsCorrectlyInstalled(); + case XPRESS_LINEAR_PROGRAMMING: + case XPRESS_MIXED_INTEGER_PROGRAMMING: + return XpressIsCorrectlyInstalled(); + default: + break; } -#endif -#ifdef USE_PDLP - if (problem_type == PDLP_LINEAR_PROGRAMMING) return true; -#endif - if (problem_type == GUROBI_LINEAR_PROGRAMMING || - problem_type == GUROBI_MIXED_INTEGER_PROGRAMMING) { - return GurobiIsCorrectlyInstalled(); - } - if (problem_type == SAT_INTEGER_PROGRAMMING) return true; -#ifdef USE_SCIP - if (problem_type == SCIP_MIXED_INTEGER_PROGRAMMING) return true; -#endif -#ifdef USE_CPLEX - if (problem_type == CPLEX_LINEAR_PROGRAMMING || - problem_type == CPLEX_MIXED_INTEGER_PROGRAMMING) { - return true; - } -#endif - if (problem_type == XPRESS_MIXED_INTEGER_PROGRAMMING || - problem_type == XPRESS_LINEAR_PROGRAMMING) { - return XpressIsCorrectlyInstalled(); - } - - return false; + return true; } // TODO(user): post c++ 14, instead use @@ -740,7 +623,7 @@ class MPVariableNamesIterator { int index_ = 0; }; -// Iterates over all the constraint and general_constaint names. See usage. +// Iterates over all the constraint and general_constraint names. See usage. class MPConstraintNamesIterator { public: explicit MPConstraintNamesIterator(const MPModelProto& model) @@ -2339,4 +2222,68 @@ std::string MPSolver::GetMPModelRequestLoggingInfo( return out; } +MPSolverInterfaceFactoryRepository* +MPSolverInterfaceFactoryRepository::GetInstance() { + static auto* const kInstance = new MPSolverInterfaceFactoryRepository(); + return kInstance; +} + +// This can't be covered by unit test coverage framework, because the Singleton +// destruction occurs after it finished collecting coverage. +// COV_NF_START +MPSolverInterfaceFactoryRepository::~MPSolverInterfaceFactoryRepository() { + absl::MutexLock lock(mutex_); + map_.clear(); +} +// COV_NF_END + +void MPSolverInterfaceFactoryRepository::Register( + MPSolverInterfaceFactory factory, + MPSolver::OptimizationProblemType problem_type) { + absl::MutexLock lock(mutex_); + map_[problem_type] = std::move(factory); +} + +bool MPSolverInterfaceFactoryRepository::Unregister( + MPSolver::OptimizationProblemType problem_type) { + absl::MutexLock lock(mutex_); + return map_.erase(problem_type) == 1; +} + +MPSolverInterface* MPSolverInterfaceFactoryRepository::Create( + MPSolver* solver) const { + absl::MutexLock lock(mutex_); + const MPSolverInterfaceFactory factory = + gtl::FindWithDefault(map_, solver->ProblemType(), nullptr); + if (!factory) { + return nullptr; + } + return factory(solver); +} + +bool MPSolverInterfaceFactoryRepository::Supports( + MPSolver::OptimizationProblemType problem_type) const { + return map_.count(problem_type) > 0; +} + +std::vector +MPSolverInterfaceFactoryRepository::ListAllRegisteredProblemTypes() const { + std::vector out; + for (auto it = map_.begin(); it != map_.end(); ++it) { + out.push_back(it->first); + } + return out; +} + +std::string MPSolverInterfaceFactoryRepository // NOLINT + ::PrettyPrintAllRegisteredProblemTypes() const { + std::string out; + for (auto it = map_.begin(); it != map_.end(); ++it) { + out += ProtoEnumToString( + static_cast(it->first)) + + "\n"; + } + return out; +} + } // namespace operations_research diff --git a/ortools/linear_solver/linear_solver.h b/ortools/linear_solver/linear_solver.h index e989620425..4656ac1369 100644 --- a/ortools/linear_solver/linear_solver.h +++ b/ortools/linear_solver/linear_solver.h @@ -157,7 +157,6 @@ #include "absl/strings/string_view.h" #include "absl/time/clock.h" #include "absl/time/time.h" -#include "absl/types/optional.h" #include "ortools/base/base_export.h" #include "ortools/base/logging.h" #include "ortools/linear_solver/linear_expr.h" @@ -1231,23 +1230,23 @@ class MPVariable { void SetBranchingPriority(int priority); protected: - friend class MPSolver; - friend class MPSolverInterface; + friend class BopInterface; friend class CBCInterface; friend class CLPInterface; - friend class GLPKInterface; - friend class SCIPInterface; - friend class SLMInterface; - friend class GurobiInterface; friend class CplexInterface; - friend class XpressInterface; friend class GLOPInterface; - friend class MPVariableSolutionValueTest; - friend class BopInterface; - friend class SatInterface; - friend class PdlpInterface; + friend class GLPKInterface; + friend class GurobiInterface; friend class HighsInterface; friend class KnapsackInterface; + friend class MPSolver; + friend class MPSolverInterface; + friend class MPVariableSolutionValueTest; + friend class PdlpInterface; + friend class SatInterface; + friend class SCIPInterface; + friend class SLMInterface; + friend class XpressInterface; // Constructor. A variable points to a single MPSolverInterface that // is specified in the constructor. A variable cannot belong to @@ -1929,6 +1928,72 @@ class MPSolverInterface { virtual void SetLpAlgorithm(int value) = 0; }; +// Handy type name for callbacks that create a fresh MPSolverInterface tied +// to the given MPSolver. The underlying callback should *always* be permanent. +typedef std::function MPSolverInterfaceFactory; + +// This class must be instantiated only once, through GetInstance(). It is +// thread-safe. +// +// - To register an existing MPSolverInterface in the global repository: +// +// MPSolverInterfaceFactory cbc_solver_interface_factory = +// CreateCBCInterface; +// MPSolverInterfaceFactoryRepository::GetInstance()->Register( +// cbc_solver_interface_factory, CBC_MIXED_INTEGER_PROGRAMMING); +// +// - To get the MPSolverInterfaceFactory associated to a given problem type: +// +// MPSolverInterface* my_solver_interface = +// MPSolverInterfaceFactoryRepository::GetInstance()->Create( +// CBC_MIXED_INTEGER_PROGRAMMING); +// CHECK(my_solver_interface != NULL) << "CBC not supported."; +// +// The implementations of MPSolverInterface defined here (e.g. ScipInterface) +// are registered at with the MPSolverInterfaceFactoryRepository Singleton +// automatically at link time, as long as the cc file where they are defined +// (e.g. scip_interface.cc) is included in your binary (i.e. you have a +// transitive dependency on the build rule ":linear_solver_scip", which sets +// alwayslink=1). +class MPSolverInterfaceFactoryRepository { + public: + static MPSolverInterfaceFactoryRepository* GetInstance(); + + // Maps the given factory to the given problem type. If a factory was already + // assigned to this problem type, it will be replaced. + void Register(MPSolverInterfaceFactory factory, + MPSolver::OptimizationProblemType problem_type); + + // Invokes the factory associated to the given solver's problem type, + // or return NULL if no factory was found for it. + MPSolverInterface* Create(MPSolver* solver) const; + + // Whether the implementation associated to the given problem type is + // available. + bool Supports(MPSolver::OptimizationProblemType problem_type) const; + + // List all the problem types. + std::vector ListAllRegisteredProblemTypes() + const; + + // Returns a human-readable list of supported OptimizationProblemType. + std::string PrettyPrintAllRegisteredProblemTypes() const; + + // FOR TESTING ONLY. + bool Unregister(MPSolver::OptimizationProblemType problem_type); + + private: + // The constructor / destructor are private to prevent this class from ever + // being instantiated outside GetInstance(). + // TODO(user): consider adding the GLOP factory by default, and then expose + // it via a CreateDefault() method. + MPSolverInterfaceFactoryRepository() = default; + ~MPSolverInterfaceFactoryRepository(); + + mutable absl::Mutex mutex_; + std::map map_; +}; + } // namespace operations_research #endif // ORTOOLS_LINEAR_SOLVER_LINEAR_SOLVER_H_ diff --git a/ortools/linear_solver/pdlp_interface.cc b/ortools/linear_solver/pdlp_interface.cc index 5ac4cc97c7..24ec56061b 100644 --- a/ortools/linear_solver/pdlp_interface.cc +++ b/ortools/linear_solver/pdlp_interface.cc @@ -11,11 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if defined(USE_PDLP) - #include #include -#include #include #include #include @@ -24,7 +21,6 @@ #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" -#include "absl/types/optional.h" #include "ortools/base/logging.h" #include "ortools/linear_solver/linear_solver.h" #include "ortools/linear_solver/linear_solver.pb.h" @@ -310,10 +306,16 @@ void PdlpInterface::NonIncrementalChange() { sync_status_ = MUST_RELOAD; } -// Register PDLP in the global linear solver factory. -MPSolverInterface* BuildPdlpInterface(MPSolver* const solver) { - return new PdlpInterface(solver); -} +namespace { + +// See MpSolverInterfaceFactoryRepository for details. +const void* const kRegisterPdlp ABSL_ATTRIBUTE_UNUSED = [] { + MPSolverInterfaceFactoryRepository::GetInstance()->Register( + [](MPSolver* const solver) { return new PdlpInterface(solver); }, + MPSolver::PDLP_LINEAR_PROGRAMMING); + return nullptr; +}(); + +} // namespace } // namespace operations_research -#endif // #if defined(USE_PDLP) diff --git a/ortools/linear_solver/proto_solver/CMakeLists.txt b/ortools/linear_solver/proto_solver/CMakeLists.txt index c3a4b5cb1d..f6c00ad792 100644 --- a/ortools/linear_solver/proto_solver/CMakeLists.txt +++ b/ortools/linear_solver/proto_solver/CMakeLists.txt @@ -11,33 +11,54 @@ # See the License for the specific language governing permissions and # limitations under the License. -file(GLOB _SRCS "*.h" "*.cc") -if(NOT USE_COINOR) - list(FILTER _SRCS EXCLUDE REGEX "/clp_proto_solver.") - list(FILTER _SRCS EXCLUDE REGEX "/cbc_proto_solver.") +list(APPEND _SRCS + preprocessor.cc + preprocessor.h + proto_utils.h + sat_proto_solver.cc + sat_proto_solver.h + sat_solver_utils.cc + sat_solver_utils.h +) + +if(USE_GLOP) + list(APPEND _SRCS + glop_proto_solver.cc + glop_proto_solver.h + ) endif() -if(NOT USE_GLPK) - list(FILTER _SRCS EXCLUDE REGEX "/glpk_proto_solver.") +if(USE_GUROBI) + list(APPEND _SRCS + gurobi_proto_solver.cc + gurobi_proto_solver.h + ) endif() -if(NOT USE_HIGHS) - list(FILTER _SRCS EXCLUDE REGEX "/highs_proto_solver.") +if(USE_HIGHS) + list(APPEND _SRCS + highs_proto_solver.cc + highs_proto_solver.h + ) endif() -if(NOT USE_PDLP) - list(FILTER _SRCS EXCLUDE REGEX "/pdlp_proto_solver.") +if(USE_PDLP) + list(APPEND _SRCS + pdlp_proto_solver.cc + pdlp_proto_solver.h + ) endif() -if(NOT USE_SCIP) - list(FILTER _SRCS EXCLUDE REGEX "/scip_proto_solver.") - list(FILTER _SRCS EXCLUDE REGEX "/scip_params.") +if(USE_SCIP) + list(APPEND _SRCS + scip_params.cc + scip_params.h + scip_proto_solver.cc + scip_proto_solver.h + ) endif() set(NAME ${PROJECT_NAME}_linear_solver_proto_solver) # Will be merge in libortools.so -#add_library(${NAME} STATIC ${_SRCS}) add_library(${NAME} OBJECT ${_SRCS}) -set_target_properties(${NAME} PROPERTIES - POSITION_INDEPENDENT_CODE ON - ) +set_target_properties(${NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) target_include_directories(${NAME} PUBLIC $ $) @@ -50,4 +71,3 @@ target_link_libraries(${NAME} PRIVATE $<$:SCIP::libscip> $<$:highs::highs> ${PROJECT_NAMESPACE}::ortools_proto) -#add_library(${PROJECT_NAMESPACE}::linear_solver_proto_solver ALIAS ${NAME}) diff --git a/ortools/linear_solver/proto_solver/scip_proto_solver.cc b/ortools/linear_solver/proto_solver/scip_proto_solver.cc index 02c7268504..2cd8fee72b 100644 --- a/ortools/linear_solver/proto_solver/scip_proto_solver.cc +++ b/ortools/linear_solver/proto_solver/scip_proto_solver.cc @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if defined(USE_SCIP) - #include "ortools/linear_solver/proto_solver/scip_proto_solver.h" #include @@ -1011,5 +1009,3 @@ absl::StatusOr ScipSolveProto( } } // namespace operations_research - -#endif // #if defined(USE_SCIP) diff --git a/ortools/linear_solver/sat_interface.cc b/ortools/linear_solver/sat_interface.cc index 31da354cb6..95d8ae9ee4 100644 --- a/ortools/linear_solver/sat_interface.cc +++ b/ortools/linear_solver/sat_interface.cc @@ -13,14 +13,12 @@ #include #include -#include #include #include #include #include "absl/base/attributes.h" #include "absl/status/status.h" -#include "absl/status/statusor.h" #include "ortools/base/logging.h" #include "ortools/linear_solver/linear_solver.h" #include "ortools/linear_solver/linear_solver.pb.h" @@ -271,9 +269,16 @@ void SatInterface::NonIncrementalChange() { sync_status_ = MUST_RELOAD; } -// Register Sat in the global linear solver factory. -MPSolverInterface* BuildSatInterface(MPSolver* const solver) { - return new SatInterface(solver); -} +namespace { + +// See MpSolverInterfaceFactoryRepository for details. +const void* const kRegisterSat ABSL_ATTRIBUTE_UNUSED = [] { + MPSolverInterfaceFactoryRepository::GetInstance()->Register( + [](MPSolver* const solver) { return new SatInterface(solver); }, + MPSolver::SAT_INTEGER_PROGRAMMING); + return nullptr; +}(); + +} // namespace } // namespace operations_research diff --git a/ortools/linear_solver/scip_callback.cc b/ortools/linear_solver/scip_callback.cc index 0762e82284..b1c54be395 100644 --- a/ortools/linear_solver/scip_callback.cc +++ b/ortools/linear_solver/scip_callback.cc @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if defined(USE_SCIP) - #include "ortools/linear_solver/scip_callback.h" #include @@ -22,9 +20,9 @@ #include #include -#include "absl/strings/str_cat.h" #include "absl/types/span.h" #include "ortools/base/logging.h" +#include "ortools/linear_solver/linear_solver.h" #include "ortools/linear_solver/scip_helper_macros.h" #include "scip/cons_linear.h" #include "scip/def.h" @@ -459,4 +457,3 @@ void AddCallbackConstraintImpl(SCIP* scip, const std::string& handler_name, } // namespace internal } // namespace operations_research -#endif // #if defined(USE_SCIP) diff --git a/ortools/linear_solver/scip_interface.cc b/ortools/linear_solver/scip_interface.cc index ce7e906da0..2e5f385091 100644 --- a/ortools/linear_solver/scip_interface.cc +++ b/ortools/linear_solver/scip_interface.cc @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if defined(USE_SCIP) - #include #include @@ -1165,12 +1163,19 @@ void SCIPInterface::SetCallback(MPCallback* mp_callback) { callback_ = mp_callback; } -MPSolverInterface* BuildSCIPInterface(MPSolver* const solver) { - return new SCIPInterface(solver); -} +namespace { + +// See MpSolverInterfaceFactoryRepository for details. +const void* const kRegisterSCIP ABSL_ATTRIBUTE_UNUSED = [] { + MPSolverInterfaceFactoryRepository::GetInstance()->Register( + [](MPSolver* const solver) { return new SCIPInterface(solver); }, + MPSolver::SCIP_MIXED_INTEGER_PROGRAMMING); + return nullptr; +}(); + +} // namespace } // namespace operations_research -#endif // #if defined(USE_SCIP) #undef RETURN_AND_STORE_IF_SCIP_ERROR #undef RETURN_IF_ALREADY_IN_ERROR_STATE diff --git a/ortools/linear_solver/wrappers/CMakeLists.txt b/ortools/linear_solver/wrappers/CMakeLists.txt index 97d8136fa0..3f6234f94f 100644 --- a/ortools/linear_solver/wrappers/CMakeLists.txt +++ b/ortools/linear_solver/wrappers/CMakeLists.txt @@ -15,11 +15,8 @@ file(GLOB _SRCS "*.h" "*.cc") set(NAME ${PROJECT_NAME}_linear_solver_wrappers) # Will be merge in libortools.so -#add_library(${NAME} STATIC ${_SRCS}) add_library(${NAME} OBJECT ${_SRCS}) -set_target_properties(${NAME} PROPERTIES - POSITION_INDEPENDENT_CODE ON - ) +set_target_properties(${NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) target_include_directories(${NAME} PRIVATE ${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR}) @@ -27,4 +24,3 @@ target_link_libraries(${NAME} PRIVATE absl::status $<$:SCIP::libscip> ${PROJECT_NAMESPACE}::ortools_proto) -#add_library(${PROJECT_NAMESPACE}::linear_solver_wrappers ALIAS ${NAME}) diff --git a/ortools/linear_solver/xpress_interface.cc b/ortools/linear_solver/xpress_interface.cc index aaa3b9abfb..40b14845df 100644 --- a/ortools/linear_solver/xpress_interface.cc +++ b/ortools/linear_solver/xpress_interface.cc @@ -1,4 +1,4 @@ -// Copyright 2019-2023 RTE +// Copyright 2010-2025 Google LLC // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -2098,10 +2098,6 @@ void XpressInterface::Write(const std::string& filename) { LOG(ERROR) << "Xpress: Failed to write MPS!"; } } - -MPSolverInterface* BuildXpressInterface(bool mip, MPSolver* const solver) { - return new XpressInterface(solver, mip); -} // TODO useless ? template void splitMyString(const std::string& str, Container& cont, char delim = ' ') { @@ -2287,4 +2283,24 @@ double XpressMPCallbackContext::SuggestSolution( return NAN; } +namespace { + +// See MpSolverInterfaceFactoryRepository for details. +const void* const kRegisterXpress ABSL_ATTRIBUTE_UNUSED = [] { + MPSolverInterfaceFactoryRepository::GetInstance()->Register( + [](MPSolver* const solver) { return new XpressInterface(solver, false); }, + MPSolver::XPRESS_LINEAR_PROGRAMMING); + return nullptr; +}(); + +// See MpSolverInterfaceFactoryRepository for details. +const void* const kRegisterXpressMip ABSL_ATTRIBUTE_UNUSED = [] { + MPSolverInterfaceFactoryRepository::GetInstance()->Register( + [](MPSolver* const solver) { return new XpressInterface(solver, true); }, + MPSolver::XPRESS_MIXED_INTEGER_PROGRAMMING); + return nullptr; +}(); + +} // namespace + } // namespace operations_research diff --git a/ortools/pdlp/CMakeLists.txt b/ortools/pdlp/CMakeLists.txt index 222bb2913b..535740d6a7 100644 --- a/ortools/pdlp/CMakeLists.txt +++ b/ortools/pdlp/CMakeLists.txt @@ -15,19 +15,36 @@ if(NOT USE_PDLP) return() endif() -file(GLOB _SRCS "*.h" "*.cc") -list(FILTER _SRCS EXCLUDE REGEX "/[^/]*_test\\.cc$") -list(FILTER _SRCS EXCLUDE REGEX "/gtest[^/]*$") -list(FILTER _SRCS EXCLUDE REGEX "/test[^/]*$") +list(APPEND _SRCS + iteration_stats.cc + iteration_stats.h + primal_dual_hybrid_gradient.cc + primal_dual_hybrid_gradient.h + quadratic_program_io.cc + quadratic_program_io.h + quadratic_program.cc + quadratic_program.h + scheduler.cc + scheduler.h + sharded_optimization_utils.cc + sharded_optimization_utils.h + sharded_quadratic_program.cc + sharded_quadratic_program.h + sharder.cc + sharder.h + solvers_proto_validation.cc + solvers_proto_validation.h + termination.cc + termination.h + trust_region.cc + trust_region.h +) set(NAME ${PROJECT_NAME}_pdlp) # Will be merge in libortools.so -#add_library(${NAME} STATIC ${_SRCS}) add_library(${NAME} OBJECT ${_SRCS}) -set_target_properties(${NAME} PROPERTIES - POSITION_INDEPENDENT_CODE ON - ) +set_target_properties(${NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) target_include_directories(${NAME} PUBLIC $ $) @@ -37,4 +54,3 @@ target_link_libraries(${NAME} PRIVATE absl::str_format Eigen3::Eigen ${PROJECT_NAMESPACE}::ortools_proto) -#add_library(${PROJECT_NAMESPACE}::pdlp ALIAS ${NAME})