28#include "absl/base/attributes.h"
29#include "absl/cleanup/cleanup.h"
30#include "absl/status/status.h"
31#include "absl/strings/str_format.h"
32#include "absl/types/optional.h"
40#include "ortools/linear_solver/linear_solver.pb.h"
47#include "scip/cons_indicator.h"
49#include "scip/scip_copy.h"
50#include "scip/scip_numerics.h"
51#include "scip/scip_param.h"
52#include "scip/scip_prob.h"
53#include "scip/scipdefplugins.h"
56 "When true, emphasize search towards feasibility. This may or "
57 "may not result in speedups in some problems.");
65class ScipConstraintHandlerForMPCallback;
77 std::atomic<bool>* interrupt)
override;
79 void Reset()
override;
91 double new_value,
double old_value)
override;
100 int64_t
nodes()
const override;
102 LOG(DFATAL) <<
"Basis status only available for continuous problems";
106 LOG(DFATAL) <<
"Basis status only available for continuous problems";
111 bool IsLP()
const override {
return false; }
112 bool IsMIP()
const override {
return true; }
119 return absl::StrFormat(
"SCIP %d.%d.%d [LP solver: %s]", SCIPmajorVersion(),
120 SCIPminorVersion(), SCIPtechVersion(),
121 SCIPlpiGetSolverName());
125 const absl::MutexLock lock(&hold_interruptions_mutex_);
126 if (scip_ ==
nullptr) {
127 LOG_IF(DFATAL, status_.ok()) <<
"scip_ is null is unexpected here, since "
128 "status_ did not report any error";
131 return SCIPinterruptSolve(scip_) == SCIP_OKAY;
164 void SetRelativeMipGap(
double value)
override;
165 void SetPrimalTolerance(
double value)
override;
166 void SetDualTolerance(
double value)
override;
167 void SetPresolveMode(
int presolve)
override;
168 void SetScalingMode(
int scaling)
override;
169 void SetLpAlgorithm(
int lp_algorithm)
override;
179 absl::Status SetNumThreads(
int num_threads)
override;
181 bool SetSolverSpecificParametersAsString(
184 void SetUnsupportedIntegerParam(
191 void SetSolution(SCIP_SOL*
solution);
193 absl::Status CreateSCIP();
197 SCIP* DeleteSCIP(
bool return_scip =
false);
205 absl::Status status_;
208 std::vector<SCIP_VAR*> scip_variables_;
209 std::vector<SCIP_CONS*> scip_constraints_;
210 int current_solution_index_ = 0;
212 std::unique_ptr<ScipConstraintHandlerForMPCallback> scip_constraint_handler_;
214 EmptyStruct constraint_data_for_handler_;
215 bool branching_priority_reset_ =
false;
216 bool callback_reset_ =
false;
221 mutable absl::Mutex hold_interruptions_mutex_;
238 std::vector<CallbackRangeConstraint> SeparateSolution(
244#define RETURN_IF_ALREADY_IN_ERROR_STATE \
246 if (!status_.ok()) { \
247 VLOG_EVERY_N(1, 10) << "Early abort: SCIP is in error state."; \
252#define RETURN_AND_STORE_IF_SCIP_ERROR(x) \
254 status_ = SCIP_TO_STATUS(x); \
255 if (!status_.ok()) return; \
260 status_ = CreateSCIP();
267 const absl::MutexLock lock(&hold_interruptions_mutex_);
270 SCIP* old_scip = DeleteSCIP(
true);
271 const auto scip_deleter = absl::MakeCleanup(
272 [&old_scip]() { CHECK_EQ(SCIPfree(&old_scip), SCIP_OKAY); });
274 scip_constraint_handler_.reset();
278 status_ = CreateSCIP();
292absl::Status SCIPInterface::CreateSCIP() {
297 if (absl::GetFlag(FLAGS_scip_feasibility_emphasis)) {
308 SCIPsetIntParam(scip_,
"timing/clocktype", SCIP_CLOCKTYPE_WALL));
310 nullptr,
nullptr,
nullptr,
nullptr,
313 scip_,
maximize_ ? SCIP_OBJSENSE_MAXIMIZE : SCIP_OBJSENSE_MINIMIZE));
314 return absl::OkStatus();
319SCIP* SCIPInterface::DeleteSCIP(
bool return_scip) {
324 CHECK(scip_ !=
nullptr);
325 for (
int i = 0; i < scip_variables_.size(); ++i) {
326 CHECK_EQ(SCIPreleaseVar(scip_, &scip_variables_[i]), SCIP_OKAY);
328 scip_variables_.clear();
329 for (
int j = 0; j < scip_constraints_.size(); ++j) {
330 CHECK_EQ(SCIPreleaseCons(scip_, &scip_constraints_[j]), SCIP_OKAY);
332 scip_constraints_.clear();
334 SCIP* old_scip = scip_;
337 CHECK_EQ(SCIPfree(&old_scip), SCIP_OKAY);
348 scip_, maximize ? SCIP_OBJSENSE_MAXIMIZE : SCIP_OBJSENSE_MINIMIZE));
359 SCIPchgVarLb(scip_, scip_variables_[
var_index], lb));
361 SCIPchgVarUb(scip_, scip_variables_[
var_index], ub));
373#if (SCIP_VERSION >= 210)
374 SCIP_Bool infeasible =
false;
377 integer ? SCIP_VARTYPE_INTEGER : SCIP_VARTYPE_CONTINUOUS, &infeasible));
381 integer ? SCIP_VARTYPE_INTEGER : SCIP_VARTYPE_CONTINUOUS));
396 SCIPchgLhsLinear(scip_, scip_constraints_[
index], lb));
398 SCIPchgRhsLinear(scip_, scip_constraints_[
index], ub));
419 scip_, scip_constraints_[constraint->
index()],
420 scip_variables_[variable->
index()], new_value - old_value));
435 for (
const auto& entry : constraint->coefficients_) {
436 const int var_index = entry.first->index();
437 const double old_coef_value = entry.second;
443 scip_variables_[
var_index], -old_coef_value));
466 for (
const auto& entry :
solver_->objective_->coefficients_) {
467 const int var_index = entry.first->index();
473 SCIPchgVarObj(scip_, scip_variables_[
var_index], 0.0));
491 branching_priority_reset_ =
true;
508 int total_num_vars =
solver_->variables_.size();
516 SCIP_VAR* scip_var =
nullptr;
518 double tmp_obj_coef = 0.0;
520 scip_, &scip_var,
var->name().c_str(),
var->lb(),
var->ub(),
522 var->integer() ? SCIP_VARTYPE_INTEGER : SCIP_VARTYPE_CONTINUOUS,
true,
523 false,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr));
525 scip_variables_.push_back(scip_var);
526 const int branching_priority =
var->branching_priority();
527 if (branching_priority != 0) {
530 scip_, scip_variables_[
index], branching_priority));
536 for (
const auto& entry :
ct->coefficients_) {
537 const int var_index = entry.first->index();
543 SCIPaddCoefLinear(scip_, scip_constraints_[i],
544 scip_variables_[
var_index], entry.second));
553 int total_num_rows =
solver_->constraints_.size();
557 int max_row_length = 0;
562 if (
ct->coefficients_.size() > max_row_length) {
563 max_row_length =
ct->coefficients_.size();
566 std::unique_ptr<SCIP_VAR*[]> vars(
new SCIP_VAR*[max_row_length]);
567 std::unique_ptr<double[]> coeffs(
new double[max_row_length]);
572 const int size =
ct->coefficients_.size();
574 for (
const auto& entry :
ct->coefficients_) {
575 const int var_index = entry.first->index();
578 coeffs[j] = entry.second;
581 SCIP_CONS* scip_constraint =
nullptr;
582 const bool is_lazy =
ct->is_lazy();
583 if (
ct->indicator_variable() !=
nullptr) {
584 const int ind_index =
ct->indicator_variable()->index();
586 SCIP_VAR* ind_var = scip_variables_[ind_index];
587 if (
ct->indicator_value() == 0) {
589 SCIPgetNegatedVar(scip_, scip_variables_[ind_index], &ind_var));
592 if (
ct->ub() < std::numeric_limits<double>::infinity()) {
594 scip_, &scip_constraint,
ct->name().c_str(), ind_var,
size,
595 vars.get(), coeffs.get(),
ct->ub(),
606 scip_constraints_.push_back(scip_constraint);
608 if (
ct->lb() > -std::numeric_limits<double>::infinity()) {
609 for (
int i = 0; i <
size; ++i) {
613 scip_, &scip_constraint,
ct->name().c_str(), ind_var,
size,
614 vars.get(), coeffs.get(), -
ct->lb(),
625 scip_constraints_.push_back(scip_constraint);
632 scip_, &scip_constraint,
ct->name().c_str(),
size, vars.get(),
633 coeffs.get(),
ct->lb(),
ct->ub(),
645 scip_constraints_.push_back(scip_constraint);
656 for (
const auto& entry :
solver_->objective_->coefficients_) {
657 const int var_index = entry.first->index();
658 const double obj_coef = entry.second;
660 SCIPchgVarObj(scip_, scip_variables_[
var_index], obj_coef));
668#define RETURN_ABNORMAL_IF_BAD_STATUS \
670 if (!status_.ok()) { \
671 LOG_IF(INFO, solver_->OutputIsEnabled()) \
672 << "Invalid SCIP status: " << status_; \
673 return result_status_ = MPSolver::ABNORMAL; \
677#define RETURN_ABNORMAL_IF_SCIP_ERROR(x) \
679 RETURN_ABNORMAL_IF_BAD_STATUS; \
680 status_ = SCIP_TO_STATUS(x); \
681 RETURN_ABNORMAL_IF_BAD_STATUS; \
698 branching_priority_reset_ || callback_reset_) {
700 branching_priority_reset_ =
false;
701 callback_reset_ =
false;
705 SCIPsetMessagehdlrQuiet(scip_,
quiet_);
708 if (
solver_->variables_.empty() &&
solver_->constraints_.empty()) {
717 VLOG(1) << absl::StrFormat(
"Model built in %s.",
719 if (scip_constraint_handler_ !=
nullptr) {
724 CHECK_EQ(scip_constraint_handler_->mp_callback(), callback_);
725 }
else if (callback_ !=
nullptr) {
726 scip_constraint_handler_ =
727 std::make_unique<ScipConstraintHandlerForMPCallback>(callback_);
731 "mp_solver_callback_constraint_for_scip",
732 &constraint_data_for_handler_,
750 SetParameters(param);
752 solver_->solver_specific_parameter_string_);
755 if (!
solver_->solution_hint_.empty()) {
757 bool is_solution_partial =
false;
758 const int num_vars =
solver_->variables_.size();
759 if (
solver_->solution_hint_.size() != num_vars) {
762 SCIPcreatePartialSol(scip_, &
solution,
nullptr));
763 is_solution_partial =
true;
770 for (
const std::pair<const MPVariable*, double>& p :
773 scip_,
solution, scip_variables_[p.first->index()], p.second));
776 if (!is_solution_partial) {
777 SCIP_Bool is_feasible;
782 VLOG(1) <<
"Solution hint is "
783 << (is_feasible ?
"FEASIBLE" :
"INFEASIBLE");
791 if (!is_solution_partial && SCIPisTransformed(scip_)) {
798 SCIPaddSolFree(scip_, &
solution, &is_stored));
805 ? SCIPsolveConcurrent(scip_)
807 VLOG(1) << absl::StrFormat(
"Solved in %s.",
809 current_solution_index_ = 0;
811 SCIP_SOL*
const solution = SCIPgetBestSol(scip_);
816 VLOG(1) <<
"No feasible solution found.";
820 SCIP_STATUS scip_status = SCIPgetStatus(scip_);
821 switch (scip_status) {
822 case SCIP_STATUS_OPTIMAL:
825 case SCIP_STATUS_GAPLIMIT:
829 case SCIP_STATUS_INFEASIBLE:
832 case SCIP_STATUS_UNBOUNDED:
835 case SCIP_STATUS_INFORUNBD:
843 }
else if (scip_status == SCIP_STATUS_TIMELIMIT ||
844 scip_status == SCIP_STATUS_TOTALNODELIMIT) {
858void SCIPInterface::SetSolution(SCIP_SOL*
solution) {
863 for (
int i = 0; i <
solver_->variables_.size(); ++i) {
868 var->set_solution_value(val);
869 VLOG(3) <<
var->name() <<
"=" << val;
874 std::atomic<bool>* interrupt)
const {
879 if (interrupt !=
nullptr)
return false;
886 const bool log_error = request->enable_internal_solver_output();
891int SCIPInterface::SolutionCount() {
return SCIPgetNSols(scip_); }
898 if (current_solution_index_ + 1 >= SolutionCount()) {
901 current_solution_index_++;
902 SCIP_SOL** all_solutions = SCIPgetSols(scip_);
903 SetSolution(all_solutions[current_solution_index_]);
911 return SCIPgetNLPIterations(scip_);
920 return SCIPgetNTotalNodes(scip_);
928void SCIPInterface::SetRelativeMipGap(
double value) {
941 if (status_.ok()) status_ =
status;
944void SCIPInterface::SetPrimalTolerance(
double value) {
948 if (status_.ok()) status_ =
status;
951void SCIPInterface::SetDualTolerance(
double value) {
954 if (status_.ok()) status_ =
status;
957void SCIPInterface::SetPresolveMode(
int presolve) {
962 SCIP_TO_STATUS(SCIPsetIntParam(scip_,
"presolving/maxrounds", 0));
963 if (status_.ok()) status_ =
status;
968 SCIP_TO_STATUS(SCIPsetIntParam(scip_,
"presolving/maxrounds", -1));
969 if (status_.ok()) status_ =
status;
979void SCIPInterface::SetScalingMode(
int scaling) {
986void SCIPInterface::SetLpAlgorithm(
int lp_algorithm) {
988 switch (lp_algorithm) {
992 if (status_.ok()) status_ =
status;
998 if (status_.ok()) status_ =
status;
1004 SCIP_TO_STATUS(SCIPsetCharParam(scip_,
"lp/initalgorithm",
'p'));
1005 if (status_.ok()) status_ =
status;
1016void SCIPInterface::SetUnsupportedIntegerParam(
1020 status_ = absl::InvalidArgumentError(absl::StrFormat(
1021 "Tried to set unsupported integer parameter %d", param));
1025void SCIPInterface::SetIntegerParamToUnsupportedValue(
1029 status_ = absl::InvalidArgumentError(absl::StrFormat(
1030 "Tried to set integer parameter %d to unsupported value %d", param,
1035absl::Status SCIPInterface::SetNumThreads(
int num_threads) {
1036 if (SetSolverSpecificParametersAsString(
1037 absl::StrFormat(
"parallel/maxnthreads = %d\n", num_threads))) {
1038 return absl::OkStatus();
1040 return absl::InternalError(
1041 "Could not set parallel/maxnthreads, which may "
1042 "indicate that SCIP API has changed.");
1045bool SCIPInterface::SetSolverSpecificParametersAsString(
1047 const absl::Status s =
1050 LOG(WARNING) <<
"Failed to set SCIP parameter string: " <<
parameters
1051 <<
", error is: " << s;
1059 bool at_integer_solution)
1060 : scip_context_(scip_context),
1061 at_integer_solution_(at_integer_solution) {}
1064 if (at_integer_solution_) {
1081 constraint.
is_cut =
true;
1082 constraint.
range = cutting_plane;
1083 constraint.
local =
false;
1084 constraints_added_.push_back(std::move(constraint));
1089 constraint.
is_cut =
false;
1090 constraint.
range = lazy_constraint;
1091 constraint.
local =
false;
1092 constraints_added_.push_back(std::move(constraint));
1096 const absl::flat_hash_map<const MPVariable*, double>&
solution)
override {
1097 LOG(FATAL) <<
"SuggestSolution() not currently supported for SCIP.";
1112 return constraints_added_;
1117 bool at_integer_solution_;
1119 std::vector<CallbackRangeConstraint> constraints_added_;
1125 mp_callback_(mp_callback) {}
1127std::vector<CallbackRangeConstraint>
1130 return SeparateSolution(
context,
false);
1133std::vector<CallbackRangeConstraint>
1136 return SeparateSolution(
context,
true);
1139std::vector<CallbackRangeConstraint>
1140ScipConstraintHandlerForMPCallback::SeparateSolution(
1142 const bool at_integer_solution) {
1145 return mp_context.constraints_added();
1149 if (callback_ !=
nullptr) {
1150 callback_reset_ =
true;
1152 callback_ = mp_callback;
1162#undef RETURN_AND_STORE_IF_SCIP_ERROR
1163#undef RETURN_IF_ALREADY_IN_ERROR_STATE
1164#undef RETURN_ABNORMAL_IF_BAD_STATUS
1165#undef RETURN_ABNORMAL_IF_SCIP_ERROR
absl::Duration GetDuration() const
void Start()
When Start() is called multiple times, only the most recent is used.
virtual void RunCallback(MPCallbackContext *callback_context)=0
int index() const
Returns the index of the constraint in the MPSolver::constraints_.
double offset() const
Gets the constant term in the objective.
void set_variable_as_extracted(int var_index, bool extracted)
bool CheckSolutionIsSynchronized() const
static constexpr int64_t kUnknownNumberOfIterations
void InvalidateSolutionSynchronization()
void set_constraint_as_extracted(int ct_index, bool extracted)
void ResetExtractionInformation()
Resets the extraction information.
int last_variable_index_
Index in MPSolver::constraints_ of last variable extracted.
virtual void SetIntegerParamToUnsupportedValue(MPSolverParameters::IntegerParam param, int value)
Sets a supported integer parameter to an unsupported value.
int last_constraint_index_
Index in MPSolver::variables_ of last constraint extracted.
virtual void SetUnsupportedIntegerParam(MPSolverParameters::IntegerParam param)
Sets an unsupported integer parameter.
bool variable_is_extracted(int var_index) const
bool constraint_is_extracted(int ct_index) const
static constexpr int64_t kUnknownNumberOfNodes
void ExtractModel()
Extracts model stored in MPSolver.
double objective_value_
The value of the objective function.
double best_objective_bound_
The value of the best objective bound. Used only for MIP solvers.
bool maximize_
Optimization direction.
void SetMIPParameters(const MPSolverParameters ¶m)
Sets MIP specific parameters in the underlying solver.
bool quiet_
Boolean indicator for the verbosity of the solver output.
void SetCommonParameters(const MPSolverParameters ¶m)
Sets parameters common to LP and MIP in the underlying solver.
bool CheckSolutionIsSynchronizedAndExists() const
Handy shortcut to do both checks above (it is often used).
MPSolver::ResultStatus result_status_
SynchronizationStatus sync_status_
Indicates whether the model and the solution are synchronized.
@ PRESOLVE_OFF
Presolve is off.
@ PRESOLVE_ON
Presolve is on.
@ BARRIER
Barrier algorithm.
IntegerParam
Enumeration of parameters that take integer or categorical values.
@ INCREMENTALITY
Advanced usage: incrementality from one solve to the next.
@ PRESOLVE
Advanced usage: presolve mode.
@ LP_ALGORITHM
Algorithm to solve linear programs.
@ SCALING
Advanced usage: enable or disable matrix scaling.
@ INCREMENTALITY_OFF
Start solve from scratch.
int GetIntegerParam(MPSolverParameters::IntegerParam param) const
Returns the value of an integer parameter.
int64_t time_limit() const
double time_limit_in_secs() const
@ FEASIBLE
feasible, or stopped by limit.
@ NOT_SOLVED
not been solved yet.
@ INFEASIBLE
proven infeasible.
@ UNBOUNDED
proven unbounded.
@ ABNORMAL
abnormal, i.e., error of some kind.
const MPObjective & Objective() const
bool SetSolverSpecificParametersAsString(const std::string ¶meters)
int GetNumThreads() const
Returns the number of threads to be used during solve.
The class for variables of a Mathematical Programming (MP) model.
int index() const
Returns the index of the variable in the MPSolver::variables_.
MPSolver::BasisStatus row_status(int constraint_index) const override
Returns the basis status of a row.
void ExtractObjective() override
Extracts the objective.
bool InterruptSolve() override
void ExtractNewConstraints() override
Extracts the constraints that have not been extracted yet.
void AddVariable(MPVariable *var) override
Add a variable.
int64_t nodes() const override
double infinity() override
void SetObjectiveCoefficient(const MPVariable *variable, double coefficient) override
Cached.
void SetObjectiveOffset(double value) override
Cached.
void ExtractNewVariables() override
Extracts the variables that have not been extracted yet.
void * underlying_solver() override
Returns the underlying solver.
bool IsContinuous() const override
MPSolver::BasisStatus column_status(int variable_index) const override
Returns the basis status of a constraint.
~SCIPInterface() override
int64_t iterations() const override
void SetVariableInteger(int var_index, bool integer) override
Modifies integrality of an extracted variable.
SCIPInterface(MPSolver *solver)
void ClearConstraint(MPConstraint *constraint) override
Not cached.
void SetCallback(MPCallback *mp_callback) override
MPCallback API.
void ClearObjective() override
Clear objective of all its terms.
MPSolver::ResultStatus Solve(const MPSolverParameters ¶m) override
void BranchingPriorityChangedForVariable(int var_index) override
void SetVariableBounds(int var_index, double lb, double ub) override
Modifies bounds of an extracted variable.
bool IsMIP() const override
Returns true if the problem is discrete and linear.
void SetCoefficient(MPConstraint *constraint, const MPVariable *variable, double new_value, double old_value) override
Changes a coefficient in a constraint.
void SetConstraintBounds(int row_index, double lb, double ub) override
Modify bounds of an extracted variable.
bool SupportsCallbacks() const override
void AddRowConstraint(MPConstraint *ct) override
Adds a linear constraint.
std::string SolverVersion() const override
Returns a string describing the underlying solver and its version.
bool IsLP() const override
Returns true if the problem is continuous and linear.
bool SupportsDirectlySolveProto(std::atomic< bool > *interrupt) const override
bool NextSolution() override
MPSolutionResponse DirectlySolveProto(LazyMutableCopy< MPModelRequest > request, std::atomic< bool > *interrupt) override
void SetOptimizationDirection(bool maximize) override
Not cached.
bool AddIndicatorConstraint(MPConstraint *ct) override
double VariableValue(const MPVariable *variable) const
int64_t NumNodesProcessed() const
bool is_pseudo_solution() const
void AddLazyConstraint(const LinearRange &lazy_constraint) override
void AddCut(const LinearRange &cutting_plane) override
int64_t NumExploredNodes() override
MPCallbackEvent Event() override
bool CanQueryVariableValues() override
double VariableValue(const MPVariable *variable) override
ScipMPCallbackContext(const ScipConstraintHandlerContext *scip_context, bool at_integer_solution)
double SuggestSolution(const absl::flat_hash_map< const MPVariable *, double > &solution) override
const std::vector< CallbackRangeConstraint > & constraints_added()
GurobiMPCallbackContext * context
In SWIG mode, we don't want anything besides these top-level includes.
void AddCallbackConstraint(SCIP *scip, ScipConstraintHandler< ConstraintData > *handler, const std::string &constraint_name, const ConstraintData *constraint_data, const ScipCallbackConstraintOptions &options)
constraint_data is not owned but held.
absl::StatusOr< MPSolutionResponse > ScipSolveProto(LazyMutableCopy< MPModelRequest > request)
@ kMipSolution
Called every time a new MIP incumbent is found.
@ kMipNode
Called once per pass of the cut loop inside each MIP node.
void RegisterConstraintHandler(ScipConstraintHandler< Constraint > *handler, SCIP *scip)
handler is not owned but held.
MPSolutionResponse ConvertStatusOrMPSolutionResponse(bool log_error, absl::StatusOr< MPSolutionResponse > response)
absl::Status LegacyScipSetSolverSpecificParameters(absl::string_view parameters, SCIP *scip)
MPSolverInterface * BuildSCIPInterface(MPSolver *const solver)
#define SCIP_TO_STATUS(x)
#define RETURN_IF_SCIP_ERROR(x)
ABSL_FLAG(bool, scip_feasibility_emphasis, false, "When true, emphasize search towards feasibility. This may or " "may not result in speedups in some problems.")
#define RETURN_IF_ALREADY_IN_ERROR_STATE
#define RETURN_ABNORMAL_IF_SCIP_ERROR(x)
#define RETURN_AND_STORE_IF_SCIP_ERROR(x)
#define RETURN_ABNORMAL_IF_BAD_STATUS