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