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