Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
gscip.cc
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14#include "ortools/gscip/gscip.h"
15
16#include <algorithm>
17#include <cstdint>
18#include <cstdio>
19#include <limits>
20#include <memory>
21#include <string>
22#include <utility>
23#include <vector>
24
25#include "absl/cleanup/cleanup.h"
26#include "absl/container/flat_hash_map.h"
27#include "absl/container/flat_hash_set.h"
28#include "absl/log/check.h"
29#include "absl/memory/memory.h"
30#include "absl/status/status.h"
31#include "absl/status/statusor.h"
32#include "absl/strings/str_cat.h"
33#include "absl/strings/str_format.h"
34#include "absl/strings/string_view.h"
35#include "absl/synchronization/mutex.h"
36#include "absl/types/span.h"
37#include "lpi/lpi.h"
40#include "ortools/gscip/gscip.pb.h"
47#include "scip/cons_and.h"
48#include "scip/cons_indicator.h"
49#include "scip/cons_linear.h"
50#include "scip/cons_or.h"
51#include "scip/cons_quadratic.h"
52#include "scip/cons_sos1.h"
53#include "scip/cons_sos2.h"
54#include "scip/def.h"
55#include "scip/pub_cons.h"
56#include "scip/pub_var.h"
57#include "scip/scip_cons.h"
58#include "scip/scip_general.h"
59#include "scip/scip_message.h"
60#include "scip/scip_numerics.h"
61#include "scip/scip_param.h"
62#include "scip/scip_prob.h"
63#include "scip/scip_sol.h"
64#include "scip/scip_solve.h"
65#include "scip/scip_solvingstats.h"
66#include "scip/scip_var.h"
67#include "scip/scipdefplugins.h"
68#include "scip/type_cons.h"
69#include "scip/type_event.h"
70#include "scip/type_paramset.h"
71#include "scip/type_prob.h"
72#include "scip/type_retcode.h"
73#include "scip/type_scip.h"
74#include "scip/type_set.h"
75#include "scip/type_sol.h"
76#include "scip/type_stat.h"
77#include "scip/type_var.h"
78
79namespace operations_research {
80
81#define RETURN_ERROR_UNLESS(x) \
82 if (!(x)) \
83 return util::StatusBuilder(absl::InvalidArgumentError(absl::StrFormat( \
84 "Condition violated at %s:%d: %s", __FILE__, __LINE__, #x)))
85
86namespace {
87
88constexpr absl::string_view kLinearConstraintHandlerName = "linear";
89
90SCIP_VARTYPE ConvertVarType(const GScipVarType var_type) {
91 switch (var_type) {
92 case GScipVarType::kContinuous:
93 return SCIP_VARTYPE_CONTINUOUS;
94 case GScipVarType::kBinary:
95 return SCIP_VARTYPE_BINARY;
96 case GScipVarType::kImpliedInteger:
97 return SCIP_VARTYPE_IMPLINT;
98 case GScipVarType::kInteger:
99 return SCIP_VARTYPE_INTEGER;
100 }
101}
102
103GScipVarType ConvertVarType(const SCIP_VARTYPE var_type) {
104 switch (var_type) {
105 case SCIP_VARTYPE_CONTINUOUS:
106 return GScipVarType::kContinuous;
107 case SCIP_VARTYPE_IMPLINT:
108 return GScipVarType::kImpliedInteger;
109 case SCIP_VARTYPE_INTEGER:
110 return GScipVarType::kInteger;
111 case SCIP_VARTYPE_BINARY:
112 return GScipVarType::kBinary;
113 }
114}
115
116GScipOutput::Status ConvertStatus(const SCIP_STATUS scip_status) {
117 switch (scip_status) {
118 case SCIP_STATUS_UNKNOWN:
119 return GScipOutput::UNKNOWN;
120 case SCIP_STATUS_USERINTERRUPT:
121 return GScipOutput::USER_INTERRUPT;
122 case SCIP_STATUS_BESTSOLLIMIT:
123 return GScipOutput::BEST_SOL_LIMIT;
124 case SCIP_STATUS_MEMLIMIT:
125 return GScipOutput::MEM_LIMIT;
126 case SCIP_STATUS_NODELIMIT:
127 return GScipOutput::NODE_LIMIT;
128 case SCIP_STATUS_RESTARTLIMIT:
129 return GScipOutput::RESTART_LIMIT;
130 case SCIP_STATUS_SOLLIMIT:
131 return GScipOutput::SOL_LIMIT;
132 case SCIP_STATUS_STALLNODELIMIT:
133 return GScipOutput::STALL_NODE_LIMIT;
134 case SCIP_STATUS_TIMELIMIT:
135 return GScipOutput::TIME_LIMIT;
136 case SCIP_STATUS_TOTALNODELIMIT:
137 return GScipOutput::TOTAL_NODE_LIMIT;
138 case SCIP_STATUS_OPTIMAL:
139 return GScipOutput::OPTIMAL;
140 case SCIP_STATUS_GAPLIMIT:
141 return GScipOutput::GAP_LIMIT;
142 case SCIP_STATUS_INFEASIBLE:
143 return GScipOutput::INFEASIBLE;
144 case SCIP_STATUS_UNBOUNDED:
145 return GScipOutput::UNBOUNDED;
146 case SCIP_STATUS_INFORUNBD:
147 return GScipOutput::INF_OR_UNBD;
148 case SCIP_STATUS_TERMINATE:
149 return GScipOutput::TERMINATE;
150 default:
151 LOG(FATAL) << "Unrecognized scip status: " << scip_status;
152 }
153}
154
155SCIP_PARAMEMPHASIS ConvertEmphasis(
156 const GScipParameters::Emphasis gscip_emphasis) {
157 switch (gscip_emphasis) {
158 case GScipParameters::DEFAULT_EMPHASIS:
159 return SCIP_PARAMEMPHASIS_DEFAULT;
160 case GScipParameters::CP_SOLVER:
161 return SCIP_PARAMEMPHASIS_CPSOLVER;
162 case GScipParameters::EASY_CIP:
163 return SCIP_PARAMEMPHASIS_EASYCIP;
164 case GScipParameters::FEASIBILITY:
165 return SCIP_PARAMEMPHASIS_FEASIBILITY;
166 case GScipParameters::HARD_LP:
167 return SCIP_PARAMEMPHASIS_HARDLP;
168 case GScipParameters::OPTIMALITY:
169 return SCIP_PARAMEMPHASIS_OPTIMALITY;
170 case GScipParameters::COUNTER:
171 return SCIP_PARAMEMPHASIS_COUNTER;
172 case GScipParameters::PHASE_FEAS:
173 return SCIP_PARAMEMPHASIS_PHASEFEAS;
174 case GScipParameters::PHASE_IMPROVE:
175 return SCIP_PARAMEMPHASIS_PHASEIMPROVE;
176 case GScipParameters::PHASE_PROOF:
177 return SCIP_PARAMEMPHASIS_PHASEPROOF;
178 default:
179 LOG(FATAL) << "Unrecognized gscip_emphasis: "
180 << ProtoEnumToString(gscip_emphasis);
181 }
182}
183
184SCIP_PARAMSETTING ConvertMetaParamValue(
185 const GScipParameters::MetaParamValue gscip_meta_param_value) {
186 switch (gscip_meta_param_value) {
187 case GScipParameters::DEFAULT_META_PARAM_VALUE:
188 return SCIP_PARAMSETTING_DEFAULT;
189 case GScipParameters::AGGRESSIVE:
190 return SCIP_PARAMSETTING_AGGRESSIVE;
191 case GScipParameters::FAST:
192 return SCIP_PARAMSETTING_FAST;
193 case GScipParameters::OFF:
194 return SCIP_PARAMSETTING_OFF;
195 default:
196 LOG(FATAL) << "Unrecognized gscip_meta_param_value: "
197 << ProtoEnumToString(gscip_meta_param_value);
198 }
199}
200
201absl::Status CheckSolutionsInOrder(const GScipResult& result,
202 const bool is_maximize) {
203 auto objective_as_good_as = [is_maximize](double left, double right) {
204 if (is_maximize) {
205 return left >= right;
206 }
207 return left <= right;
208 };
209 for (int i = 1; i < result.objective_values.size(); ++i) {
210 const double previous = result.objective_values[i - 1];
211 const double current = result.objective_values[i];
212 if (!objective_as_good_as(previous, current)) {
214 << "Expected SCIP solutions to be in best objective order "
215 "first, but for "
216 << (is_maximize ? "maximization" : "minimization")
217 << " problem, the " << i - 1 << " objective is " << previous
218 << " and the " << i << " objective is " << current;
219 }
220 }
221 return absl::OkStatus();
222}
223
224} // namespace
225
226void GScip::InterruptEventHandler::set_interrupter(
227 const GScip::Interrupter* interrupter) {
228 interrupter_ = interrupter;
229}
230
231GScip::InterruptEventHandler::InterruptEventHandler()
232 : GScipEventHandler(
233 {.name = "interrupt event handler",
234 .description = "Event handler to call SCIPinterruptSolve() when a "
235 "user SolveInterrupter is triggered."}) {}
236
237SCIP_RETCODE GScip::InterruptEventHandler::Init(GScip* const gscip) {
238 // We don't register any event if we don't have an interrupter.
239 if (interrupter_ == nullptr) {
240 return SCIP_OKAY;
241 }
242
243 // TODO(b/193537362): see if these events are enough or if we should have more
244 // of these.
245 CatchEvent(SCIP_EVENTTYPE_PRESOLVEROUND);
246 CatchEvent(SCIP_EVENTTYPE_NODEEVENT);
247 CatchEvent(SCIP_EVENTTYPE_ROWEVENT);
248
249 return TryCallInterruptIfNeeded(gscip);
250}
251
252SCIP_RETCODE GScip::InterruptEventHandler::Execute(
253 const GScipEventHandlerContext context) {
254 return TryCallInterruptIfNeeded(context.gscip());
255}
256
257SCIP_RETCODE GScip::InterruptEventHandler::TryCallInterruptIfNeeded(
258 GScip* const gscip) {
259 if (interrupter_ == nullptr) {
260 LOG(WARNING) << "TryCallInterruptIfNeeded() called after interrupter has "
261 "been reset!";
262 return SCIP_OKAY;
263 }
264
265 if (!interrupter_->is_interrupted()) {
266 return SCIP_OKAY;
267 }
268
269 const SCIP_STAGE stage = SCIPgetStage(gscip->scip());
270 switch (stage) {
271 case SCIP_STAGE_INIT:
272 case SCIP_STAGE_FREE:
273 // This should never happen anyway; but if this happens, we may want to
274 // know about it in unit tests.
275 LOG(DFATAL) << "TryCallInterruptIfNeeded() called in stage "
276 << (stage == SCIP_STAGE_INIT ? "INIT" : "FREE");
277 return SCIP_OKAY;
278 case SCIP_STAGE_INITSOLVE:
279 LOG(WARNING) << "TryCallInterruptIfNeeded() called in INITSOLVE stage; "
280 "we can't call SCIPinterruptSolve() in this stage.";
281 return SCIP_OKAY;
282 default:
283 return SCIPinterruptSolve(gscip->scip());
284 }
285}
286
287const GScipVariableOptions& DefaultGScipVariableOptions() {
288 static GScipVariableOptions var_options;
289 return var_options;
290}
291
292const GScipConstraintOptions& DefaultGScipConstraintOptions() {
293 static GScipConstraintOptions constraint_options;
294 return constraint_options;
295}
296
297absl::Status GScip::SetParams(const GScipParameters& params,
298 absl::string_view legacy_params) {
299 if (params.has_silence_output()) {
300 SCIPsetMessagehdlrQuiet(scip_, params.silence_output());
301 }
302 if (!params.search_logs_filename().empty()) {
303 SCIPsetMessagehdlrLogfile(scip_, params.search_logs_filename().c_str());
304 }
305
306 const SCIP_Bool set_param_quiet =
307 static_cast<SCIP_Bool>(!params.silence_output());
308
309 RETURN_IF_SCIP_ERROR(SCIPsetEmphasis(
310 scip_, ConvertEmphasis(params.emphasis()), set_param_quiet));
311 if (params.has_heuristics()) {
312 RETURN_IF_SCIP_ERROR(SCIPsetHeuristics(
313 scip_, ConvertMetaParamValue(params.heuristics()), set_param_quiet));
314 }
315 if (params.has_presolve()) {
316 RETURN_IF_SCIP_ERROR(SCIPsetPresolving(
317 scip_, ConvertMetaParamValue(params.presolve()), set_param_quiet));
318 }
319 if (params.has_separating()) {
320 RETURN_IF_SCIP_ERROR(SCIPsetSeparating(
321 scip_, ConvertMetaParamValue(params.separating()), set_param_quiet));
322 }
323 for (const auto& bool_param : params.bool_params()) {
325 (SCIPsetBoolParam(scip_, bool_param.first.c_str(), bool_param.second)));
326 }
327 for (const auto& int_param : params.int_params()) {
329 (SCIPsetIntParam(scip_, int_param.first.c_str(), int_param.second)));
330 }
331 for (const auto& long_param : params.long_params()) {
332 RETURN_IF_SCIP_ERROR((SCIPsetLongintParam(scip_, long_param.first.c_str(),
333 long_param.second)));
334 }
335 for (const auto& char_param : params.char_params()) {
336 if (char_param.second.size() != 1) {
337 return absl::InvalidArgumentError(
338 absl::StrCat("Character parameters must be single character strings, "
339 "but parameter: ",
340 char_param.first, " was: ", char_param.second));
341 }
342 RETURN_IF_SCIP_ERROR((SCIPsetCharParam(scip_, char_param.first.c_str(),
343 char_param.second[0])));
344 }
345 for (const auto& string_param : params.string_params()) {
346 RETURN_IF_SCIP_ERROR((SCIPsetStringParam(scip_, string_param.first.c_str(),
347 string_param.second.c_str())));
348 }
349 for (const auto& real_param : params.real_params()) {
351 (SCIPsetRealParam(scip_, real_param.first.c_str(), real_param.second)));
352 }
353 if (!legacy_params.empty()) {
355 LegacyScipSetSolverSpecificParameters(legacy_params, scip_));
356 }
357 return absl::OkStatus();
358}
359
360absl::StatusOr<std::unique_ptr<GScip>> GScip::Create(
361 const std::string& problem_name) {
362 SCIP* scip = nullptr;
363 RETURN_IF_SCIP_ERROR(SCIPcreate(&scip));
364 absl::Cleanup scip_cleanup = [&]() { SCIPfree(&scip); };
365 RETURN_IF_SCIP_ERROR(SCIPincludeDefaultPlugins(scip));
366 RETURN_IF_SCIP_ERROR(SCIPcreateProbBasic(scip, problem_name.c_str()));
367 auto result = absl::WrapUnique(new GScip(scip));
368 // GScip takes ownership of `scip`.
369 std::move(scip_cleanup).Cancel();
370 RETURN_IF_ERROR(result->interrupt_event_handler_.Register(result.get()));
371 return result;
372}
373
374GScip::GScip(SCIP* scip) : scip_(scip) {}
375
376double GScip::ScipInf() { return SCIPinfinity(scip_); }
377
378absl::Status GScip::FreeTransform() {
379 return SCIP_TO_STATUS(SCIPfreeTransform(scip_));
380}
381
382std::string GScip::ScipVersion() {
383 return absl::StrFormat("SCIP %d.%d.%d [LP solver: %s]", SCIPmajorVersion(),
384 SCIPminorVersion(), SCIPtechVersion(),
385 SCIPlpiGetSolverName());
386}
387
388void GScip::InterruptSolveFromCallbackOnCallbackError(
389 absl::Status error_status) {
390 CHECK(!error_status.ok());
391 {
392 const absl::MutexLock lock(&callback_status_mutex_);
393 if (!callback_status_.ok()) {
394 return;
395 }
396 callback_status_ = std::move(error_status);
397 }
398 // We are already in an error state, do not propagate.
399 const absl::Status status = SCIP_TO_STATUS(SCIPinterruptSolve(scip_));
400 if (!status.ok()) {
401 LOG(WARNING) << "Error trying to interrupt solve after error in callback: "
402 << status;
403 }
404}
405
406absl::Status GScip::CleanUp() {
407 if (scip_ != nullptr) {
408 for (SCIP_VAR* variable : variables_) {
409 if (variable != nullptr) {
410 RETURN_IF_SCIP_ERROR(SCIPreleaseVar(scip_, &variable));
411 }
412 }
413 for (SCIP_CONS* constraint : constraints_) {
414 if (constraint != nullptr) {
415 RETURN_IF_SCIP_ERROR(SCIPreleaseCons(scip_, &constraint));
416 }
417 }
418 RETURN_IF_SCIP_ERROR(SCIPfree(&scip_));
419 }
420 return absl::OkStatus();
421}
422
423GScip::~GScip() {
424 const absl::Status clean_up_status = CleanUp();
425 LOG_IF(DFATAL, !clean_up_status.ok()) << clean_up_status;
426}
427
428absl::StatusOr<SCIP_VAR*> GScip::AddVariable(
429 double lb, double ub, double obj_coef, GScipVarType var_type,
430 const std::string& var_name, const GScipVariableOptions& options) {
431 SCIP_VAR* var = nullptr;
432 OR_ASSIGN_OR_RETURN3(lb, ScipInfClamp(lb),
433 _ << "invalid lower bound for variable: " << var_name);
434 OR_ASSIGN_OR_RETURN3(ub, ScipInfClamp(ub),
435 _ << "invalid upper bound for variable: " << var_name);
436 RETURN_IF_ERROR(CheckScipFinite(obj_coef))
437 << "invalid objective coefficient for variable: " << var_name;
438 RETURN_IF_SCIP_ERROR(SCIPcreateVarBasic(scip_, /*var=*/&var,
439 /*name=*/var_name.c_str(),
440 /*lb=*/lb, /*ub=*/ub,
441 /*obj=*/obj_coef,
442 ConvertVarType(var_type)));
443 RETURN_IF_SCIP_ERROR(SCIPvarSetInitial(var, options.initial));
444 RETURN_IF_SCIP_ERROR(SCIPvarSetRemovable(var, options.removable));
445 RETURN_IF_SCIP_ERROR(SCIPaddVar(scip_, var));
446 if (options.keep_alive) {
447 variables_.insert(var);
448 } else {
449 RETURN_IF_SCIP_ERROR(SCIPreleaseVar(scip_, &var));
450 }
451 return var;
452}
453
454absl::Status GScip::MaybeKeepConstraintAlive(
455 SCIP_CONS* constraint, const GScipConstraintOptions& options) {
456 if (options.keep_alive) {
457 constraints_.insert(constraint);
458 } else {
459 RETURN_IF_SCIP_ERROR(SCIPreleaseCons(scip_, &constraint));
460 }
461 return absl::OkStatus();
462}
463
464absl::StatusOr<SCIP_CONS*> GScip::AddLinearConstraint(
465 const GScipLinearRange& range, const std::string& name,
466 const GScipConstraintOptions& options) {
467 SCIP_CONS* constraint = nullptr;
468 RETURN_ERROR_UNLESS(range.variables.size() == range.coefficients.size())
469 << "Error adding constraint: " << name << ".";
470 OR_ASSIGN_OR_RETURN3(const double lb, ScipInfClamp(range.lower_bound),
471 _ << "invalid lower bound for constraint: " << name);
472 OR_ASSIGN_OR_RETURN3(const double ub, ScipInfClamp(range.upper_bound),
473 _ << "invalid upper bound for constraint: " << name);
474 for (int i = 0; i < range.coefficients.size(); ++i) {
475 RETURN_IF_ERROR(CheckScipFinite(range.coefficients[i]))
476 << "invalid coefficient at index " << i << " of constraint: " << name;
477 }
478 RETURN_IF_SCIP_ERROR(SCIPcreateConsLinear(
479 scip_, &constraint, name.c_str(), range.variables.size(),
480 const_cast<SCIP_VAR**>(range.variables.data()),
481 const_cast<double*>(range.coefficients.data()),
482 /*lhs=*/lb, /*rhs=*/ub,
483 /*initial=*/options.initial,
484 /*separate=*/options.separate,
485 /*enforce=*/options.enforce,
486 /*check=*/options.check,
487 /*propagate=*/options.propagate,
488 /*local=*/options.local,
489 /*modifiable=*/options.modifiable,
490 /*dynamic=*/options.dynamic,
491 /*removable=*/options.removable,
492 /*stickingatnode=*/options.sticking_at_node));
493 RETURN_IF_SCIP_ERROR(SCIPaddCons(scip_, constraint));
494 RETURN_IF_ERROR(MaybeKeepConstraintAlive(constraint, options));
495 return constraint;
496}
497
498absl::StatusOr<SCIP_CONS*> GScip::AddQuadraticConstraint(
499 const GScipQuadraticRange& range, const std::string& name,
500 const GScipConstraintOptions& options) {
501 SCIP_CONS* constraint = nullptr;
502 const int num_lin_vars = range.linear_variables.size();
503 RETURN_ERROR_UNLESS(num_lin_vars == range.linear_coefficients.size())
504 << "Error adding quadratic constraint: " << name << " in linear term.";
505 const int num_quad_vars = range.quadratic_variables1.size();
506 RETURN_ERROR_UNLESS(num_quad_vars == range.quadratic_variables2.size())
507 << "Error adding quadratic constraint: " << name << " in quadratic term.";
508 RETURN_ERROR_UNLESS(num_quad_vars == range.quadratic_coefficients.size())
509 << "Error adding quadratic constraint: " << name << " in quadratic term.";
510 OR_ASSIGN_OR_RETURN3(const double lb, ScipInfClamp(range.lower_bound),
511 _ << "invalid lower bound for constraint: " << name);
512 OR_ASSIGN_OR_RETURN3(const double ub, ScipInfClamp(range.upper_bound),
513 _ << "invalid upper bound for constraint: " << name);
514 for (int i = 0; i < range.linear_coefficients.size(); ++i) {
515 RETURN_IF_ERROR(CheckScipFinite(range.linear_coefficients[i]))
516 << "invalid linear coefficient at index " << i
517 << " of constraint: " << name;
518 }
519 for (int i = 0; i < range.quadratic_coefficients.size(); ++i) {
520 RETURN_IF_ERROR(CheckScipFinite(range.quadratic_coefficients[i]))
521 << "invalid quadratic coefficient at index " << i
522 << " of constraint: " << name;
523 }
524 RETURN_IF_SCIP_ERROR(SCIPcreateConsQuadratic(
525 scip_, &constraint, name.c_str(), num_lin_vars,
526 const_cast<SCIP_Var**>(range.linear_variables.data()),
527 const_cast<double*>(range.linear_coefficients.data()), num_quad_vars,
528 const_cast<SCIP_Var**>(range.quadratic_variables1.data()),
529 const_cast<SCIP_Var**>(range.quadratic_variables2.data()),
530 const_cast<double*>(range.quadratic_coefficients.data()),
531 /*lhs=*/lb, /*rhs=*/ub,
532 /*initial=*/options.initial,
533 /*separate=*/options.separate,
534 /*enforce=*/options.enforce,
535 /*check=*/options.check,
536 /*propagate=*/options.propagate,
537 /*local=*/options.local,
538 /*modifiable=*/options.modifiable,
539 /*dynamic=*/options.dynamic,
540 /*removable=*/options.removable));
541 RETURN_IF_SCIP_ERROR(SCIPaddCons(scip_, constraint));
542 RETURN_IF_ERROR(MaybeKeepConstraintAlive(constraint, options));
543 return constraint;
544}
545
546absl::StatusOr<SCIP_CONS*> GScip::AddIndicatorConstraint(
547 const GScipIndicatorConstraint& indicator_constraint,
548 const std::string& name, const GScipConstraintOptions& options) {
549 SCIP_VAR* indicator = indicator_constraint.indicator_variable;
550 RETURN_ERROR_UNLESS(indicator != nullptr)
551 << "Error adding indicator constraint: " << name << ".";
552 if (indicator_constraint.negate_indicator) {
553 RETURN_IF_SCIP_ERROR(SCIPgetNegatedVar(scip_, indicator, &indicator));
554 }
555
556 SCIP_CONS* constraint = nullptr;
557 RETURN_ERROR_UNLESS(indicator_constraint.variables.size() ==
558 indicator_constraint.coefficients.size())
559 << "Error adding indicator constraint: " << name << ".";
560 OR_ASSIGN_OR_RETURN3(const double ub,
561 ScipInfClamp(indicator_constraint.upper_bound),
562 _ << "invalid upper bound for constraint: " << name);
563 for (int i = 0; i < indicator_constraint.coefficients.size(); ++i) {
564 RETURN_IF_ERROR(CheckScipFinite(indicator_constraint.coefficients[i]))
565 << "invalid coefficient at index " << i << " of constraint: " << name;
566 }
567 RETURN_IF_SCIP_ERROR(SCIPcreateConsIndicator(
568 scip_, &constraint, name.c_str(), indicator,
569 indicator_constraint.variables.size(),
570 const_cast<SCIP_Var**>(indicator_constraint.variables.data()),
571 const_cast<double*>(indicator_constraint.coefficients.data()),
572 /*rhs=*/ub,
573 /*initial=*/options.initial,
574 /*separate=*/options.separate,
575 /*enforce=*/options.enforce,
576 /*check=*/options.check,
577 /*propagate=*/options.propagate,
578 /*local=*/options.local,
579 /*dynamic=*/options.dynamic,
580 /*removable=*/options.removable,
581 /*stickingatnode=*/options.sticking_at_node));
582 RETURN_IF_SCIP_ERROR(SCIPaddCons(scip_, constraint));
583 RETURN_IF_ERROR(MaybeKeepConstraintAlive(constraint, options));
584 return constraint;
585}
586
587absl::StatusOr<SCIP_CONS*> GScip::AddAndConstraint(
588 const GScipLogicalConstraintData& logical_data, const std::string& name,
589 const GScipConstraintOptions& options) {
590 RETURN_ERROR_UNLESS(logical_data.resultant != nullptr)
591 << "Error adding and constraint: " << name << ".";
592 SCIP_CONS* constraint = nullptr;
594 SCIPcreateConsAnd(scip_, &constraint, name.c_str(),
595 logical_data.resultant, logical_data.operators.size(),
596 const_cast<SCIP_VAR**>(logical_data.operators.data()),
597 /*initial=*/options.initial,
598 /*separate=*/options.separate,
599 /*enforce=*/options.enforce,
600 /*check=*/options.check,
601 /*propagate=*/options.propagate,
602 /*local=*/options.local,
603 /*modifiable=*/options.modifiable,
604 /*dynamic=*/options.dynamic,
605 /*removable=*/options.removable,
606 /*stickingatnode=*/options.sticking_at_node));
607 RETURN_IF_SCIP_ERROR(SCIPaddCons(scip_, constraint));
608 RETURN_IF_ERROR(MaybeKeepConstraintAlive(constraint, options));
609 return constraint;
610}
611
612absl::StatusOr<SCIP_CONS*> GScip::AddOrConstraint(
613 const GScipLogicalConstraintData& logical_data, const std::string& name,
614 const GScipConstraintOptions& options) {
615 RETURN_ERROR_UNLESS(logical_data.resultant != nullptr)
616 << "Error adding or constraint: " << name << ".";
617 SCIP_CONS* constraint = nullptr;
619 SCIPcreateConsOr(scip_, &constraint, name.c_str(), logical_data.resultant,
620 logical_data.operators.size(),
621 const_cast<SCIP_Var**>(logical_data.operators.data()),
622 /*initial=*/options.initial,
623 /*separate=*/options.separate,
624 /*enforce=*/options.enforce,
625 /*check=*/options.check,
626 /*propagate=*/options.propagate,
627 /*local=*/options.local,
628 /*modifiable=*/options.modifiable,
629 /*dynamic=*/options.dynamic,
630 /*removable=*/options.removable,
631 /*stickingatnode=*/options.sticking_at_node));
632 RETURN_IF_SCIP_ERROR(SCIPaddCons(scip_, constraint));
633 RETURN_IF_ERROR(MaybeKeepConstraintAlive(constraint, options));
634 return constraint;
635}
636
637namespace {
638
639absl::Status ValidateSOSData(const GScipSOSData& sos_data,
640 absl::string_view name) {
641 RETURN_ERROR_UNLESS(!sos_data.variables.empty())
642 << "Error adding SOS constraint: " << name << ".";
643 if (!sos_data.weights.empty()) {
644 RETURN_ERROR_UNLESS(sos_data.variables.size() == sos_data.weights.size())
645 << " Error adding SOS constraint: " << name << ".";
646 }
647 absl::flat_hash_set<double> distinct_weights;
648 for (const double w : sos_data.weights) {
649 RETURN_ERROR_UNLESS(!distinct_weights.contains(w))
650 << "Error adding SOS constraint: " << name
651 << ", weights must be distinct, but found value " << w << " twice.";
652 distinct_weights.insert(w);
653 }
654 return absl::OkStatus();
655}
656
657} // namespace
658
659absl::StatusOr<SCIP_CONS*> GScip::AddSOS1Constraint(
660 const GScipSOSData& sos_data, const std::string& name,
661 const GScipConstraintOptions& options) {
662 RETURN_IF_ERROR(ValidateSOSData(sos_data, name));
663 SCIP_CONS* constraint = nullptr;
664 double* weights = nullptr;
665 if (!sos_data.weights.empty()) {
666 weights = const_cast<double*>(sos_data.weights.data());
667 }
668
669 RETURN_IF_SCIP_ERROR(SCIPcreateConsSOS1(
670 scip_, &constraint, name.c_str(), sos_data.variables.size(),
671 const_cast<SCIP_Var**>(sos_data.variables.data()), weights,
672 /*initial=*/options.initial,
673 /*separate=*/options.separate,
674 /*enforce=*/options.enforce,
675 /*check=*/options.check,
676 /*propagate=*/options.propagate,
677 /*local=*/options.local,
678 /*dynamic=*/options.dynamic,
679 /*removable=*/options.removable,
680 /*stickingatnode=*/options.sticking_at_node));
681 RETURN_IF_SCIP_ERROR(SCIPaddCons(scip_, constraint));
682 RETURN_IF_ERROR(MaybeKeepConstraintAlive(constraint, options));
683 return constraint;
684}
685
686absl::StatusOr<SCIP_CONS*> GScip::AddSOS2Constraint(
687 const GScipSOSData& sos_data, const std::string& name,
688 const GScipConstraintOptions& options) {
689 RETURN_IF_ERROR(ValidateSOSData(sos_data, name));
690 SCIP_CONS* constraint = nullptr;
691 double* weights = nullptr;
692 if (!sos_data.weights.empty()) {
693 weights = const_cast<double*>(sos_data.weights.data());
694 }
695 RETURN_IF_SCIP_ERROR(SCIPcreateConsSOS2(
696 scip_, &constraint, name.c_str(), sos_data.variables.size(),
697 const_cast<SCIP_Var**>(sos_data.variables.data()), weights,
698 /*initial=*/options.initial,
699 /*separate=*/options.separate,
700 /*enforce=*/options.enforce,
701 /*check=*/options.check,
702 /*propagate=*/options.propagate,
703 /*local=*/options.local,
704 /*dynamic=*/options.dynamic,
705 /*removable=*/options.removable,
706 /*stickingatnode=*/options.sticking_at_node));
707 RETURN_IF_SCIP_ERROR(SCIPaddCons(scip_, constraint));
708 RETURN_IF_ERROR(MaybeKeepConstraintAlive(constraint, options));
709 return constraint;
710}
711
712absl::Status GScip::SetMaximize(bool is_maximize) {
713 RETURN_IF_SCIP_ERROR(SCIPsetObjsense(
714 scip_, is_maximize ? SCIP_OBJSENSE_MAXIMIZE : SCIP_OBJSENSE_MINIMIZE));
715 return absl::OkStatus();
716}
717
718absl::Status GScip::SetObjectiveOffset(double offset) {
719 RETURN_IF_ERROR(CheckScipFinite(offset)) << "invalid objective offset";
720 double old_offset = SCIPgetOrigObjoffset(scip_);
721 double delta_offset = offset - old_offset;
722 RETURN_IF_SCIP_ERROR(SCIPaddOrigObjoffset(scip_, delta_offset));
723 return absl::OkStatus();
724}
725
726bool GScip::ObjectiveIsMaximize() {
727 return SCIPgetObjsense(scip_) == SCIP_OBJSENSE_MAXIMIZE;
728}
729
730double GScip::ObjectiveOffset() { return SCIPgetOrigObjoffset(scip_); }
731
732absl::Status GScip::SetBranchingPriority(SCIP_VAR* var, int priority) {
733 RETURN_IF_SCIP_ERROR(SCIPchgVarBranchPriority(scip_, var, priority));
734 return absl::OkStatus();
735}
736
737absl::Status GScip::SetLb(SCIP_VAR* var, double lb) {
738 OR_ASSIGN_OR_RETURN3(lb, ScipInfClamp(lb), _ << "invalid lower bound");
739 RETURN_IF_SCIP_ERROR(SCIPchgVarLb(scip_, var, lb));
740 return absl::OkStatus();
741}
742
743absl::Status GScip::SetUb(SCIP_VAR* var, double ub) {
744 OR_ASSIGN_OR_RETURN3(ub, ScipInfClamp(ub), _ << "invalid upper bound");
745 RETURN_IF_SCIP_ERROR(SCIPchgVarUb(scip_, var, ub));
746 return absl::OkStatus();
747}
748
749absl::Status GScip::SetObjCoef(SCIP_VAR* var, double obj_coef) {
750 RETURN_IF_ERROR(CheckScipFinite(obj_coef)) << "invalid objective coefficient";
751 RETURN_IF_SCIP_ERROR(SCIPchgVarObj(scip_, var, obj_coef));
752 return absl::OkStatus();
753}
754
755absl::Status GScip::SetVarType(SCIP_VAR* var, GScipVarType var_type) {
756 SCIP_Bool infeasible;
758 SCIPchgVarType(scip_, var, ConvertVarType(var_type), &infeasible));
759 return absl::OkStatus();
760}
761
762absl::Status GScip::DeleteVariable(SCIP_VAR* var) {
763 SCIP_Bool did_delete;
764 RETURN_IF_SCIP_ERROR(SCIPdelVar(scip_, var, &did_delete));
765 RETURN_ERROR_UNLESS(static_cast<bool>(did_delete))
766 << "Failed to delete variable named: " << Name(var);
767 variables_.erase(var);
768 RETURN_IF_SCIP_ERROR(SCIPreleaseVar(scip_, &var));
769 return absl::OkStatus();
770}
771
772absl::Status GScip::CanSafeBulkDelete(
773 const absl::flat_hash_set<SCIP_VAR*>& vars) {
774 if (vars.empty()) {
775 return absl::OkStatus();
776 }
777 for (SCIP_CONS* constraint : constraints_) {
778 if (!IsConstraintLinear(constraint)) {
779 return absl::InvalidArgumentError(absl::StrCat(
780 "Model contains nonlinear constraint: ", Name(constraint)));
781 }
782 }
783 return absl::OkStatus();
784}
785
786absl::Status GScip::SafeBulkDelete(const absl::flat_hash_set<SCIP_VAR*>& vars) {
787 RETURN_IF_ERROR(CanSafeBulkDelete(vars));
788 if (vars.empty()) {
789 return absl::OkStatus();
790 }
791 // Now, we can assume that all constraints are linear.
792 for (SCIP_CONS* constraint : constraints_) {
793 const absl::Span<SCIP_VAR* const> nonzeros =
794 LinearConstraintVariables(constraint);
795 const std::vector<SCIP_VAR*> nonzeros_copy(nonzeros.begin(),
796 nonzeros.end());
797 for (SCIP_VAR* var : nonzeros_copy) {
798 if (vars.contains(var)) {
799 RETURN_IF_ERROR(SetLinearConstraintCoef(constraint, var, 0.0));
800 }
801 }
802 }
803 for (SCIP_VAR* const var : vars) {
804 RETURN_IF_ERROR(DeleteVariable(var));
805 }
806 return absl::OkStatus();
807}
808
809double GScip::Lb(SCIP_VAR* var) {
810 return ScipInfUnclamp(SCIPvarGetLbOriginal(var));
811}
812
813double GScip::Ub(SCIP_VAR* var) {
814 return ScipInfUnclamp(SCIPvarGetUbOriginal(var));
815}
816
817double GScip::ObjCoef(SCIP_VAR* var) { return SCIPvarGetObj(var); }
818
819GScipVarType GScip::VarType(SCIP_VAR* var) {
820 return ConvertVarType(SCIPvarGetType(var));
821}
822
823absl::string_view GScip::Name(SCIP_VAR* var) { return SCIPvarGetName(var); }
824
825absl::string_view GScip::ConstraintType(SCIP_CONS* constraint) {
826 return absl::string_view(SCIPconshdlrGetName(SCIPconsGetHdlr(constraint)));
827}
828
829bool GScip::IsConstraintLinear(SCIP_CONS* constraint) {
830 return ConstraintType(constraint) == kLinearConstraintHandlerName;
831}
832
833absl::Span<const double> GScip::LinearConstraintCoefficients(
834 SCIP_CONS* constraint) {
835 int num_vars = SCIPgetNVarsLinear(scip_, constraint);
836 return absl::MakeConstSpan(SCIPgetValsLinear(scip_, constraint), num_vars);
837}
838
839absl::Span<SCIP_VAR* const> GScip::LinearConstraintVariables(
840 SCIP_CONS* constraint) {
841 int num_vars = SCIPgetNVarsLinear(scip_, constraint);
842 return absl::MakeConstSpan(SCIPgetVarsLinear(scip_, constraint), num_vars);
843}
844
845double GScip::LinearConstraintLb(SCIP_CONS* constraint) {
846 return ScipInfUnclamp(SCIPgetLhsLinear(scip_, constraint));
847}
848
849double GScip::LinearConstraintUb(SCIP_CONS* constraint) {
850 return ScipInfUnclamp(SCIPgetRhsLinear(scip_, constraint));
851}
852
853absl::string_view GScip::Name(SCIP_CONS* constraint) {
854 return SCIPconsGetName(constraint);
855}
856
857absl::Status GScip::SetLinearConstraintLb(SCIP_CONS* constraint, double lb) {
858 OR_ASSIGN_OR_RETURN3(lb, ScipInfClamp(lb), _ << "invalid lower bound");
859 RETURN_IF_SCIP_ERROR(SCIPchgLhsLinear(scip_, constraint, lb));
860 return absl::OkStatus();
861}
862
863absl::Status GScip::SetLinearConstraintUb(SCIP_CONS* constraint, double ub) {
864 OR_ASSIGN_OR_RETURN3(ub, ScipInfClamp(ub), _ << "invalid upper bound");
865 RETURN_IF_SCIP_ERROR(SCIPchgRhsLinear(scip_, constraint, ub));
866 return absl::OkStatus();
867}
868
869absl::Status GScip::DeleteConstraint(SCIP_CONS* constraint) {
870 RETURN_IF_SCIP_ERROR(SCIPdelCons(scip_, constraint));
871 constraints_.erase(constraint);
872 RETURN_IF_SCIP_ERROR(SCIPreleaseCons(scip_, &constraint));
873 return absl::OkStatus();
874}
875
876absl::Status GScip::SetLinearConstraintCoef(SCIP_CONS* constraint,
877 SCIP_VAR* var, double value) {
878 // TODO(user): this operation is slow (linear in the nnz in the constraint).
879 // It would be better to just use a bulk operation, but there doesn't appear
880 // to be any?
881 RETURN_IF_ERROR(CheckScipFinite(value)) << "invalid coefficient";
882 RETURN_IF_SCIP_ERROR(SCIPchgCoefLinear(scip_, constraint, var, value));
883 return absl::OkStatus();
884}
885
886absl::Status GScip::AddLinearConstraintCoef(SCIP_CONS* const constraint,
887 SCIP_VAR* const var,
888 const double value) {
889 RETURN_IF_ERROR(CheckScipFinite(value)) << "invalid coefficient";
890 RETURN_IF_SCIP_ERROR(SCIPaddCoefLinear(scip_, constraint, var, value));
891 return absl::OkStatus();
892}
893
894absl::StatusOr<GScipHintResult> GScip::SuggestHint(
895 const GScipSolution& partial_solution) {
896 SCIP_SOL* solution;
897 const int scip_num_vars = SCIPgetNOrigVars(scip_);
898 const bool is_solution_partial = partial_solution.size() < scip_num_vars;
899 if (is_solution_partial) {
900 RETURN_IF_SCIP_ERROR(SCIPcreatePartialSol(scip_, &solution, nullptr));
901 } else {
902 // This is actually a full solution
903 RETURN_ERROR_UNLESS(partial_solution.size() == scip_num_vars)
904 << "Error suggesting hint.";
905 RETURN_IF_SCIP_ERROR(SCIPcreateSol(scip_, &solution, nullptr));
906 }
907 for (const auto& var_value_pair : partial_solution) {
908 RETURN_IF_SCIP_ERROR(SCIPsetSolVal(scip_, solution, var_value_pair.first,
909 var_value_pair.second));
910 }
911 if (!is_solution_partial) {
912 SCIP_Bool is_feasible;
913 RETURN_IF_SCIP_ERROR(SCIPcheckSol(
914 scip_, solution, /*printreason=*/false, /*completely=*/true,
915 /*checkbounds=*/true, /*checkintegrality=*/true, /*checklprows=*/true,
916 &is_feasible));
917 if (!static_cast<bool>(is_feasible)) {
918 RETURN_IF_SCIP_ERROR(SCIPfreeSol(scip_, &solution));
919 return GScipHintResult::kInfeasible;
920 }
921 }
922 SCIP_Bool is_stored;
923 RETURN_IF_SCIP_ERROR(SCIPaddSolFree(scip_, &solution, &is_stored));
924 if (static_cast<bool>(is_stored)) {
925 return GScipHintResult::kAccepted;
926 } else {
927 return GScipHintResult::kRejected;
928 }
929}
930
931absl::StatusOr<GScipResult> GScip::Solve(
932 const GScipParameters& params, absl::string_view legacy_params,
933 const GScipMessageHandler message_handler,
934 const Interrupter* const interrupter) {
935 if (InErrorState()) {
936 return absl::InvalidArgumentError(
937 "GScip is in an error state due to a previous GScip::Solve()");
938 }
939 interrupt_event_handler_.set_interrupter(interrupter);
940 const absl::Cleanup interrupt_cleanup = [this]() {
941 interrupt_event_handler_.set_interrupter(nullptr);
942 };
943
944 // A four step process:
945 // 1. Apply parameters.
946 // 2. Solve the problem.
947 // 3. Extract solution and solve statistics.
948 // 4. Prepare the solver for further modification/solves (reset parameters,
949 // free the solutions found).
950 GScipResult result;
951
952 // Step 1: apply parameters.
953 const absl::Status param_status = SetParams(params, legacy_params);
954 if (!param_status.ok()) {
955 result.gscip_output.set_status(GScipOutput::INVALID_SOLVER_PARAMETERS);
956 // Conversion to std::string for open source build.
957 result.gscip_output.set_status_detail(
958 std::string(param_status.message())); // NOLINT
959 return result;
960 }
961 if (params.print_scip_model()) {
962 RETURN_IF_SCIP_ERROR(SCIPwriteOrigProblem(scip_, nullptr, "cip", FALSE));
963 }
964 if (!params.scip_model_filename().empty()) {
965 RETURN_IF_SCIP_ERROR(SCIPwriteOrigProblem(
966 scip_, params.scip_model_filename().c_str(), "cip", FALSE));
967 }
968 if (params.has_objective_limit()) {
969 OR_ASSIGN_OR_RETURN3(const double scip_obj_limit,
970 ScipInfClamp(params.objective_limit()),
971 _ << "invalid objective_limit");
972 RETURN_IF_SCIP_ERROR(SCIPsetObjlimit(scip_, scip_obj_limit));
973 }
974
975 // Install the message handler if necessary. We do this after setting the
976 // parameters so that parameters that applies to the default message handler
977 // like `quiet` are indeed applied to it and not to our temporary
978 // handler.
979 using internal::CaptureMessageHandlerPtr;
980 using internal::MessageHandlerPtr;
981 MessageHandlerPtr previous_handler;
982 MessageHandlerPtr new_handler;
983 if (message_handler != nullptr) {
984 previous_handler = CaptureMessageHandlerPtr(SCIPgetMessagehdlr(scip_));
985 ASSIGN_OR_RETURN(new_handler,
986 internal::MakeSCIPMessageHandler(message_handler));
987 SCIPsetMessagehdlr(scip_, new_handler.get());
988 }
989 // Make sure we prevent any call of message_handler after this function has
990 // returned, until the new_handler is reset (see below).
991 const internal::ScopedSCIPMessageHandlerDisabler new_handler_disabler(
992 new_handler);
993
994 // Step 2: Solve.
995 // NOTE(user): after solve, SCIP will either be in stage PRESOLVING,
996 // SOLVING, OR SOLVED.
997 if (GScipMaxNumThreads(params) > 1) {
998 RETURN_IF_SCIP_ERROR(SCIPsolveConcurrent(scip_));
999 } else {
1000 RETURN_IF_SCIP_ERROR(SCIPsolve(scip_));
1001 }
1002 const SCIP_STAGE stage = SCIPgetStage(scip_);
1003 if (stage != SCIP_STAGE_PRESOLVING && stage != SCIP_STAGE_SOLVING &&
1004 stage != SCIP_STAGE_SOLVED) {
1005 result.gscip_output.set_status(GScipOutput::UNKNOWN);
1006 result.gscip_output.set_status_detail(
1007 absl::StrCat("Unexpected SCIP final stage= ", stage,
1008 " was expected to be either SCIP_STAGE_PRESOLVING, "
1009 "SCIP_STAGE_SOLVING, or SCIP_STAGE_SOLVED"));
1010 return result;
1011 }
1012 if (params.print_detailed_solving_stats()) {
1013 RETURN_IF_SCIP_ERROR(SCIPprintStatistics(scip_, nullptr));
1014 }
1015 if (!params.detailed_solving_stats_filename().empty()) {
1016 FILE* file = fopen(params.detailed_solving_stats_filename().c_str(), "w");
1017 if (file == nullptr) {
1018 return absl::InvalidArgumentError(absl::StrCat(
1019 "Could not open file: ", params.detailed_solving_stats_filename(),
1020 " to write SCIP solve stats."));
1021 }
1022 RETURN_IF_SCIP_ERROR(SCIPprintStatistics(scip_, file));
1023 int close_result = fclose(file);
1024 if (close_result != 0) {
1025 return absl::InvalidArgumentError(absl::StrCat(
1026 "Error: ", close_result,
1027 " closing file: ", params.detailed_solving_stats_filename(),
1028 " when writing solve stats."));
1029 }
1030 }
1031 absl::Status callback_status;
1032 {
1033 const absl::MutexLock callback_status_lock(&callback_status_mutex_);
1034 callback_status = util::StatusBuilder(callback_status_)
1035 << "error in a callback that interrupted the solve";
1036 }
1037 if (!callback_status.ok()) {
1038 const absl::Status status = FreeTransform();
1039 if (status.ok()) {
1040 return callback_status;
1041 }
1042 LOG(ERROR) << "GScip::FreeTransform() failed after interrupting "
1043 "the solve due to an error in a callback: "
1044 << callback_status;
1045 return status;
1046 }
1047 // Step 3: Extract solution information.
1048 // Some outputs are available unconditionally, and some are only ready if at
1049 // least presolve succeeded.
1050 GScipSolvingStats* stats = result.gscip_output.mutable_stats();
1051 const int num_scip_solutions = SCIPgetNSols(scip_);
1052 const int num_returned_solutions =
1053 std::min(num_scip_solutions, std::max(1, params.num_solutions()));
1054 SCIP_SOL** all_solutions = SCIPgetSols(scip_);
1055 stats->set_best_objective(ScipInfUnclamp(SCIPgetPrimalbound(scip_)));
1056 for (int i = 0; i < num_returned_solutions; ++i) {
1057 SCIP_SOL* scip_sol = all_solutions[i];
1058 const double obj_value = ScipInfUnclamp(SCIPgetSolOrigObj(scip_, scip_sol));
1059 GScipSolution solution;
1060 for (SCIP_VAR* v : variables_) {
1061 solution[v] = SCIPgetSolVal(scip_, scip_sol, v);
1062 }
1063 result.solutions.push_back(solution);
1064 result.objective_values.push_back(obj_value);
1065 }
1066 RETURN_IF_ERROR(CheckSolutionsInOrder(result, ObjectiveIsMaximize()));
1067 // Can only check for primal ray if we made it past presolve.
1068 if (stage != SCIP_STAGE_PRESOLVING && SCIPhasPrimalRay(scip_)) {
1069 for (SCIP_VAR* v : variables_) {
1070 result.primal_ray[v] = SCIPgetPrimalRayVal(scip_, v);
1071 }
1072 }
1073 // TODO(user): refactor this into a new method.
1074 stats->set_best_bound(ScipInfUnclamp(SCIPgetDualbound(scip_)));
1075 stats->set_node_count(SCIPgetNTotalNodes(scip_));
1076 stats->set_first_lp_relaxation_bound(SCIPgetFirstLPDualboundRoot(scip_));
1077 stats->set_root_node_bound(SCIPgetDualboundRoot(scip_));
1078 if (stage != SCIP_STAGE_PRESOLVING) {
1079 stats->set_total_lp_iterations(SCIPgetNLPIterations(scip_));
1080 stats->set_primal_simplex_iterations(SCIPgetNPrimalLPIterations(scip_));
1081 stats->set_dual_simplex_iterations(SCIPgetNDualLPIterations(scip_));
1082 stats->set_barrier_iterations(SCIPgetNBarrierLPIterations(scip_));
1083 stats->set_deterministic_time(SCIPgetDeterministicTime(scip_));
1084 }
1085 result.gscip_output.set_status(ConvertStatus(SCIPgetStatus(scip_)));
1086
1087 // Step 4: clean up.
1088 RETURN_IF_ERROR(FreeTransform());
1089
1090 // Restore the previous message handler. We must do so AFTER we reset the
1091 // stage of the problem with FreeTransform(). Doing so before will fail since
1092 // changing the message handler is only possible in INIT and PROBLEM stages.
1093 if (message_handler != nullptr) {
1094 RETURN_IF_SCIP_ERROR(SCIPsetMessagehdlr(scip_, previous_handler.get()));
1095
1096 // Resetting the unique_ptr will free the associated handler which will
1097 // flush the buffer if the last log line was unfinished. If we were not
1098 // resetting it, the last new_handler_disabler would disable the handler and
1099 // the remainder of the buffer content would be lost.
1100 new_handler.reset();
1101 }
1102 if (params.has_objective_limit()) {
1103 RETURN_IF_SCIP_ERROR(SCIPsetObjlimit(scip_, SCIP_INVALID));
1104 }
1105
1106 RETURN_IF_SCIP_ERROR(SCIPresetParams(scip_));
1107 // The `silence_output` and `search_logs_filename` parameters are special
1108 // since those are not parameters but properties of the SCIP message
1109 // handler. Hence we reset them explicitly.
1110 SCIPsetMessagehdlrQuiet(scip_, false);
1111 SCIPsetMessagehdlrLogfile(scip_, nullptr);
1112
1113 return result;
1114}
1115
1116absl::StatusOr<bool> GScip::DefaultBoolParamValue(
1117 const std::string& parameter_name) {
1118 SCIP_Bool default_value;
1120 SCIPgetBoolParam(scip_, parameter_name.c_str(), &default_value));
1121 return static_cast<bool>(default_value);
1122}
1123
1124absl::StatusOr<int> GScip::DefaultIntParamValue(
1125 const std::string& parameter_name) {
1126 int default_value;
1128 SCIPgetIntParam(scip_, parameter_name.c_str(), &default_value));
1129 return default_value;
1130}
1131
1132absl::StatusOr<int64_t> GScip::DefaultLongParamValue(
1133 const std::string& parameter_name) {
1134 SCIP_Longint result;
1136 SCIPgetLongintParam(scip_, parameter_name.c_str(), &result));
1137 return static_cast<int64_t>(result);
1138}
1139
1140absl::StatusOr<double> GScip::DefaultRealParamValue(
1141 const std::string& parameter_name) {
1142 double result;
1144 SCIPgetRealParam(scip_, parameter_name.c_str(), &result));
1145 return result;
1146}
1147
1148absl::StatusOr<char> GScip::DefaultCharParamValue(
1149 const std::string& parameter_name) {
1150 char result;
1152 SCIPgetCharParam(scip_, parameter_name.c_str(), &result));
1153 return result;
1154}
1155
1156absl::StatusOr<std::string> GScip::DefaultStringParamValue(
1157 const std::string& parameter_name) {
1158 char* result;
1160 SCIPgetStringParam(scip_, parameter_name.c_str(), &result));
1161 return std::string(result);
1162}
1163
1164absl::StatusOr<double> GScip::ScipInfClamp(const double d) {
1165 const double kScipInf = ScipInf();
1166 if (d == std::numeric_limits<double>::infinity()) {
1167 return kScipInf;
1168 }
1169 if (d == -std::numeric_limits<double>::infinity()) {
1170 return -kScipInf;
1171 }
1172 // NaN is considered finite here.
1173 if (d >= kScipInf || d <= -kScipInf) {
1175 << d << " is not in SCIP's finite range: (" << -kScipInf << ", "
1176 << kScipInf << ")";
1177 }
1178 return d;
1179}
1180
1181double GScip::ScipInfUnclamp(double d) {
1182 const double kScipInf = ScipInf();
1183 if (d >= kScipInf) return std::numeric_limits<double>::infinity();
1184 if (d <= -kScipInf) return -std::numeric_limits<double>::infinity();
1185 return d;
1186}
1187
1188absl::Status GScip::CheckScipFinite(double d) {
1189 const double kScipInf = ScipInf();
1190 // NaN is considered finite here.
1191 if (d >= kScipInf || d <= -kScipInf) {
1193 << d << " is not in SCIP's finite range: (" << -kScipInf << ", "
1194 << kScipInf << ")";
1195 }
1196 return absl::OkStatus();
1197}
1198
1199bool GScip::InErrorState() {
1200 const absl::MutexLock lock(&callback_status_mutex_);
1201 return !callback_status_.ok();
1202}
1203
1204#undef RETURN_ERROR_UNLESS
1205
1206} // namespace operations_research
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
const std::string name
A name for logging purposes.
int64_t value
IntVar * var
absl::Status status
Definition g_gurobi.cc:44
#define RETURN_ERROR_UNLESS(x)
Definition gscip.cc:81
GurobiMPCallbackContext * context
double solution
Definition file.cc:169
MessageHandlerPtr CaptureMessageHandlerPtr(SCIP_MESSAGEHDLR *const handler)
std::unique_ptr< SCIP_MESSAGEHDLR, ReleaseSCIPMessageHandler > MessageHandlerPtr
In SWIG mode, we don't want anything besides these top-level includes.
const GScipConstraintOptions & DefaultGScipConstraintOptions()
Definition gscip.cc:292
std::string ProtoEnumToString(ProtoEnumType enum_value)
Definition proto_utils.h:50
int GScipMaxNumThreads(const GScipParameters &parameters)
Returns 1 if the number of threads it not specified.
absl::Status LegacyScipSetSolverSpecificParameters(absl::string_view parameters, SCIP *scip)
const GScipVariableOptions & DefaultGScipVariableOptions()
Definition gscip.cc:287
StatusBuilder InternalErrorBuilder()
StatusBuilder InvalidArgumentErrorBuilder()
trees with all degrees equal w the current value of w
#define SCIP_TO_STATUS(x)
#define RETURN_IF_SCIP_ERROR(x)
const std::optional< Range > & range
Definition statistics.cc:37
#define OR_ASSIGN_OR_RETURN3(lhs, rexpr, error_expression)