26 #include "absl/container/flat_hash_map.h" 27 #include "absl/container/flat_hash_set.h" 28 #include "absl/memory/memory.h" 29 #include "absl/status/status.h" 30 #include "absl/status/statusor.h" 31 #include "absl/strings/str_cat.h" 32 #include "absl/strings/string_view.h" 33 #include "absl/time/clock.h" 34 #include "absl/time/time.h" 35 #include "absl/types/optional.h" 36 #include "absl/types/span.h" 37 #include "scip/type_cons.h" 38 #include "scip/type_var.h" 43 #include "ortools/math_opt/callback.pb.h" 47 #include "ortools/math_opt/model.pb.h" 48 #include "ortools/math_opt/model_parameters.pb.h" 49 #include "ortools/math_opt/model_update.pb.h" 50 #include "ortools/math_opt/parameters.pb.h" 51 #include "ortools/math_opt/result.pb.h" 52 #include "ortools/math_opt/solution.pb.h" 54 #include "ortools/math_opt/sparse_containers.pb.h" 56 #include "absl/status/status.h" 65 int64_t SafeId(
const VariablesProto& variables,
int index) {
66 if (variables.ids().empty()) {
69 return variables.ids(
index);
72 const std::string& EmptyString() {
73 static const std::string*
const empty_string =
new std::string;
77 const std::string& SafeName(
const VariablesProto& variables,
int index) {
78 if (variables.names().empty()) {
81 return variables.names(
index);
84 int64_t SafeId(
const LinearConstraintsProto& linear_constraints,
int index) {
85 if (linear_constraints.ids().empty()) {
88 return linear_constraints.ids(
index);
91 const std::string& SafeName(
const LinearConstraintsProto& linear_constraints,
93 if (linear_constraints.names().empty()) {
96 return linear_constraints.names(
index);
99 absl::flat_hash_map<int64_t, double> SparseDoubleVectorAsMap(
100 const SparseDoubleVectorProto& vector) {
101 CHECK_EQ(vector.ids_size(), vector.values_size());
102 absl::flat_hash_map<int64_t, double> result;
103 result.reserve(vector.ids_size());
104 for (
int i = 0; i < vector.ids_size(); ++i) {
105 result[vector.ids(i)] = vector.values(i);
114 inline int FindRowStart(
const SparseDoubleMatrixProto& matrix,
const int row_id,
115 const int scan_start) {
116 int result = scan_start;
117 while (result < matrix.row_ids_size() && matrix.row_ids(result) < row_id) {
123 struct LinearConstraintView {
138 class LinearConstraintIterator {
140 LinearConstraintIterator(
141 const LinearConstraintsProto* linear_constraints,
142 const SparseDoubleMatrixProto* linear_constraint_matrix)
146 const int64_t first_constraint = SafeId(*linear_constraints_, 0);
148 FindRowStart(*linear_constraint_matrix_, first_constraint, 0);
149 matrix_end_ = FindRowStart(*linear_constraint_matrix_,
150 first_constraint + 1, matrix_start_);
153 matrix_end_ = matrix_start_;
157 bool IsDone()
const {
162 LinearConstraintView Current()
const {
164 LinearConstraintView result;
165 result.lower_bound = linear_constraints_->lower_bounds(current_con_);
166 result.upper_bound = linear_constraints_->upper_bounds(current_con_);
167 result.name = SafeName(*linear_constraints_, current_con_);
168 result.linear_constraint_id = SafeId(*linear_constraints_, current_con_);
170 const auto vars_begin = linear_constraint_matrix_->column_ids().begin();
171 result.variable_ids = absl::MakeConstSpan(vars_begin + matrix_start_,
172 vars_begin + matrix_end_);
173 const auto coefficients_begins =
174 linear_constraint_matrix_->coefficients().begin();
175 result.coefficients = absl::MakeConstSpan(
176 coefficients_begins + matrix_start_, coefficients_begins + matrix_end_);
186 matrix_end_ = matrix_start_;
189 const int64_t current_row_id = SafeId(*linear_constraints_, current_con_);
191 FindRowStart(*linear_constraint_matrix_, current_row_id, matrix_end_);
193 matrix_end_ = FindRowStart(*linear_constraint_matrix_, current_row_id + 1,
199 const LinearConstraintsProto*
const linear_constraints_;
201 const SparseDoubleMatrixProto*
const linear_constraint_matrix_;
204 int current_con_ = 0;
217 int matrix_start_ = 0;
221 inline GScipVarType GScipVarTypeFromIsInteger(
const bool is_integer) {
247 template <
typename T>
248 class LazyInitialized {
251 explicit LazyInitialized(std::function<T()> initializer)
255 const T& GetOrCreate() {
257 value_ = initializer_();
263 const std::function<T()> initializer_;
264 absl::optional<T> value_;
267 template <
typename T>
269 const std::vector<int64_t>& ids_in_order,
270 const absl::flat_hash_map<int64_t, T>& id_map,
271 const absl::flat_hash_map<T, double>& value_map,
272 const SparseVectorFilterProto& filter) {
273 SparseVectorFilterPredicate predicate(filter);
274 SparseDoubleVectorProto result;
275 for (
const int64_t variable_id : ids_in_order) {
276 const double value = value_map.at(id_map.at(variable_id));
277 if (predicate.AcceptsAndUpdate(variable_id,
value)) {
278 result.add_ids(variable_id);
279 result.add_values(
value);
287 absl::Status GScipSolver::AddVariables(
288 const VariablesProto& variables,
289 const absl::flat_hash_map<int64_t, double>& linear_objective_coefficients) {
291 const int64_t
id = SafeId(variables, i);
292 CHECK_GE(
id, next_unused_variable_id_);
296 variables.lower_bounds(i), variables.upper_bounds(i),
298 GScipVarTypeFromIsInteger(variables.integers(i)),
299 SafeName(variables, i)));
301 next_unused_variable_id_ =
id + 1;
303 return absl::OkStatus();
306 absl::Status GScipSolver::UpdateVariables(
307 const VariableUpdatesProto& variable_updates) {
308 for (
const auto [
id, lb] :
MakeView(variable_updates.lower_bounds())) {
311 for (
const auto [
id, ub] :
MakeView(variable_updates.upper_bounds())) {
314 for (
const auto [
id, is_integer] :
MakeView(variable_updates.integers())) {
316 GScipVarTypeFromIsInteger(is_integer)));
318 return absl::OkStatus();
321 absl::Status GScipSolver::AddLinearConstraints(
322 const LinearConstraintsProto& linear_constraints,
323 const SparseDoubleMatrixProto& linear_constraint_matrix) {
324 for (LinearConstraintIterator lin_con_it(&linear_constraints,
325 &linear_constraint_matrix);
326 !lin_con_it.IsDone(); lin_con_it.Next()) {
327 const LinearConstraintView current = lin_con_it.Current();
328 CHECK_GE(current.linear_constraint_id, next_unused_linear_constraint_id_);
330 GScipLinearRange range;
331 range.lower_bound = current.lower_bound;
332 range.upper_bound = current.upper_bound;
333 range.coefficients = std::vector<double>(current.coefficients.begin(),
334 current.coefficients.end());
335 range.variables.reserve(current.variable_ids.size());
336 for (
const int64_t var_id : current.variable_ids) {
337 range.variables.push_back(variables_.at(var_id));
340 SCIP_CONS*
const scip_con,
341 gscip_->AddLinearConstraint(range, std::string(current.name)));
344 next_unused_linear_constraint_id_ = current.linear_constraint_id + 1;
346 return absl::OkStatus();
349 absl::Status GScipSolver::UpdateLinearConstraints(
350 const LinearConstraintUpdatesProto linear_constraint_updates,
351 const SparseDoubleMatrixProto& linear_constraint_matrix) {
352 for (
const auto [
id, lb] :
353 MakeView(linear_constraint_updates.lower_bounds())) {
355 gscip_->SetLinearConstraintLb(linear_constraints_.at(
id), lb));
357 for (
const auto [
id, ub] :
358 MakeView(linear_constraint_updates.upper_bounds())) {
360 gscip_->SetLinearConstraintUb(linear_constraints_.at(
id), ub));
362 for (
int i = 0; i < linear_constraint_matrix.row_ids_size(); ++i) {
363 const int64_t lin_con_id = linear_constraint_matrix.row_ids(i);
364 if (lin_con_id >= next_unused_linear_constraint_id_) {
367 const int64_t var_id = linear_constraint_matrix.column_ids(i);
368 const double value = linear_constraint_matrix.coefficients(i);
370 linear_constraints_.at(lin_con_id), variables_.at(var_id),
value));
372 return absl::OkStatus();
381 case EMPHASIS_MEDIUM:
382 case EMPHASIS_UNSPECIFIED:
385 case EMPHASIS_VERY_HIGH:
388 LOG(
FATAL) <<
"Unsupported MathOpt Emphasis value: " 390 <<
" unknown, error setting gSCIP parameters";
395 const CommonSolveParametersProto& common_solver_parameters,
402 if (common_solver_parameters.has_time_limit()) {
408 if (common_solver_parameters.has_threads()) {
411 if (common_solver_parameters.has_enable_output()) {
420 if (common_solver_parameters.has_random_seed()) {
423 if (common_solver_parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED) {
425 switch (common_solver_parameters.lp_algorithm()) {
426 case LP_ALGORITHM_PRIMAL_SIMPLEX:
429 case LP_ALGORITHM_DUAL_SIMPLEX:
432 case LP_ALGORITHM_BARRIER:
438 <<
" unknown, error setting gSCIP parameters";
442 if (common_solver_parameters.cuts() != EMPHASIS_UNSPECIFIED) {
446 if (common_solver_parameters.heuristics() != EMPHASIS_UNSPECIFIED) {
450 if (common_solver_parameters.presolve() != EMPHASIS_UNSPECIFIED) {
454 if (common_solver_parameters.scaling() != EMPHASIS_UNSPECIFIED) {
456 switch (common_solver_parameters.scaling()) {
461 case EMPHASIS_MEDIUM:
465 case EMPHASIS_VERY_HIGH:
471 <<
" unknown, error setting gSCIP parameters";
481 std::string JoinDetails(
const std::string& gscip_detail,
482 const std::string& math_opt_detail) {
483 if (gscip_detail.empty()) {
484 return math_opt_detail;
486 if (math_opt_detail.empty()) {
489 return absl::StrCat(gscip_detail,
"; ", math_opt_detail);
494 absl::StatusOr<std::pair<SolveResultProto::TerminationReason, std::string>>
496 const std::string& gscip_status_detail,
497 const bool has_feasible_solution) {
498 switch (gscip_status) {
500 return std::make_pair(SolveResultProto::TERMINATION_REASON_UNSPECIFIED,
501 gscip_status_detail);
505 return std::make_pair(
506 SolveResultProto::NODE_LIMIT,
507 JoinDetails(gscip_status_detail,
508 "Underlying gSCIP status: NODE_LIMIT."));
510 return std::make_pair(
511 SolveResultProto::NODE_LIMIT,
512 JoinDetails(gscip_status_detail,
513 "Underlying gSCIP status: TOTAL_NODE_LIMIT."));
515 return std::make_pair(SolveResultProto::SLOW_PROGRESS,
516 gscip_status_detail);
518 return std::make_pair(SolveResultProto::TIME_LIMIT, gscip_status_detail);
520 return std::make_pair(SolveResultProto::MEMORY_LIMIT,
521 gscip_status_detail);
524 return std::make_pair(SolveResultProto::SOLUTION_LIMIT,
525 JoinDetails(gscip_status_detail,
526 "Underlying gSCIP status: SOL_LIMIT."));
528 return std::make_pair(
529 SolveResultProto::SOLUTION_LIMIT,
530 JoinDetails(gscip_status_detail,
531 "Underlying gSCIP status: BEST_SOL_LIMIT."));
534 return std::make_pair(
535 SolveResultProto::OTHER_LIMIT,
536 JoinDetails(gscip_status_detail,
537 "Underlying gSCIP status: RESTART_LIMIT."));
540 JoinDetails(gscip_status_detail,
541 "Underlying gSCIP status: OPTIMAL."));
544 JoinDetails(gscip_status_detail,
545 "Underlying gSCIP status: GAP_LIMIT."));
549 if (has_feasible_solution) {
550 return std::make_pair(
551 SolveResultProto::UNBOUNDED,
552 JoinDetails(gscip_status_detail,
553 "Underlying gSCIP status was UNBOUNDED, both primal " 554 "ray and feasible solution are present."));
556 return std::make_pair(
557 SolveResultProto::DUAL_INFEASIBLE,
560 "Underlying gSCIP status was UNBOUNDED, but only primal ray " 561 "was given, no feasible solution was found."));
566 return std::make_pair(
567 SolveResultProto::DUAL_INFEASIBLE,
568 JoinDetails(gscip_status_detail,
569 "Underlying gSCIP status: INF_OR_UNBD."));
571 return std::make_pair(
572 SolveResultProto::OTHER_ERROR,
573 JoinDetails(gscip_status_detail,
574 "Underlying gSCIP status: OTHER_ERROR."));
576 return absl::InvalidArgumentError(gscip_status_detail);
578 return absl::InternalError(JoinDetails(
579 gscip_status_detail, absl::StrCat(
"Missing GScipOutput.status case: ",
584 absl::StatusOr<SolveResultProto> GScipSolver::CreateSolveResultProto(
585 GScipResult gscip_result,
586 const ModelSolveParametersProto& model_parameters) {
587 SolveResultProto solve_result;
589 const auto reason_and_detail,
590 ConvertTerminationReason(gscip_result.gscip_output.status(),
591 gscip_result.gscip_output.status_detail(),
592 !gscip_result.solutions.empty()));
593 solve_result.set_termination_reason(reason_and_detail.first);
594 solve_result.set_termination_detail(reason_and_detail.second);
596 const int num_solutions = gscip_result.solutions.size();
597 CHECK_EQ(num_solutions, gscip_result.objective_values.size());
599 LazyInitialized<std::vector<int64_t>> sorted_variables([&]() {
600 std::vector<int64_t> sorted;
601 sorted.reserve(variables_.size());
602 for (
const auto& entry : variables_) {
603 sorted.emplace_back(entry.first);
605 std::sort(sorted.begin(), sorted.end());
608 for (
int i = 0; i < gscip_result.solutions.size(); ++i) {
609 PrimalSolutionProto*
const primal_solution =
610 solve_result.add_primal_solutions();
611 primal_solution->set_objective_value(gscip_result.objective_values[i]);
613 sorted_variables.GetOrCreate(), variables_, gscip_result.solutions[i],
614 model_parameters.primal_variables_filter());
616 if (!gscip_result.primal_ray.empty()) {
617 *solve_result.add_primal_rays()->mutable_variable_values() =
619 gscip_result.primal_ray,
620 model_parameters.primal_variables_filter());
624 SolveStatsProto*
const common_stats = solve_result.mutable_solve_stats();
625 const GScipSolvingStats& gscip_stats = gscip_result.gscip_output.stats();
626 common_stats->set_best_dual_bound(gscip_stats.best_bound());
627 common_stats->set_best_primal_bound(gscip_stats.best_objective());
628 common_stats->set_node_count(gscip_stats.node_count());
629 common_stats->set_simplex_iterations(gscip_stats.primal_simplex_iterations() +
630 gscip_stats.dual_simplex_iterations());
631 common_stats->set_barrier_iterations(gscip_stats.total_lp_iterations() -
632 common_stats->simplex_iterations());
633 *solve_result.mutable_gscip_output() = std::move(gscip_result.gscip_output);
638 const ModelProto&
model,
const SolverInitializerProto& initializer) {
643 solver->gscip_->SetObjectiveOffset(
model.objective().offset()));
646 SparseDoubleVectorAsMap(
model.objective().linear_coefficients())));
648 model.linear_constraints(),
model.linear_constraint_matrix()));
654 const ModelSolveParametersProto& model_parameters,
655 const CallbackRegistrationProto& callback_registration,
const Callback cb) {
656 const absl::Time start = absl::Now();
658 const std::unique_ptr<GScipSolverCallbackHandler> callback_handler =
660 start, gscip_->scip());
672 callback_handler ? callback_handler->MessageHandler() :
nullptr));
673 if (callback_handler) {
677 SolveResultProto result,
678 CreateSolveResultProto(std::move(gscip_result), model_parameters));
680 absl::Now() - start, result.mutable_solve_stats()->mutable_solve_time()));
684 absl::flat_hash_set<SCIP_VAR*> GScipSolver::LookupAllVariables(
686 absl::flat_hash_set<SCIP_VAR*> result;
689 result.insert(variables_.at(var_id));
697 LookupAllVariables(model_update.deleted_variable_ids()))
702 for (
const int64_t constraint_id :
703 model_update.deleted_linear_constraint_ids()) {
704 SCIP_CONS*
const scip_cons = linear_constraints_.at(constraint_id);
705 linear_constraints_.erase(constraint_id);
709 const absl::flat_hash_set<SCIP_VAR*> vars_to_delete =
710 LookupAllVariables(model_update.deleted_variable_ids());
711 for (
const int64_t deleted_variable_id :
712 model_update.deleted_variable_ids()) {
713 variables_.erase(deleted_variable_id);
717 if (model_update.objective_updates().has_direction_update()) {
719 model_update.objective_updates().direction_update()));
721 if (model_update.objective_updates().has_offset_update()) {
723 model_update.objective_updates().offset_update()));
726 const absl::flat_hash_map<int64_t, double> linear_objective_updates =
727 SparseDoubleVectorAsMap(
728 model_update.objective_updates().linear_coefficients());
729 for (
const auto& obj_pair : linear_objective_updates) {
730 if (obj_pair.first < next_unused_variable_id_) {
732 gscip_->SetObjCoef(variables_.at(obj_pair.first), obj_pair.second));
736 AddVariables(model_update.new_variables(), linear_objective_updates));
738 UpdateLinearConstraints(model_update.linear_constraint_updates(),
739 model_update.linear_constraint_matrix_updates()));
741 AddLinearConstraints(model_update.new_linear_constraints(),
742 model_update.linear_constraint_matrix_updates()));
743 return absl::OkStatus();
void GScipSetMaxNumThreads(int num_threads, GScipParameters *parameters)
static constexpr Status TERMINATE
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
static constexpr Status TOTAL_NODE_LIMIT
void set_silence_output(bool value)
#define CHECK_GE(val1, val2)
static absl::StatusOr< std::unique_ptr< SolverInterface > > New(const ModelProto &model, const SolverInitializerProto &initializer)
void set_presolve(::operations_research::GScipParameters_MetaParamValue value)
static absl::StatusOr< std::unique_ptr< GScip > > Create(const std::string &problem_name)
static constexpr Status STALL_NODE_LIMIT
::PROTOBUF_NAMESPACE_ID::Map< std::string, ::PROTOBUF_NAMESPACE_ID::int32 > * mutable_int_params()
::PROTOBUF_NAMESPACE_ID::Map< std::string, std::string > * mutable_char_params()
void MergeFrom(const GScipParameters &from)
void InsertOrDie(Collection *const collection, const typename Collection::value_type &value)
static constexpr MetaParamValue OFF
void set_separating(::operations_research::GScipParameters_MetaParamValue value)
static constexpr Status USER_INTERRUPT
static constexpr Status MEM_LIMIT
int NumMatrixNonzeros(const SparseDoubleMatrixProto &matrix)
int NumVariables(const VariablesProto &variables)
void set_heuristics(::operations_research::GScipParameters_MetaParamValue value)
SparseDoubleVectorProto FillSparseDoubleVector(const std::vector< int64_t > &ids_in_order, const absl::flat_hash_map< int64_t, IndexType > &id_map, const glop::StrictITIVector< IndexType, glop::Fractional > &values, const SparseVectorFilterProto &filter)
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
void GScipSetTimeLimit(absl::Duration time_limit, GScipParameters *parameters)
absl::StatusOr< SolveResultProto > Solve(const SolveParametersProto ¶meters, const ModelSolveParametersProto &model_parameters, const CallbackRegistrationProto &callback_registration, Callback cb) override
static constexpr Status BEST_SOL_LIMIT
GScipParameters_MetaParamValue
static constexpr Status UNKNOWN
inline ::absl::StatusOr< absl::Duration > DecodeGoogleApiProto(const google::protobuf::Duration &proto)
static constexpr MetaParamValue FAST
bool CanUpdate(const ModelUpdateProto &model_update) override
static std::unique_ptr< GScipSolverCallbackHandler > RegisterIfNeeded(const CallbackRegistrationProto &callback_registration, SolverInterface::Callback callback, absl::Time solve_start, SCIP *scip)
static constexpr Status INF_OR_UNBD
int64_t linear_constraint_id
static constexpr Status GAP_LIMIT
static constexpr Status TIME_LIMIT
std::string ProtoEnumToString(ProtoEnumType enum_value)
GScipParameters::MetaParamValue ConvertMathOptEmphasis(Emphasis emphasis)
#define CHECK_EQ(val1, val2)
int NumConstraints(const LinearConstraintsProto &linear_constraints)
static constexpr Status NODE_LIMIT
void GScipSetRandomSeed(GScipParameters *parameters, int random_seed)
static constexpr Status INVALID_SOLVER_PARAMETERS
const Collection::value_type::second_type & FindWithDefault(const Collection &collection, const typename Collection::value_type::first_type &key, const typename Collection::value_type::second_type &value)
static constexpr MetaParamValue DEFAULT_META_PARAM_VALUE
static constexpr Status OPTIMAL
absl::Status Update(const ModelUpdateProto &model_update) override
std::function< absl::StatusOr< CallbackResultProto >(const CallbackDataProto &)> Callback
absl::Span< const int64_t > variable_ids
static constexpr MetaParamValue AGGRESSIVE
static GScipParameters MergeCommonParameters(const CommonSolveParametersProto &common_solver_parameters, const GScipParameters &gscip_parameters)
Collection of objects used to extend the Constraint Solver library.
static constexpr Status RESTART_LIMIT
MATH_OPT_REGISTER_SOLVER(SOLVER_TYPE_CP_SAT, CpSatSolver::New)
absl::Span< const double > coefficients
GScipOutput_Status Status
#define RETURN_IF_ERROR(expr)
static constexpr Status INFEASIBLE
static constexpr Status SOL_LIMIT
#define ASSIGN_OR_RETURN(lhs, rexpr)
static constexpr Status UNBOUNDED