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