18 #include "absl/container/flat_hash_set.h"
19 #include "absl/status/statusor.h"
20 #include "absl/strings/str_cat.h"
28 #include "scip/cons_linear.h"
29 #include "scip/scip_general.h"
30 #include "scip/scip_param.h"
31 #include "scip/scip_solvingstats.h"
32 #include "scip/scipdefplugins.h"
33 #include "scip/type_cons.h"
37 #define RETURN_ERROR_UNLESS(x) \
39 return util::StatusBuilder(absl::InvalidArgumentError(absl::StrFormat( \
40 "Condition violated at %s:%d: %s", __FILE__, __LINE__, #x)))
44 constexpr absl::string_view kLinearConstraintHandlerName =
"linear";
46 SCIP_VARTYPE ConvertVarType(
const GScipVarType var_type) {
48 case GScipVarType::kContinuous:
49 return SCIP_VARTYPE_CONTINUOUS;
51 return SCIP_VARTYPE_IMPLINT;
52 case GScipVarType::kInteger:
53 return SCIP_VARTYPE_INTEGER;
57 GScipVarType ConvertVarType(
const SCIP_VARTYPE var_type) {
59 case SCIP_VARTYPE_CONTINUOUS:
60 return GScipVarType::kContinuous;
61 case SCIP_VARTYPE_IMPLINT:
63 case SCIP_VARTYPE_INTEGER:
64 case SCIP_VARTYPE_BINARY:
65 return GScipVarType::kInteger;
69 GScipOutput::Status ConvertStatus(
const SCIP_STATUS scip_status) {
70 switch (scip_status) {
71 case SCIP_STATUS_UNKNOWN:
73 case SCIP_STATUS_USERINTERRUPT:
74 return GScipOutput::USER_INTERRUPT;
75 case SCIP_STATUS_BESTSOLLIMIT:
76 return GScipOutput::BEST_SOL_LIMIT;
77 case SCIP_STATUS_MEMLIMIT:
78 return GScipOutput::MEM_LIMIT;
79 case SCIP_STATUS_NODELIMIT:
80 return GScipOutput::NODE_LIMIT;
81 case SCIP_STATUS_RESTARTLIMIT:
82 return GScipOutput::RESTART_LIMIT;
83 case SCIP_STATUS_SOLLIMIT:
84 return GScipOutput::SOL_LIMIT;
85 case SCIP_STATUS_STALLNODELIMIT:
86 return GScipOutput::STALL_NODE_LIMIT;
87 case SCIP_STATUS_TIMELIMIT:
88 return GScipOutput::TIME_LIMIT;
89 case SCIP_STATUS_TOTALNODELIMIT:
90 return GScipOutput::TOTAL_NODE_LIMIT;
91 case SCIP_STATUS_OPTIMAL:
93 case SCIP_STATUS_GAPLIMIT:
94 return GScipOutput::GAP_LIMIT;
95 case SCIP_STATUS_INFEASIBLE:
96 return GScipOutput::INFEASIBLE;
97 case SCIP_STATUS_UNBOUNDED:
98 return GScipOutput::UNBOUNDED;
99 case SCIP_STATUS_INFORUNBD:
100 return GScipOutput::INF_OR_UNBD;
101 case SCIP_STATUS_TERMINATE:
102 return GScipOutput::TERMINATE;
104 LOG(
FATAL) <<
"Unrecognized scip status: " << scip_status;
108 SCIP_PARAMEMPHASIS ConvertEmphasis(
109 const GScipParameters::Emphasis gscip_emphasis) {
110 switch (gscip_emphasis) {
111 case GScipParameters::DEFAULT_EMPHASIS:
112 return SCIP_PARAMEMPHASIS_DEFAULT;
113 case GScipParameters::CP_SOLVER:
114 return SCIP_PARAMEMPHASIS_CPSOLVER;
115 case GScipParameters::EASY_CIP:
116 return SCIP_PARAMEMPHASIS_EASYCIP;
117 case GScipParameters::FEASIBILITY:
118 return SCIP_PARAMEMPHASIS_FEASIBILITY;
119 case GScipParameters::HARD_LP:
120 return SCIP_PARAMEMPHASIS_HARDLP;
121 case GScipParameters::OPTIMALITY:
122 return SCIP_PARAMEMPHASIS_OPTIMALITY;
124 return SCIP_PARAMEMPHASIS_COUNTER;
125 case GScipParameters::PHASE_FEAS:
126 return SCIP_PARAMEMPHASIS_PHASEFEAS;
127 case GScipParameters::PHASE_IMPROVE:
128 return SCIP_PARAMEMPHASIS_PHASEIMPROVE;
129 case GScipParameters::PHASE_PROOF:
130 return SCIP_PARAMEMPHASIS_PHASEPROOF;
132 LOG(
FATAL) <<
"Unrecognized gscip_emphasis: "
137 SCIP_PARAMSETTING ConvertMetaParamValue(
138 const GScipParameters::MetaParamValue gscip_meta_param_value) {
139 switch (gscip_meta_param_value) {
140 case GScipParameters::DEFAULT_META_PARAM_VALUE:
141 return SCIP_PARAMSETTING_DEFAULT;
142 case GScipParameters::AGGRESSIVE:
143 return SCIP_PARAMSETTING_AGGRESSIVE;
144 case GScipParameters::FAST:
145 return SCIP_PARAMSETTING_FAST;
146 case GScipParameters::OFF:
147 return SCIP_PARAMSETTING_OFF;
149 LOG(
FATAL) <<
"Unrecognized gscip_meta_param_value: "
162 return constraint_options;
165 absl::Status GScip::SetParams(
const GScipParameters& params,
166 const std::string& legacy_params) {
167 SCIPsetMessagehdlrQuiet(scip_, params.silence_output());
168 if (!params.search_logs_filename().empty()) {
169 SCIPsetMessagehdlrLogfile(scip_, params.search_logs_filename().c_str());
171 const SCIP_Bool set_param_quiet =
172 static_cast<SCIP_Bool
>(!params.silence_output());
174 scip_, ConvertEmphasis(params.emphasis()), set_param_quiet));
176 scip_, ConvertMetaParamValue(params.heuristics()), set_param_quiet));
178 scip_, ConvertMetaParamValue(params.presolve()), set_param_quiet));
180 scip_, ConvertMetaParamValue(params.separating()), set_param_quiet));
181 for (
const auto& bool_param : params.bool_params()) {
183 (SCIPsetBoolParam(scip_, bool_param.first.c_str(), bool_param.second)));
185 for (
const auto& int_param : params.int_params()) {
187 (SCIPsetIntParam(scip_, int_param.first.c_str(), int_param.second)));
189 for (
const auto& long_param : params.long_params()) {
191 long_param.second)));
193 for (
const auto& char_param : params.char_params()) {
194 if (char_param.second.size() != 1) {
195 return absl::InvalidArgumentError(
196 absl::StrCat(
"Character parameters must be single character strings, "
198 char_param.first,
" was: ", char_param.second));
201 char_param.second[0])));
203 for (
const auto& string_param : params.string_params()) {
205 string_param.second.c_str())));
207 for (
const auto& real_param : params.real_params()) {
209 (SCIPsetRealParam(scip_, real_param.first.c_str(), real_param.second)));
211 if (!legacy_params.empty()) {
215 return absl::OkStatus();
219 const std::string& problem_name) {
220 SCIP*
scip =
nullptr;
225 return absl::WrapUnique(
new GScip(
scip));
228 GScip::GScip(SCIP* scip) : scip_(scip) {}
232 absl::Status GScip::FreeTransform() {
237 return absl::StrFormat(
"SCIP %d.%d.%d [LP solver: %s]", SCIPmajorVersion(),
238 SCIPminorVersion(), SCIPtechVersion(),
243 if (scip_ ==
nullptr) {
246 return SCIPinterruptSolve(scip_) == SCIP_OKAY;
249 absl::Status GScip::CleanUp() {
250 if (scip_ !=
nullptr) {
251 for (SCIP_VAR* variable : variables_) {
252 if (variable !=
nullptr) {
256 for (SCIP_CONS* constraint : constraints_) {
257 if (constraint !=
nullptr) {
263 return absl::OkStatus();
267 const absl::Status clean_up_status = CleanUp();
268 LOG_IF(DFATAL, !clean_up_status.ok()) << clean_up_status;
272 double lb,
double ub,
double obj_coef,
GScipVarType var_type,
274 SCIP_VAR*
var =
nullptr;
275 lb = ScipInfClamp(lb);
276 ub = ScipInfClamp(ub);
281 ConvertVarType(var_type)));
286 variables_.insert(
var);
293 absl::Status GScip::MaybeKeepConstraintAlive(
296 constraints_.insert(constraint);
300 return absl::OkStatus();
306 SCIP_CONS* constraint =
nullptr;
308 <<
"Error adding constraint: " <<
name <<
".";
311 const_cast<SCIP_VAR**
>(range.
variables.data()),
332 SCIP_CONS* constraint =
nullptr;
335 <<
"Error adding quadratic constraint: " <<
name <<
" in linear term.";
338 <<
"Error adding quadratic constraint: " <<
name <<
" in quadratic term.";
340 <<
"Error adding quadratic constraint: " <<
name <<
" in quadratic term.";
342 scip_, &constraint,
name.c_str(), num_lin_vars,
368 <<
"Error adding indicator constraint: " <<
name <<
".";
373 SCIP_CONS* constraint =
nullptr;
376 <<
"Error adding indicator constraint: " <<
name <<
".";
378 scip_, &constraint,
name.c_str(), indicator,
380 const_cast<SCIP_Var**
>(indicator_constraint.
variables.data()),
381 const_cast<double*
>(indicator_constraint.
coefficients.data()),
401 <<
"Error adding and constraint: " <<
name <<
".";
402 SCIP_CONS* constraint =
nullptr;
404 SCIPcreateConsAnd(scip_, &constraint,
name.c_str(),
406 const_cast<SCIP_VAR**
>(logical_data.
operators.data()),
426 <<
"Error adding or constraint: " <<
name <<
".";
427 SCIP_CONS* constraint =
nullptr;
429 SCIPcreateConsOr(scip_, &constraint,
name.c_str(), logical_data.
resultant,
431 const_cast<SCIP_Var**
>(logical_data.
operators.data()),
449 absl::Status ValidateSOSData(
const GScipSOSData& sos_data,
450 const std::string&
name) {
452 <<
"Error adding SOS constraint: " <<
name <<
".";
453 if (!sos_data.
weights.empty()) {
455 <<
" Error adding SOS constraint: " <<
name <<
".";
457 absl::flat_hash_set<double> distinct_weights;
458 for (
const double w : sos_data.
weights) {
460 <<
"Error adding SOS constraint: " <<
name
461 <<
", weights must be distinct, but found value " << w <<
" twice.";
462 distinct_weights.insert(w);
464 return absl::OkStatus();
473 SCIP_CONS* constraint =
nullptr;
474 double* weights =
nullptr;
475 if (!sos_data.
weights.empty()) {
476 weights =
const_cast<double*
>(sos_data.
weights.data());
481 const_cast<SCIP_Var**
>(sos_data.
variables.data()), weights,
500 SCIP_CONS* constraint =
nullptr;
501 double* weights =
nullptr;
502 if (!sos_data.
weights.empty()) {
503 weights =
const_cast<double*
>(sos_data.
weights.data());
507 const_cast<SCIP_Var**
>(sos_data.
variables.data()), weights,
524 scip_, is_maximize ? SCIP_OBJSENSE_MAXIMIZE : SCIP_OBJSENSE_MINIMIZE));
525 return absl::OkStatus();
529 double old_offset = SCIPgetOrigObjoffset(scip_);
530 double delta_offset = offset - old_offset;
532 return absl::OkStatus();
536 return SCIPgetObjsense(scip_) == SCIP_OBJSENSE_MAXIMIZE;
543 return absl::OkStatus();
547 lb = ScipInfClamp(lb);
549 return absl::OkStatus();
553 ub = ScipInfClamp(ub);
555 return absl::OkStatus();
560 return absl::OkStatus();
564 SCIP_Bool infeasible;
566 SCIPchgVarType(scip_,
var, ConvertVarType(var_type), &infeasible));
567 return absl::OkStatus();
571 SCIP_Bool did_delete;
574 <<
"Failed to delete variable named: " <<
Name(
var);
575 variables_.erase(
var);
577 return absl::OkStatus();
581 const absl::flat_hash_set<SCIP_VAR*>& vars) {
582 for (SCIP_CONS* constraint : constraints_) {
584 return absl::InvalidArgumentError(absl::StrCat(
585 "Model contains nonlinear constraint: ",
Name(constraint)));
588 return absl::OkStatus();
594 for (SCIP_CONS* constraint : constraints_) {
595 const absl::Span<SCIP_VAR* const> nonzeros =
597 const std::vector<SCIP_VAR*> nonzeros_copy(nonzeros.begin(),
599 for (SCIP_VAR*
var : nonzeros_copy) {
600 if (vars.contains(
var)) {
605 for (SCIP_VAR*
const var : vars) {
608 return absl::OkStatus();
612 return ScipInfUnclamp(SCIPvarGetLbOriginal(
var));
616 return ScipInfUnclamp(SCIPvarGetUbOriginal(
var));
622 return ConvertVarType(SCIPvarGetType(
var));
628 return absl::string_view(SCIPconshdlrGetName(SCIPconsGetHdlr(constraint)));
632 return ConstraintType(constraint) == kLinearConstraintHandlerName;
636 SCIP_CONS* constraint) {
637 int num_vars = SCIPgetNVarsLinear(scip_, constraint);
638 return absl::MakeConstSpan(SCIPgetValsLinear(scip_, constraint), num_vars);
642 SCIP_CONS* constraint) {
643 int num_vars = SCIPgetNVarsLinear(scip_, constraint);
644 return absl::MakeConstSpan(SCIPgetVarsLinear(scip_, constraint), num_vars);
648 return ScipInfUnclamp(SCIPgetLhsLinear(scip_, constraint));
652 return ScipInfUnclamp(SCIPgetRhsLinear(scip_, constraint));
656 return SCIPconsGetName(constraint);
660 lb = ScipInfClamp(lb);
662 return absl::OkStatus();
666 ub = ScipInfClamp(ub);
668 return absl::OkStatus();
673 constraints_.erase(constraint);
675 return absl::OkStatus();
684 return absl::OkStatus();
690 const int scip_num_vars = SCIPgetNOrigVars(scip_);
691 const bool is_solution_partial = partial_solution.size() < scip_num_vars;
692 if (is_solution_partial) {
697 <<
"Error suggesting hint.";
700 for (
const auto& var_value_pair : partial_solution) {
702 var_value_pair.second));
704 if (!is_solution_partial) {
705 SCIP_Bool is_feasible;
707 scip_, solution,
false,
true,
710 if (!
static_cast<bool>(is_feasible)) {
712 return GScipHintResult::kInfeasible;
717 if (
static_cast<bool>(is_stored)) {
720 return GScipHintResult::kRejected;
724 absl::StatusOr<GScipResult>
GScip::Solve(
const GScipParameters& params,
725 const std::string& legacy_params) {
735 const absl::Status param_status = SetParams(params, legacy_params);
736 if (!param_status.ok()) {
737 result.
gscip_output.set_status(GScipOutput::INVALID_SOLVER_PARAMETERS);
740 std::string(param_status.message()));
743 if (params.print_scip_model()) {
746 if (!params.scip_model_filename().empty()) {
748 scip_, params.scip_model_filename().c_str(),
"cip", FALSE));
759 const SCIP_STAGE stage = SCIPgetStage(scip_);
760 if (stage != SCIP_STAGE_PRESOLVING && stage != SCIP_STAGE_SOLVING &&
761 stage != SCIP_STAGE_SOLVED) {
764 absl::StrCat(
"Unpexpected SCIP final stage= ", stage,
765 " was expected to be either SCIP_STAGE_PRESOLVING, "
766 "SCIP_STAGE_SOLVING, or SCIP_STAGE_SOLVED"));
769 if (params.print_detailed_solving_stats()) {
772 if (!params.detailed_solving_stats_filename().empty()) {
773 FILE*
file = fopen(params.detailed_solving_stats_filename().c_str(),
"w");
774 if (
file ==
nullptr) {
775 return absl::InvalidArgumentError(absl::StrCat(
776 "Could not open file: ", params.detailed_solving_stats_filename(),
777 " to write SCIP solve stats."));
780 int close_result = fclose(
file);
781 if (close_result != 0) {
782 return absl::InvalidArgumentError(absl::StrCat(
783 "Error: ", close_result,
784 " closing file: ", params.detailed_solving_stats_filename(),
785 " when writing solve stats."));
791 GScipSolvingStats* stats = result.
gscip_output.mutable_stats();
792 const int num_scip_solutions = SCIPgetNSols(scip_);
793 const int num_returned_solutions =
795 SCIP_SOL** all_solutions = SCIPgetSols(scip_);
796 stats->set_best_objective(ScipInfUnclamp(SCIPgetPrimalbound(scip_)));
797 for (
int i = 0; i < num_returned_solutions; ++i) {
798 SCIP_SOL* scip_sol = all_solutions[i];
799 const double obj_value = ScipInfUnclamp(SCIPgetSolOrigObj(scip_, scip_sol));
801 for (SCIP_VAR* v : variables_) {
802 solution[v] = SCIPgetSolVal(scip_, scip_sol, v);
808 if (stage != SCIP_STAGE_PRESOLVING && SCIPhasPrimalRay(scip_)) {
809 for (SCIP_VAR* v : variables_) {
810 result.
primal_ray[v] = SCIPgetPrimalRayVal(scip_, v);
814 stats->set_best_bound(ScipInfUnclamp(SCIPgetDualbound(scip_)));
815 stats->set_node_count(SCIPgetNTotalNodes(scip_));
816 stats->set_first_lp_relaxation_bound(SCIPgetFirstLPDualboundRoot(scip_));
817 stats->set_root_node_bound(SCIPgetDualboundRoot(scip_));
818 if (stage != SCIP_STAGE_PRESOLVING) {
819 stats->set_total_lp_iterations(SCIPgetNLPIterations(scip_));
820 stats->set_primal_simplex_iterations(SCIPgetNPrimalLPIterations(scip_));
821 stats->set_dual_simplex_iterations(SCIPgetNDualLPIterations(scip_));
822 stats->set_deterministic_time(SCIPgetDeterministicTime(scip_));
824 result.
gscip_output.set_status(ConvertStatus(SCIPgetStatus(scip_)));
833 const std::string& parameter_name) {
834 SCIP_Bool default_value;
836 SCIPgetBoolParam(scip_, parameter_name.c_str(), &default_value));
837 return static_cast<bool>(default_value);
841 const std::string& parameter_name) {
844 SCIPgetIntParam(scip_, parameter_name.c_str(), &default_value));
845 return default_value;
849 const std::string& parameter_name) {
852 SCIPgetLongintParam(scip_, parameter_name.c_str(), &result));
853 return static_cast<int64_t
>(result);
857 const std::string& parameter_name) {
860 SCIPgetRealParam(scip_, parameter_name.c_str(), &result));
865 const std::string& parameter_name) {
868 SCIPgetCharParam(scip_, parameter_name.c_str(), &result));
873 const std::string& parameter_name) {
876 SCIPgetStringParam(scip_, parameter_name.c_str(), &result));
877 return std::string(result);
880 double GScip::ScipInfClamp(
double d) {
881 const double kScipInf =
ScipInf();
882 if (d > kScipInf)
return kScipInf;
883 if (d < -kScipInf)
return -kScipInf;
887 double GScip::ScipInfUnclamp(
double d) {
888 const double kScipInf =
ScipInf();
889 if (d >= kScipInf)
return std::numeric_limits<double>::infinity();
890 if (d <= -kScipInf)
return -std::numeric_limits<double>::infinity();
894 #undef RETURN_ERROR_UNLESS