27 #include "absl/container/flat_hash_map.h"
28 #include "absl/container/flat_hash_set.h"
29 #include "absl/memory/memory.h"
30 #include "absl/status/status.h"
31 #include "absl/status/statusor.h"
32 #include "absl/strings/str_cat.h"
33 #include "absl/strings/str_join.h"
34 #include "absl/strings/string_view.h"
35 #include "absl/time/clock.h"
36 #include "absl/time/time.h"
37 #include "absl/types/span.h"
38 #include "google/protobuf/map.h"
39 #include "absl/log/check.h"
41 #include "absl/log/die_if_null.h"
48 #include "ortools/gscip/gscip.pb.h"
51 #include "ortools/math_opt/callback.pb.h"
59 #include "ortools/math_opt/model.pb.h"
60 #include "ortools/math_opt/model_parameters.pb.h"
61 #include "ortools/math_opt/model_update.pb.h"
62 #include "ortools/math_opt/parameters.pb.h"
63 #include "ortools/math_opt/result.pb.h"
64 #include "ortools/math_opt/solution.pb.h"
67 #include "ortools/math_opt/sparse_containers.pb.h"
70 #include "scip/scip.h"
71 #include "scip/type_cons.h"
72 #include "scip/type_event.h"
73 #include "scip/type_var.h"
80 constexpr
double kInf = std::numeric_limits<double>::infinity();
82 constexpr SupportedProblemStructures kGscipSupportedStructures = {
90 int64_t SafeId(
const VariablesProto& variables,
int index) {
91 if (variables.ids().empty()) {
94 return variables.ids(
index);
97 const std::string& EmptyString() {
98 static const std::string*
const empty_string =
new std::string;
102 const std::string& SafeName(
const VariablesProto& variables,
int index) {
103 if (variables.names().empty()) {
104 return EmptyString();
106 return variables.names(
index);
109 int64_t SafeId(
const LinearConstraintsProto& linear_constraints,
int index) {
110 if (linear_constraints.ids().empty()) {
113 return linear_constraints.ids(
index);
116 const std::string& SafeName(
const LinearConstraintsProto& linear_constraints,
118 if (linear_constraints.names().empty()) {
119 return EmptyString();
121 return linear_constraints.names(
index);
124 absl::flat_hash_map<int64_t, double> SparseDoubleVectorAsMap(
125 const SparseDoubleVectorProto& vector) {
126 CHECK_EQ(vector.ids_size(), vector.values_size());
127 absl::flat_hash_map<int64_t, double> result;
128 result.reserve(vector.ids_size());
129 for (
int i = 0; i < vector.ids_size(); ++i) {
130 result[vector.ids(i)] = vector.values(i);
139 inline int FindRowStart(
const SparseDoubleMatrixProto& matrix,
140 const int64_t row_id,
const int scan_start) {
141 int result = scan_start;
142 while (result < matrix.row_ids_size() && matrix.row_ids(result) < row_id) {
148 struct LinearConstraintView {
163 class LinearConstraintIterator {
165 LinearConstraintIterator(
166 const LinearConstraintsProto* linear_constraints,
167 const SparseDoubleMatrixProto* linear_constraint_matrix)
168 : linear_constraints_(ABSL_DIE_IF_NULL(linear_constraints)),
169 linear_constraint_matrix_(ABSL_DIE_IF_NULL(linear_constraint_matrix)) {
171 const int64_t first_constraint = SafeId(*linear_constraints_, 0);
173 FindRowStart(*linear_constraint_matrix_, first_constraint, 0);
174 matrix_end_ = FindRowStart(*linear_constraint_matrix_,
175 first_constraint + 1, matrix_start_);
178 matrix_end_ = matrix_start_;
182 bool IsDone()
const {
187 LinearConstraintView Current()
const {
189 LinearConstraintView result;
190 result.lower_bound = linear_constraints_->lower_bounds(current_con_);
191 result.upper_bound = linear_constraints_->upper_bounds(current_con_);
192 result.name = SafeName(*linear_constraints_, current_con_);
193 result.linear_constraint_id = SafeId(*linear_constraints_, current_con_);
195 const auto vars_begin = linear_constraint_matrix_->column_ids().data();
196 result.variable_ids = absl::MakeConstSpan(vars_begin + matrix_start_,
197 vars_begin + matrix_end_);
198 const auto coefficients_begins =
199 linear_constraint_matrix_->coefficients().data();
200 result.coefficients = absl::MakeConstSpan(
201 coefficients_begins + matrix_start_, coefficients_begins + matrix_end_);
211 matrix_end_ = matrix_start_;
214 const int64_t current_row_id = SafeId(*linear_constraints_, current_con_);
216 FindRowStart(*linear_constraint_matrix_, current_row_id, matrix_end_);
218 matrix_end_ = FindRowStart(*linear_constraint_matrix_, current_row_id + 1,
224 const LinearConstraintsProto*
const linear_constraints_;
226 const SparseDoubleMatrixProto*
const linear_constraint_matrix_;
229 int current_con_ = 0;
242 int matrix_start_ = 0;
246 inline GScipVarType GScipVarTypeFromIsInteger(
const bool is_integer) {
272 template <
typename T>
273 class LazyInitialized {
276 explicit LazyInitialized(std::function<T()> initializer)
277 : initializer_(ABSL_DIE_IF_NULL(initializer)) {}
280 const T& GetOrCreate() {
282 value_ = initializer_();
288 const std::function<T()> initializer_;
289 std::optional<T> value_;
292 template <
typename T>
294 const std::vector<int64_t>& ids_in_order,
295 const absl::flat_hash_map<int64_t, T>& id_map,
296 const absl::flat_hash_map<T, double>& value_map,
297 const SparseVectorFilterProto& filter) {
298 SparseVectorFilterPredicate predicate(filter);
299 SparseDoubleVectorProto result;
300 for (
const int64_t variable_id : ids_in_order) {
301 const double value = value_map.at(id_map.at(variable_id));
302 if (predicate.AcceptsAndUpdate(variable_id,
value)) {
303 result.add_ids(variable_id);
304 result.add_values(
value);
312 absl::Status GScipSolver::AddVariables(
313 const VariablesProto& variables,
314 const absl::flat_hash_map<int64_t, double>& linear_objective_coefficients) {
316 const int64_t
id = SafeId(variables, i);
321 const bool inverted_bounds =
322 variables.lower_bounds(i) > variables.upper_bounds(i);
326 variables.lower_bounds(i),
327 inverted_bounds ? variables.lower_bounds(i)
328 : variables.upper_bounds(i),
330 GScipVarTypeFromIsInteger(variables.integers(i)),
331 SafeName(variables, i)));
332 if (inverted_bounds) {
333 const double ub = variables.upper_bounds(i);
346 return absl::OkStatus();
349 absl::StatusOr<bool> GScipSolver::UpdateVariables(
350 const VariableUpdatesProto& variable_updates) {
351 for (
const auto [
id, is_integer] :
MakeView(variable_updates.integers())) {
354 SCIP_VAR*
const var = variables_.at(
id);
361 gscip_->SetVarType(
var, GScipVarTypeFromIsInteger(is_integer)));
363 for (
const auto [
id, lb] :
MakeView(variable_updates.lower_bounds())) {
364 SCIP_VAR*
const var = variables_.at(
id);
372 for (
const auto [
id, ub] :
MakeView(variable_updates.upper_bounds())) {
373 SCIP_VAR*
const var = variables_.at(
id);
389 absl::Status GScipSolver::AddQuadraticObjectiveTerms(
390 const SparseDoubleMatrixProto& new_qp_terms,
const bool maximize) {
391 const int num_qp_terms = new_qp_terms.row_ids_size();
392 if (num_qp_terms == 0) {
393 return absl::OkStatus();
396 SCIP_VAR*
const qp_auxiliary_variable,
398 GScipQuadraticRange
range{
399 .lower_bound = maximize ? 0.0 : -
kInf,
400 .linear_variables = {qp_auxiliary_variable},
401 .linear_coefficients = {-1.0},
402 .upper_bound = maximize ?
kInf : 0.0,
404 range.quadratic_variables1.reserve(num_qp_terms);
405 range.quadratic_variables2.reserve(num_qp_terms);
406 range.quadratic_coefficients.reserve(num_qp_terms);
407 for (
int i = 0; i < num_qp_terms; ++i) {
408 range.quadratic_variables1.push_back(
409 variables_.at(new_qp_terms.row_ids(i)));
410 range.quadratic_variables2.push_back(
411 variables_.at(new_qp_terms.column_ids(i)));
412 range.quadratic_coefficients.push_back(new_qp_terms.coefficients(i));
415 has_quadratic_objective_ =
true;
416 return absl::OkStatus();
419 absl::Status GScipSolver::AddLinearConstraints(
420 const LinearConstraintsProto& linear_constraints,
421 const SparseDoubleMatrixProto& linear_constraint_matrix) {
422 for (LinearConstraintIterator lin_con_it(&linear_constraints,
423 &linear_constraint_matrix);
424 !lin_con_it.IsDone(); lin_con_it.Next()) {
425 const LinearConstraintView current = lin_con_it.Current();
427 GScipLinearRange
range;
428 range.lower_bound = current.lower_bound;
429 range.upper_bound = current.upper_bound;
430 range.coefficients = std::vector<double>(current.coefficients.begin(),
431 current.coefficients.end());
432 range.variables.reserve(current.variable_ids.size());
433 for (
const int64_t var_id : current.variable_ids) {
434 range.variables.push_back(variables_.at(var_id));
437 SCIP_CONS*
const scip_con,
438 gscip_->AddLinearConstraint(
range, std::string(current.name)));
442 return absl::OkStatus();
445 absl::Status GScipSolver::UpdateLinearConstraints(
446 const LinearConstraintUpdatesProto linear_constraint_updates,
447 const SparseDoubleMatrixProto& linear_constraint_matrix,
448 const std::optional<int64_t> first_new_var_id,
449 const std::optional<int64_t> first_new_cstr_id) {
450 for (
const auto [
id, lb] :
451 MakeView(linear_constraint_updates.lower_bounds())) {
453 gscip_->SetLinearConstraintLb(linear_constraints_.at(
id), lb));
455 for (
const auto [
id, ub] :
456 MakeView(linear_constraint_updates.upper_bounds())) {
458 gscip_->SetLinearConstraintUb(linear_constraints_.at(
id), ub));
461 linear_constraint_matrix, 0,
462 first_new_cstr_id, 0,
464 for (
const auto& [var_id,
value] : var_coeffs) {
466 linear_constraints_.at(lin_con_id), variables_.at(var_id),
value));
469 return absl::OkStatus();
472 absl::Status GScipSolver::AddQuadraticConstraints(
473 const google::protobuf::Map<int64_t, QuadraticConstraintProto>&
474 quadratic_constraints) {
475 for (
const auto& [
id, constraint] : quadratic_constraints) {
476 GScipQuadraticRange
range{
477 .lower_bound = constraint.lower_bound(),
478 .upper_bound = constraint.upper_bound(),
481 const int num_linear_terms = constraint.linear_terms().ids_size();
482 range.linear_variables.reserve(num_linear_terms);
483 range.linear_coefficients.reserve(num_linear_terms);
484 for (
const auto [var_id, coeff] :
MakeView(constraint.linear_terms())) {
485 range.linear_variables.push_back(variables_.at(var_id));
486 range.linear_coefficients.push_back(coeff);
490 const SparseDoubleMatrixProto& quad_terms = constraint.quadratic_terms();
491 const int num_quad_terms = constraint.quadratic_terms().row_ids_size();
492 range.quadratic_variables1.reserve(num_quad_terms);
493 range.quadratic_variables2.reserve(num_quad_terms);
494 range.quadratic_coefficients.reserve(num_quad_terms);
495 for (
int i = 0; i < num_quad_terms; ++i) {
496 range.quadratic_variables1.push_back(
497 variables_.at(quad_terms.row_ids(i)));
498 range.quadratic_variables2.push_back(
499 variables_.at(quad_terms.column_ids(i)));
500 range.quadratic_coefficients.push_back(quad_terms.coefficients(i));
504 gscip_->AddQuadraticConstraint(
range, constraint.name()));
507 return absl::OkStatus();
510 absl::Status GScipSolver::AddIndicatorConstraints(
511 const google::protobuf::Map<int64_t, IndicatorConstraintProto>&
512 indicator_constraints) {
513 for (
const auto& [
id, constraint] : indicator_constraints) {
514 if (!constraint.has_indicator_id()) {
518 SCIP_VAR*
const indicator_var = variables_.at(constraint.indicator_id());
522 GScipIndicatorConstraint data{
523 .indicator_variable = indicator_var,
524 .negate_indicator = constraint.activate_on_zero(),
526 const double lb = constraint.lower_bound();
527 const double ub = constraint.upper_bound();
530 <<
"gSCIP does not support indicator constraints with ranged "
531 "implied constraints; bounds are: "
532 << lb <<
" <= ... <= " << ub;
538 data.upper_bound = ub;
541 data.upper_bound = -lb;
545 const int num_terms = constraint.expression().ids_size();
546 data.variables.reserve(num_terms);
547 data.coefficients.reserve(num_terms);
548 for (
const auto [var_id, coeff] :
MakeView(constraint.expression())) {
549 data.variables.push_back(variables_.at(var_id));
550 data.coefficients.push_back(scaling * coeff);
554 gscip_->AddIndicatorConstraint(data, constraint.name()));
556 std::make_pair(scip_con, constraint.indicator_id()));
558 return absl::OkStatus();
561 absl::StatusOr<std::pair<SCIP_VAR*, SCIP_CONS*>>
562 GScipSolver::AddSlackVariableEqualToExpression(
563 const LinearExpressionProto& expression) {
567 GScipLinearRange
range{
568 .lower_bound = -expression.offset(),
569 .upper_bound = -expression.offset(),
571 range.variables.push_back(aux_var);
572 range.coefficients.push_back(-1.0);
573 for (
int i = 0; i < expression.ids_size(); ++i) {
574 range.variables.push_back(variables_.at(expression.ids(i)));
575 range.coefficients.push_back(expression.coefficients(i));
578 return std::make_pair(aux_var, aux_constr);
581 absl::Status GScipSolver::AuxiliaryStructureHandler::DeleteStructure(
583 for (SCIP_CONS*
const constraint : constraints) {
586 for (SCIP_VAR*
const variable : variables) {
591 return absl::OkStatus();
594 absl::StatusOr<std::pair<GScipSOSData, GScipSolver::AuxiliaryStructureHandler>>
595 GScipSolver::ProcessSosProto(
const SosConstraintProto& sos_constraint) {
597 AuxiliaryStructureHandler handler;
598 for (
const LinearExpressionProto& expr : sos_constraint.expressions()) {
601 if (expr.ids_size() == 1 && expr.coefficients(0) == 1.0 &&
602 expr.offset() == 0.0) {
603 data.variables.push_back(variables_.at(expr.ids(0)));
606 AddSlackVariableEqualToExpression(expr));
607 handler.variables.push_back(slack_var);
608 handler.constraints.push_back(slack_constr);
609 data.variables.push_back(slack_var);
612 for (
const double weight : sos_constraint.weights()) {
613 data.weights.push_back(
weight);
615 return std::make_pair(data, handler);
618 absl::Status GScipSolver::AddSos1Constraints(
619 const google::protobuf::Map<int64_t, SosConstraintProto>&
621 for (
const auto& [
id, constraint] : sos1_constraints) {
623 ProcessSosProto(constraint));
625 SCIP_CONS*
const scip_con,
626 gscip_->AddSOS1Constraint(std::move(sos_data), constraint.name()));
628 std::make_pair(scip_con, std::move(slack_handler)));
630 return absl::OkStatus();
633 absl::Status GScipSolver::AddSos2Constraints(
634 const google::protobuf::Map<int64_t, SosConstraintProto>&
636 for (
const auto& [
id, constraint] : sos2_constraints) {
638 ProcessSosProto(constraint));
640 gscip_->AddSOS2Constraint(sos_data, constraint.name()));
642 std::make_pair(scip_con, std::move(slack_handler)));
644 return absl::OkStatus();
650 return GScipParameters::OFF;
652 return GScipParameters::FAST;
653 case EMPHASIS_MEDIUM:
654 case EMPHASIS_UNSPECIFIED:
655 return GScipParameters::DEFAULT_META_PARAM_VALUE;
657 case EMPHASIS_VERY_HIGH:
658 return GScipParameters::AGGRESSIVE;
660 LOG(FATAL) <<
"Unsupported MathOpt Emphasis value: "
662 <<
" unknown, error setting gSCIP parameters";
667 const SolveParametersProto& solve_parameters) {
672 GScipParameters result;
673 std::vector<std::string> warnings;
679 if (solve_parameters.has_time_limit()) {
685 if (solve_parameters.has_threads()) {
689 if (solve_parameters.has_relative_gap_tolerance()) {
690 (*result.mutable_real_params())[
"limits/gap"] =
691 solve_parameters.relative_gap_tolerance();
694 if (solve_parameters.has_absolute_gap_tolerance()) {
695 (*result.mutable_real_params())[
"limits/absgap"] =
696 solve_parameters.absolute_gap_tolerance();
698 if (solve_parameters.has_node_limit()) {
699 (*result.mutable_long_params())[
"limits/totalnodes"] =
700 solve_parameters.node_limit();
703 if (solve_parameters.has_objective_limit()) {
704 warnings.push_back(
"parameter objective_limit not supported for gSCIP.");
706 if (solve_parameters.has_best_bound_limit()) {
707 warnings.push_back(
"parameter best_bound_limit not supported for gSCIP.");
710 if (solve_parameters.has_cutoff_limit()) {
711 result.set_objective_limit(solve_parameters.cutoff_limit());
714 if (solve_parameters.has_solution_limit()) {
715 (*result.mutable_int_params())[
"limits/solutions"] =
716 solve_parameters.solution_limit();
719 if (solve_parameters.has_solution_pool_size()) {
720 result.set_num_solutions(solve_parameters.solution_pool_size());
726 (*result.mutable_int_params())[
"limits/maxsol"] =
727 std::max(100, solve_parameters.solution_pool_size());
728 (*result.mutable_int_params())[
"limits/maxorigsol"] =
729 solve_parameters.solution_pool_size();
738 result.set_silence_output(!solve_parameters.enable_output());
740 if (solve_parameters.has_random_seed()) {
744 if (solve_parameters.lp_algorithm() != LP_ALGORITHM_UNSPECIFIED) {
746 switch (solve_parameters.lp_algorithm()) {
747 case LP_ALGORITHM_PRIMAL_SIMPLEX:
750 case LP_ALGORITHM_DUAL_SIMPLEX:
753 case LP_ALGORITHM_BARRIER:
757 LOG(FATAL) <<
"LPAlgorithm: "
759 <<
" unknown, error setting gSCIP parameters";
761 (*result.mutable_char_params())[
"lp/initalgorithm"] = alg;
764 if (solve_parameters.cuts() != EMPHASIS_UNSPECIFIED) {
767 if (solve_parameters.heuristics() != EMPHASIS_UNSPECIFIED) {
768 result.set_heuristics(
771 if (solve_parameters.presolve() != EMPHASIS_UNSPECIFIED) {
774 if (solve_parameters.scaling() != EMPHASIS_UNSPECIFIED) {
776 switch (solve_parameters.scaling()) {
781 case EMPHASIS_MEDIUM:
785 case EMPHASIS_VERY_HIGH:
789 LOG(FATAL) <<
"Scaling emphasis: "
791 <<
" unknown, error setting gSCIP parameters";
793 (*result.mutable_int_params())[
"lp/scaling"] = scaling_value;
796 result.MergeFrom(solve_parameters.gscip());
798 if (!warnings.empty()) {
799 return absl::InvalidArgumentError(absl::StrJoin(warnings,
"; "));
806 std::string JoinDetails(
const std::string& gscip_detail,
807 const std::string& math_opt_detail) {
808 if (gscip_detail.empty()) {
809 return math_opt_detail;
811 if (math_opt_detail.empty()) {
814 return absl::StrCat(gscip_detail,
"; ", math_opt_detail);
817 ProblemStatusProto GetProblemStatusProto(
const GScipOutput::Status gscip_status,
818 const bool has_feasible_solution,
819 const bool has_finite_dual_bound,
820 const bool was_cutoff) {
821 ProblemStatusProto problem_status;
822 if (has_feasible_solution) {
823 problem_status.set_primal_status(FEASIBILITY_STATUS_FEASIBLE);
825 problem_status.set_primal_status(FEASIBILITY_STATUS_UNDETERMINED);
827 problem_status.set_dual_status(FEASIBILITY_STATUS_UNDETERMINED);
829 switch (gscip_status) {
831 problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
835 problem_status.set_primal_status(FEASIBILITY_STATUS_INFEASIBLE);
839 problem_status.set_dual_status(FEASIBILITY_STATUS_INFEASIBLE);
841 case GScipOutput::INF_OR_UNBD:
842 problem_status.set_primal_or_dual_infeasible(
true);
847 if (has_finite_dual_bound) {
848 problem_status.set_dual_status(FEASIBILITY_STATUS_FEASIBLE);
850 return problem_status;
853 absl::StatusOr<TerminationProto> ConvertTerminationReason(
854 const GScipOutput::Status gscip_status,
855 const std::string& gscip_status_detail,
const bool has_feasible_solution,
856 const bool had_cutoff) {
857 switch (gscip_status) {
858 case GScipOutput::USER_INTERRUPT:
860 LIMIT_INTERRUPTED, has_feasible_solution,
861 JoinDetails(gscip_status_detail,
862 "underlying gSCIP status: USER_INTERRUPT"));
863 case GScipOutput::NODE_LIMIT:
865 LIMIT_NODE, has_feasible_solution,
866 JoinDetails(gscip_status_detail,
867 "underlying gSCIP status: NODE_LIMIT"));
868 case GScipOutput::TOTAL_NODE_LIMIT:
870 LIMIT_NODE, has_feasible_solution,
871 JoinDetails(gscip_status_detail,
872 "underlying gSCIP status: TOTAL_NODE_LIMIT"));
873 case GScipOutput::STALL_NODE_LIMIT:
875 has_feasible_solution,
876 gscip_status_detail);
877 case GScipOutput::TIME_LIMIT:
879 gscip_status_detail);
880 case GScipOutput::MEM_LIMIT:
882 gscip_status_detail);
883 case GScipOutput::SOL_LIMIT:
885 LIMIT_SOLUTION, has_feasible_solution,
886 JoinDetails(gscip_status_detail,
887 "underlying gSCIP status: SOL_LIMIT"));
888 case GScipOutput::BEST_SOL_LIMIT:
890 LIMIT_SOLUTION, has_feasible_solution,
891 JoinDetails(gscip_status_detail,
892 "underlying gSCIP status: BEST_SOL_LIMIT"));
893 case GScipOutput::RESTART_LIMIT:
895 LIMIT_OTHER, has_feasible_solution,
896 JoinDetails(gscip_status_detail,
897 "underlying gSCIP status: RESTART_LIMIT"));
900 TERMINATION_REASON_OPTIMAL,
901 JoinDetails(gscip_status_detail,
"underlying gSCIP status: OPTIMAL"));
902 case GScipOutput::GAP_LIMIT:
904 TERMINATION_REASON_OPTIMAL,
905 JoinDetails(gscip_status_detail,
906 "underlying gSCIP status: GAP_LIMIT"));
910 false, gscip_status_detail);
913 gscip_status_detail);
916 if (has_feasible_solution) {
918 TERMINATION_REASON_UNBOUNDED,
919 JoinDetails(gscip_status_detail,
920 "underlying gSCIP status was UNBOUNDED, both primal "
921 "ray and feasible solution are present"));
924 TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED,
927 "underlying gSCIP status was UNBOUNDED, but only primal ray "
928 "was given, no feasible solution was found"));
932 case GScipOutput::INF_OR_UNBD:
934 TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED,
935 JoinDetails(gscip_status_detail,
936 "underlying gSCIP status: INF_OR_UNBD"));
938 case GScipOutput::TERMINATE:
940 LIMIT_INTERRUPTED, has_feasible_solution,
941 JoinDetails(gscip_status_detail,
942 "underlying gSCIP status: TERMINATE"));
944 return absl::InvalidArgumentError(gscip_status_detail);
945 case GScipOutput::UNKNOWN:
946 return absl::InternalError(JoinDetails(
947 gscip_status_detail,
"Unexpected GScipOutput.status: UNKNOWN"));
949 return absl::InternalError(JoinDetails(
950 gscip_status_detail, absl::StrCat(
"Missing GScipOutput.status case: ",
957 absl::StatusOr<SolveResultProto> GScipSolver::CreateSolveResultProto(
958 GScipResult gscip_result,
const ModelSolveParametersProto& model_parameters,
959 const std::optional<double> cutoff) {
960 SolveResultProto solve_result;
961 const bool is_maximize = gscip_->ObjectiveIsMaximize();
964 const auto meets_cutoff = [cutoff, is_maximize](
const double obj_value) {
965 if (!cutoff.has_value()) {
969 return obj_value >= *cutoff;
971 return obj_value <= *cutoff;
975 LazyInitialized<std::vector<int64_t>> sorted_variables([&]() {
976 std::vector<int64_t> sorted;
977 sorted.reserve(variables_.size());
978 for (
const auto& entry : variables_) {
979 sorted.emplace_back(entry.first);
981 std::sort(sorted.begin(), sorted.end());
984 CHECK_EQ(gscip_result.solutions.size(), gscip_result.objective_values.size());
985 for (
int i = 0; i < gscip_result.solutions.size(); ++i) {
987 if (!meets_cutoff(gscip_result.objective_values[i])) {
990 SolutionProto*
const solution = solve_result.add_solutions();
991 PrimalSolutionProto*
const primal_solution =
992 solution->mutable_primal_solution();
993 primal_solution->set_objective_value(gscip_result.objective_values[i]);
994 primal_solution->set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
996 sorted_variables.GetOrCreate(), variables_, gscip_result.solutions[i],
997 model_parameters.variable_values_filter());
999 if (!gscip_result.primal_ray.empty()) {
1000 *solve_result.add_primal_rays()->mutable_variable_values() =
1002 gscip_result.primal_ray,
1003 model_parameters.variable_values_filter());
1005 const bool has_feasible_solution = solve_result.solutions_size() > 0;
1007 *solve_result.mutable_termination(),
1008 ConvertTerminationReason(gscip_result.gscip_output.status(),
1009 gscip_result.gscip_output.status_detail(),
1010 has_feasible_solution,
1011 cutoff.has_value()));
1012 *solve_result.mutable_solve_stats()->mutable_problem_status() =
1013 GetProblemStatusProto(
1014 gscip_result.gscip_output.status(),
1015 has_feasible_solution,
1017 std::isfinite(gscip_result.gscip_output.stats().best_bound()),
1018 solve_result.termination().limit() == LIMIT_CUTOFF);
1019 SolveStatsProto*
const common_stats = solve_result.mutable_solve_stats();
1020 const GScipSolvingStats& gscip_stats = gscip_result.gscip_output.stats();
1021 common_stats->set_best_dual_bound(gscip_stats.best_bound());
1023 if (has_feasible_solution) {
1024 common_stats->set_best_primal_bound(gscip_stats.best_objective());
1026 common_stats->set_best_primal_bound(is_maximize ? -
kInf :
kInf);
1029 common_stats->set_node_count(gscip_stats.node_count());
1030 common_stats->set_simplex_iterations(gscip_stats.primal_simplex_iterations() +
1031 gscip_stats.dual_simplex_iterations());
1032 common_stats->set_barrier_iterations(gscip_stats.total_lp_iterations() -
1033 common_stats->simplex_iterations());
1034 *solve_result.mutable_gscip_output() = std::move(gscip_result.gscip_output);
1035 return solve_result;
1038 GScipSolver::GScipSolver(std::unique_ptr<GScip> gscip)
1039 : gscip_(std::move(ABSL_DIE_IF_NULL(gscip))) {
1040 interrupt_event_handler_.Register(gscip_.get());
1051 auto solver = absl::WrapUnique(
new GScipSolver(std::move(gscip)));
1055 SparseDoubleVectorAsMap(
model.objective().linear_coefficients())));
1057 model.objective().quadratic_coefficients(),
1058 model.objective().maximize()));
1060 model.linear_constraints(),
model.linear_constraint_matrix()));
1062 solver->AddQuadraticConstraints(
model.quadratic_constraints()));
1064 solver->AddIndicatorConstraints(
model.indicator_constraints()));
1073 const ModelSolveParametersProto& model_parameters,
1075 const CallbackRegistrationProto& callback_registration,
const Callback cb,
1077 const absl::Time
start = absl::Now();
1082 const std::unique_ptr<GScipSolverCallbackHandler> callback_handler =
1084 start, gscip_->scip());
1086 std::unique_ptr<GScipSolverMessageCallbackHandler> message_cb_handler;
1087 if (message_cb !=
nullptr) {
1088 message_cb_handler =
1089 std::make_unique<GScipSolverMessageCallbackHandler>(message_cb);
1094 for (
const SolutionHintProto& hint : model_parameters.solution_hints()) {
1095 absl::flat_hash_map<SCIP_VAR*, double> partial_solution;
1096 for (
const auto [
id, val] :
MakeView(hint.variable_values())) {
1097 partial_solution.insert({variables_.at(
id), val});
1101 for (
const auto [
id,
value] :
1102 MakeView(model_parameters.branching_priorities())) {
1108 if (interrupter !=
nullptr) {
1109 interrupt_event_handler_.interrupter = interrupter;
1112 [&]() { interrupt_event_handler_.interrupter =
nullptr; });
1119 gscip_->Solve(gscip_parameters,
1121 message_cb_handler !=
nullptr
1122 ? message_cb_handler->MessageHandler()
1126 message_cb_handler.reset();
1128 if (callback_handler) {
1133 SolveResultProto result,
1134 CreateSolveResultProto(std::move(gscip_result), model_parameters,
1136 ? std::make_optional(
parameters.cutoff_limit())
1139 absl::Now() -
start, result.mutable_solve_stats()->mutable_solve_time()));
1143 absl::flat_hash_set<SCIP_VAR*> GScipSolver::LookupAllVariables(
1145 absl::flat_hash_set<SCIP_VAR*> result;
1148 result.insert(variables_.at(var_id));
1154 InvertedBounds GScipSolver::ListInvertedBounds()
const {
1156 InvertedBounds inverted_bounds;
1157 for (
const auto& [
id,
var] : variables_) {
1158 if (gscip_->Lb(
var) > gscip_->Ub(
var)) {
1159 inverted_bounds.variables.push_back(
id);
1162 for (
const auto& [
id, cstr] : linear_constraints_) {
1163 if (gscip_->LinearConstraintLb(cstr) > gscip_->LinearConstraintUb(cstr)) {
1164 inverted_bounds.linear_constraints.push_back(
id);
1169 std::sort(inverted_bounds.variables.begin(), inverted_bounds.variables.end());
1170 std::sort(inverted_bounds.linear_constraints.begin(),
1171 inverted_bounds.linear_constraints.end());
1172 return inverted_bounds;
1175 InvalidIndicators GScipSolver::ListInvalidIndicators()
const {
1176 InvalidIndicators invalid_indicators;
1177 for (
const auto& [constraint_id, gscip_data] : indicator_constraints_) {
1178 if (!gscip_data.has_value()) {
1181 const auto [gscip_constraint, indicator_id] = *gscip_data;
1182 SCIP_VAR*
const indicator_var = variables_.at(indicator_id);
1184 gscip_->Lb(indicator_var) < 0.0 || gscip_->Ub(indicator_var) > 1.0) {
1185 invalid_indicators.invalid_indicators.push_back(
1186 {.variable = indicator_id, .constraint = constraint_id});
1189 invalid_indicators.Sort();
1190 return invalid_indicators;
1195 ->CanSafeBulkDelete(
1196 LookupAllVariables(model_update.deleted_variable_ids()))
1205 if (has_quadratic_objective_ &&
1206 (model_update.objective_updates().has_direction_update() ||
1207 model_update.objective_updates()
1208 .quadratic_coefficients()
1209 .row_ids_size() > 0)) {
1213 for (
const int64_t constraint_id :
1214 model_update.deleted_linear_constraint_ids()) {
1215 SCIP_CONS*
const scip_cons = linear_constraints_.at(constraint_id);
1216 linear_constraints_.erase(constraint_id);
1220 const absl::flat_hash_set<SCIP_VAR*> vars_to_delete =
1221 LookupAllVariables(model_update.deleted_variable_ids());
1222 for (
const int64_t deleted_variable_id :
1223 model_update.deleted_variable_ids()) {
1224 variables_.erase(deleted_variable_id);
1229 const std::optional<int64_t> first_new_var_id =
1231 const std::optional<int64_t> first_new_cstr_id =
1234 if (model_update.objective_updates().has_direction_update()) {
1236 model_update.objective_updates().direction_update()));
1238 if (model_update.objective_updates().has_offset_update()) {
1240 model_update.objective_updates().offset_update()));
1242 if (
const auto response = UpdateVariables(model_update.variable_updates());
1246 const absl::flat_hash_map<int64_t, double> linear_objective_updates =
1247 SparseDoubleVectorAsMap(
1248 model_update.objective_updates().linear_coefficients());
1249 for (
const auto& obj_pair : linear_objective_updates) {
1251 if (!first_new_var_id.has_value() || obj_pair.first < *first_new_var_id) {
1253 gscip_->SetObjCoef(variables_.at(obj_pair.first), obj_pair.second));
1285 AddVariables(model_update.new_variables(), linear_objective_updates));
1288 model_update.objective_updates().quadratic_coefficients(),
1289 gscip_->ObjectiveIsMaximize()));
1293 UpdateLinearConstraints(model_update.linear_constraint_updates(),
1294 model_update.linear_constraint_matrix_updates(),
1296 first_new_cstr_id));
1299 const std::optional first_new_variable_id =
1301 if (first_new_variable_id.has_value()) {
1302 for (
const auto& [lin_con_id, var_coeffs] :
1306 *first_new_variable_id,
1308 for (
const auto& [var_id,
value] : var_coeffs) {
1311 linear_constraints_.at(lin_con_id), variables_.at(var_id),
value));
1318 AddLinearConstraints(model_update.new_linear_constraints(),
1319 model_update.linear_constraint_matrix_updates()));
1322 for (
const int64_t constraint_id :
1323 model_update.quadratic_constraint_updates().deleted_constraint_ids()) {
1325 quadratic_constraints_.extract(constraint_id).mapped()));
1328 model_update.quadratic_constraint_updates().new_constraints()));
1331 for (
const int64_t constraint_id :
1332 model_update.indicator_constraint_updates().deleted_constraint_ids()) {
1333 const auto gscip_data =
1334 indicator_constraints_.extract(constraint_id).mapped();
1335 if (!gscip_data.has_value()) {
1338 SCIP_CONS*
const gscip_constraint = gscip_data->first;
1339 CHECK_NE(gscip_constraint,
nullptr);
1343 model_update.indicator_constraint_updates().new_constraints()));
1346 for (
const int64_t constraint_id :
1347 model_update.sos1_constraint_updates().deleted_constraint_ids()) {
1348 auto [gscip_constraint, slack_handler] =
1349 sos1_constraints_.extract(constraint_id).mapped();
1354 model_update.sos1_constraint_updates().new_constraints()));
1357 for (
const int64_t constraint_id :
1358 model_update.sos2_constraint_updates().deleted_constraint_ids()) {
1359 auto [gscip_constraint, slack_handler] =
1360 sos2_constraints_.extract(constraint_id).mapped();
1365 model_update.sos2_constraint_updates().new_constraints()));
1370 GScipSolver::InterruptEventHandler::InterruptEventHandler()
1372 {.name =
"interrupt event handler",
1373 .description =
"Event handler to call SCIPinterruptSolve() when a "
1374 "user SolveInterrupter is triggered."}) {}
1376 SCIP_RETCODE GScipSolver::InterruptEventHandler::Init(GScip*
const gscip) {
1378 if (interrupter ==
nullptr) {
1384 CatchEvent(SCIP_EVENTTYPE_PRESOLVEROUND);
1385 CatchEvent(SCIP_EVENTTYPE_NODEEVENT);
1386 CatchEvent(SCIP_EVENTTYPE_ROWEVENT);
1388 return TryCallInterruptIfNeeded(gscip);
1391 SCIP_RETCODE GScipSolver::InterruptEventHandler::Execute(
1392 const GScipEventHandlerContext
context) {
1393 return TryCallInterruptIfNeeded(
context.gscip());
1396 SCIP_RETCODE GScipSolver::InterruptEventHandler::TryCallInterruptIfNeeded(
1397 GScip*
const gscip) {
1398 if (interrupter ==
nullptr) {
1399 LOG(WARNING) <<
"TryCallInterruptIfNeeded() called after interrupter has "
1404 if (!interrupter->IsInterrupted()) {
1408 const SCIP_STAGE stage = SCIPgetStage(gscip->scip());
1410 case SCIP_STAGE_INIT:
1411 case SCIP_STAGE_FREE:
1414 LOG(DFATAL) <<
"TryCallInterruptIfNeeded() called in stage "
1415 << (stage == SCIP_STAGE_INIT ?
"INIT" :
"FREE");
1417 case SCIP_STAGE_INITSOLVE:
1418 LOG(WARNING) <<
"TryCallInterruptIfNeeded() called in INITSOLVE stage; "
1419 "we can't call SCIPinterruptSolve() in this stage.";
1422 return SCIPinterruptSolve(gscip->scip());
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
static absl::StatusOr< std::unique_ptr< GScip > > Create(const std::string &problem_name)
static std::unique_ptr< GScipSolverCallbackHandler > RegisterIfNeeded(const CallbackRegistrationProto &callback_registration, SolverInterface::Callback callback, absl::Time solve_start, SCIP *scip)
absl::StatusOr< bool > Update(const ModelUpdateProto &model_update) override
static absl::StatusOr< std::unique_ptr< SolverInterface > > New(const ModelProto &model, const InitArgs &init_args)
absl::StatusOr< SolveResultProto > Solve(const SolveParametersProto ¶meters, const ModelSolveParametersProto &model_parameters, MessageCallback message_cb, const CallbackRegistrationProto &callback_registration, Callback cb, SolveInterrupter *interrupter) override
static absl::StatusOr< GScipParameters > MergeParameters(const SolveParametersProto &solve_parameters)
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >(const CallbackDataProto &)> Callback
SharedResponseManager * response
int64_t linear_constraint_id
absl::Span< const int64_t > variable_ids
absl::Span< const double > coefficients
GurobiMPCallbackContext * context
absl::Cleanup< absl::decay_t< Callback > > MakeCleanup(Callback &&callback)
void InsertOrDie(Collection *const collection, const typename Collection::value_type &value)
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)
absl::Status CheckRegisteredCallbackEvents(const CallbackRegistrationProto ®istration, const absl::flat_hash_set< CallbackEventProto > &supported_events)
MATH_OPT_REGISTER_SOLVER(SOLVER_TYPE_CP_SAT, CpSatSolver::New)
int NumMatrixNonzeros(const SparseDoubleMatrixProto &matrix)
int NumVariables(const VariablesProto &variables)
std::optional< int64_t > FirstLinearConstraintId(const LinearConstraintsProto &linear_constraints)
absl::Status ModelIsSupported(const ModelProto &model, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
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)
bool UpdateIsSupported(const ModelUpdateProto &update, const SupportedProblemStructures &support_menu)
TerminationProto TerminateForLimit(const LimitProto limit, const bool feasible, const absl::string_view detail)
SparseSubmatrixRowsView SparseSubmatrixByRows(const SparseDoubleMatrixProto &matrix, const int64_t start_row_id, const std::optional< int64_t > end_row_id, const int64_t start_col_id, const std::optional< int64_t > end_col_id)
int NumConstraints(const LinearConstraintsProto &linear_constraints)
TerminationProto TerminateForReason(const TerminationReasonProto reason, const absl::string_view detail)
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
GScipParameters::MetaParamValue ConvertMathOptEmphasis(EmphasisProto emphasis)
std::optional< int64_t > FirstVariableId(const VariablesProto &variables)
Collection of objects used to extend the Constraint Solver library.
void GScipSetCatchCtrlC(const bool catch_ctrl_c, GScipParameters *const parameters)
void GScipSetTimeLimit(absl::Duration time_limit, GScipParameters *parameters)
void GScipSetRandomSeed(GScipParameters *parameters, int random_seed)
std::string ProtoEnumToString(ProtoEnumType enum_value)
@ INVALID_SOLVER_PARAMETERS
void GScipSetMaxNumThreads(int num_threads, GScipParameters *parameters)
inline ::absl::StatusOr< absl::Duration > DecodeGoogleApiProto(const google::protobuf::Duration &proto)
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
StatusBuilder InvalidArgumentErrorBuilder()
const std::optional< Range > & range