update linear solver code

This commit is contained in:
Laurent Perron
2019-09-02 11:32:05 +02:00
parent dbc4c9e673
commit 1387c63869
9 changed files with 392 additions and 117 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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_