update linear solver code
This commit is contained in:
@@ -128,6 +128,7 @@ UTIL_DEPS = \
|
||||
$(SRC_DIR)/ortools/util/functions_swig_test_helpers.h \
|
||||
$(SRC_DIR)/ortools/util/graph_export.h \
|
||||
$(SRC_DIR)/ortools/util/integer_pq.h \
|
||||
$(SRC_DIR)/ortools/util/lazy_mutable_copy.h \
|
||||
$(SRC_DIR)/ortools/util/monoid_operation_tree.h \
|
||||
$(SRC_DIR)/ortools/util/permutation.h \
|
||||
$(SRC_DIR)/ortools/util/piecewise_linear_function.h \
|
||||
@@ -1182,8 +1183,8 @@ SAT_LIB_OBJS = \
|
||||
$(OBJ_DIR)/sat/optimization.$O \
|
||||
$(OBJ_DIR)/sat/overload_checker.$O \
|
||||
$(OBJ_DIR)/sat/pb_constraint.$O \
|
||||
$(OBJ_DIR)/sat/presolve_util.$O \
|
||||
$(OBJ_DIR)/sat/precedences.$O \
|
||||
$(OBJ_DIR)/sat/presolve_util.$O \
|
||||
$(OBJ_DIR)/sat/probing.$O \
|
||||
$(OBJ_DIR)/sat/pseudo_costs.$O \
|
||||
$(OBJ_DIR)/sat/restart.$O \
|
||||
@@ -1322,12 +1323,13 @@ objs/sat/cp_model_expand.$O: ortools/sat/cp_model_expand.cc \
|
||||
ortools/sat/cp_model_presolve.h ortools/sat/cp_model_utils.h \
|
||||
ortools/base/integral_types.h ortools/base/logging.h \
|
||||
ortools/base/macros.h ortools/util/sorted_interval_list.h \
|
||||
ortools/sat/presolve_util.h ortools/base/int_type.h \
|
||||
ortools/base/int_type_indexed_vector.h ortools/util/bitset.h \
|
||||
ortools/gen/ortools/sat/sat_parameters.pb.h \
|
||||
ortools/util/affine_relation.h ortools/base/iterator_adaptors.h \
|
||||
ortools/util/bitset.h ortools/util/time_limit.h \
|
||||
ortools/base/commandlineflags.h ortools/base/timer.h \
|
||||
ortools/base/basictypes.h ortools/util/running_stat.h \
|
||||
ortools/base/hash.h ortools/base/map_util.h \
|
||||
ortools/util/time_limit.h ortools/base/commandlineflags.h \
|
||||
ortools/base/timer.h ortools/base/basictypes.h \
|
||||
ortools/util/running_stat.h ortools/base/hash.h ortools/base/map_util.h \
|
||||
ortools/util/saturated_arithmetic.h | $(OBJ_DIR)/sat
|
||||
$(CCC) $(CFLAGS) -c $(SRC_DIR)$Sortools$Ssat$Scp_model_expand.cc $(OBJ_OUT)$(OBJ_DIR)$Ssat$Scp_model_expand.$O
|
||||
|
||||
@@ -1408,21 +1410,21 @@ objs/sat/cp_model_presolve.$O: ortools/sat/cp_model_presolve.cc \
|
||||
ortools/sat/cp_model_presolve.h ortools/gen/ortools/sat/cp_model.pb.h \
|
||||
ortools/sat/cp_model_utils.h ortools/base/integral_types.h \
|
||||
ortools/base/logging.h ortools/base/macros.h \
|
||||
ortools/util/sorted_interval_list.h \
|
||||
ortools/gen/ortools/sat/sat_parameters.pb.h \
|
||||
ortools/util/affine_relation.h ortools/base/iterator_adaptors.h \
|
||||
ortools/util/bitset.h ortools/util/time_limit.h \
|
||||
ortools/base/commandlineflags.h ortools/base/timer.h \
|
||||
ortools/base/basictypes.h ortools/util/running_stat.h \
|
||||
ortools/base/hash.h ortools/base/map_util.h ortools/base/mathutil.h \
|
||||
ortools/base/stl_util.h ortools/port/proto_utils.h \
|
||||
ortools/sat/cp_model_checker.h ortools/sat/cp_model_loader.h \
|
||||
ortools/util/sorted_interval_list.h ortools/sat/presolve_util.h \
|
||||
ortools/base/int_type.h ortools/base/int_type_indexed_vector.h \
|
||||
ortools/sat/integer.h ortools/graph/iterators.h ortools/sat/model.h \
|
||||
ortools/base/typeid.h ortools/sat/sat_base.h ortools/sat/sat_solver.h \
|
||||
ortools/sat/clause.h ortools/sat/drat_proof_handler.h \
|
||||
ortools/sat/drat_checker.h ortools/sat/drat_writer.h ortools/base/file.h \
|
||||
ortools/base/status.h ortools/util/random_engine.h ortools/util/stats.h \
|
||||
ortools/util/bitset.h ortools/gen/ortools/sat/sat_parameters.pb.h \
|
||||
ortools/util/affine_relation.h ortools/base/iterator_adaptors.h \
|
||||
ortools/util/time_limit.h ortools/base/commandlineflags.h \
|
||||
ortools/base/timer.h ortools/base/basictypes.h \
|
||||
ortools/util/running_stat.h ortools/base/hash.h ortools/base/map_util.h \
|
||||
ortools/base/mathutil.h ortools/base/stl_util.h \
|
||||
ortools/port/proto_utils.h ortools/sat/cp_model_checker.h \
|
||||
ortools/sat/cp_model_loader.h ortools/sat/integer.h \
|
||||
ortools/graph/iterators.h ortools/sat/model.h ortools/base/typeid.h \
|
||||
ortools/sat/sat_base.h ortools/sat/sat_solver.h ortools/sat/clause.h \
|
||||
ortools/sat/drat_proof_handler.h ortools/sat/drat_checker.h \
|
||||
ortools/sat/drat_writer.h ortools/base/file.h ortools/base/status.h \
|
||||
ortools/util/random_engine.h ortools/util/stats.h \
|
||||
ortools/sat/pb_constraint.h ortools/sat/restart.h \
|
||||
ortools/sat/sat_decision.h ortools/util/integer_pq.h ortools/util/rev.h \
|
||||
ortools/util/saturated_arithmetic.h ortools/sat/intervals.h \
|
||||
@@ -1474,14 +1476,15 @@ objs/sat/cp_model_solver.$O: ortools/sat/cp_model_solver.cc \
|
||||
ortools/util/saturated_arithmetic.h ortools/util/sorted_interval_list.h \
|
||||
ortools/sat/cp_model_checker.h ortools/sat/cp_model_expand.h \
|
||||
ortools/sat/cp_model_presolve.h ortools/sat/cp_model_utils.h \
|
||||
ortools/util/affine_relation.h ortools/base/iterator_adaptors.h \
|
||||
ortools/sat/cp_model_lns.h ortools/sat/subsolver.h \
|
||||
ortools/sat/synchronization.h ortools/util/adaptative_parameter_value.h \
|
||||
ortools/sat/cp_model_loader.h ortools/sat/intervals.h \
|
||||
ortools/sat/cp_constraints.h ortools/sat/integer_expr.h \
|
||||
ortools/sat/precedences.h ortools/sat/cp_model_search.h \
|
||||
ortools/sat/integer_search.h ortools/sat/cuts.h \
|
||||
ortools/sat/linear_constraint.h ortools/sat/linear_constraint_manager.h \
|
||||
ortools/sat/presolve_util.h ortools/util/affine_relation.h \
|
||||
ortools/base/iterator_adaptors.h ortools/sat/cp_model_lns.h \
|
||||
ortools/sat/subsolver.h ortools/sat/synchronization.h \
|
||||
ortools/util/adaptative_parameter_value.h ortools/sat/cp_model_loader.h \
|
||||
ortools/sat/intervals.h ortools/sat/cp_constraints.h \
|
||||
ortools/sat/integer_expr.h ortools/sat/precedences.h \
|
||||
ortools/sat/cp_model_search.h ortools/sat/integer_search.h \
|
||||
ortools/sat/cuts.h ortools/sat/linear_constraint.h \
|
||||
ortools/sat/linear_constraint_manager.h \
|
||||
ortools/sat/linear_programming_constraint.h \
|
||||
ortools/glop/revised_simplex.h ortools/glop/basis_representation.h \
|
||||
ortools/glop/lu_factorization.h ortools/glop/markowitz.h \
|
||||
@@ -1971,26 +1974,6 @@ objs/sat/pb_constraint.$O: ortools/sat/pb_constraint.cc \
|
||||
ortools/base/thorough_hash.h ortools/util/saturated_arithmetic.h | $(OBJ_DIR)/sat
|
||||
$(CCC) $(CFLAGS) -c $(SRC_DIR)$Sortools$Ssat$Spb_constraint.cc $(OBJ_OUT)$(OBJ_DIR)$Ssat$Spb_constraint.$O
|
||||
|
||||
objs/sat/presolve_util.$O: ortools/sat/presolve_util.cc \
|
||||
ortools/sat/presolve_util.h ortools/base/int_type.h ortools/base/macros.h \
|
||||
ortools/base/int_type_indexed_vector.h ortools/base/integral_types.h \
|
||||
ortools/sat/integer.h ortools/base/hash.h ortools/base/basictypes.h \
|
||||
ortools/base/logging.h ortools/base/map_util.h ortools/graph/iterators.h \
|
||||
ortools/sat/model.h ortools/base/typeid.h ortools/sat/sat_base.h \
|
||||
ortools/util/bitset.h ortools/sat/sat_solver.h ortools/base/timer.h \
|
||||
ortools/sat/clause.h ortools/sat/drat_proof_handler.h \
|
||||
ortools/sat/drat_checker.h ortools/sat/drat_writer.h ortools/base/file.h \
|
||||
ortools/base/status.h ortools/gen/ortools/sat/sat_parameters.pb.h \
|
||||
ortools/util/random_engine.h ortools/util/stats.h \
|
||||
ortools/sat/pb_constraint.h ortools/sat/restart.h \
|
||||
ortools/util/running_stat.h ortools/sat/sat_decision.h \
|
||||
ortools/util/integer_pq.h ortools/util/time_limit.h \
|
||||
ortools/base/commandlineflags.h ortools/util/rev.h \
|
||||
ortools/util/saturated_arithmetic.h ortools/util/sorted_interval_list.h \
|
||||
ortools/base/cleanup.h ortools/base/stl_util.h \
|
||||
ortools/sat/cp_constraints.h | $(OBJ_DIR)/sat
|
||||
$(CCC) $(CFLAGS) -c $(SRC_DIR)$Sortools$Ssat$Spresolve_util.cc $(OBJ_OUT)$(OBJ_DIR)$Ssat$Spresolve_util.$O
|
||||
|
||||
objs/sat/precedences.$O: ortools/sat/precedences.cc \
|
||||
ortools/sat/precedences.h ortools/base/int_type.h ortools/base/macros.h \
|
||||
ortools/base/int_type_indexed_vector.h ortools/base/integral_types.h \
|
||||
@@ -2011,6 +1994,14 @@ objs/sat/precedences.$O: ortools/sat/precedences.cc \
|
||||
ortools/sat/cp_constraints.h | $(OBJ_DIR)/sat
|
||||
$(CCC) $(CFLAGS) -c $(SRC_DIR)$Sortools$Ssat$Sprecedences.cc $(OBJ_OUT)$(OBJ_DIR)$Ssat$Sprecedences.$O
|
||||
|
||||
objs/sat/presolve_util.$O: ortools/sat/presolve_util.cc \
|
||||
ortools/sat/presolve_util.h ortools/base/int_type.h \
|
||||
ortools/base/macros.h ortools/base/int_type_indexed_vector.h \
|
||||
ortools/base/integral_types.h ortools/base/logging.h \
|
||||
ortools/util/bitset.h ortools/util/sorted_interval_list.h \
|
||||
ortools/base/map_util.h | $(OBJ_DIR)/sat
|
||||
$(CCC) $(CFLAGS) -c $(SRC_DIR)$Sortools$Ssat$Spresolve_util.cc $(OBJ_OUT)$(OBJ_DIR)$Ssat$Spresolve_util.$O
|
||||
|
||||
objs/sat/probing.$O: ortools/sat/probing.cc ortools/sat/probing.h \
|
||||
ortools/sat/model.h ortools/base/logging.h ortools/base/integral_types.h \
|
||||
ortools/base/macros.h ortools/base/map_util.h ortools/base/typeid.h \
|
||||
@@ -2843,7 +2834,8 @@ objs/linear_solver/linear_solver.$O: \
|
||||
ortools/base/status_macros.h ortools/base/statusor.h \
|
||||
ortools/base/stl_util.h ortools/linear_solver/model_exporter.h \
|
||||
ortools/base/hash.h ortools/linear_solver/model_validator.h \
|
||||
ortools/port/file.h ortools/util/fp_utils.h | $(OBJ_DIR)/linear_solver
|
||||
ortools/util/lazy_mutable_copy.h ortools/port/file.h \
|
||||
ortools/util/fp_utils.h | $(OBJ_DIR)/linear_solver
|
||||
$(CCC) $(CFLAGS) -c $(SRC_DIR)$Sortools$Slinear_solver$Slinear_solver.cc $(OBJ_OUT)$(OBJ_DIR)$Slinear_solver$Slinear_solver.$O
|
||||
|
||||
objs/linear_solver/model_exporter.$O: \
|
||||
@@ -2863,9 +2855,10 @@ objs/linear_solver/model_validator.$O: \
|
||||
ortools/linear_solver/model_validator.h \
|
||||
ortools/gen/ortools/linear_solver/linear_solver.pb.h \
|
||||
ortools/gen/ortools/util/optional_boolean.pb.h \
|
||||
ortools/base/accurate_sum.h ortools/port/proto_utils.h \
|
||||
ortools/util/fp_utils.h ortools/base/logging.h \
|
||||
ortools/base/integral_types.h ortools/base/macros.h | $(OBJ_DIR)/linear_solver
|
||||
ortools/util/lazy_mutable_copy.h ortools/base/accurate_sum.h \
|
||||
ortools/base/status.h ortools/base/logging.h \
|
||||
ortools/base/integral_types.h ortools/base/macros.h ortools/port/file.h \
|
||||
ortools/port/proto_utils.h ortools/util/fp_utils.h | $(OBJ_DIR)/linear_solver
|
||||
$(CCC) $(CFLAGS) -c $(SRC_DIR)$Sortools$Slinear_solver$Smodel_validator.cc $(OBJ_OUT)$(OBJ_DIR)$Slinear_solver$Smodel_validator.$O
|
||||
|
||||
objs/linear_solver/scip_interface.$O: \
|
||||
@@ -3871,3 +3864,4 @@ $(GEN_DIR)/ortools/constraint_solver/solver_parameters.pb.h: \
|
||||
$(OBJ_DIR)/constraint_solver/solver_parameters.pb.$O: \
|
||||
$(GEN_DIR)/ortools/constraint_solver/solver_parameters.pb.cc | $(OBJ_DIR)/constraint_solver
|
||||
$(CCC) $(CFLAGS) -c $(GEN_PATH)$Sortools$Sconstraint_solver$Ssolver_parameters.pb.cc $(OBJ_OUT)$(OBJ_DIR)$Sconstraint_solver$Ssolver_parameters.pb.$O
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "ortools/base/canonical_errors.h"
|
||||
#include "ortools/base/cleanup.h"
|
||||
#include "ortools/base/status.h"
|
||||
@@ -31,6 +32,7 @@
|
||||
#include "ortools/linear_solver/gurobi_environment.h"
|
||||
#include "ortools/linear_solver/linear_solver.pb.h"
|
||||
#include "ortools/linear_solver/model_validator.h"
|
||||
#include "ortools/util/lazy_mutable_copy.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
@@ -188,11 +190,11 @@ int AddMaxConstraint(const MPGeneralConstraintProto& gen_cst,
|
||||
util::StatusOr<MPSolutionResponse> GurobiSolveProto(
|
||||
const MPModelRequest& request) {
|
||||
MPSolutionResponse response;
|
||||
if (MPRequestIsEmptyOrInvalid(request, &response)) {
|
||||
return response;
|
||||
}
|
||||
const absl::optional<LazyMutableCopy<MPModelProto>> optional_model =
|
||||
ExtractValidMPModelOrPopulateResponseStatus(request, &response);
|
||||
if (!optional_model) return response;
|
||||
const MPModelProto& model = optional_model->get();
|
||||
|
||||
const MPModelProto& model = request.model();
|
||||
if (request.has_solver_specific_parameters()) {
|
||||
// TODO(user): Support solver-specific parameters without duplicating
|
||||
// all of the write file / read file code in linear_solver.cc.
|
||||
|
||||
@@ -582,6 +582,7 @@ MPSolverResponseStatus MPSolver::LoadModelFromProto(
|
||||
// unlike the MPSolver C++ API which crashes if there are duplicate names.
|
||||
// Clearing the names makes the MPSolver generate unique names.
|
||||
return LoadModelFromProtoInternal(input_model, /*clear_names=*/true,
|
||||
/*check_model_validity=*/true,
|
||||
error_message);
|
||||
}
|
||||
|
||||
@@ -592,26 +593,29 @@ MPSolverResponseStatus MPSolver::LoadModelFromProtoWithUniqueNamesOrDie(
|
||||
GenerateConstraintNameIndex();
|
||||
|
||||
return LoadModelFromProtoInternal(input_model, /*clear_names=*/false,
|
||||
/*check_model_validity=*/true,
|
||||
error_message);
|
||||
}
|
||||
|
||||
MPSolverResponseStatus MPSolver::LoadModelFromProtoInternal(
|
||||
const MPModelProto& input_model, bool clear_names,
|
||||
std::string* error_message) {
|
||||
bool check_model_validity, std::string* error_message) {
|
||||
CHECK(error_message != nullptr);
|
||||
const std::string error = FindErrorInMPModelProto(input_model);
|
||||
if (!error.empty()) {
|
||||
*error_message = error;
|
||||
LOG_IF(INFO, OutputIsEnabled())
|
||||
<< "Invalid model given to LoadModelFromProto(): " << error;
|
||||
if (FLAGS_mpsolver_bypass_model_validation) {
|
||||
if (check_model_validity) {
|
||||
const std::string error = FindErrorInMPModelProto(input_model);
|
||||
if (!error.empty()) {
|
||||
*error_message = error;
|
||||
LOG_IF(INFO, OutputIsEnabled())
|
||||
<< "Ignoring the model error(s) because of"
|
||||
<< " --mpsolver_bypass_model_validation.";
|
||||
} else {
|
||||
return error.find("Infeasible") == std::string::npos
|
||||
? MPSOLVER_MODEL_INVALID
|
||||
: MPSOLVER_INFEASIBLE;
|
||||
<< "Invalid model given to LoadModelFromProto(): " << error;
|
||||
if (FLAGS_mpsolver_bypass_model_validation) {
|
||||
LOG_IF(INFO, OutputIsEnabled())
|
||||
<< "Ignoring the model error(s) because of"
|
||||
<< " --mpsolver_bypass_model_validation.";
|
||||
} else {
|
||||
return error.find("Infeasible") == std::string::npos
|
||||
? MPSOLVER_MODEL_INVALID
|
||||
: MPSOLVER_INFEASIBLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -773,9 +777,9 @@ void MPSolver::FillSolutionResponseProto(MPSolutionResponse* response) const {
|
||||
void MPSolver::SolveWithProto(const MPModelRequest& model_request,
|
||||
MPSolutionResponse* response) {
|
||||
CHECK(response != nullptr);
|
||||
const MPModelProto& model = model_request.model();
|
||||
MPSolver solver(model.name(), static_cast<MPSolver::OptimizationProblemType>(
|
||||
model_request.solver_type()));
|
||||
MPSolver solver(model_request.model().name(),
|
||||
static_cast<MPSolver::OptimizationProblemType>(
|
||||
model_request.solver_type()));
|
||||
if (model_request.enable_internal_solver_output()) {
|
||||
solver.EnableOutput();
|
||||
}
|
||||
@@ -786,12 +790,26 @@ void MPSolver::SolveWithProto(const MPModelRequest& model_request,
|
||||
return;
|
||||
}
|
||||
|
||||
const absl::optional<LazyMutableCopy<MPModelProto>> optional_model =
|
||||
ExtractValidMPModelOrPopulateResponseStatus(model_request, response);
|
||||
if (!optional_model) {
|
||||
LOG_IF(WARNING, model_request.enable_internal_solver_output())
|
||||
<< "Failed to extract a valid model from protocol buffer. Status: "
|
||||
<< ProtoEnumToString<MPSolverResponseStatus>(response->status()) << " ("
|
||||
<< response->status() << "): " << response->status_str();
|
||||
return;
|
||||
}
|
||||
std::string error_message;
|
||||
response->set_status(solver.LoadModelFromProto(model, &error_message));
|
||||
response->set_status(solver.LoadModelFromProtoInternal(
|
||||
optional_model->get(), /*clear_names=*/true,
|
||||
/*check_model_validity=*/false, &error_message));
|
||||
// Even though we don't re-check model validity here, there can be some
|
||||
// problems found by LoadModelFromProto, eg. unsupported features.
|
||||
if (response->status() != MPSOLVER_MODEL_IS_VALID) {
|
||||
response->set_status_str(error_message);
|
||||
LOG_IF(WARNING, model_request.enable_internal_solver_output())
|
||||
<< "Loading model from protocol buffer failed, load status = "
|
||||
<< "LoadModelFromProtoInternal() failed even though the model was "
|
||||
<< "valid! Status: "
|
||||
<< ProtoEnumToString<MPSolverResponseStatus>(response->status()) << " ("
|
||||
<< response->status() << "); Error: " << error_message;
|
||||
return;
|
||||
@@ -864,7 +882,7 @@ void MPSolver::ExportModelToProto(MPModelProto* output_model) const {
|
||||
constraint_proto->set_is_lazy(constraint->is_lazy());
|
||||
// Vector linear_term will contain pairs (variable index, coeff), that will
|
||||
// be sorted by variable index.
|
||||
std::vector<std::pair<int, double> > linear_term;
|
||||
std::vector<std::pair<int, double>> linear_term;
|
||||
for (const auto& entry : constraint->coefficients_) {
|
||||
const MPVariable* const var = entry.first;
|
||||
const int var_index = gtl::FindWithDefault(var_to_index, var, -1);
|
||||
@@ -1452,8 +1470,7 @@ bool MPSolver::ExportModelAsMpsFormat(bool fixed_format, bool obfuscate,
|
||||
return status_or.ok();
|
||||
}
|
||||
|
||||
void MPSolver::SetHint(
|
||||
std::vector<std::pair<const MPVariable*, double> > hint) {
|
||||
void MPSolver::SetHint(std::vector<std::pair<const MPVariable*, double>> hint) {
|
||||
for (const auto& var_value_pair : hint) {
|
||||
CHECK(OwnsVariable(var_value_pair.first))
|
||||
<< "hint variable does not belong to this solver";
|
||||
|
||||
@@ -840,7 +840,7 @@ class MPSolver {
|
||||
|
||||
MPSolverResponseStatus LoadModelFromProtoInternal(
|
||||
const MPModelProto& input_model, bool clear_names,
|
||||
std::string* error_message);
|
||||
bool check_model_validity, std::string* error_message);
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MPSolver);
|
||||
};
|
||||
|
||||
@@ -454,6 +454,11 @@ message MPModelRequest {
|
||||
// MPSOLVER_MODEL_INVALID_SOLVER_PARAMETERS.
|
||||
optional string solver_specific_parameters = 5;
|
||||
|
||||
|
||||
// Advanced usage: model "delta". If used, "model" must be unset. See the
|
||||
// definition of MPModelDeltaProto.
|
||||
optional MPModelDeltaProto model_delta = 8;
|
||||
|
||||
}
|
||||
|
||||
// Status returned by the solver. They follow a hierarchical nomenclature, to
|
||||
|
||||
@@ -17,13 +17,18 @@
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "ortools/base/accurate_sum.h"
|
||||
#include "ortools/base/status.h"
|
||||
#include "ortools/linear_solver/linear_solver.pb.h"
|
||||
#include "ortools/port/file.h"
|
||||
#include "ortools/port/proto_utils.h"
|
||||
#include "ortools/util/fp_utils.h"
|
||||
#include "ortools/util/lazy_mutable_copy.h"
|
||||
|
||||
namespace operations_research {
|
||||
namespace {
|
||||
@@ -478,27 +483,60 @@ std::string FindErrorInMPModelProto(const MPModelProto& model) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
bool MPRequestIsEmptyOrInvalid(const MPModelRequest& request,
|
||||
MPSolutionResponse* response) {
|
||||
absl::optional<LazyMutableCopy<MPModelProto>>
|
||||
ExtractValidMPModelOrPopulateResponseStatus(const MPModelRequest& request,
|
||||
MPSolutionResponse* response) {
|
||||
CHECK(response != nullptr);
|
||||
|
||||
if (!request.has_model()) {
|
||||
if (!request.has_model() && !request.has_model_delta()) {
|
||||
response->set_status(MPSOLVER_OPTIMAL);
|
||||
response->set_status_str("Requests without model are considered OPTIMAL");
|
||||
return true;
|
||||
return absl::nullopt;
|
||||
}
|
||||
const MPModelProto& model = request.model();
|
||||
if (model.variable_size() == 0 && model.constraint_size() == 0 &&
|
||||
model.general_constraint_size() == 0) {
|
||||
response->set_status(MPSOLVER_OPTIMAL);
|
||||
response->set_objective_value(request.model().objective_offset());
|
||||
response->set_best_objective_bound(request.model().objective_offset());
|
||||
if (request.has_model() && request.has_model_delta()) {
|
||||
response->set_status(MPSOLVER_MODEL_INVALID);
|
||||
response->set_status_str(
|
||||
"Requests without variables and constraints are considered OPTIMAL");
|
||||
return true;
|
||||
"Fields 'model' and 'model_delta' are mutually exclusive");
|
||||
return absl::nullopt;
|
||||
}
|
||||
|
||||
const std::string error = FindErrorInMPModelProto(model);
|
||||
// Extract the baseline model.
|
||||
LazyMutableCopy<MPModelProto> model(request.model());
|
||||
if (request.has_model_delta()) {
|
||||
// NOTE(user): This library needs to be portable, so we can't include
|
||||
// ortools/base/file.h; see ../port/file.h.
|
||||
std::string contents;
|
||||
const util::Status file_read_status = PortableFileGetContents(
|
||||
request.model_delta().baseline_model_file_path(), &contents);
|
||||
if (!file_read_status.ok()) {
|
||||
response->set_status(MPSOLVER_MODEL_INVALID);
|
||||
response->set_status_str(
|
||||
"Error when reading model_delta.baseline_model_file_path: '" +
|
||||
file_read_status.ToString());
|
||||
return absl::nullopt;
|
||||
}
|
||||
if (!model.get_mutable()->ParseFromString(contents)) {
|
||||
response->set_status(MPSOLVER_MODEL_INVALID);
|
||||
response->set_status_str(
|
||||
absl::StrFormat("The contents of baseline model file '%s' couldn't "
|
||||
"be parsed as a raw serialized MPModelProto",
|
||||
request.model_delta().baseline_model_file_path()));
|
||||
return absl::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the baseline model.
|
||||
std::string error = FindErrorInMPModelProto(model.get());
|
||||
|
||||
// If the baseline is valid and we have a model delta, validate the delta,
|
||||
// then apply it.
|
||||
if (error.empty() && request.has_model_delta()) {
|
||||
const MPModelDeltaProto& delta = request.model_delta();
|
||||
error = FindErrorInMPModelDeltaProto(delta, model.get());
|
||||
if (error.empty()) ApplyVerifiedMPModelDelta(delta, model.get_mutable());
|
||||
}
|
||||
|
||||
// Deal with errors.
|
||||
if (!error.empty()) {
|
||||
if (request.enable_internal_solver_output()) {
|
||||
LOG(ERROR) << absl::StrCat("Invalid model: ", error);
|
||||
@@ -507,9 +545,31 @@ bool MPRequestIsEmptyOrInvalid(const MPModelRequest& request,
|
||||
? MPSOLVER_MODEL_INVALID
|
||||
: MPSOLVER_INFEASIBLE);
|
||||
response->set_status_str(error);
|
||||
return true;
|
||||
return absl::nullopt;
|
||||
}
|
||||
return false;
|
||||
|
||||
if (model.get().variable_size() == 0 && model.get().constraint_size() == 0 &&
|
||||
model.get().general_constraint_size() == 0) {
|
||||
response->set_status(MPSOLVER_OPTIMAL);
|
||||
response->set_objective_value(model.get().objective_offset());
|
||||
response->set_best_objective_bound(response->objective_value());
|
||||
response->set_status_str(
|
||||
"Requests without variables and constraints are considered OPTIMAL");
|
||||
return absl::nullopt;
|
||||
}
|
||||
|
||||
return std::move(model);
|
||||
}
|
||||
|
||||
bool ExtractValidMPModelInPlaceOrPopulateResponseStatus(
|
||||
MPModelRequest* request, MPSolutionResponse* response) {
|
||||
absl::optional<LazyMutableCopy<MPModelProto>> lazy_copy =
|
||||
ExtractValidMPModelOrPopulateResponseStatus(*request, response);
|
||||
if (!lazy_copy) return false;
|
||||
if (lazy_copy->was_copied()) {
|
||||
lazy_copy->get_mutable()->Swap(request->mutable_model());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO(user): Add a general FindFeasibilityErrorInSolution() and factor out the
|
||||
@@ -673,4 +733,105 @@ void MergeMPConstraintProtoExceptTerms(const MPConstraintProto& from,
|
||||
#undef COPY_FIELD_IF_PRESENT
|
||||
}
|
||||
|
||||
namespace {
|
||||
void PruneZeroTermsInMpConstraint(MPConstraintProto* ct) {
|
||||
// Optimize the fast path (when no term is pruned) by doing a first quick scan
|
||||
// until the first zero.
|
||||
int first_zero = 0;
|
||||
while (first_zero < ct->var_index_size() &&
|
||||
ct->coefficient(first_zero) != 0.0) {
|
||||
++first_zero;
|
||||
}
|
||||
int num_kept = first_zero;
|
||||
for (int i = first_zero; i < ct->var_index_size(); ++i) {
|
||||
if (ct->coefficient(i) == 0.0) continue;
|
||||
if (num_kept != i) {
|
||||
ct->set_var_index(num_kept, ct->var_index(i));
|
||||
ct->set_coefficient(num_kept, ct->coefficient(i));
|
||||
}
|
||||
++num_kept;
|
||||
}
|
||||
ct->mutable_var_index()->Truncate(num_kept);
|
||||
ct->mutable_coefficient()->Truncate(num_kept);
|
||||
}
|
||||
|
||||
// Adds default entries to a repeated message field until it has the wanted
|
||||
// size. We don't use google::protobuf::util::Resize() because it's not
|
||||
// compatible with 'light' protos.
|
||||
template <class T>
|
||||
void ExtendRepeatedPtrFieldToSize(const int size, T* repeated_messages) {
|
||||
DCHECK_GE(size, repeated_messages->size());
|
||||
while (repeated_messages->size() < size) repeated_messages->Add();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ApplyVerifiedMPModelDelta(const MPModelDeltaProto& delta,
|
||||
MPModelProto* model) {
|
||||
// Apply the delta to the variables: first, resize the variable array.
|
||||
int max_var_index = -1;
|
||||
for (const auto& p : delta.variable_overrides()) {
|
||||
max_var_index = std::max(max_var_index, p.first);
|
||||
}
|
||||
if (max_var_index >= model->variable_size()) {
|
||||
ExtendRepeatedPtrFieldToSize(max_var_index + 1, model->mutable_variable());
|
||||
}
|
||||
// Then, apply the variable overrides.
|
||||
for (const auto& p : delta.variable_overrides()) {
|
||||
model->mutable_variable(p.first)->MergeFrom(p.second);
|
||||
}
|
||||
|
||||
// Apply the delta to the constraints: first, resize the constraint array.
|
||||
int max_ct_index = -1;
|
||||
for (const auto& p : delta.constraint_overrides()) {
|
||||
max_ct_index = std::max(max_ct_index, p.first);
|
||||
}
|
||||
const int old_num_constraints = model->constraint_size();
|
||||
if (max_ct_index >= old_num_constraints) {
|
||||
ExtendRepeatedPtrFieldToSize(max_ct_index + 1, model->mutable_constraint());
|
||||
}
|
||||
// Then, apply the constraint overrides.
|
||||
for (const auto& p : delta.constraint_overrides()) {
|
||||
const MPConstraintProto& override_ct = p.second;
|
||||
MPConstraintProto* baseline = model->mutable_constraint(p.first);
|
||||
// Fast path for added constraints.
|
||||
if (p.first >= old_num_constraints) {
|
||||
*baseline = override_ct;
|
||||
continue;
|
||||
}
|
||||
MergeMPConstraintProtoExceptTerms(/*from=*/override_ct, /*to=*/baseline);
|
||||
// Special case: the override is neutralized.
|
||||
if (override_ct.has_lower_bound() &&
|
||||
override_ct.lower_bound() == -kInfinity &&
|
||||
override_ct.has_upper_bound() &&
|
||||
override_ct.upper_bound() == kInfinity) {
|
||||
baseline->clear_var_index();
|
||||
baseline->clear_coefficient();
|
||||
continue;
|
||||
}
|
||||
// Otherwise we have to apply the term overrides. We can't do that in less
|
||||
// than O(|baseline| + |override_ct|) because the baseline doesn't have a
|
||||
// lookup-friendly data structure. But we still try to do it as efficiently
|
||||
// as possible. In particular, we only use O(|override_ct|) extra memory.
|
||||
absl::flat_hash_map<int, double> term_overrides;
|
||||
term_overrides.reserve(override_ct.var_index_size());
|
||||
for (int i = 0; i < override_ct.var_index_size(); ++i) {
|
||||
term_overrides[override_ct.var_index(i)] = override_ct.coefficient(i);
|
||||
}
|
||||
for (int i = 0; i < baseline->var_index_size(); ++i) {
|
||||
auto it = term_overrides.find(baseline->var_index(i));
|
||||
if (it == term_overrides.end()) continue;
|
||||
baseline->set_coefficient(i, it->second);
|
||||
it->second = 0.0; // To mark this term override as 'has been applied'.
|
||||
}
|
||||
PruneZeroTermsInMpConstraint(baseline);
|
||||
// Add the term overrides which haven't been used: those are added terms.
|
||||
for (const auto& p : term_overrides) {
|
||||
if (p.second != 0.0) {
|
||||
baseline->add_var_index(p.first);
|
||||
baseline->add_coefficient(p.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace operations_research
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "absl/types/optional.h"
|
||||
#include "ortools/linear_solver/linear_solver.pb.h"
|
||||
#include "ortools/util/lazy_mutable_copy.h"
|
||||
|
||||
namespace operations_research {
|
||||
/**
|
||||
@@ -31,7 +33,7 @@ namespace operations_research {
|
||||
std::string FindErrorInMPModelProto(const MPModelProto& model);
|
||||
|
||||
/**
|
||||
* Like FindErrorInMPModelProto, but for a MPModelDeltaProto applied to a given
|
||||
* Like FindErrorInMPModelProto, but for a MPModelDeltaProto applied to a given
|
||||
* baseline model (assumed valid, eg. FindErrorInMPModelProto(model)="").
|
||||
* Works in O(|model_delta|) + O(num_vars in model), but the latter term has a
|
||||
* very small constant factor.
|
||||
@@ -40,12 +42,20 @@ std::string FindErrorInMPModelDeltaProto(const MPModelDeltaProto& delta,
|
||||
const MPModelProto& model);
|
||||
|
||||
/**
|
||||
* Updates `response` and returns true if errors, infeasibilities, or trivial
|
||||
* optimals were found. Returns false if the model is valid and non-trivially
|
||||
* solvable.
|
||||
* If the model is valid and non-empty, returns it (possibly after extracting
|
||||
* the model_delta). If invalid or empty, updates `response` and returns null.
|
||||
*/
|
||||
bool MPRequestIsEmptyOrInvalid(const MPModelRequest& request,
|
||||
MPSolutionResponse* response);
|
||||
absl::optional<LazyMutableCopy<MPModelProto>>
|
||||
ExtractValidMPModelOrPopulateResponseStatus(const MPModelRequest& request,
|
||||
MPSolutionResponse* response);
|
||||
|
||||
/**
|
||||
* Like ExtractValidMPModelOrPopulateResponseStatus(), but works in-place:
|
||||
* if the MPModel needed extraction, it will be populated in the request, and
|
||||
* it returns the success boolean.
|
||||
*/
|
||||
bool ExtractValidMPModelInPlaceOrPopulateResponseStatus(
|
||||
MPModelRequest* request, MPSolutionResponse* response);
|
||||
|
||||
/**
|
||||
* Returns an empty std::string if the solution hint given in the model is a
|
||||
@@ -60,7 +70,6 @@ bool MPRequestIsEmptyOrInvalid(const MPModelRequest& request,
|
||||
std::string FindFeasibilityErrorInSolutionHint(const MPModelProto& model,
|
||||
double tolerance);
|
||||
|
||||
// PUBLIC ONLY FOR TESTING.
|
||||
// Partially merges a MPConstraintProto onto another, skipping only the
|
||||
// repeated fields "var_index" and "coefficients". This is used within
|
||||
// FindErrorInMPModelDeltaProto.
|
||||
@@ -69,6 +78,12 @@ std::string FindFeasibilityErrorInSolutionHint(const MPModelProto& model,
|
||||
void MergeMPConstraintProtoExceptTerms(const MPConstraintProto& from,
|
||||
MPConstraintProto* to);
|
||||
|
||||
// PUBLIC FOR TESTING ONLY.
|
||||
// Applies the given model_delta to "model". Assumes that
|
||||
// FindErrorInMPModelDeltaProto() found no error.
|
||||
void ApplyVerifiedMPModelDelta(const MPModelDeltaProto& delta,
|
||||
MPModelProto* model);
|
||||
|
||||
} // namespace operations_research
|
||||
|
||||
#endif // OR_TOOLS_LINEAR_SOLVER_MODEL_VALIDATOR_H_
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "ortools/base/canonical_errors.h"
|
||||
#include "ortools/base/cleanup.h"
|
||||
#include "ortools/base/status.h"
|
||||
@@ -34,6 +35,7 @@
|
||||
#include "ortools/linear_solver/linear_solver.pb.h"
|
||||
#include "ortools/linear_solver/model_validator.h"
|
||||
#include "ortools/linear_solver/scip_helper_macros.h"
|
||||
#include "ortools/util/lazy_mutable_copy.h"
|
||||
#include "scip/cons_disjunction.h"
|
||||
#include "scip/cons_linear.h"
|
||||
#include "scip/pub_var.h"
|
||||
@@ -350,12 +352,13 @@ util::Status AddAbsConstraint(const MPGeneralConstraintProto& gen_cst,
|
||||
std::vector<SCIP_CONS*> cons;
|
||||
auto add_abs_constraint =
|
||||
[&](const std::string& name_prefix) -> util::Status {
|
||||
SCIP_CONS* scip_cons;
|
||||
SCIP_CONS* scip_cons = nullptr;
|
||||
CHECK(vars.size() == vals.size());
|
||||
const std::string name =
|
||||
gen_cst.has_name() ? absl::StrCat(gen_cst.name(), name_prefix) : "";
|
||||
RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicLinear(
|
||||
scip, /*cons=*/&scip_cons,
|
||||
/*name=*/name.c_str(), /*nvars=*/2, /*vars=*/vars.data(),
|
||||
/*name=*/name.c_str(), /*nvars=*/vars.size(), /*vars=*/vars.data(),
|
||||
/*vals=*/vals.data(), /*lhs=*/0.0, /*rhs=*/0.0));
|
||||
// Note that the constraints are, by design, not added into the model using
|
||||
// SCIPaddCons.
|
||||
@@ -377,7 +380,7 @@ util::Status AddAbsConstraint(const MPGeneralConstraintProto& gen_cst,
|
||||
gen_cst.has_name() ? absl::StrCat(gen_cst.name(), "_disj") : "";
|
||||
RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicDisjunction(
|
||||
scip, /*cons=*/scip_cst, /*name=*/name.c_str(),
|
||||
/*nconss=*/2, /*conss=*/cons.data(), /*relaxcons=*/nullptr));
|
||||
/*nconss=*/cons.size(), /*conss=*/cons.data(), /*relaxcons=*/nullptr));
|
||||
RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
|
||||
|
||||
return util::OkStatus();
|
||||
@@ -455,12 +458,13 @@ util::Status AddMinMaxConstraint(const MPGeneralConstraintProto& gen_cst,
|
||||
auto add_lin_constraint = [&](const std::string& name_prefix,
|
||||
double lower_bound = 0.0,
|
||||
double upper_bound = 0.0) -> util::Status {
|
||||
SCIP_CONS* scip_cons;
|
||||
SCIP_CONS* scip_cons = nullptr;
|
||||
CHECK(vars.size() == vals.size());
|
||||
const std::string name =
|
||||
gen_cst.has_name() ? absl::StrCat(gen_cst.name(), name_prefix) : "";
|
||||
RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicLinear(
|
||||
scip, /*cons=*/&scip_cons,
|
||||
/*name=*/name.c_str(), /*nvars=*/2, /*vars=*/vars.data(),
|
||||
/*name=*/name.c_str(), /*nvars=*/vars.size(), /*vars=*/vars.data(),
|
||||
/*vals=*/vals.data(), /*lhs=*/lower_bound, /*rhs=*/upper_bound));
|
||||
// Note that the constraints are, by design, not added into the model using
|
||||
// SCIPaddCons.
|
||||
@@ -488,7 +492,7 @@ util::Status AddMinMaxConstraint(const MPGeneralConstraintProto& gen_cst,
|
||||
gen_cst.has_name() ? absl::StrCat(gen_cst.name(), "_disj") : "";
|
||||
RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicDisjunction(
|
||||
scip, /*cons=*/scip_cst, /*name=*/name.c_str(),
|
||||
/*nconss=*/2, /*conss=*/cons.data(), /*relaxcons=*/nullptr));
|
||||
/*nconss=*/cons.size(), /*conss=*/cons.data(), /*relaxcons=*/nullptr));
|
||||
RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
|
||||
|
||||
// Add all of the inequality constraints.
|
||||
@@ -505,14 +509,16 @@ util::Status AddMinMaxConstraint(const MPGeneralConstraintProto& gen_cst,
|
||||
kInfinity));
|
||||
}
|
||||
}
|
||||
vars = {scip_resultant_var};
|
||||
vals = {1};
|
||||
if (gen_cst.has_min_constraint()) {
|
||||
RETURN_IF_ERROR(add_lin_constraint(absl::StrCat("_ineq_constant"),
|
||||
-kInfinity, minmax.constant()));
|
||||
} else {
|
||||
RETURN_IF_ERROR(add_lin_constraint(absl::StrCat("_ineq_constant"),
|
||||
minmax.constant(), kInfinity));
|
||||
if (minmax.has_constant()) {
|
||||
vars = {scip_resultant_var};
|
||||
vals = {1};
|
||||
if (gen_cst.has_min_constraint()) {
|
||||
RETURN_IF_ERROR(add_lin_constraint(absl::StrCat("_ineq_constant"),
|
||||
-kInfinity, minmax.constant()));
|
||||
} else {
|
||||
RETURN_IF_ERROR(add_lin_constraint(absl::StrCat("_ineq_constant"),
|
||||
minmax.constant(), kInfinity));
|
||||
}
|
||||
}
|
||||
for (SCIP_CONS* scip_cons : cons) {
|
||||
scip_constraints->push_back(scip_cons);
|
||||
@@ -652,10 +658,10 @@ bool MPModelIsInvalidForScip(const MPModelProto& model, SCIP* scip,
|
||||
util::StatusOr<MPSolutionResponse> ScipSolveProto(
|
||||
const MPModelRequest& request) {
|
||||
MPSolutionResponse response;
|
||||
if (MPRequestIsEmptyOrInvalid(request, &response)) return response;
|
||||
|
||||
const MPModelProto& model = request.model();
|
||||
|
||||
const absl::optional<LazyMutableCopy<MPModelProto>> optional_model =
|
||||
ExtractValidMPModelOrPopulateResponseStatus(request, &response);
|
||||
if (!optional_model) return response;
|
||||
const MPModelProto& model = optional_model->get();
|
||||
SCIP* scip = nullptr;
|
||||
std::vector<SCIP_VAR*> scip_variables(model.variable_size(), nullptr);
|
||||
std::vector<SCIP_CONS*> scip_constraints(
|
||||
|
||||
75
ortools/util/lazy_mutable_copy.h
Normal file
75
ortools/util/lazy_mutable_copy.h
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright 2010-2018 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.
|
||||
|
||||
#ifndef OR_TOOLS_UTIL_LAZY_MUTABLE_COPY_H_
|
||||
#define OR_TOOLS_UTIL_LAZY_MUTABLE_COPY_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
|
||||
namespace operations_research {
|
||||
|
||||
// LazyMutableCopy<T> is a helper class for making an on-demand copy of an
|
||||
// object of arbitrary type T. Type T must have a copy constructor.
|
||||
//
|
||||
// Sample usage:
|
||||
// const Proto& original_input = ...;
|
||||
// LazyMutableCopy<Proto> input(original_input);
|
||||
// if (input.get().foo() == BAD_VALUE) {
|
||||
// input.get_mutable()->set_foo(GOOD_VALUE); // Copies the object.
|
||||
// }
|
||||
// // Process "input" here without worrying about BAD_VALUE.
|
||||
// A good pattern is to have function taking LazyMutableCopy<> as argument:
|
||||
// void ProcessProto(LazyMutableCopy<Proto> input) { // pass by copy
|
||||
// ...
|
||||
// }
|
||||
// At the call site: ProcessProto({const_ref_to_my_proto});
|
||||
//
|
||||
// In basic usage, a LazyMutableCopy is in one of two states:
|
||||
// - original: points to the const original. No memory allocated.
|
||||
// - copy: points to a mutable copy of the original and owns it. Owning the
|
||||
// copy means that the destructor will delete it, like std::unique_ptr<>.
|
||||
// This is what you get by calling get_mutable().
|
||||
template <class T>
|
||||
class LazyMutableCopy {
|
||||
public:
|
||||
// You always construct a LazyMutableCopy with a const reference to an object,
|
||||
// which must outlive this class (unless get_mutable() was called).
|
||||
LazyMutableCopy(const T& obj) // NOLINT(google-explicit-constructor)
|
||||
: original_(&obj) {}
|
||||
|
||||
// You can move a LazyMutableCopy, much like a std::unique_ptr<> or a const*.
|
||||
// We simply rely on the default move constructors being available.
|
||||
|
||||
const T& get() const { return copy_ != nullptr ? *copy_ : *original_; }
|
||||
T* get_mutable() {
|
||||
if (copy_ == nullptr) {
|
||||
copy_ = absl::make_unique<T>(*original_);
|
||||
original_ = nullptr;
|
||||
}
|
||||
return copy_.get();
|
||||
}
|
||||
|
||||
// True iff get_mutable() was called at least once (in which case the object
|
||||
// was copied).
|
||||
bool was_copied() const { return copy_ != nullptr; }
|
||||
|
||||
private:
|
||||
const T* original_;
|
||||
std::unique_ptr<T> copy_;
|
||||
};
|
||||
|
||||
} // namespace operations_research
|
||||
|
||||
#endif // OR_TOOLS_UTIL_LAZY_MUTABLE_COPY_H_
|
||||
Reference in New Issue
Block a user