OR-Tools  8.2
scip_interface.cc
Go to the documentation of this file.
1// Copyright 2010-2018 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14#if defined(USE_SCIP)
15
16#include <stddef.h>
17
18#include <algorithm>
19#include <limits>
20#include <memory>
21#include <string>
22#include <vector>
23
24#include "absl/status/status.h"
25#include "absl/strings/str_format.h"
26#include "absl/types/optional.h"
29#include "ortools/base/hash.h"
33#include "ortools/base/timer.h"
36#include "ortools/linear_solver/linear_solver.pb.h"
41#include "scip/cons_indicator.h"
42#include "scip/scip.h"
43#include "scip/scip_copy.h"
44#include "scip/scip_param.h"
45#include "scip/scip_prob.h"
46#include "scip/scipdefplugins.h"
47
48ABSL_FLAG(bool, scip_feasibility_emphasis, false,
49 "When true, emphasize search towards feasibility. This may or "
50 "may not result in speedups in some problems.");
51
52namespace operations_research {
53namespace {
54// See the class ScipConstraintHandlerForMPCallback below.
55struct EmptyStruct {};
56} // namespace
57
58class ScipConstraintHandlerForMPCallback;
59
61 public:
62 explicit SCIPInterface(MPSolver* solver);
63 ~SCIPInterface() override;
64
65 void SetOptimizationDirection(bool maximize) override;
66 MPSolver::ResultStatus Solve(const MPSolverParameters& param) override;
67 absl::optional<MPSolutionResponse> DirectlySolveProto(
68 const MPModelRequest& request) override;
69 void Reset() override;
70
71 void SetVariableBounds(int var_index, double lb, double ub) override;
72 void SetVariableInteger(int var_index, bool integer) override;
73 void SetConstraintBounds(int row_index, double lb, double ub) override;
74
75 void AddRowConstraint(MPConstraint* ct) override;
77 void AddVariable(MPVariable* var) override;
78 void SetCoefficient(MPConstraint* constraint, const MPVariable* variable,
79 double new_value, double old_value) override;
80 void ClearConstraint(MPConstraint* constraint) override;
81 void SetObjectiveCoefficient(const MPVariable* variable,
82 double coefficient) override;
83 void SetObjectiveOffset(double value) override;
84 void ClearObjective() override;
85 void BranchingPriorityChangedForVariable(int var_index) override;
86
87 int64 iterations() const override;
88 int64 nodes() const override;
89 MPSolver::BasisStatus row_status(int constraint_index) const override {
90 LOG(DFATAL) << "Basis status only available for continuous problems";
91 return MPSolver::FREE;
92 }
93 MPSolver::BasisStatus column_status(int variable_index) const override {
94 LOG(DFATAL) << "Basis status only available for continuous problems";
95 return MPSolver::FREE;
96 }
97
98 bool IsContinuous() const override { return false; }
99 bool IsLP() const override { return false; }
100 bool IsMIP() const override { return true; }
101
102 void ExtractNewVariables() override;
103 void ExtractNewConstraints() override;
104 void ExtractObjective() override;
105
106 std::string SolverVersion() const override {
107 return absl::StrFormat("SCIP %d.%d.%d [LP solver: %s]", SCIPmajorVersion(),
108 SCIPminorVersion(), SCIPtechVersion(),
109 SCIPlpiGetSolverName());
110 }
111
112 bool InterruptSolve() override {
113 const absl::MutexLock lock(&hold_interruptions_mutex_);
114 if (scip_ == nullptr) {
115 LOG_IF(DFATAL, status_.ok()) << "scip_ is null is unexpected here, since "
116 "status_ did not report any error";
117 return true;
118 }
119 return SCIPinterruptSolve(scip_) == SCIP_OKAY;
120 }
121
122 void* underlying_solver() override { return reinterpret_cast<void*>(scip_); }
123
124 // MULTIPLE SOLUTIONS SUPPORT
125 // The default behavior of scip is to store the top incidentally generated
126 // integer solutions in the solution pool. The default maximum size is 100.
127 // This can be adjusted by setting the param limits/maxsol. There is no way
128 // to ensure that the pool will actually be full.
129 //
130 // You can also ask SCIP to enumerate all feasible solutions. Combined with
131 // an equality or inequality constraint on the objective (after solving once
132 // to find the optimal solution), you can use this to find all high quality
133 // solutions. See https://scip.zib.de/doc/html/COUNTER.php. This behavior is
134 // not supported directly through MPSolver, but in theory can be controlled
135 // entirely through scip parameters.
136 bool NextSolution() override;
137
138 // CALLBACK SUPPORT:
139 // * We support MPSolver's callback API via MPCallback.
140 // See ./linear_solver_callback.h.
141 // * We also support SCIP's more general callback interface, built on
142 // 'constraint handlers'. See ./scip_callback.h and test, these are added
143 // directly to the underlying SCIP object, bypassing SCIPInterface.
144 // The former works by calling the latter. See go/scip-callbacks for
145 // a complete documentation of this design.
146
147 // MPCallback API
148 void SetCallback(MPCallback* mp_callback) override;
149 bool SupportsCallbacks() const override { return true; }
150
151 private:
152 void SetParameters(const MPSolverParameters& param) override;
153 void SetRelativeMipGap(double value) override;
154 void SetPrimalTolerance(double value) override;
155 void SetDualTolerance(double value) override;
156 void SetPresolveMode(int presolve) override;
157 void SetScalingMode(int scaling) override;
158 void SetLpAlgorithm(int lp_algorithm) override;
159
160 // SCIP parameters allow to lower and upper bound the number of threads used
161 // (via "parallel/minnthreads" and "parallel/maxnthread", respectively). Here,
162 // we interpret "num_threads" to mean "parallel/maxnthreads", as this is what
163 // most clients probably want to do. To change "parallel/minnthreads" use
164 // SetSolverSpecificParametersAsString(). However, one must change
165 // "parallel/maxnthread" with SetNumThreads() because only this will inform
166 // the interface to run SCIPsolveConcurrent() instead of SCIPsolve() which is
167 // necessery to enable multi-threading.
168 absl::Status SetNumThreads(int num_threads) override;
169
170 bool SetSolverSpecificParametersAsString(
171 const std::string& parameters) override;
172
173 void SetUnsupportedIntegerParam(
174 MPSolverParameters::IntegerParam param) override;
175 void SetIntegerParamToUnsupportedValue(MPSolverParameters::IntegerParam param,
176 int value) override;
177 // How many solutions SCIP found.
178 int SolutionCount();
179 // Copy sol from SCIP to MPSolver.
180 void SetSolution(SCIP_SOL* solution);
181
182 absl::Status CreateSCIP();
183 // Deletes variables and constraints from scip_ and reset scip_ to null. If
184 // return_scip is false, deletes the SCIP object; if true, returns it (but
185 // scip_ is still set to null).
186 SCIP* DeleteSCIP(bool return_scip = false);
187
188 // SCIP has many internal checks (many of which are numerical) that can fail
189 // during various phases: upon startup, when loading the model, when solving,
190 // etc. Often, the user is meant to stop at the first error, but since most
191 // of the linear solver interface API doesn't support "error reporting", we
192 // store a potential error status here.
193 // If this status isn't OK, then most operations will silently be cancelled.
194 absl::Status status_;
195
196 SCIP* scip_;
197 std::vector<SCIP_VAR*> scip_variables_;
198 std::vector<SCIP_CONS*> scip_constraints_;
199 int current_solution_index_ = 0;
200 MPCallback* callback_ = nullptr;
201 std::unique_ptr<ScipConstraintHandlerForMPCallback> scip_constraint_handler_;
202 // See ScipConstraintHandlerForMPCallback below.
203 EmptyStruct constraint_data_for_handler_;
204 bool branching_priority_reset_ = false;
205 bool callback_reset_ = false;
206
207 // Mutex that is held to prevent InterruptSolve() to call SCIPinterruptSolve()
208 // when scip_ is being built. It also prevents rebuilding scip_ until
209 // SCIPinterruptSolve() has returned.
210 mutable absl::Mutex hold_interruptions_mutex_;
211};
212
214 : public ScipConstraintHandler<EmptyStruct> {
215 public:
217
218 std::vector<CallbackRangeConstraint> SeparateFractionalSolution(
219 const ScipConstraintHandlerContext& context, const EmptyStruct&) override;
220
221 std::vector<CallbackRangeConstraint> SeparateIntegerSolution(
222 const ScipConstraintHandlerContext& context, const EmptyStruct&) override;
223
224 MPCallback* const mp_callback() const { return mp_callback_; }
225
226 private:
227 std::vector<CallbackRangeConstraint> SeparateSolution(
229 const bool at_integer_solution);
230
231 MPCallback* const mp_callback_;
232};
233
234#define RETURN_IF_ALREADY_IN_ERROR_STATE \
235 do { \
236 if (!status_.ok()) { \
237 VLOG_EVERY_N(1, 10) << "Early abort: SCIP is in error state."; \
238 return; \
239 } \
240 } while (false)
241
242#define RETURN_AND_STORE_IF_SCIP_ERROR(x) \
243 do { \
244 status_ = SCIP_TO_STATUS(x); \
245 if (!status_.ok()) return; \
246 } while (false)
247
249 : MPSolverInterface(solver), scip_(nullptr) {
250 status_ = CreateSCIP();
251}
252
254
256 // We hold calls to SCIPinterruptSolve() until the new scip_ is fully built.
257 const absl::MutexLock lock(&hold_interruptions_mutex_);
258
259 // Remove existing one but keep it alive to copy parameters from it.
260 SCIP* old_scip = DeleteSCIP(/*return_scip=*/true);
261 const auto scip_deleter = absl::MakeCleanup(
262 [&old_scip]() { CHECK_EQ(SCIPfree(&old_scip), SCIP_OKAY); });
263
264 scip_constraint_handler_.reset();
266
267 // Install the new one.
268 status_ = CreateSCIP();
269 if (!status_.ok()) {
270 return;
271 }
272
273 // Copy all existing parameters from the previous SCIP to the new one. This
274 // ensures that if a user calls multiple times
275 // SetSolverSpecificParametersAsString() and then Reset() is called, we still
276 // take into account all parameters. Note though that at the end of Solve(),
277 // parameters are reset so after Solve() has been called, only the last set
278 // parameters are kept.
279 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPcopyParamSettings(old_scip, scip_));
280}
281
282absl::Status SCIPInterface::CreateSCIP() {
283 RETURN_IF_SCIP_ERROR(SCIPcreate(&scip_));
284 RETURN_IF_SCIP_ERROR(SCIPincludeDefaultPlugins(scip_));
285 // Set the emphasis to enum SCIP_PARAMEMPHASIS_FEASIBILITY. Do not print
286 // the new parameter (quiet = true).
287 if (absl::GetFlag(FLAGS_scip_feasibility_emphasis)) {
288 RETURN_IF_SCIP_ERROR(SCIPsetEmphasis(scip_, SCIP_PARAMEMPHASIS_FEASIBILITY,
289 /*quiet=*/true));
290 }
291 // Default clock type. We use wall clock time because getting CPU user seconds
292 // involves calling times() which is very expensive.
293 // NOTE(user): Also, time limit based on CPU user seconds is *NOT* thread
294 // safe. We observed that different instances of SCIP running concurrently
295 // in different threads consume the time limit *together*. E.g., 2 threads
296 // running SCIP with time limit 10s each will both terminate after ~5s.
298 SCIPsetIntParam(scip_, "timing/clocktype", SCIP_CLOCKTYPE_WALL));
299 RETURN_IF_SCIP_ERROR(SCIPcreateProb(scip_, solver_->name_.c_str(), nullptr,
300 nullptr, nullptr, nullptr, nullptr,
301 nullptr, nullptr));
302 RETURN_IF_SCIP_ERROR(SCIPsetObjsense(
303 scip_, maximize_ ? SCIP_OBJSENSE_MAXIMIZE : SCIP_OBJSENSE_MINIMIZE));
304 return absl::OkStatus();
305}
306
307SCIP* SCIPInterface::DeleteSCIP(bool return_scip) {
308 // NOTE(user): DeleteSCIP() shouldn't "give up" mid-stage if it fails, since
309 // it might be the user's chance to reset the solver to start fresh without
310 // errors. The current code isn't perfect, since some CHECKs() remain, but
311 // hopefully they'll never be triggered in practice.
312 CHECK(scip_ != nullptr);
313 for (int i = 0; i < scip_variables_.size(); ++i) {
314 CHECK_EQ(SCIPreleaseVar(scip_, &scip_variables_[i]), SCIP_OKAY);
315 }
316 scip_variables_.clear();
317 for (int j = 0; j < scip_constraints_.size(); ++j) {
318 CHECK_EQ(SCIPreleaseCons(scip_, &scip_constraints_[j]), SCIP_OKAY);
319 }
320 scip_constraints_.clear();
321
322 SCIP* old_scip = scip_;
323 scip_ = nullptr;
324 if (!return_scip) {
325 CHECK_EQ(SCIPfree(&old_scip), SCIP_OKAY);
326 }
327 return old_scip;
328}
329
330// Not cached.
334 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPfreeTransform(scip_));
335 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPsetObjsense(
336 scip_, maximize ? SCIP_OBJSENSE_MAXIMIZE : SCIP_OBJSENSE_MINIMIZE));
337}
338
339void SCIPInterface::SetVariableBounds(int var_index, double lb, double ub) {
342 if (variable_is_extracted(var_index)) {
343 // Not cached if the variable has been extracted.
345 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPfreeTransform(scip_));
347 SCIPchgVarLb(scip_, scip_variables_[var_index], lb));
349 SCIPchgVarUb(scip_, scip_variables_[var_index], ub));
350 } else {
352 }
353}
354
355void SCIPInterface::SetVariableInteger(int var_index, bool integer) {
358 if (variable_is_extracted(var_index)) {
359 // Not cached if the variable has been extracted.
360 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPfreeTransform(scip_));
361#if (SCIP_VERSION >= 210)
362 SCIP_Bool infeasible = false;
363 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPchgVarType(
364 scip_, scip_variables_[var_index],
365 integer ? SCIP_VARTYPE_INTEGER : SCIP_VARTYPE_CONTINUOUS, &infeasible));
366#else
367 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPchgVarType(
368 scip_, scip_variables_[var_index],
369 integer ? SCIP_VARTYPE_INTEGER : SCIP_VARTYPE_CONTINUOUS));
370#endif // SCIP_VERSION >= 210
371 } else {
373 }
374}
375
376void SCIPInterface::SetConstraintBounds(int index, double lb, double ub) {
380 // Not cached if the row has been extracted.
382 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPfreeTransform(scip_));
384 SCIPchgLhsLinear(scip_, scip_constraints_[index], lb));
386 SCIPchgRhsLinear(scip_, scip_constraints_[index], ub));
387 } else {
389 }
390}
391
393 const MPVariable* variable, double new_value,
394 double old_value) {
397 if (variable_is_extracted(variable->index()) &&
398 constraint_is_extracted(constraint->index())) {
399 // The modification of the coefficient for an extracted row and
400 // variable is not cached.
401 DCHECK_LT(constraint->index(), last_constraint_index_);
403 // SCIP does not allow to set a coefficient directly, so we add the
404 // difference between the new and the old value instead.
405 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPfreeTransform(scip_));
406 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPaddCoefLinear(
407 scip_, scip_constraints_[constraint->index()],
408 scip_variables_[variable->index()], new_value - old_value));
409 } else {
410 // The modification of an unextracted row or variable is cached
411 // and handled in ExtractModel.
413 }
414}
415
416// Not cached
420 const int constraint_index = constraint->index();
421 // Constraint may not have been extracted yet.
422 if (!constraint_is_extracted(constraint_index)) return;
423 for (const auto& entry : constraint->coefficients_) {
424 const int var_index = entry.first->index();
425 const double old_coef_value = entry.second;
426 DCHECK(variable_is_extracted(var_index));
427 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPfreeTransform(scip_));
428 // Set coefficient to zero by subtracting the old coefficient value.
430 SCIPaddCoefLinear(scip_, scip_constraints_[constraint_index],
431 scip_variables_[var_index], -old_coef_value));
432 }
433}
434
435// Cached
437 double coefficient) {
439}
440
441// Cached
444}
445
446// Clear objective of all its terms.
450
452 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPfreeTransform(scip_));
453 // Clear linear terms
454 for (const auto& entry : solver_->objective_->coefficients_) {
455 const int var_index = entry.first->index();
456 // Variable may have not been extracted yet.
457 if (!variable_is_extracted(var_index)) {
459 } else {
461 SCIPchgVarObj(scip_, scip_variables_[var_index], 0.0));
462 }
463 }
464 // Note: we don't clear the objective offset here because it's not necessary
465 // (it's always reset anyway in ExtractObjective) and we sometimes run into
466 // crashes when clearing the whole model (see
467 // http://test/OCL:253365573:BASE:253566457:1560777456754:e181f4ab).
468 // It's not worth to spend time investigating this issue.
469}
470
472 // As of 2019-05, SCIP does not support setting branching priority for
473 // variables in models that have already been solved. Therefore, we force
474 // reset the model when setting the priority on an already extracted variable.
475 // Note that this is a more drastic step than merely changing the sync_status.
476 // This may be slightly conservative, as it is technically possible that
477 // the extraction has occurred without a call to Solve().
478 if (variable_is_extracted(var_index)) {
479 branching_priority_reset_ = true;
480 }
481}
482
485}
486
489 return true;
490}
491
493
496 int total_num_vars = solver_->variables_.size();
497 if (total_num_vars > last_variable_index_) {
498 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPfreeTransform(scip_));
499 // Define new variables
500 for (int j = last_variable_index_; j < total_num_vars; ++j) {
501 MPVariable* const var = solver_->variables_[j];
504 SCIP_VAR* scip_var = nullptr;
505 // The true objective coefficient will be set later in ExtractObjective.
506 double tmp_obj_coef = 0.0;
507 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPcreateVar(
508 scip_, &scip_var, var->name().c_str(), var->lb(), var->ub(),
509 tmp_obj_coef,
510 var->integer() ? SCIP_VARTYPE_INTEGER : SCIP_VARTYPE_CONTINUOUS, true,
511 false, nullptr, nullptr, nullptr, nullptr, nullptr));
512 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPaddVar(scip_, scip_var));
513 scip_variables_.push_back(scip_var);
514 const int branching_priority = var->branching_priority();
515 if (branching_priority != 0) {
516 const int index = var->index();
517 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPchgVarBranchPriority(
518 scip_, scip_variables_[index], branching_priority));
519 }
520 }
521 // Add new variables to existing constraints.
522 for (int i = 0; i < last_constraint_index_; i++) {
523 MPConstraint* const ct = solver_->constraints_[i];
524 for (const auto& entry : ct->coefficients_) {
525 const int var_index = entry.first->index();
526 DCHECK(variable_is_extracted(var_index));
527 if (var_index >= last_variable_index_) {
528 // The variable is new, so we know the previous coefficient
529 // value was 0 and we can directly add the coefficient.
531 SCIPaddCoefLinear(scip_, scip_constraints_[i],
532 scip_variables_[var_index], entry.second));
533 }
534 }
535 }
536 }
537}
538
541 int total_num_rows = solver_->constraints_.size();
542 if (last_constraint_index_ < total_num_rows) {
543 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPfreeTransform(scip_));
544 // Find the length of the longest row.
545 int max_row_length = 0;
546 for (int i = last_constraint_index_; i < total_num_rows; ++i) {
547 MPConstraint* const ct = solver_->constraints_[i];
550 if (ct->coefficients_.size() > max_row_length) {
551 max_row_length = ct->coefficients_.size();
552 }
553 }
554 std::unique_ptr<SCIP_VAR*[]> vars(new SCIP_VAR*[max_row_length]);
555 std::unique_ptr<double[]> coeffs(new double[max_row_length]);
556 // Add each new constraint.
557 for (int i = last_constraint_index_; i < total_num_rows; ++i) {
558 MPConstraint* const ct = solver_->constraints_[i];
560 const int size = ct->coefficients_.size();
561 int j = 0;
562 for (const auto& entry : ct->coefficients_) {
563 const int var_index = entry.first->index();
564 DCHECK(variable_is_extracted(var_index));
565 vars[j] = scip_variables_[var_index];
566 coeffs[j] = entry.second;
567 j++;
568 }
569 SCIP_CONS* scip_constraint = nullptr;
570 const bool is_lazy = ct->is_lazy();
571 if (ct->indicator_variable() != nullptr) {
572 const int ind_index = ct->indicator_variable()->index();
573 DCHECK(variable_is_extracted(ind_index));
574 SCIP_VAR* ind_var = scip_variables_[ind_index];
575 if (ct->indicator_value() == 0) {
577 SCIPgetNegatedVar(scip_, scip_variables_[ind_index], &ind_var));
578 }
579
580 if (ct->ub() < std::numeric_limits<double>::infinity()) {
581 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPcreateConsIndicator(
582 scip_, &scip_constraint, ct->name().c_str(), ind_var, size,
583 vars.get(), coeffs.get(), ct->ub(),
584 /*initial=*/!is_lazy,
585 /*separate=*/true,
586 /*enforce=*/true,
587 /*check=*/true,
588 /*propagate=*/true,
589 /*local=*/false,
590 /*dynamic=*/false,
591 /*removable=*/is_lazy,
592 /*stickingatnode=*/false));
593 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPaddCons(scip_, scip_constraint));
594 scip_constraints_.push_back(scip_constraint);
595 }
596 if (ct->lb() > -std::numeric_limits<double>::infinity()) {
597 for (int i = 0; i < size; ++i) {
598 coeffs[i] *= -1;
599 }
600 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPcreateConsIndicator(
601 scip_, &scip_constraint, ct->name().c_str(), ind_var, size,
602 vars.get(), coeffs.get(), -ct->lb(),
603 /*initial=*/!is_lazy,
604 /*separate=*/true,
605 /*enforce=*/true,
606 /*check=*/true,
607 /*propagate=*/true,
608 /*local=*/false,
609 /*dynamic=*/false,
610 /*removable=*/is_lazy,
611 /*stickingatnode=*/false));
612 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPaddCons(scip_, scip_constraint));
613 scip_constraints_.push_back(scip_constraint);
614 }
615 } else {
616 // See
617 // http://scip.zib.de/doc/html/cons__linear_8h.php#aa7aed137a4130b35b168812414413481
618 // for an explanation of the parameters.
619 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPcreateConsLinear(
620 scip_, &scip_constraint, ct->name().c_str(), size, vars.get(),
621 coeffs.get(), ct->lb(), ct->ub(),
622 /*initial=*/!is_lazy,
623 /*separate=*/true,
624 /*enforce=*/true,
625 /*check=*/true,
626 /*propagate=*/true,
627 /*local=*/false,
628 /*modifiable=*/false,
629 /*dynamic=*/false,
630 /*removable=*/is_lazy,
631 /*stickingatnode=*/false));
632 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPaddCons(scip_, scip_constraint));
633 scip_constraints_.push_back(scip_constraint);
634 }
635 }
636 }
637}
638
641 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPfreeTransform(scip_));
642 // Linear objective: set objective coefficients for all variables (some might
643 // have been modified).
644 for (const auto& entry : solver_->objective_->coefficients_) {
645 const int var_index = entry.first->index();
646 const double obj_coef = entry.second;
648 SCIPchgVarObj(scip_, scip_variables_[var_index], obj_coef));
649 }
650
651 // Constant term: change objective offset.
652 RETURN_AND_STORE_IF_SCIP_ERROR(SCIPaddOrigObjoffset(
653 scip_, solver_->Objective().offset() - SCIPgetOrigObjoffset(scip_)));
654}
655
656#define RETURN_ABNORMAL_IF_BAD_STATUS \
657 do { \
658 if (!status_.ok()) { \
659 LOG_IF(INFO, solver_->OutputIsEnabled()) \
660 << "Invalid SCIP status: " << status_; \
661 return result_status_ = MPSolver::ABNORMAL; \
662 } \
663 } while (false)
664
665#define RETURN_ABNORMAL_IF_SCIP_ERROR(x) \
666 do { \
667 RETURN_ABNORMAL_IF_BAD_STATUS; \
668 status_ = SCIP_TO_STATUS(x); \
669 RETURN_ABNORMAL_IF_BAD_STATUS; \
670 } while (false);
671
673 // "status_" may encode a variety of failure scenarios, many of which would
674 // correspond to another MPResultStatus than ABNORMAL, but since SCIP is a
675 // moving target, we use the most likely error code here (abnormalities,
676 // often numeric), and rely on the user enabling output to see more details.
678
679 WallTimer timer;
680 timer.Start();
681
682 // Note that SCIP does not provide any incrementality.
683 // TODO(user): Is that still true now (2018) ?
686 branching_priority_reset_ || callback_reset_) {
687 Reset();
688 branching_priority_reset_ = false;
689 callback_reset_ = false;
690 }
691
692 // Set log level.
693 SCIPsetMessagehdlrQuiet(scip_, quiet_);
694
695 // Special case if the model is empty since SCIP expects a non-empty model.
696 if (solver_->variables_.empty() && solver_->constraints_.empty()) {
701 return result_status_;
702 }
703
704 ExtractModel();
705 VLOG(1) << absl::StrFormat("Model built in %s.",
706 absl::FormatDuration(timer.GetDuration()));
707 if (scip_constraint_handler_ != nullptr) {
708 // When the value of `callback_` is changed, `callback_reset_` is set and
709 // code above you call Reset() that should have cleared
710 // `scip_constraint_handler_`. Here we assert that if this has not happened
711 // then `callback_` value has not changed.
712 CHECK_EQ(scip_constraint_handler_->mp_callback(), callback_);
713 } else if (callback_ != nullptr) {
714 scip_constraint_handler_ =
715 absl::make_unique<ScipConstraintHandlerForMPCallback>(callback_);
716 RegisterConstraintHandler<EmptyStruct>(scip_constraint_handler_.get(),
717 scip_);
718 AddCallbackConstraint<EmptyStruct>(scip_, scip_constraint_handler_.get(),
719 "mp_solver_callback_constraint_for_scip",
720 &constraint_data_for_handler_,
722 }
723
724 // Time limit.
725 if (solver_->time_limit() != 0) {
726 VLOG(1) << "Setting time limit = " << solver_->time_limit() << " ms.";
728 SCIPsetRealParam(scip_, "limits/time", solver_->time_limit_in_secs()));
729 } else {
730 RETURN_ABNORMAL_IF_SCIP_ERROR(SCIPresetParam(scip_, "limits/time"));
731 }
732
733 // We first set our internal MPSolverParameters from param and then set any
734 // user specified internal solver, ie. SCIP, parameters via
735 // solver_specific_parameter_string_.
736 // Default MPSolverParameters can override custom parameters (for example for
737 // presolving) and therefore we apply MPSolverParameters first.
738 SetParameters(param);
740 solver_->solver_specific_parameter_string_);
741
742 // Use the solution hint if any.
743 if (!solver_->solution_hint_.empty()) {
744 SCIP_SOL* solution;
745 bool is_solution_partial = false;
746 const int num_vars = solver_->variables_.size();
747 if (solver_->solution_hint_.size() != num_vars) {
748 // We start by creating an empty partial solution.
750 SCIPcreatePartialSol(scip_, &solution, nullptr));
751 is_solution_partial = true;
752 } else {
753 // We start by creating the all-zero solution.
754 RETURN_ABNORMAL_IF_SCIP_ERROR(SCIPcreateSol(scip_, &solution, nullptr));
755 }
756
757 // Fill the other variables from the given solution hint.
758 for (const std::pair<const MPVariable*, double>& p :
759 solver_->solution_hint_) {
760 RETURN_ABNORMAL_IF_SCIP_ERROR(SCIPsetSolVal(
761 scip_, solution, scip_variables_[p.first->index()], p.second));
762 }
763
764 if (!is_solution_partial) {
765 SCIP_Bool is_feasible;
767 scip_, solution, /*printreason=*/false, /*completely=*/true,
768 /*checkbounds=*/true, /*checkintegrality=*/true, /*checklprows=*/true,
769 &is_feasible));
770 VLOG(1) << "Solution hint is "
771 << (is_feasible ? "FEASIBLE" : "INFEASIBLE");
772 }
773
774 // TODO(user): I more or less copied this from the SCIPreadSol() code that
775 // reads a solution from a file. I am not sure what SCIPisTransformed() is
776 // or what is the difference between the try and add version. In any case
777 // this seems to always call SCIPaddSolFree() for now and it works.
778 SCIP_Bool is_stored;
779 if (!is_solution_partial && SCIPisTransformed(scip_)) {
780 RETURN_ABNORMAL_IF_SCIP_ERROR(SCIPtrySolFree(
781 scip_, &solution, /*printreason=*/false, /*completely=*/true,
782 /*checkbounds=*/true, /*checkintegrality=*/true, /*checklprows=*/true,
783 &is_stored));
784 } else {
786 SCIPaddSolFree(scip_, &solution, &is_stored));
787 }
788 }
789
790 // Solve.
791 timer.Restart();
793 ? SCIPsolveConcurrent(scip_)
794 : SCIPsolve(scip_));
795 VLOG(1) << absl::StrFormat("Solved in %s.",
796 absl::FormatDuration(timer.GetDuration()));
797 current_solution_index_ = 0;
798 // Get the results.
799 SCIP_SOL* const solution = SCIPgetBestSol(scip_);
800 if (solution != nullptr) {
801 // If optimal or feasible solution is found.
802 SetSolution(solution);
803 } else {
804 VLOG(1) << "No feasible solution found.";
805 }
806
807 // Check the status: optimal, infeasible, etc.
808 SCIP_STATUS scip_status = SCIPgetStatus(scip_);
809 switch (scip_status) {
810 case SCIP_STATUS_OPTIMAL:
812 break;
813 case SCIP_STATUS_GAPLIMIT:
814 // To be consistent with the other solvers.
816 break;
817 case SCIP_STATUS_INFEASIBLE:
819 break;
820 case SCIP_STATUS_UNBOUNDED:
822 break;
823 case SCIP_STATUS_INFORUNBD:
824 // TODO(user): We could introduce our own "infeasible or
825 // unbounded" status.
827 break;
828 default:
829 if (solution != nullptr) {
831 } else if (scip_status == SCIP_STATUS_TIMELIMIT ||
832 scip_status == SCIP_STATUS_TOTALNODELIMIT) {
834 } else {
836 }
837 break;
838 }
839
840 RETURN_ABNORMAL_IF_SCIP_ERROR(SCIPresetParams(scip_));
841
843 return result_status_;
844}
845
846void SCIPInterface::SetSolution(SCIP_SOL* solution) {
847 objective_value_ = SCIPgetSolOrigObj(scip_, solution);
848 best_objective_bound_ = SCIPgetDualbound(scip_);
849 VLOG(1) << "objective=" << objective_value_
850 << ", bound=" << best_objective_bound_;
851 for (int i = 0; i < solver_->variables_.size(); ++i) {
852 MPVariable* const var = solver_->variables_[i];
853 const int var_index = var->index();
854 const double val =
855 SCIPgetSolVal(scip_, solution, scip_variables_[var_index]);
856 var->set_solution_value(val);
857 VLOG(3) << var->name() << "=" << val;
858 }
859}
860
861absl::optional<MPSolutionResponse> SCIPInterface::DirectlySolveProto(
862 const MPModelRequest& request) {
863 // ScipSolveProto doesn't solve concurrently.
864 if (solver_->GetNumThreads() > 1) return absl::nullopt;
865
866 const auto status_or = ScipSolveProto(request);
867 if (status_or.ok()) return status_or.value();
868 // Special case: if something is not implemented yet, fall back to solving
869 // through MPSolver.
870 if (absl::IsUnimplemented(status_or.status())) return absl::nullopt;
871
872 if (request.enable_internal_solver_output()) {
873 LOG(INFO) << "Invalid SCIP status: " << status_or.status();
874 }
875 MPSolutionResponse response;
876 response.set_status(MPSOLVER_NOT_SOLVED);
877 response.set_status_str(status_or.status().ToString());
878 return response;
879}
880
881int SCIPInterface::SolutionCount() { return SCIPgetNSols(scip_); }
882
884 // Make sure we have successfully solved the problem and not modified it.
886 return false;
887 }
888 if (current_solution_index_ + 1 >= SolutionCount()) {
889 return false;
890 }
891 current_solution_index_++;
892 SCIP_SOL** all_solutions = SCIPgetSols(scip_);
893 SetSolution(all_solutions[current_solution_index_]);
894 return true;
895}
896
898 // NOTE(user): As of 2018-12 it doesn't run in the stubby server, and is
899 // a specialized call, so it's ok to crash if the status is broken.
901 return SCIPgetNLPIterations(scip_);
902}
903
905 // NOTE(user): Same story as iterations(): it's OK to crash here.
907 // This is the total number of nodes used in the solve, potentially across
908 // multiple branch-and-bound trees. Use limits/totalnodes (rather than
909 // limits/nodes) to control this value.
910 return SCIPgetNTotalNodes(scip_);
911}
912
913void SCIPInterface::SetParameters(const MPSolverParameters& param) {
914 SetCommonParameters(param);
915 SetMIPParameters(param);
916}
917
918void SCIPInterface::SetRelativeMipGap(double value) {
919 // NOTE(user): We don't want to call RETURN_IF_ALREADY_IN_ERROR_STATE here,
920 // because even if the solver is in an error state, the user might be setting
921 // some parameters and then "restoring" the solver to a non-error state by
922 // calling Reset(), which should *not* reset the parameters.
923 // So we want the parameter-setting functions to be resistant to being in an
924 // error state, essentially. What we do is:
925 // - we call the parameter-setting function anyway (I'm assuming that SCIP
926 // won't crash even if we're in an error state. I did *not* verify this).
927 // - if that call yielded an error *and* we weren't already in an error state,
928 // set the state to that error we just got.
929 const auto status =
930 SCIP_TO_STATUS(SCIPsetRealParam(scip_, "limits/gap", value));
931 if (status_.ok()) status_ = status;
932}
933
934void SCIPInterface::SetPrimalTolerance(double value) {
935 // See the NOTE on SetRelativeMipGap().
936 const auto status =
937 SCIP_TO_STATUS(SCIPsetRealParam(scip_, "numerics/feastol", value));
938 if (status_.ok()) status_ = status;
939}
940
941void SCIPInterface::SetDualTolerance(double value) {
942 const auto status =
943 SCIP_TO_STATUS(SCIPsetRealParam(scip_, "numerics/dualfeastol", value));
944 if (status_.ok()) status_ = status;
945}
946
947void SCIPInterface::SetPresolveMode(int presolve) {
948 // See the NOTE on SetRelativeMipGap().
949 switch (presolve) {
951 const auto status =
952 SCIP_TO_STATUS(SCIPsetIntParam(scip_, "presolving/maxrounds", 0));
953 if (status_.ok()) status_ = status;
954 return;
955 }
957 const auto status =
958 SCIP_TO_STATUS(SCIPsetIntParam(scip_, "presolving/maxrounds", -1));
959 if (status_.ok()) status_ = status;
960 return;
961 }
962 default: {
963 SetIntegerParamToUnsupportedValue(MPSolverParameters::PRESOLVE, presolve);
964 return;
965 }
966 }
967}
968
969void SCIPInterface::SetScalingMode(int scaling) {
970 SetUnsupportedIntegerParam(MPSolverParameters::SCALING);
971}
972
973// Only the root LP algorithm is set as setting the node LP to a
974// non-default value rarely is beneficial. The node LP algorithm could
975// be set as well with "lp/resolvealgorithm".
976void SCIPInterface::SetLpAlgorithm(int lp_algorithm) {
977 // See the NOTE on SetRelativeMipGap().
978 switch (lp_algorithm) {
980 const auto status =
981 SCIP_TO_STATUS(SCIPsetCharParam(scip_, "lp/initalgorithm", 'd'));
982 if (status_.ok()) status_ = status;
983 return;
984 }
986 const auto status =
987 SCIP_TO_STATUS(SCIPsetCharParam(scip_, "lp/initalgorithm", 'p'));
988 if (status_.ok()) status_ = status;
989 return;
990 }
992 // Barrier with crossover.
993 const auto status =
994 SCIP_TO_STATUS(SCIPsetCharParam(scip_, "lp/initalgorithm", 'p'));
995 if (status_.ok()) status_ = status;
996 return;
997 }
998 default: {
999 SetIntegerParamToUnsupportedValue(MPSolverParameters::LP_ALGORITHM,
1000 lp_algorithm);
1001 return;
1002 }
1003 }
1004}
1005
1006void SCIPInterface::SetUnsupportedIntegerParam(
1009 if (status_.ok()) {
1010 status_ = absl::InvalidArgumentError(absl::StrFormat(
1011 "Tried to set unsupported integer parameter %d", param));
1012 }
1013}
1014
1015void SCIPInterface::SetIntegerParamToUnsupportedValue(
1018 if (status_.ok()) {
1019 status_ = absl::InvalidArgumentError(absl::StrFormat(
1020 "Tried to set integer parameter %d to unsupported value %d", param,
1021 value));
1022 }
1023}
1024
1025absl::Status SCIPInterface::SetNumThreads(int num_threads) {
1026 if (SetSolverSpecificParametersAsString(
1027 absl::StrFormat("parallel/maxnthreads = %d\n", num_threads))) {
1028 return absl::OkStatus();
1029 }
1030 return absl::InternalError(
1031 "Could not set parallel/maxnthreads, which may "
1032 "indicate that SCIP API has changed.");
1033}
1034
1035bool SCIPInterface::SetSolverSpecificParametersAsString(
1036 const std::string& parameters) {
1037 const absl::Status s =
1039 if (!s.ok()) {
1040 LOG(WARNING) << "Failed to set SCIP parameter string: " << parameters
1041 << ", error is: " << s;
1042 }
1043 return s.ok();
1044}
1045
1047 public:
1049 bool at_integer_solution)
1050 : scip_context_(scip_context),
1051 at_integer_solution_(at_integer_solution) {}
1052
1054 if (at_integer_solution_) {
1056 }
1058 }
1059
1060 bool CanQueryVariableValues() override {
1061 return !scip_context_->is_pseudo_solution();
1062 }
1063
1064 double VariableValue(const MPVariable* variable) override {
1066 return scip_context_->VariableValue(variable);
1067 }
1068
1069 void AddCut(const LinearRange& cutting_plane) override {
1070 CallbackRangeConstraint constraint;
1071 constraint.is_cut = true;
1072 constraint.range = cutting_plane;
1073 constraint.local = false;
1074 constraints_added_.push_back(std::move(constraint));
1075 }
1076
1077 void AddLazyConstraint(const LinearRange& lazy_constraint) override {
1078 CallbackRangeConstraint constraint;
1079 constraint.is_cut = false;
1080 constraint.range = lazy_constraint;
1081 constraint.local = false;
1082 constraints_added_.push_back(std::move(constraint));
1083 }
1084
1086 const absl::flat_hash_map<const MPVariable*, double>& solution) override {
1087 LOG(FATAL) << "SuggestSolution() not currently supported for SCIP.";
1088 }
1089
1091 // scip_context_->NumNodesProcessed() returns:
1092 // 0 before the root node is solved, e.g. if a heuristic finds a solution.
1093 // 1 at the root node
1094 // > 1 after the root node.
1095 // The NumExploredNodes spec requires that we return 0 at the root node,
1096 // (this is consistent with gurobi). Below is a bandaid to try and make the
1097 // behavior consistent, although some information is lost.
1098 return std::max(int64{0}, scip_context_->NumNodesProcessed() - 1);
1099 }
1100
1101 const std::vector<CallbackRangeConstraint>& constraints_added() {
1102 return constraints_added_;
1103 }
1104
1105 private:
1106 const ScipConstraintHandlerContext* scip_context_;
1107 bool at_integer_solution_;
1108 // second value of pair is true for cuts and false for lazy constraints.
1109 std::vector<CallbackRangeConstraint> constraints_added_;
1110};
1111
1113 MPCallback* mp_callback)
1114 : ScipConstraintHandler<EmptyStruct>(
1115 // MOE(begin-strip):
1116 {/*name=*/"mp_solver_constraint_handler",
1117 /*description=*/
1118 "A single constraint handler for all MPSolver models."}
1119 // MOE(end-strip-and-replace): ScipConstraintHandlerDescription()
1120 ),
1121 mp_callback_(mp_callback) {}
1122
1123std::vector<CallbackRangeConstraint>
1125 const ScipConstraintHandlerContext& context, const EmptyStruct&) {
1126 return SeparateSolution(context, /*at_integer_solution=*/false);
1127}
1128
1129std::vector<CallbackRangeConstraint>
1131 const ScipConstraintHandlerContext& context, const EmptyStruct&) {
1132 return SeparateSolution(context, /*at_integer_solution=*/true);
1133}
1134
1135std::vector<CallbackRangeConstraint>
1136ScipConstraintHandlerForMPCallback::SeparateSolution(
1138 const bool at_integer_solution) {
1139 ScipMPCallbackContext mp_context(&context, at_integer_solution);
1140 mp_callback_->RunCallback(&mp_context);
1141 return mp_context.constraints_added();
1142}
1143
1145 if (callback_ != nullptr) {
1146 callback_reset_ = true;
1147 }
1148 callback_ = mp_callback;
1149}
1150
1152 return new SCIPInterface(solver);
1153}
1154
1155} // namespace operations_research
1156#endif // #if defined(USE_SCIP)
1157
1158#undef RETURN_AND_STORE_IF_SCIP_ERROR
1159#undef RETURN_IF_ALREADY_IN_ERROR_STATE
1160#undef RETURN_ABNORMAL_IF_BAD_STATUS
1161#undef RETURN_ABNORMAL_IF_SCIP_ERROR
int64 max
Definition: alldiff_cst.cc:139
#define LOG_IF(severity, condition)
Definition: base/logging.h:479
#define CHECK(condition)
Definition: base/logging.h:495
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:886
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:697
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:888
#define LOG(severity)
Definition: base/logging.h:420
#define DCHECK(condition)
Definition: base/logging.h:884
#define VLOG(verboselevel)
Definition: base/logging.h:978
void Start()
Definition: timer.h:31
absl::Duration GetDuration() const
Definition: timer.h:48
void Restart()
Definition: timer.h:35
An expression of the form:
Definition: linear_expr.h:192
virtual void RunCallback(MPCallbackContext *callback_context)=0
The class for constraints of a Mathematical Programming (MP) model.
int index() const
Returns the index of the constraint in the MPSolver::constraints_.
double offset() const
Gets the constant term in the objective.
This mathematical programming (MP) solver class is the main class though which users build and solve ...
ResultStatus
The status of solving the problem.
@ 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.
bool SetSolverSpecificParametersAsString(const std::string &parameters)
Advanced usage: pass solver specific parameters in text format.
int GetNumThreads() const
Returns the number of threads to be used during solve.
const MPObjective & Objective() const
Returns the objective object.
BasisStatus
Advanced usage: possible basis status values for a variable and the slack variable of a linear constr...
static constexpr int64 kUnknownNumberOfNodes
virtual void SetIntegerParamToUnsupportedValue(MPSolverParameters::IntegerParam param, int value)
static constexpr int64 kUnknownNumberOfIterations
void set_constraint_as_extracted(int ct_index, bool extracted)
void SetMIPParameters(const MPSolverParameters &param)
bool constraint_is_extracted(int ct_index) const
bool variable_is_extracted(int var_index) const
virtual void SetUnsupportedIntegerParam(MPSolverParameters::IntegerParam param)
void set_variable_as_extracted(int var_index, bool extracted)
void SetCommonParameters(const MPSolverParameters &param)
This class stores parameter settings for LP and MIP solvers.
@ INCREMENTALITY_OFF
Start solve from scratch.
IntegerParam
Enumeration of parameters that take integer or categorical values.
@ LP_ALGORITHM
Algorithm to solve linear programs.
@ SCALING
Advanced usage: enable or disable matrix scaling.
@ PRESOLVE
Advanced usage: presolve mode.
@ INCREMENTALITY
Advanced usage: incrementality from one solve to the next.
int GetIntegerParam(MPSolverParameters::IntegerParam param) const
Returns the value of an integer parameter.
The class for variables of a Mathematical Programming (MP) model.
int index() const
Returns the index of the variable in the MPSolver::variables_.
void BranchingPriorityChangedForVariable(int var_index) override
void SetCoefficient(MPConstraint *constraint, const MPVariable *variable, double new_value, double old_value) override
void AddRowConstraint(MPConstraint *ct) override
bool IsContinuous() const override
void SetConstraintBounds(int row_index, double lb, double ub) override
MPSolver::ResultStatus Solve(const MPSolverParameters &param) override
void ClearConstraint(MPConstraint *constraint) override
MPSolver::BasisStatus row_status(int constraint_index) const override
bool SupportsCallbacks() const override
void SetVariableInteger(int var_index, bool integer) override
void SetCallback(MPCallback *mp_callback) override
void SetObjectiveOffset(double value) override
void AddVariable(MPVariable *var) override
std::string SolverVersion() const override
void SetObjectiveCoefficient(const MPVariable *variable, double coefficient) override
bool AddIndicatorConstraint(MPConstraint *ct) override
void SetVariableBounds(int var_index, double lb, double ub) override
absl::optional< MPSolutionResponse > DirectlySolveProto(const MPModelRequest &request) override
void SetOptimizationDirection(bool maximize) override
MPSolver::BasisStatus column_status(int variable_index) const override
int64 iterations() const override
double VariableValue(const MPVariable *variable) const
std::vector< CallbackRangeConstraint > SeparateFractionalSolution(const ScipConstraintHandlerContext &context, const EmptyStruct &) override
std::vector< CallbackRangeConstraint > SeparateIntegerSolution(const ScipConstraintHandlerContext &context, const EmptyStruct &) override
ScipMPCallbackContext(const ScipConstraintHandlerContext *scip_context, bool at_integer_solution)
const std::vector< CallbackRangeConstraint > & constraints_added()
void AddLazyConstraint(const LinearRange &lazy_constraint) override
void AddCut(const LinearRange &cutting_plane) override
double SuggestSolution(const absl::flat_hash_map< const MPVariable *, double > &solution) override
double VariableValue(const MPVariable *variable) override
SatParameters parameters
SharedResponseManager * response
const Constraint * ct
int64 value
IntVar * var
Definition: expr_array.cc:1858
GurobiMPCallbackContext * context
int64_t int64
A C++ wrapper that provides a simple and unified interface to several linear programming and mixed in...
const int WARNING
Definition: log_severity.h:31
const int INFO
Definition: log_severity.h:31
const int FATAL
Definition: log_severity.h:32
absl::Cleanup< absl::decay_t< Callback > > MakeCleanup(Callback &&callback)
Definition: cleanup.h:120
The vehicle routing library lets one model and solve generic vehicle routing problems ranging from th...
MPSolverInterface * BuildSCIPInterface(MPSolver *const solver)
absl::StatusOr< MPSolutionResponse > ScipSolveProto(const MPModelRequest &request)
absl::Status LegacyScipSetSolverSpecificParameters(const std::string &parameters, SCIP *scip)
int index
Definition: pack.cc:508
int64 coefficient
#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