math_opt: update from google3

This commit is contained in:
Corentin Le Molgat
2023-12-04 18:29:50 +01:00
parent 9119872e1f
commit 56291e0486
12 changed files with 119 additions and 92 deletions

View File

@@ -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"],
)

View File

@@ -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",

View File

@@ -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<TerminationReason> 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});
}

View File

@@ -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<TerminationReason> reasons) const;
// Returns termination with reason kOptimal, the provided objective for both

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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",

View File

@@ -353,15 +353,10 @@ absl::StatusOr<glop::GlopParameters> 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() &&

View File

@@ -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> xxx([&]() {
// return Xxx(...);
// });
//
// if (predicate_1) {
// ...
// f(xxx.GetOrCreate());
// ...
// }
// if (predicate_2) {
// ...
// f(xxx.GetOrCreate());
// ...
// }
template <typename T>
class LazyInitialized {
public:
// Checks that the input initializer is not nullptr.
explicit LazyInitialized(std::function<T()> 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<T()> initializer_;
std::optional<T> value_;
};
template <typename T>
SparseDoubleVectorProto FillSparseDoubleVector(
const std::vector<int64_t>& ids_in_order,
const absl::flat_hash_map<int64_t, T>& id_map,
const gtl::linked_hash_map<int64_t, T>& id_map,
const absl::flat_hash_map<T, double>& 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<SolveResultProto> GScipSolver::CreateSolveResultProto(
}
};
LazyInitialized<std::vector<int64_t>> sorted_variables([&]() {
std::vector<int64_t> 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<SolveResultProto> 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(),

View File

@@ -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> gscip_;
InterruptEventHandler interrupt_event_handler_;
absl::flat_hash_map<int64_t, SCIP_VAR*> variables_;
gtl::linked_hash_map<int64_t, SCIP_VAR*> variables_;
bool has_quadratic_objective_ = false;
absl::flat_hash_map<int64_t, SCIP_CONS*> linear_constraints_;
absl::flat_hash_map<int64_t, SCIP_CONS*> quadratic_constraints_;

View File

@@ -891,6 +891,7 @@ absl::StatusOr<SolveResultProto> 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<SolveResultProto> 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

View File

@@ -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<VariableEqualToExpression>
CreateSlackVariableEqualToExpression(const LinearExpressionProto& expression);