From 56291e0486f9a07124af9945f9d22e9421ef435b Mon Sep 17 00:00:00 2001 From: Corentin Le Molgat Date: Mon, 4 Dec 2023 18:29:50 +0100 Subject: [PATCH] math_opt: update from google3 --- ortools/math_opt/BUILD.bazel | 35 +++++++---- ortools/math_opt/core/python/BUILD.bazel | 4 +- ortools/math_opt/cpp/solve_result.cc | 8 +-- ortools/math_opt/cpp/solve_result.h | 4 +- ortools/math_opt/python/normalize.py | 2 +- ortools/math_opt/rpc.proto | 61 +++++++++++++++++++ ortools/math_opt/solvers/BUILD.bazel | 3 +- ortools/math_opt/solvers/glop_solver.cc | 11 +--- ortools/math_opt/solvers/gscip_solver.cc | 72 ++++------------------- ortools/math_opt/solvers/gscip_solver.h | 4 +- ortools/math_opt/solvers/gurobi_solver.cc | 5 +- ortools/math_opt/solvers/gurobi_solver.h | 2 + 12 files changed, 119 insertions(+), 92 deletions(-) create mode 100644 ortools/math_opt/rpc.proto diff --git a/ortools/math_opt/BUILD.bazel b/ortools/math_opt/BUILD.bazel index 48724c3055..3c91e4fd14 100644 --- a/ortools/math_opt/BUILD.bazel +++ b/ortools/math_opt/BUILD.bazel @@ -11,20 +11,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("@rules_python//python:proto.bzl", "py_proto_library") load("@rules_cc//cc:defs.bzl", "cc_proto_library") +load("@rules_python//python:proto.bzl", "py_proto_library") package(default_visibility = ["//visibility:public"]) -# Features that are new or under construction have restricted access, contact -# math-opt-dev@ if you want try using these with submitted code. -package_group( - name = "math_opt_allow_list", - packages = [ - "//ortools/...", - ], -) - proto_library( name = "callback_proto", srcs = ["callback.proto"], @@ -215,3 +206,27 @@ py_proto_library( name = "infeasible_subsystem_py_pb2", deps = [":infeasible_subsystem_proto"], ) + +proto_library( + name = "rpc_proto", + srcs = ["rpc.proto"], + deps = [ + ":callback_proto", + ":infeasible_subsystem_proto", + ":model_parameters_proto", + ":model_proto", + ":model_update_proto", + ":parameters_proto", + ":result_proto", + ], +) + +py_proto_library( + name = "rpc_py_pb2", + deps = [":rpc_proto"], +) + +cc_proto_library( + name = "rpc_cc_proto", + deps = [":rpc_proto"], +) diff --git a/ortools/math_opt/core/python/BUILD.bazel b/ortools/math_opt/core/python/BUILD.bazel index c1aba82f1b..8450038f35 100644 --- a/ortools/math_opt/core/python/BUILD.bazel +++ b/ortools/math_opt/core/python/BUILD.bazel @@ -20,7 +20,7 @@ pybind_extension( srcs = ["solver.cc"], deps = select({ - "//ortools/linear_solver:use_scip": ["//ortools/math_opt/solvers:gscip_solver"], + "//ortools/linear_solver:use_cp_sat": ["//ortools/math_opt/solvers:cp_sat_solver"], "//conditions:default": [], }) + select({ @@ -32,7 +32,7 @@ pybind_extension( "//conditions:default": [], }) + select({ - "//ortools/linear_solver:use_cp_sat": ["//ortools/math_opt/solvers:cp_sat_solver"], + "//ortools/linear_solver:use_scip": ["//ortools/math_opt/solvers:gscip_solver"], "//conditions:default": [], }) + [ "//ortools/math_opt:result_cc_proto", diff --git a/ortools/math_opt/cpp/solve_result.cc b/ortools/math_opt/cpp/solve_result.cc index e987197ba9..0552b2ae3e 100644 --- a/ortools/math_opt/cpp/solve_result.cc +++ b/ortools/math_opt/cpp/solve_result.cc @@ -325,13 +325,13 @@ bool Termination::limit_reached() const { reason == TerminationReason::kNoSolutionFound; } -absl::Status Termination::ReasonIs(const TerminationReason reason) const { +absl::Status Termination::EnsureReasonIs(TerminationReason reason) const { if (this->reason == reason) return absl::OkStatus(); return util::InternalErrorBuilder() << "expected termination reason '" << reason << "' but got " << *this; } -absl::Status Termination::ReasonIsAnyOf( +absl::Status Termination::EnsureReasonIsAnyOf( std::initializer_list reasons) const { for (const TerminationReason reason : reasons) { if (this->reason == reason) return absl::OkStatus(); @@ -348,11 +348,11 @@ absl::Status Termination::ReasonIsAnyOf( } absl::Status Termination::IsOptimal() const { - return ReasonIs(TerminationReason::kOptimal); + return EnsureReasonIs(TerminationReason::kOptimal); } absl::Status Termination::IsOptimalOrFeasible() const { - return ReasonIsAnyOf( + return EnsureReasonIsAnyOf( {TerminationReason::kOptimal, TerminationReason::kFeasible}); } diff --git a/ortools/math_opt/cpp/solve_result.h b/ortools/math_opt/cpp/solve_result.h index 7498fe5026..db0be39b5d 100644 --- a/ortools/math_opt/cpp/solve_result.h +++ b/ortools/math_opt/cpp/solve_result.h @@ -334,11 +334,11 @@ struct Termination { // Returns an OkStatus if the reason of this `Termination` is `reason`, or an // `InternalError` otherwise. - absl::Status ReasonIs(TerminationReason reason) const; + absl::Status EnsureReasonIs(TerminationReason reason) const; // Returns an OkStatus if the reason of this `Termination` is in `reasons`, or // an `InternalError` otherwise. - absl::Status ReasonIsAnyOf( + absl::Status EnsureReasonIsAnyOf( std::initializer_list reasons) const; // Returns termination with reason kOptimal, the provided objective for both diff --git a/ortools/math_opt/python/normalize.py b/ortools/math_opt/python/normalize.py index 576dcd253b..6347411988 100644 --- a/ortools/math_opt/python/normalize.py +++ b/ortools/math_opt/python/normalize.py @@ -20,7 +20,7 @@ """Utility functions for normalizing proto3 message objects in Python.""" -from google3.google.protobuf import duration_pb2 +from google.protobuf import duration_pb2 from google.protobuf import descriptor from google.protobuf import message diff --git a/ortools/math_opt/rpc.proto b/ortools/math_opt/rpc.proto new file mode 100644 index 0000000000..30dd7eeba8 --- /dev/null +++ b/ortools/math_opt/rpc.proto @@ -0,0 +1,61 @@ +// Copyright 2010-2022 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 +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package operations_research.math_opt; + +import "ortools/math_opt/callback.proto"; +import "ortools/math_opt/infeasible_subsystem.proto"; +import "ortools/math_opt/model.proto"; +import "ortools/math_opt/model_parameters.proto"; +import "ortools/math_opt/model_update.proto"; +import "ortools/math_opt/parameters.proto"; +import "ortools/math_opt/result.proto"; + +option java_package = "com.google.ortools.mathopt"; +option java_multiple_files = true; + +// Request for a unary remote solve in MathOpt. +message SolveRequest { + // Solver type to numerically solve the problem. Note that if a solver does + // not support a specific feautre in the model, the optimization procedure + // won't be successful. + SolverTypeProto solver_type = 1; + + // A mathematical representation of the optimization problem to solve. + ModelProto model = 2; + + SolverInitializerProto initializer = 3; + + // Parameters to control a single solve. The enable_output parameter is + // handled specifically. For solvers that support messages callbacks, setting + // it to true will have the server register a message callback. The resulting + // messages will be returned in SolveResponse.messages. For other + // solvers, setting enable_output to true will result in an error. + SolveParametersProto parameters = 4; + + // Parameters to control a single solve that are specific to the input model + // (see SolveParametersProto for model independent parameters). + ModelSolveParametersProto model_parameters = 5; +} + +// Response for a unary remote solve in MathOpt. +message SolveResponse { + // Description of the output of solving the model in the request. + SolveResultProto result = 1; + + // If SolveParametersProto.enable_output has been used, this will contain log + // messages for solvers that support message callbacks. + repeated string messages = 2; +} diff --git a/ortools/math_opt/solvers/BUILD.bazel b/ortools/math_opt/solvers/BUILD.bazel index bae6cfd88a..6203c2df1e 100644 --- a/ortools/math_opt/solvers/BUILD.bazel +++ b/ortools/math_opt/solvers/BUILD.bazel @@ -11,8 +11,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("@rules_python//python:proto.bzl", "py_proto_library") load("@rules_cc//cc:defs.bzl", "cc_proto_library") +load("@rules_python//python:proto.bzl", "py_proto_library") package(default_visibility = ["//ortools/math_opt:__subpackages__"]) @@ -27,6 +27,7 @@ cc_library( ":gscip_solver_callback", ":message_callback_data", "//ortools/base:cleanup", + "//ortools/base:linked_hash_map", "//ortools/base:map_util", "//ortools/base:protoutil", "//ortools/base:status_macros", diff --git a/ortools/math_opt/solvers/glop_solver.cc b/ortools/math_opt/solvers/glop_solver.cc index 03cdf8afa5..298b665ffb 100644 --- a/ortools/math_opt/solvers/glop_solver.cc +++ b/ortools/math_opt/solvers/glop_solver.cc @@ -353,15 +353,10 @@ absl::StatusOr GlopSolver::MergeSolveParameters( case LP_ALGORITHM_DUAL_SIMPLEX: result.set_use_dual_simplex(true); break; - case LP_ALGORITHM_BARRIER: - warnings.emplace_back( - "GLOP does not support 'LP_ALGORITHM_BARRIER' value for " - "'lp_algorithm' parameter."); - break; default: - LOG(FATAL) << "LPAlgorithm: " - << ProtoEnumToString(solve_parameters.lp_algorithm()) - << " unknown, error setting GLOP parameters"; + warnings.emplace_back(absl::StrCat( + "GLOP does not support the 'lp_algorithm' parameter value: ", + ProtoEnumToString(solve_parameters.lp_algorithm()))); } } if (!result.has_use_scaling() && !result.has_scaling_method() && diff --git a/ortools/math_opt/solvers/gscip_solver.cc b/ortools/math_opt/solvers/gscip_solver.cc index b38ebb4395..8fccc67924 100644 --- a/ortools/math_opt/solvers/gscip_solver.cc +++ b/ortools/math_opt/solvers/gscip_solver.cc @@ -39,6 +39,7 @@ #include "absl/types/span.h" #include "google/protobuf/repeated_ptr_field.h" #include "ortools/base/cleanup.h" +#include "ortools/base/linked_hash_map.h" #include "ortools/base/logging.h" #include "ortools/base/map_util.h" #include "ortools/base/protoutil.h" @@ -246,60 +247,17 @@ inline GScipVarType GScipVarTypeFromIsInteger(const bool is_integer) { return is_integer ? GScipVarType::kInteger : GScipVarType::kContinuous; } -// Used to delay the evaluation of a costly computation until the first time it -// is actually needed. -// -// The typical use is when we have two independent branches that need the same -// data but we don't want to compute these data if we don't enter any of those -// branches. -// -// Usage: -// LazyInitialized xxx([&]() { -// return Xxx(...); -// }); -// -// if (predicate_1) { -// ... -// f(xxx.GetOrCreate()); -// ... -// } -// if (predicate_2) { -// ... -// f(xxx.GetOrCreate()); -// ... -// } -template -class LazyInitialized { - public: - // Checks that the input initializer is not nullptr. - explicit LazyInitialized(std::function initializer) - : initializer_(ABSL_DIE_IF_NULL(initializer)) {} - - // Returns the value returned by initializer(), calling it the first time. - const T& GetOrCreate() { - if (!value_) { - value_ = initializer_(); - } - return *value_; - } - - private: - const std::function initializer_; - std::optional value_; -}; - template SparseDoubleVectorProto FillSparseDoubleVector( - const std::vector& ids_in_order, - const absl::flat_hash_map& id_map, + const gtl::linked_hash_map& id_map, const absl::flat_hash_map& value_map, const SparseVectorFilterProto& filter) { SparseVectorFilterPredicate predicate(filter); SparseDoubleVectorProto result; - for (const int64_t variable_id : ids_in_order) { - const double value = value_map.at(id_map.at(variable_id)); - if (predicate.AcceptsAndUpdate(variable_id, value)) { - result.add_ids(variable_id); + for (const auto [mathopt_id, scip_id] : id_map) { + const double value = value_map.at(scip_id); + if (predicate.AcceptsAndUpdate(mathopt_id, value)) { + result.add_ids(mathopt_id); result.add_values(value); } } @@ -989,15 +947,6 @@ absl::StatusOr GScipSolver::CreateSolveResultProto( } }; - LazyInitialized> sorted_variables([&]() { - std::vector sorted; - sorted.reserve(variables_.size()); - for (const auto& entry : variables_) { - sorted.emplace_back(entry.first); - } - std::sort(sorted.begin(), sorted.end()); - return sorted; - }); CHECK_EQ(gscip_result.solutions.size(), gscip_result.objective_values.size()); for (int i = 0; i < gscip_result.solutions.size(); ++i) { // GScip ensures the solutions are returned best objective first. @@ -1009,14 +958,13 @@ absl::StatusOr GScipSolver::CreateSolveResultProto( solution->mutable_primal_solution(); primal_solution->set_objective_value(gscip_result.objective_values[i]); primal_solution->set_feasibility_status(SOLUTION_STATUS_FEASIBLE); - *primal_solution->mutable_variable_values() = FillSparseDoubleVector( - sorted_variables.GetOrCreate(), variables_, gscip_result.solutions[i], - model_parameters.variable_values_filter()); + *primal_solution->mutable_variable_values() = + FillSparseDoubleVector(variables_, gscip_result.solutions[i], + model_parameters.variable_values_filter()); } if (!gscip_result.primal_ray.empty()) { *solve_result.add_primal_rays()->mutable_variable_values() = - FillSparseDoubleVector(sorted_variables.GetOrCreate(), variables_, - gscip_result.primal_ray, + FillSparseDoubleVector(variables_, gscip_result.primal_ray, model_parameters.variable_values_filter()); } ASSIGN_OR_RETURN(*solve_result.mutable_termination(), diff --git a/ortools/math_opt/solvers/gscip_solver.h b/ortools/math_opt/solvers/gscip_solver.h index c6c6a32ee1..c9a8a2b1ed 100644 --- a/ortools/math_opt/solvers/gscip_solver.h +++ b/ortools/math_opt/solvers/gscip_solver.h @@ -25,6 +25,7 @@ #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/types/span.h" +#include "ortools/base/linked_hash_map.h" #include "ortools/gscip/gscip.h" #include "ortools/gscip/gscip.pb.h" #include "ortools/gscip/gscip_event_handler.h" @@ -195,8 +196,9 @@ class GScipSolver : public SolverInterface { absl::Status RegisterHandlers(); const std::unique_ptr gscip_; + InterruptEventHandler interrupt_event_handler_; - absl::flat_hash_map variables_; + gtl::linked_hash_map variables_; bool has_quadratic_objective_ = false; absl::flat_hash_map linear_constraints_; absl::flat_hash_map quadratic_constraints_; diff --git a/ortools/math_opt/solvers/gurobi_solver.cc b/ortools/math_opt/solvers/gurobi_solver.cc index eaa22b27f7..d1d4218627 100644 --- a/ortools/math_opt/solvers/gurobi_solver.cc +++ b/ortools/math_opt/solvers/gurobi_solver.cc @@ -891,6 +891,7 @@ absl::StatusOr GurobiSolver::ExtractSolveResultProto( GetBestDualBound(solution_and_claims.solutions)); solution_claims = solution_and_claims.solution_claims; + // TODO(b/195295177): Add tests for rays in unbounded MIPs RETURN_IF_ERROR(FillRays(model_parameters, solution_claims, result)); for (SolutionProto& solution : solution_and_claims.solutions) { @@ -2929,7 +2930,9 @@ absl::StatusOr GurobiSolver::Solve( RETURN_IF_ERROR( UpdateInt32ListAttribute(model_parameters.branching_priorities(), GRB_INT_ATTR_BRANCHPRIORITY, variables_map_)); - RETURN_IF_ERROR(SetMultiObjectiveTolerances(model_parameters)); + if (is_multi_objective_mode()) { + RETURN_IF_ERROR(SetMultiObjectiveTolerances(model_parameters)); + } // Here we register the callback when we either have a user callback or a // local interrupter. The rationale for doing so when we have only an diff --git a/ortools/math_opt/solvers/gurobi_solver.h b/ortools/math_opt/solvers/gurobi_solver.h index abbb715d8c..e1271785f4 100644 --- a/ortools/math_opt/solvers/gurobi_solver.h +++ b/ortools/math_opt/solvers/gurobi_solver.h @@ -379,6 +379,8 @@ class GurobiSolver : public SolverInterface { // `.constraint_index` is set and refers to the Gurobi linear constraint index // for a slack constraint just added to the model such that: // `expression` == `.variable_index`. + // TODO(b/267310257): Use this for linear constraint slacks, and maybe move it + // up the stack to a bridge. absl::StatusOr CreateSlackVariableEqualToExpression(const LinearExpressionProto& expression);