From a70cc56969dab8bebac50d16539d9ec45cc750ec Mon Sep 17 00:00:00 2001 From: Corentin Le Molgat Date: Fri, 8 Nov 2024 17:36:50 +0100 Subject: [PATCH] math_opt: Export from google3 --- ortools/gurobi/environment.cc | 6 +++ ortools/gurobi/environment.h | 2 + ortools/math_opt/BUILD.bazel | 3 +- .../quadratic/quadratic_constraint.h | 2 +- .../constraints/second_order_cone/BUILD.bazel | 1 + .../second_order_cone_constraint.h | 2 + ortools/math_opt/core/math_opt_proto_utils.cc | 36 +++++++++++++++ ortools/math_opt/core/math_opt_proto_utils.h | 8 ++++ ortools/math_opt/cpp/BUILD.bazel | 5 ++ .../compute_infeasible_subsystem_result.cc | 6 ++- ortools/math_opt/cpp/enums.h | 10 ++-- ortools/math_opt/cpp/message_callback.cc | 3 +- ortools/math_opt/cpp/model.cc | 25 +++++++++- ortools/math_opt/cpp/model.h | 29 +++++++++--- .../math_opt/cpp/model_solve_parameters.cc | 46 +++++++++++++++---- ortools/math_opt/cpp/model_solve_parameters.h | 20 ++++++-- ortools/math_opt/cpp/solve_impl.cc | 4 +- ortools/math_opt/cpp/solver_resources.h | 16 ++++--- ortools/math_opt/model_parameters.proto | 10 ++++ ortools/math_opt/solver_tests/BUILD.bazel | 1 + ortools/math_opt/solver_tests/CMakeLists.txt | 3 +- .../solver_tests/multi_objective_tests.cc | 2 + ortools/math_opt/solvers/gurobi/BUILD.bazel | 1 + ortools/math_opt/solvers/gurobi/g_gurobi.cc | 28 +++++++++++ ortools/math_opt/solvers/gurobi/g_gurobi.h | 15 ++++++ ortools/math_opt/storage/BUILD.bazel | 1 + ortools/math_opt/storage/iterators.h | 2 +- ortools/math_opt/storage/model_storage.cc | 2 + ortools/math_opt/storage/model_storage.h | 6 +-- ortools/math_opt/validators/BUILD.bazel | 3 ++ .../validators/model_parameters_validator.cc | 14 ++++++ 31 files changed, 268 insertions(+), 44 deletions(-) diff --git a/ortools/gurobi/environment.cc b/ortools/gurobi/environment.cc index 1e606fc9b8..483bb658b0 100644 --- a/ortools/gurobi/environment.cc +++ b/ortools/gurobi/environment.cc @@ -226,6 +226,8 @@ std::function GRBemptyenv = nullptr; std::function GRBloadenv = nullptr; std::function GRBstartenv = nullptr; std::function GRBgetenv = nullptr; +std::function GRBgetmultiobjenv = nullptr; +std::function GRBdiscardmultiobjenvs = nullptr; std::function GRBfreeenv = nullptr; std::function GRBgeterrormsg = nullptr; std::function GRBversion = @@ -337,6 +339,10 @@ void LoadGurobiFunctions(DynamicLibrary* gurobi_dynamic_library) { gurobi_dynamic_library->GetFunction(&GRBgetstrparaminfo, "GRBgetstrparaminfo"); gurobi_dynamic_library->GetFunction(&GRBgetenv, "GRBgetenv"); + gurobi_dynamic_library->GetFunction(&GRBgetmultiobjenv, + "GRBgetmultiobjenv"); + gurobi_dynamic_library->GetFunction(&GRBdiscardmultiobjenvs, + "GRBdiscardmultiobjenvs"); gurobi_dynamic_library->GetFunction(&GRBfreeenv, "GRBfreeenv"); gurobi_dynamic_library->GetFunction(&GRBgeterrormsg, "GRBgeterrormsg"); gurobi_dynamic_library->GetFunction(&GRBversion, "GRBversion"); diff --git a/ortools/gurobi/environment.h b/ortools/gurobi/environment.h index 900fcf7f88..a6ad5995b1 100644 --- a/ortools/gurobi/environment.h +++ b/ortools/gurobi/environment.h @@ -723,6 +723,8 @@ extern std::function GRBgetdblparaminfo; extern std::function GRBgetstrparaminfo; extern std::function GRBgetenv; +extern std::function GRBgetmultiobjenv; +extern std::function GRBdiscardmultiobjenvs; extern std::function GRBfreeenv; extern std::function GRBgeterrormsg; extern std::function GRBversion; diff --git a/ortools/math_opt/BUILD.bazel b/ortools/math_opt/BUILD.bazel index ef0f064291..1ea2bcfa53 100644 --- a/ortools/math_opt/BUILD.bazel +++ b/ortools/math_opt/BUILD.bazel @@ -11,9 +11,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +load("@com_google_protobuf//bazel:proto_library.bzl", "proto_library") load("@rules_cc//cc:defs.bzl", "cc_proto_library") load("@rules_python//python:proto.bzl", "py_proto_library") -load("@com_google_protobuf//bazel:proto_library.bzl", "proto_library") package(default_visibility = ["//visibility:public"]) @@ -64,6 +64,7 @@ proto_library( deps = [ ":solution_proto", ":sparse_containers_proto", + "@com_google_protobuf//:duration_proto", ], ) diff --git a/ortools/math_opt/constraints/quadratic/quadratic_constraint.h b/ortools/math_opt/constraints/quadratic/quadratic_constraint.h index 639097da6e..a808c06215 100644 --- a/ortools/math_opt/constraints/quadratic/quadratic_constraint.h +++ b/ortools/math_opt/constraints/quadratic/quadratic_constraint.h @@ -79,7 +79,7 @@ class QuadraticConstraint { // The quadratic expression will have a zero offset, even if the constraint // was created with a non-zero one. For example: // - // const LinearConstraint c = + // const QuadraticConstraint c = // model.AddQuadraticConstraint(3.2 <= x*x + 1.0 <= 4.2); // // // Here `e` will contain 3.2 - 1.0 <= x*x <= 4.2 - 1.0. diff --git a/ortools/math_opt/constraints/second_order_cone/BUILD.bazel b/ortools/math_opt/constraints/second_order_cone/BUILD.bazel index 37ed646b57..9b5b5dc37a 100644 --- a/ortools/math_opt/constraints/second_order_cone/BUILD.bazel +++ b/ortools/math_opt/constraints/second_order_cone/BUILD.bazel @@ -24,6 +24,7 @@ cc_library( "//ortools/math_opt/cpp:variable_and_expressions", "//ortools/math_opt/storage:linear_expression_data", "//ortools/math_opt/storage:model_storage", + "//ortools/math_opt/storage:model_storage_types", "@com_google_absl//absl/strings", ], ) diff --git a/ortools/math_opt/constraints/second_order_cone/second_order_cone_constraint.h b/ortools/math_opt/constraints/second_order_cone/second_order_cone_constraint.h index 490be37bba..9042513679 100644 --- a/ortools/math_opt/constraints/second_order_cone/second_order_cone_constraint.h +++ b/ortools/math_opt/constraints/second_order_cone/second_order_cone_constraint.h @@ -24,9 +24,11 @@ #include "absl/strings/string_view.h" #include "ortools/base/strong_int.h" +#include "ortools/math_opt/constraints/second_order_cone/storage.h" // IWYU pragma: keep (`AtomicConstraintTraits`) #include "ortools/math_opt/constraints/util/model_util.h" #include "ortools/math_opt/cpp/variable_and_expressions.h" #include "ortools/math_opt/storage/model_storage.h" +#include "ortools/math_opt/storage/model_storage_types.h" namespace operations_research::math_opt { diff --git a/ortools/math_opt/core/math_opt_proto_utils.cc b/ortools/math_opt/core/math_opt_proto_utils.cc index 957ecc3c0d..d6034167b2 100644 --- a/ortools/math_opt/core/math_opt_proto_utils.cc +++ b/ortools/math_opt/core/math_opt_proto_utils.cc @@ -27,6 +27,7 @@ #include "absl/strings/string_view.h" #include "ortools/base/logging.h" #include "ortools/base/status_builder.h" +#include "ortools/base/status_macros.h" #include "ortools/math_opt/callback.pb.h" #include "ortools/math_opt/core/sparse_vector_view.h" #include "ortools/math_opt/model.pb.h" @@ -542,6 +543,41 @@ bool UpdateIsSupported(const ModelUpdateProto& update, return true; } +absl::Status ModelSolveParametersAreSupported( + const ModelSolveParametersProto& model_parameters, + const SupportedProblemStructures& support_menu, + const absl::string_view solver_name) { + const auto validate_support = [solver_name]( + const absl::string_view structure, + const SupportType support) -> absl::Status { + switch (support) { + case SupportType::kSupported: + return absl::OkStatus(); + case SupportType::kNotSupported: + return util::InvalidArgumentErrorBuilder() + << structure << " is not supported as " << solver_name + << " does not support multiple objectives"; + case SupportType::kNotImplemented: + return util::UnimplementedErrorBuilder() + << structure + << " is not supported as MathOpt does not currently support " + << solver_name << " models with multiple objectives"; + } + return absl::OkStatus(); + }; + if (model_parameters.has_primary_objective_parameters()) { + RETURN_IF_ERROR(validate_support( + "ModelSolveParametersProto.primary_objective_parameters", + support_menu.multi_objectives)); + } + if (!model_parameters.auxiliary_objective_parameters().empty()) { + RETURN_IF_ERROR(validate_support( + "ModelSolveParametersProto.auxiliary_objective_parameters", + support_menu.multi_objectives)); + } + return absl::OkStatus(); +} + void UpgradeSolveResultProtoForStatsMigration( SolveResultProto& solve_result_proto) { *solve_result_proto.mutable_termination()->mutable_problem_status() = diff --git a/ortools/math_opt/core/math_opt_proto_utils.h b/ortools/math_opt/core/math_opt_proto_utils.h index 337d7362f7..7304813a06 100644 --- a/ortools/math_opt/core/math_opt_proto_utils.h +++ b/ortools/math_opt/core/math_opt_proto_utils.h @@ -343,6 +343,14 @@ absl::Status ModelIsSupported(const ModelProto& model, bool UpdateIsSupported(const ModelUpdateProto& update, const SupportedProblemStructures& support_menu); +// Returns an InvalidArgumentError (respectively, UnimplementedError) if a +// problem structure is present in `model_parameters` and not supported (resp., +// not yet implemented) according to `support_menu`. +absl::Status ModelSolveParametersAreSupported( + const ModelSolveParametersProto& model_parameters, + const SupportedProblemStructures& support_menu, + absl::string_view solver_name); + void UpgradeSolveResultProtoForStatsMigration( SolveResultProto& solve_result_proto); diff --git a/ortools/math_opt/cpp/BUILD.bazel b/ortools/math_opt/cpp/BUILD.bazel index a6063883b1..343f32eb67 100644 --- a/ortools/math_opt/cpp/BUILD.bazel +++ b/ortools/math_opt/cpp/BUILD.bazel @@ -84,6 +84,7 @@ cc_library( "//ortools/math_opt/constraints/indicator:indicator_constraint", "//ortools/math_opt/constraints/quadratic:quadratic_constraint", "//ortools/math_opt/constraints/second_order_cone:second_order_cone_constraint", + "//ortools/math_opt/constraints/second_order_cone:storage", "//ortools/math_opt/constraints/sos:sos1_constraint", "//ortools/math_opt/constraints/sos:sos2_constraint", "//ortools/math_opt/constraints/util:model_util", @@ -277,6 +278,7 @@ cc_library( ":solution", ":sparse_containers", ":variable_and_expressions", + "//ortools/base:protoutil", "//ortools/base:status_macros", "//ortools/math_opt:model_parameters_cc_proto", "//ortools/math_opt:solution_cc_proto", @@ -285,8 +287,10 @@ cc_library( "//ortools/util:status_macros", "@com_google_absl//absl/algorithm:container", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log:check", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/time", "@com_google_protobuf//:protobuf", ], ) @@ -316,6 +320,7 @@ cc_library( "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/strings", "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/types:span", "@com_google_protobuf//:protobuf", ], ) diff --git a/ortools/math_opt/cpp/compute_infeasible_subsystem_result.cc b/ortools/math_opt/cpp/compute_infeasible_subsystem_result.cc index 6a35dec042..1df1c9fc19 100644 --- a/ortools/math_opt/cpp/compute_infeasible_subsystem_result.cc +++ b/ortools/math_opt/cpp/compute_infeasible_subsystem_result.cc @@ -77,7 +77,8 @@ absl::Status BoundsMapProtoToCpp( const google::protobuf::Map& source, absl::flat_hash_map& target, const ModelStorage* const model, - bool (ModelStorage::*const contains_strong_id)(typename K::IdType id) const, + bool (ModelStorage::* const contains_strong_id)(typename K::IdType id) + const, const absl::string_view object_name) { for (const auto& [raw_id, bounds_proto] : source) { const typename K::IdType strong_id(raw_id); @@ -95,7 +96,8 @@ template absl::Status RepeatedIdsProtoToCpp( const google::protobuf::RepeatedField& source, absl::flat_hash_set& target, const ModelStorage* const model, - bool (ModelStorage::*const contains_strong_id)(typename K::IdType id) const, + bool (ModelStorage::* const contains_strong_id)(typename K::IdType id) + const, const absl::string_view object_name) { for (const int64_t raw_id : source) { const typename K::IdType strong_id(raw_id); diff --git a/ortools/math_opt/cpp/enums.h b/ortools/math_opt/cpp/enums.h index 822c88024b..4cd643656e 100644 --- a/ortools/math_opt/cpp/enums.h +++ b/ortools/math_opt/cpp/enums.h @@ -228,11 +228,11 @@ std::optional EnumFromString(absl::string_view str); // // It calls EnumToOptString(), printing the returned value if not nullopt. When // nullopt it prints the enum numeric value instead. -template . - typename = std::enable_if_t::kIsImplemented>> -std::ostream& operator<<(std::ostream& out, const E value) { +// We must use enable_if here to prevent this overload to be selected +// for other types than ones that implement Enum. +template +std::enable_if_t::kIsImplemented, std::ostream&> operator<<( + std::ostream& out, const E value) { const std::optional opt_str = EnumToOptString(value); if (opt_str.has_value()) { out << *opt_str; diff --git a/ortools/math_opt/cpp/message_callback.cc b/ortools/math_opt/cpp/message_callback.cc index 892c97b394..275a6071c0 100644 --- a/ortools/math_opt/cpp/message_callback.cc +++ b/ortools/math_opt/cpp/message_callback.cc @@ -22,6 +22,7 @@ #include "absl/base/thread_annotations.h" #include "absl/strings/string_view.h" #include "absl/synchronization/mutex.h" +#include "absl/types/span.h" #include "google/protobuf/repeated_field.h" #include "google/protobuf/repeated_ptr_field.h" #include "ortools/base/logging.h" @@ -50,7 +51,7 @@ class PrinterMessageCallbackImpl { const std::string prefix_; }; -void PushBack(const std::vector& messages, +void PushBack(absl::Span messages, std::vector* const sink) { sink->insert(sink->end(), messages.begin(), messages.end()); } diff --git a/ortools/math_opt/cpp/model.cc b/ortools/math_opt/cpp/model.cc index 2fbbb2b758..0558c63473 100644 --- a/ortools/math_opt/cpp/model.cc +++ b/ortools/math_opt/cpp/model.cc @@ -32,6 +32,7 @@ #include "ortools/math_opt/constraints/indicator/indicator_constraint.h" #include "ortools/math_opt/constraints/quadratic/quadratic_constraint.h" #include "ortools/math_opt/constraints/second_order_cone/second_order_cone_constraint.h" +#include "ortools/math_opt/constraints/second_order_cone/storage.h" #include "ortools/math_opt/constraints/sos/sos1_constraint.h" #include "ortools/math_opt/constraints/sos/sos2_constraint.h" #include "ortools/math_opt/constraints/util/model_util.h" @@ -261,7 +262,7 @@ void Model::SetObjective(const Objective objective, } } -void Model::AddToObjective(Objective objective, +void Model::AddToObjective(const Objective objective, const LinearExpression& expression) { CheckModel(objective.storage()); CheckOptionalModel(expression.storage()); @@ -272,6 +273,28 @@ void Model::AddToObjective(Objective objective, } } +std::vector Model::NonzeroVariablesInLinearObjective( + const Objective objective) const { + CheckModel(objective.storage()); + std::vector result; + result.reserve(storage()->num_linear_objective_terms(objective.typed_id())); + for (const auto [var_id, unused] : + storage()->linear_objective(objective.typed_id())) { + result.push_back(Variable(storage(), var_id)); + } + return result; +} + +std::vector Model::NonzeroVariablesInQuadraticObjective() const { + std::vector result; + for (const auto& [var_id1, var_id2, unused] : + storage()->quadratic_objective_terms(kPrimaryObjectiveId)) { + result.push_back(Variable(storage(), var_id1)); + result.push_back(Variable(storage(), var_id2)); + } + return result; +} + ModelProto Model::ExportModel(const bool remove_names) const { return storage()->ExportModel(remove_names); } diff --git a/ortools/math_opt/cpp/model.h b/ortools/math_opt/cpp/model.h index c8b0ad01d2..45403c2cfb 100644 --- a/ortools/math_opt/cpp/model.h +++ b/ortools/math_opt/cpp/model.h @@ -21,7 +21,6 @@ #include #include #include -#include #include #include "absl/log/check.h" @@ -162,7 +161,7 @@ class Model { std::unique_ptr Clone( std::optional new_name = std::nullopt) const; - inline const std::string& name() const; + inline absl::string_view name() const; ////////////////////////////////////////////////////////////////////////////// // Variable methods @@ -217,7 +216,7 @@ class Model { inline Variable variable(VariableId id) const; // Returns the variable name. - inline const std::string& name(Variable variable) const; + inline absl::string_view name(Variable variable) const; // Sets a variable lower bound. inline void set_lower_bound(Variable variable, double lower_bound); @@ -318,7 +317,7 @@ class Model { inline LinearConstraint linear_constraint(LinearConstraintId id) const; // Returns the linear constraint name. - inline const std::string& name(LinearConstraint constraint) const; + inline absl::string_view name(LinearConstraint constraint) const; // Sets a linear constraint lower bound. inline void set_lower_bound(LinearConstraint constraint, double lower_bound); @@ -750,6 +749,13 @@ class Model { // Prefer set_maximize() and set_minimize() above for more readable code. inline void set_is_maximize(bool is_maximize); + // Returns all variables that have a nonzero coefficient in the linear part of + // the primary objective. Result is not sorted. + inline std::vector NonzeroVariablesInLinearObjective() const; + // Returns all variables that have a nonzero coefficient in the quadratic part + // of the primary objective. Result is not sorted. + std::vector NonzeroVariablesInQuadraticObjective() const; + ////////////////////////////////////////////////////////////////////////////// // Auxiliary objective methods // @@ -847,6 +853,11 @@ class Model { // Prefer set_maximize() and set_minimize() above for more readable code. inline void set_is_maximize(Objective objective, bool is_maximize); + // Returns all variables that have a nonzero coefficient in the linear part of + // the `objective`. Result is not sorted. + std::vector NonzeroVariablesInLinearObjective( + Objective objective) const; + // Returns a proto representation of the optimization model. // // See FromModelProto() to build a Model from a proto. @@ -922,7 +933,7 @@ class Model { // ------------------------------- Variables ----------------------------------- -const std::string& Model::name() const { return storage()->name(); } +absl::string_view Model::name() const { return storage()->name(); } Variable Model::AddVariable(const absl::string_view name) { return Variable(storage(), storage()->AddVariable(name)); @@ -978,7 +989,7 @@ Variable Model::variable(const VariableId id) const { return Variable(storage(), id); } -const std::string& Model::name(const Variable variable) const { +absl::string_view Model::name(const Variable variable) const { CheckModel(variable.storage()); return storage()->variable_name(variable.typed_id()); } @@ -1080,7 +1091,7 @@ LinearConstraint Model::linear_constraint(const LinearConstraintId id) const { return LinearConstraint(storage(), id); } -const std::string& Model::name(const LinearConstraint constraint) const { +absl::string_view Model::name(const LinearConstraint constraint) const { CheckModel(constraint.storage()); return storage()->linear_constraint_name(constraint.typed_id()); } @@ -1491,6 +1502,10 @@ void Model::set_is_maximize(const bool is_maximize) { storage()->set_is_maximize(kPrimaryObjectiveId, is_maximize); } +std::vector Model::NonzeroVariablesInLinearObjective() const { + return NonzeroVariablesInLinearObjective(primary_objective()); +} + // -------------------------- Auxiliary objectives ----------------------------- Objective Model::AddAuxiliaryObjective(const int64_t priority, diff --git a/ortools/math_opt/cpp/model_solve_parameters.cc b/ortools/math_opt/cpp/model_solve_parameters.cc index 4554dad921..50bb07f90e 100644 --- a/ortools/math_opt/cpp/model_solve_parameters.cc +++ b/ortools/math_opt/cpp/model_solve_parameters.cc @@ -20,9 +20,12 @@ #include #include "absl/algorithm/container.h" +#include "absl/log/check.h" #include "absl/status/status.h" #include "absl/status/statusor.h" +#include "absl/time/time.h" #include "google/protobuf/repeated_field.h" +#include "ortools/base/protoutil.h" #include "ortools/base/status_macros.h" #include "ortools/math_opt/cpp/linear_constraint.h" #include "ortools/math_opt/cpp/model.h" @@ -137,8 +140,8 @@ ModelSolveParameters::SolutionHint::FromProto( }; } -ObjectiveParametersProto ModelSolveParameters::ObjectiveParameters::Proto() - const { +absl::StatusOr +ModelSolveParameters::ObjectiveParameters::Proto() const { ObjectiveParametersProto params; if (objective_degradation_absolute_tolerance) { params.set_objective_degradation_absolute_tolerance( @@ -148,10 +151,14 @@ ObjectiveParametersProto ModelSolveParameters::ObjectiveParameters::Proto() params.set_objective_degradation_relative_tolerance( *objective_degradation_relative_tolerance); } + if (time_limit < absl::InfiniteDuration()) { + RETURN_IF_ERROR(util_time::EncodeGoogleApiProto( + time_limit, params.mutable_time_limit())); + } return params; } -ModelSolveParameters::ObjectiveParameters +absl::StatusOr ModelSolveParameters::ObjectiveParameters::FromProto( const ObjectiveParametersProto& proto) { ObjectiveParameters result; @@ -163,11 +170,18 @@ ModelSolveParameters::ObjectiveParameters::FromProto( result.objective_degradation_relative_tolerance = proto.objective_degradation_relative_tolerance(); } + if (proto.has_time_limit()) { + OR_ASSIGN_OR_RETURN3(result.time_limit, + util_time::DecodeGoogleApiProto(proto.time_limit()), + _ << "invalid time_limit"); + } else { + result.time_limit = absl::InfiniteDuration(); + } return result; } // TODO: b/315974557 - Return an error if a RepeatedField is too long. -ModelSolveParametersProto ModelSolveParameters::Proto() const { +absl::StatusOr ModelSolveParameters::Proto() const { ModelSolveParametersProto ret; *ret.mutable_variable_values_filter() = variable_values_filter.Proto(); *ret.mutable_dual_values_filter() = dual_values_filter.Proto(); @@ -195,11 +209,15 @@ ModelSolveParametersProto ModelSolveParameters::Proto() const { } } for (const auto& [objective, params] : objective_parameters) { - if (objective.id()) { - (*ret.mutable_auxiliary_objective_parameters())[*objective.id()] = - params.Proto(); + if (objective.id().has_value()) { + OR_ASSIGN_OR_RETURN3( + ((*ret.mutable_auxiliary_objective_parameters())[*objective.id()]), + params.Proto(), + _ << "invalid parameters for objective " << *objective.id()); } else { - *ret.mutable_primary_objective_parameters() = params.Proto(); + OR_ASSIGN_OR_RETURN3(*ret.mutable_primary_objective_parameters(), + params.Proto(), + _ << "invalid parameters for primary objective"); } } if (!lazy_linear_constraints.empty()) { @@ -253,9 +271,13 @@ absl::StatusOr ModelSolveParameters::FromProto( VariableValuesFromProto(model.storage(), proto.branching_priorities()), _ << "invalid branching_priorities"); if (proto.has_primary_objective_parameters()) { + OR_ASSIGN_OR_RETURN3( + auto primary_objective_params, + ObjectiveParameters::FromProto(proto.primary_objective_parameters()), + _ << "invalid primary_objective_parameters"); result.objective_parameters.try_emplace( Objective::Primary(model.storage()), - ObjectiveParameters::FromProto(proto.primary_objective_parameters())); + std::move(primary_objective_params)); } for (const auto& [id, aux_obj_params_proto] : proto.auxiliary_objective_parameters()) { @@ -264,9 +286,13 @@ absl::StatusOr ModelSolveParameters::FromProto( << "invalid auxiliary_objective_parameters with id: " << id << ", objective not in the model"; } + OR_ASSIGN_OR_RETURN3( + auto aux_obj_params, + ObjectiveParameters::FromProto(aux_obj_params_proto), + _ << "invalid auxiliary_objective_parameters with id: " << id); result.objective_parameters.try_emplace( Objective::Auxiliary(model.storage(), AuxiliaryObjectiveId{id}), - ObjectiveParameters::FromProto(aux_obj_params_proto)); + std::move(aux_obj_params)); } for (int64_t lin_con : proto.lazy_linear_constraint_ids()) { if (!model.has_linear_constraint(lin_con)) { diff --git a/ortools/math_opt/cpp/model_solve_parameters.h b/ortools/math_opt/cpp/model_solve_parameters.h index 88a913adef..faaacf3337 100644 --- a/ortools/math_opt/cpp/model_solve_parameters.h +++ b/ortools/math_opt/cpp/model_solve_parameters.h @@ -27,6 +27,7 @@ #include "absl/container/flat_hash_set.h" #include "absl/status/status.h" #include "absl/status/statusor.h" +#include "absl/time/time.h" #include "ortools/math_opt/cpp/linear_constraint.h" #include "ortools/math_opt/cpp/map_filter.h" // IWYU pragma: export #include "ortools/math_opt/cpp/model.h" @@ -184,10 +185,21 @@ struct ModelSolveParameters { // If set, must be nonnegative. std::optional objective_degradation_relative_tolerance; - // Returns the proto equivalent of this object. - ObjectiveParametersProto Proto() const; + // Maximum time a solver should spend on optimizing this particular + // objective (or infinite if not set). + // + // Note that this does not supersede the global time limit in + // SolveParametersProto.time_limit; both will be enforced when set. + // + // This value is not a hard limit, solve time may slightly exceed this + // value. + absl::Duration time_limit = absl::InfiniteDuration(); - static ObjectiveParameters FromProto(const ObjectiveParametersProto& proto); + // Returns the proto equivalent of this object. + absl::StatusOr Proto() const; + + static absl::StatusOr FromProto( + const ObjectiveParametersProto& proto); }; // Parameters for individual objectives in a multi-objective model. ObjectiveMap objective_parameters; @@ -209,7 +221,7 @@ struct ModelSolveParameters { // // The caller should use CheckModelStorage() as this function does not check // internal consistency of the referenced variables and constraints. - ModelSolveParametersProto Proto() const; + absl::StatusOr Proto() const; // Returns the ModelSolveParameters corresponding to this proto and the given // model. diff --git a/ortools/math_opt/cpp/solve_impl.cc b/ortools/math_opt/cpp/solve_impl.cc index 322bb10497..294ce12639 100644 --- a/ortools/math_opt/cpp/solve_impl.cc +++ b/ortools/math_opt/cpp/solve_impl.cc @@ -78,9 +78,11 @@ absl::StatusOr CallSolve( }; } + ASSIGN_OR_RETURN(ModelSolveParametersProto model_parameters, + arguments.model_parameters.Proto()); const absl::StatusOr solve_result_proto = solver.Solve( {.parameters = arguments.parameters.Proto(), - .model_parameters = arguments.model_parameters.Proto(), + .model_parameters = std::move(model_parameters), .message_callback = arguments.message_callback, .callback_registration = arguments.callback_registration.Proto(), .user_cb = std::move(cb), diff --git a/ortools/math_opt/cpp/solver_resources.h b/ortools/math_opt/cpp/solver_resources.h index 045dce3d07..05d7c6373d 100644 --- a/ortools/math_opt/cpp/solver_resources.h +++ b/ortools/math_opt/cpp/solver_resources.h @@ -30,13 +30,17 @@ namespace operations_research::math_opt { // parameters are hints and may be ignored by the remote server (in particular // in case of solve in a local subprocess, for example). // -// When using RemoteSolve() and RemoteComputeInfeasibleSubsystem(), these hints -// are mostly optional as some defaults will be computed based on the other -// parameters. +// When using: +// - RemoteSolve(), +// - RemoteComputeInfeasibleSubsystem(), +// - XxxRemoteStreamingSolve(), +// - XxxRemoteStreamingComputeInfeasibleSubsystem(), +// these hints are recommended but optional. When they are not provided, +// resource usage will be estimated based on other parameters. // -// When using RemoteStreamingSolve() these hints are used to dimension the -// resources available during the execution of every action; thus it is -// recommended to set them. +// When using NewXxxRemoteStreamingIncrementalSolver() these hints are used to +// dimension the resources available during the execution of every action; thus +// it is recommended to set them. // struct SolverResources { // The number of solver threads that are expected to actually execute in diff --git a/ortools/math_opt/model_parameters.proto b/ortools/math_opt/model_parameters.proto index 5628674746..b7858a27f8 100644 --- a/ortools/math_opt/model_parameters.proto +++ b/ortools/math_opt/model_parameters.proto @@ -17,6 +17,7 @@ syntax = "proto3"; package operations_research.math_opt; +import "google/protobuf/duration.proto"; import "ortools/math_opt/solution.proto"; import "ortools/math_opt/sparse_containers.proto"; @@ -89,6 +90,15 @@ message ObjectiveParametersProto { // // If set, must be nonnegative. optional double objective_degradation_relative_tolerance = 8; + + // Maximum time a solver should spend on optimizing this particular objective + // (or infinite if not set). + // + // Note that this does not supersede the global time limit in + // SolveParametersProto.time_limit; both will be enforced when set. + // + // This value is not a hard limit, solve time may slightly exceed this value. + google.protobuf.Duration time_limit = 9; } // TODO(b/183628247): follow naming convention in fields below. diff --git a/ortools/math_opt/solver_tests/BUILD.bazel b/ortools/math_opt/solver_tests/BUILD.bazel index 48fc1d21f7..ea0cfbe0ca 100644 --- a/ortools/math_opt/solver_tests/BUILD.bazel +++ b/ortools/math_opt/solver_tests/BUILD.bazel @@ -317,6 +317,7 @@ cc_library( "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings:string_view", + "@com_google_absl//absl/time", ], # Make sure the tests are included when using --dynamic_mode=off. alwayslink = 1, diff --git a/ortools/math_opt/solver_tests/CMakeLists.txt b/ortools/math_opt/solver_tests/CMakeLists.txt index 2b18e7de9a..648876ad97 100644 --- a/ortools/math_opt/solver_tests/CMakeLists.txt +++ b/ortools/math_opt/solver_tests/CMakeLists.txt @@ -110,7 +110,8 @@ ortools_cxx_library( TESTING ) -# In CMake or-tools is linked with all enable solvers so this test won't work. +# In CMake, or-tools is linked with all solvers enable at configure time so this +# test won't work... #ortools_cxx_test( # NAME # ${_PREFIX}_unregistered_solver_test diff --git a/ortools/math_opt/solver_tests/multi_objective_tests.cc b/ortools/math_opt/solver_tests/multi_objective_tests.cc index 5b8f1394bb..f11570647d 100644 --- a/ortools/math_opt/solver_tests/multi_objective_tests.cc +++ b/ortools/math_opt/solver_tests/multi_objective_tests.cc @@ -15,10 +15,12 @@ #include #include +#include #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/string_view.h" +#include "absl/time/time.h" #include "gtest/gtest.h" #include "ortools/base/gmock.h" #include "ortools/base/status_macros.h" diff --git a/ortools/math_opt/solvers/gurobi/BUILD.bazel b/ortools/math_opt/solvers/gurobi/BUILD.bazel index 32f8f39ffd..985df15576 100644 --- a/ortools/math_opt/solvers/gurobi/BUILD.bazel +++ b/ortools/math_opt/solvers/gurobi/BUILD.bazel @@ -30,6 +30,7 @@ cc_library( "//ortools/gurobi:environment", "//ortools/gurobi/isv_public:gurobi_isv", "//ortools/math_opt/solvers:gurobi_cc_proto", + "@com_google_absl//absl/log", "@com_google_absl//absl/log:check", "@com_google_absl//absl/log:die_if_null", "@com_google_absl//absl/memory", diff --git a/ortools/math_opt/solvers/gurobi/g_gurobi.cc b/ortools/math_opt/solvers/gurobi/g_gurobi.cc index da57a30368..bfe372f578 100644 --- a/ortools/math_opt/solvers/gurobi/g_gurobi.cc +++ b/ortools/math_opt/solvers/gurobi/g_gurobi.cc @@ -777,6 +777,23 @@ absl::Status Gurobi::ResetParameters() { return ToStatus(GRBresetparams(model_env_)); } +absl::Status Gurobi::SetMultiObjectiveDoubleParam(const char* const name, + const int obj_index, + const double value) { + ASSIGN_OR_RETURN(GRBenv* const obj_env, GetMultiObjectiveEnv(obj_index)); + return util::StatusBuilder(ToStatus(GRBsetdblparam(obj_env, name, value))) + << " for objective index: " << obj_index; +} + +absl::StatusOr Gurobi::GetMultiObjectiveDoubleParam( + const char* const name, const int obj_index) { + ASSIGN_OR_RETURN(GRBenv* const obj_env, GetMultiObjectiveEnv(obj_index)); + double result; + RETURN_IF_ERROR(ToStatus(GRBgetdblparam(obj_env, name, &result))) + << " for objective index: " << obj_index; + return result; +} + void Gurobi::Terminate() { GRBterminate(gurobi_model_); } Gurobi::CallbackContext::CallbackContext(Gurobi* const gurobi, @@ -842,4 +859,15 @@ absl::StatusOr Gurobi::CallbackContext::CbSolution( return result; } +absl::StatusOr Gurobi::GetMultiObjectiveEnv( + const int obj_index) const { + GRBenv* const obj_env = GRBgetmultiobjenv(gurobi_model_, obj_index); + if (obj_env == nullptr) { + return util::InvalidArgumentErrorBuilder() + << "Failed to get objective environment for objective index: " + << obj_index; + } + return obj_env; +} + } // namespace operations_research::math_opt diff --git a/ortools/math_opt/solvers/gurobi/g_gurobi.h b/ortools/math_opt/solvers/gurobi/g_gurobi.h index 5072db8215..9bffc46bc1 100644 --- a/ortools/math_opt/solvers/gurobi/g_gurobi.h +++ b/ortools/math_opt/solvers/gurobi/g_gurobi.h @@ -556,6 +556,20 @@ class Gurobi { // Calls GRBresetparams(). absl::Status ResetParameters(); + ////////////////////////////////////////////////////////////////////////////// + // Multi-objective Parameters + ////////////////////////////////////////////////////////////////////////////// + + // Calls GRBsetdblparam() on the environment associated with the + // `obj_index`-th objective. + absl::Status SetMultiObjectiveDoubleParam(const char* name, int obj_index, + double value); + + // Calls GRBgetdblparam() on the environment associated with the + // `obj_index`-th objective. + absl::StatusOr GetMultiObjectiveDoubleParam(const char* name, + int obj_index); + // Typically not needed. GRBmodel* model() const { return gurobi_model_; } @@ -570,6 +584,7 @@ class Gurobi { // optional_owned_primary_env can be null, primary_env cannot. static absl::StatusOr> New( GRBenvUniquePtr optional_owned_primary_env, GRBenv* primary_env); + absl::StatusOr GetMultiObjectiveEnv(int obj_index) const; const GRBenvUniquePtr owned_primary_env_; // Invariant: Not null. diff --git a/ortools/math_opt/storage/BUILD.bazel b/ortools/math_opt/storage/BUILD.bazel index cb85a81422..9119a4137d 100644 --- a/ortools/math_opt/storage/BUILD.bazel +++ b/ortools/math_opt/storage/BUILD.bazel @@ -203,6 +203,7 @@ cc_library( "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/log:check", + "@com_google_absl//absl/memory", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", diff --git a/ortools/math_opt/storage/iterators.h b/ortools/math_opt/storage/iterators.h index 738e24832c..00c2634122 100644 --- a/ortools/math_opt/storage/iterators.h +++ b/ortools/math_opt/storage/iterators.h @@ -38,7 +38,7 @@ namespace operations_research::math_opt { // const std::pair>& // The returned iterator will be over non-const references to Field as read off // the UpdateData values. -template class UpdateDataFieldIterator { public: diff --git a/ortools/math_opt/storage/model_storage.cc b/ortools/math_opt/storage/model_storage.cc index 9a596c0d15..8799d28054 100644 --- a/ortools/math_opt/storage/model_storage.cc +++ b/ortools/math_opt/storage/model_storage.cc @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -23,6 +24,7 @@ #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" #include "absl/log/check.h" +#include "absl/memory/memory.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/string_view.h" diff --git a/ortools/math_opt/storage/model_storage.h b/ortools/math_opt/storage/model_storage.h index 5574b6f5e1..372aff7f2c 100644 --- a/ortools/math_opt/storage/model_storage.h +++ b/ortools/math_opt/storage/model_storage.h @@ -164,7 +164,7 @@ class ModelStorage { // considered invalid when solving. // // See ApplyUpdateProto() for dealing with subsequent updates. - static absl::StatusOr> FromModelProto( + static absl::StatusOr > FromModelProto( const ModelProto& model_proto); // Creates an empty minimization problem. @@ -326,7 +326,7 @@ class ModelStorage { // The {linear constraint, variable, coefficient} tuples with nonzero linear // constraint matrix coefficients. - inline std::vector> + inline std::vector > linear_constraint_matrix() const; // Returns the variables with nonzero coefficients in a linear constraint. @@ -399,7 +399,7 @@ class ModelStorage { // are ordered such that .first <= .second. All values are nonempty. // // TODO(b/233630053) do no allocate the result, expose an iterator API. - inline std::vector> + inline std::vector > quadratic_objective_terms(ObjectiveId id) const; ////////////////////////////////////////////////////////////////////////////// diff --git a/ortools/math_opt/validators/BUILD.bazel b/ortools/math_opt/validators/BUILD.bazel index 5448a932cd..6777f5fc43 100644 --- a/ortools/math_opt/validators/BUILD.bazel +++ b/ortools/math_opt/validators/BUILD.bazel @@ -194,12 +194,15 @@ cc_library( ":ids_validator", ":solution_validator", ":sparse_vector_validator", + "//ortools/base:protoutil", "//ortools/base:status_macros", "//ortools/math_opt:model_parameters_cc_proto", "//ortools/math_opt:sparse_containers_cc_proto", "//ortools/math_opt/core:model_summary", "//ortools/math_opt/core:sparse_vector_view", + "//ortools/util:status_macros", "@com_google_absl//absl/status", + "@com_google_absl//absl/time", "@com_google_protobuf//:protobuf", ], ) diff --git a/ortools/math_opt/validators/model_parameters_validator.cc b/ortools/math_opt/validators/model_parameters_validator.cc index 1559f84335..92db17d683 100644 --- a/ortools/math_opt/validators/model_parameters_validator.cc +++ b/ortools/math_opt/validators/model_parameters_validator.cc @@ -16,7 +16,9 @@ #include #include "absl/status/status.h" +#include "absl/time/time.h" #include "google/protobuf/repeated_field.h" +#include "ortools/base/protoutil.h" #include "ortools/base/status_builder.h" #include "ortools/base/status_macros.h" #include "ortools/math_opt/core/model_summary.h" @@ -26,6 +28,7 @@ #include "ortools/math_opt/validators/ids_validator.h" #include "ortools/math_opt/validators/solution_validator.h" #include "ortools/math_opt/validators/sparse_vector_validator.h" +#include "ortools/util/status_macros.h" namespace operations_research { namespace math_opt { @@ -80,6 +83,17 @@ absl::Status ValidateObjectiveParameters( "tolerance = " << parameters.objective_degradation_relative_tolerance() << " < 0"; } + { + OR_ASSIGN_OR_RETURN3( + const absl::Duration time_limit, + util_time::DecodeGoogleApiProto(parameters.time_limit()), + _ << "invalid ObjectiveParametersProto.time_limit"); + if (time_limit < absl::ZeroDuration()) { + return util::InvalidArgumentErrorBuilder() + << "ObjectiveParametersProto.time_limit = " << time_limit + << " < 0"; + } + } return absl::OkStatus(); }