OR-Tools  9.3
scip_proto_solver.cc
Go to the documentation of this file.
1// Copyright 2010-2021 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
17
18#include <cmath>
19#include <limits>
20#include <memory>
21#include <numeric>
22#include <set>
23#include <string>
24#include <vector>
25
26#include "absl/status/status.h"
27#include "absl/status/statusor.h"
28#include "absl/strings/ascii.h"
29#include "absl/strings/numbers.h"
30#include "absl/strings/str_cat.h"
31#include "absl/strings/str_format.h"
32#include "absl/strings/str_split.h"
33#include "absl/time/time.h"
37#include "ortools/base/timer.h"
39#include "ortools/linear_solver/linear_solver.pb.h"
43#include "scip/cons_disjunction.h"
44#include "scip/cons_linear.h"
45#include "scip/cons_quadratic.h"
46#include "scip/pub_var.h"
47#include "scip/scip.h"
48#include "scip/scip_param.h"
49#include "scip/scip_prob.h"
50#include "scip/scip_var.h"
51#include "scip/scipdefplugins.h"
52#include "scip/set.h"
53#include "scip/struct_paramset.h"
54#include "scip/type_cons.h"
55#include "scip/type_paramset.h"
56#include "scip/type_var.h"
57
58ABSL_FLAG(std::string, scip_proto_solver_output_cip_file, "",
59 "If given, saves the generated CIP file here. Useful for "
60 "reporting bugs to SCIP.");
61namespace operations_research {
62namespace {
63
64// This function will create a new constraint if the indicator constraint has
65// both a lower bound and an upper bound.
66absl::Status AddIndicatorConstraint(const MPGeneralConstraintProto& gen_cst,
67 SCIP* scip, SCIP_CONS** scip_cst,
68 std::vector<SCIP_VAR*>* scip_variables,
69 std::vector<SCIP_CONS*>* scip_constraints,
70 std::vector<SCIP_VAR*>* tmp_variables,
71 std::vector<double>* tmp_coefficients) {
72 CHECK(scip != nullptr);
73 CHECK(scip_cst != nullptr);
74 CHECK(scip_variables != nullptr);
75 CHECK(scip_constraints != nullptr);
76 CHECK(tmp_variables != nullptr);
77 CHECK(tmp_coefficients != nullptr);
78 CHECK(gen_cst.has_indicator_constraint());
79 constexpr double kInfinity = std::numeric_limits<double>::infinity();
80
81 const auto& ind = gen_cst.indicator_constraint();
82 if (!ind.has_constraint()) return absl::OkStatus();
83
84 const MPConstraintProto& constraint = ind.constraint();
85 const int size = constraint.var_index_size();
86 tmp_variables->resize(size, nullptr);
87 tmp_coefficients->resize(size, 0);
88 for (int i = 0; i < size; ++i) {
89 (*tmp_variables)[i] = (*scip_variables)[constraint.var_index(i)];
90 (*tmp_coefficients)[i] = constraint.coefficient(i);
91 }
92
93 SCIP_VAR* ind_var = (*scip_variables)[ind.var_index()];
94 if (ind.var_value() == 0) {
96 SCIPgetNegatedVar(scip, (*scip_variables)[ind.var_index()], &ind_var));
97 }
98
99 if (ind.constraint().upper_bound() < kInfinity) {
100 RETURN_IF_SCIP_ERROR(SCIPcreateConsIndicator(
101 scip, scip_cst, gen_cst.name().c_str(), ind_var, size,
102 tmp_variables->data(), tmp_coefficients->data(),
103 ind.constraint().upper_bound(),
104 /*initial=*/!ind.constraint().is_lazy(),
105 /*separate=*/true,
106 /*enforce=*/true,
107 /*check=*/true,
108 /*propagate=*/true,
109 /*local=*/false,
110 /*dynamic=*/false,
111 /*removable=*/ind.constraint().is_lazy(),
112 /*stickingatnode=*/false));
113 RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
114 scip_constraints->push_back(nullptr);
115 scip_cst = &scip_constraints->back();
116 }
117 if (ind.constraint().lower_bound() > -kInfinity) {
118 for (int i = 0; i < size; ++i) {
119 (*tmp_coefficients)[i] *= -1;
120 }
121 RETURN_IF_SCIP_ERROR(SCIPcreateConsIndicator(
122 scip, scip_cst, gen_cst.name().c_str(), ind_var, size,
123 tmp_variables->data(), tmp_coefficients->data(),
124 -ind.constraint().lower_bound(),
125 /*initial=*/!ind.constraint().is_lazy(),
126 /*separate=*/true,
127 /*enforce=*/true,
128 /*check=*/true,
129 /*propagate=*/true,
130 /*local=*/false,
131 /*dynamic=*/false,
132 /*removable=*/ind.constraint().is_lazy(),
133 /*stickingatnode=*/false));
134 RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
135 }
136
137 return absl::OkStatus();
138}
139
140absl::Status AddSosConstraint(const MPGeneralConstraintProto& gen_cst,
141 const std::vector<SCIP_VAR*>& scip_variables,
142 SCIP* scip, SCIP_CONS** scip_cst,
143 std::vector<SCIP_VAR*>* tmp_variables,
144 std::vector<double>* tmp_weights) {
145 CHECK(scip != nullptr);
146 CHECK(scip_cst != nullptr);
147 CHECK(tmp_variables != nullptr);
148 CHECK(tmp_weights != nullptr);
149
150 CHECK(gen_cst.has_sos_constraint());
151 const MPSosConstraint& sos_cst = gen_cst.sos_constraint();
152
153 // SOS constraints of type N indicate at most N variables are non-zero.
154 // Constraints with N variables or less are valid, but useless. They also
155 // crash SCIP, so we skip them.
156 if (sos_cst.var_index_size() <= 1) return absl::OkStatus();
157 if (sos_cst.type() == MPSosConstraint::SOS2 &&
158 sos_cst.var_index_size() <= 2) {
159 return absl::OkStatus();
160 }
161
162 tmp_variables->resize(sos_cst.var_index_size(), nullptr);
163 for (int v = 0; v < sos_cst.var_index_size(); ++v) {
164 (*tmp_variables)[v] = scip_variables[sos_cst.var_index(v)];
165 }
166 tmp_weights->resize(sos_cst.var_index_size(), 0);
167 if (sos_cst.weight_size() == sos_cst.var_index_size()) {
168 for (int w = 0; w < sos_cst.weight_size(); ++w) {
169 (*tmp_weights)[w] = sos_cst.weight(w);
170 }
171 } else {
172 // In theory, SCIP should accept empty weight arrays and use natural
173 // ordering, but in practice, this crashes their code.
174 std::iota(tmp_weights->begin(), tmp_weights->end(), 1);
175 }
176 switch (sos_cst.type()) {
177 case MPSosConstraint::SOS1_DEFAULT:
179 SCIPcreateConsBasicSOS1(scip,
180 /*cons=*/scip_cst,
181 /*name=*/gen_cst.name().c_str(),
182 /*nvars=*/sos_cst.var_index_size(),
183 /*vars=*/tmp_variables->data(),
184 /*weights=*/tmp_weights->data()));
185 break;
186 case MPSosConstraint::SOS2:
188 SCIPcreateConsBasicSOS2(scip,
189 /*cons=*/scip_cst,
190 /*name=*/gen_cst.name().c_str(),
191 /*nvars=*/sos_cst.var_index_size(),
192 /*vars=*/tmp_variables->data(),
193 /*weights=*/tmp_weights->data()));
194 break;
195 }
196 RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
197 return absl::OkStatus();
198}
199
200absl::Status AddQuadraticConstraint(
201 const MPGeneralConstraintProto& gen_cst,
202 const std::vector<SCIP_VAR*>& scip_variables, SCIP* scip,
203 SCIP_CONS** scip_cst, std::vector<SCIP_VAR*>* tmp_variables,
204 std::vector<double>* tmp_coefficients,
205 std::vector<SCIP_VAR*>* tmp_qvariables1,
206 std::vector<SCIP_VAR*>* tmp_qvariables2,
207 std::vector<double>* tmp_qcoefficients) {
208 CHECK(scip != nullptr);
209 CHECK(scip_cst != nullptr);
210 CHECK(tmp_variables != nullptr);
211 CHECK(tmp_coefficients != nullptr);
212 CHECK(tmp_qvariables1 != nullptr);
213 CHECK(tmp_qvariables2 != nullptr);
214 CHECK(tmp_qcoefficients != nullptr);
215
216 CHECK(gen_cst.has_quadratic_constraint());
217 const MPQuadraticConstraint& quad_cst = gen_cst.quadratic_constraint();
218
219 // Process linear part of the constraint.
220 const int lsize = quad_cst.var_index_size();
221 CHECK_EQ(quad_cst.coefficient_size(), lsize);
222 tmp_variables->resize(lsize, nullptr);
223 tmp_coefficients->resize(lsize, 0.0);
224 for (int i = 0; i < lsize; ++i) {
225 (*tmp_variables)[i] = scip_variables[quad_cst.var_index(i)];
226 (*tmp_coefficients)[i] = quad_cst.coefficient(i);
227 }
228
229 // Process quadratic part of the constraint.
230 const int qsize = quad_cst.qvar1_index_size();
231 CHECK_EQ(quad_cst.qvar2_index_size(), qsize);
232 CHECK_EQ(quad_cst.qcoefficient_size(), qsize);
233 tmp_qvariables1->resize(qsize, nullptr);
234 tmp_qvariables2->resize(qsize, nullptr);
235 tmp_qcoefficients->resize(qsize, 0.0);
236 for (int i = 0; i < qsize; ++i) {
237 (*tmp_qvariables1)[i] = scip_variables[quad_cst.qvar1_index(i)];
238 (*tmp_qvariables2)[i] = scip_variables[quad_cst.qvar2_index(i)];
239 (*tmp_qcoefficients)[i] = quad_cst.qcoefficient(i);
240 }
241
243 SCIPcreateConsBasicQuadratic(scip,
244 /*cons=*/scip_cst,
245 /*name=*/gen_cst.name().c_str(),
246 /*nlinvars=*/lsize,
247 /*linvars=*/tmp_variables->data(),
248 /*lincoefs=*/tmp_coefficients->data(),
249 /*nquadterms=*/qsize,
250 /*quadvars1=*/tmp_qvariables1->data(),
251 /*quadvars2=*/tmp_qvariables2->data(),
252 /*quadcoefs=*/tmp_qcoefficients->data(),
253 /*lhs=*/quad_cst.lower_bound(),
254 /*rhs=*/quad_cst.upper_bound()));
255 RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
256 return absl::OkStatus();
257}
258
259// Models the constraint y = |x| as y >= 0 plus one disjunction constraint:
260// y = x OR y = -x
261absl::Status AddAbsConstraint(const MPGeneralConstraintProto& gen_cst,
262 const std::vector<SCIP_VAR*>& scip_variables,
263 SCIP* scip, SCIP_CONS** scip_cst) {
264 CHECK(scip != nullptr);
265 CHECK(scip_cst != nullptr);
266 CHECK(gen_cst.has_abs_constraint());
267 const auto& abs = gen_cst.abs_constraint();
268 SCIP_VAR* scip_var = scip_variables[abs.var_index()];
269 SCIP_VAR* scip_resultant_var = scip_variables[abs.resultant_var_index()];
270
271 // Set the resultant variable's lower bound to zero if it's negative.
272 if (SCIPvarGetLbLocal(scip_resultant_var) < 0.0) {
273 RETURN_IF_SCIP_ERROR(SCIPchgVarLb(scip, scip_resultant_var, 0.0));
274 }
275
276 std::vector<SCIP_VAR*> vars;
277 std::vector<double> vals;
278 std::vector<SCIP_CONS*> cons;
279 auto add_abs_constraint =
280 [&](const std::string& name_prefix) -> absl::Status {
281 SCIP_CONS* scip_cons = nullptr;
282 CHECK(vars.size() == vals.size());
283 const std::string name =
284 gen_cst.has_name() ? absl::StrCat(gen_cst.name(), name_prefix) : "";
285 RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicLinear(
286 scip, /*cons=*/&scip_cons,
287 /*name=*/name.c_str(), /*nvars=*/vars.size(), /*vars=*/vars.data(),
288 /*vals=*/vals.data(), /*lhs=*/0.0, /*rhs=*/0.0));
289 // Note that the constraints are, by design, not added into the model using
290 // SCIPaddCons.
291 cons.push_back(scip_cons);
292 return absl::OkStatus();
293 };
294
295 // Create an intermediary constraint such that y = -x
296 vars = {scip_resultant_var, scip_var};
297 vals = {1, 1};
298 RETURN_IF_ERROR(add_abs_constraint("_neg"));
299
300 // Create an intermediary constraint such that y = x
301 vals = {1, -1};
302 RETURN_IF_ERROR(add_abs_constraint("_pos"));
303
304 // Activate at least one of the two above constraints.
305 const std::string name =
306 gen_cst.has_name() ? absl::StrCat(gen_cst.name(), "_disj") : "";
307 RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicDisjunction(
308 scip, /*cons=*/scip_cst, /*name=*/name.c_str(),
309 /*nconss=*/cons.size(), /*conss=*/cons.data(), /*relaxcons=*/nullptr));
310 RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
311
312 return absl::OkStatus();
313}
314
315absl::Status AddAndConstraint(const MPGeneralConstraintProto& gen_cst,
316 const std::vector<SCIP_VAR*>& scip_variables,
317 SCIP* scip, SCIP_CONS** scip_cst,
318 std::vector<SCIP_VAR*>* tmp_variables) {
319 CHECK(scip != nullptr);
320 CHECK(scip_cst != nullptr);
321 CHECK(tmp_variables != nullptr);
322 CHECK(gen_cst.has_and_constraint());
323 const auto& andcst = gen_cst.and_constraint();
324
325 tmp_variables->resize(andcst.var_index_size(), nullptr);
326 for (int i = 0; i < andcst.var_index_size(); ++i) {
327 (*tmp_variables)[i] = scip_variables[andcst.var_index(i)];
328 }
329 RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicAnd(
330 scip, /*cons=*/scip_cst,
331 /*name=*/gen_cst.name().c_str(),
332 /*resvar=*/scip_variables[andcst.resultant_var_index()],
333 /*nvars=*/andcst.var_index_size(),
334 /*vars=*/tmp_variables->data()));
335 RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
336 return absl::OkStatus();
337}
338
339absl::Status AddOrConstraint(const MPGeneralConstraintProto& gen_cst,
340 const std::vector<SCIP_VAR*>& scip_variables,
341 SCIP* scip, SCIP_CONS** scip_cst,
342 std::vector<SCIP_VAR*>* tmp_variables) {
343 CHECK(scip != nullptr);
344 CHECK(scip_cst != nullptr);
345 CHECK(tmp_variables != nullptr);
346 CHECK(gen_cst.has_or_constraint());
347 const auto& orcst = gen_cst.or_constraint();
348
349 tmp_variables->resize(orcst.var_index_size(), nullptr);
350 for (int i = 0; i < orcst.var_index_size(); ++i) {
351 (*tmp_variables)[i] = scip_variables[orcst.var_index(i)];
352 }
353 RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicOr(
354 scip, /*cons=*/scip_cst,
355 /*name=*/gen_cst.name().c_str(),
356 /*resvar=*/scip_variables[orcst.resultant_var_index()],
357 /*nvars=*/orcst.var_index_size(),
358 /*vars=*/tmp_variables->data()));
359 RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
360 return absl::OkStatus();
361}
362
363// Models the constraint y = min(x1, x2, ... xn, c) with c being a constant with
364// - n + 1 constraints to ensure y <= min(x1, x2, ... xn, c)
365// - one disjunction constraint among all of the possible y = x1, y = x2, ...
366// y = xn, y = c constraints
367// Does the equivalent thing for max (with y >= max(...) instead).
368absl::Status AddMinMaxConstraint(const MPGeneralConstraintProto& gen_cst,
369 const std::vector<SCIP_VAR*>& scip_variables,
370 SCIP* scip, SCIP_CONS** scip_cst,
371 std::vector<SCIP_CONS*>* scip_constraints,
372 std::vector<SCIP_VAR*>* tmp_variables) {
373 CHECK(scip != nullptr);
374 CHECK(scip_cst != nullptr);
375 CHECK(tmp_variables != nullptr);
376 CHECK(gen_cst.has_min_constraint() || gen_cst.has_max_constraint());
377 const auto& minmax = gen_cst.has_min_constraint() ? gen_cst.min_constraint()
378 : gen_cst.max_constraint();
379 const std::set<int> unique_var_indices(minmax.var_index().begin(),
380 minmax.var_index().end());
381 SCIP_VAR* scip_resultant_var = scip_variables[minmax.resultant_var_index()];
382
383 std::vector<SCIP_VAR*> vars;
384 std::vector<double> vals;
385 std::vector<SCIP_CONS*> cons;
386 auto add_lin_constraint = [&](const std::string& name_prefix,
387 double lower_bound = 0.0,
388 double upper_bound = 0.0) -> absl::Status {
389 SCIP_CONS* scip_cons = nullptr;
390 CHECK(vars.size() == vals.size());
391 const std::string name =
392 gen_cst.has_name() ? absl::StrCat(gen_cst.name(), name_prefix) : "";
393 RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicLinear(
394 scip, /*cons=*/&scip_cons,
395 /*name=*/name.c_str(), /*nvars=*/vars.size(), /*vars=*/vars.data(),
396 /*vals=*/vals.data(), /*lhs=*/lower_bound, /*rhs=*/upper_bound));
397 // Note that the constraints are, by design, not added into the model using
398 // SCIPaddCons.
399 cons.push_back(scip_cons);
400 return absl::OkStatus();
401 };
402
403 // Create intermediary constraints such that y = xi
404 for (const int var_index : unique_var_indices) {
405 vars = {scip_resultant_var, scip_variables[var_index]};
406 vals = {1, -1};
407 RETURN_IF_ERROR(add_lin_constraint(absl::StrCat("_", var_index)));
408 }
409
410 // Create an intermediary constraint such that y = c
411 if (minmax.has_constant()) {
412 vars = {scip_resultant_var};
413 vals = {1};
415 add_lin_constraint("_constant", minmax.constant(), minmax.constant()));
416 }
417
418 // Activate at least one of the above constraints.
419 const std::string name =
420 gen_cst.has_name() ? absl::StrCat(gen_cst.name(), "_disj") : "";
421 RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicDisjunction(
422 scip, /*cons=*/scip_cst, /*name=*/name.c_str(),
423 /*nconss=*/cons.size(), /*conss=*/cons.data(), /*relaxcons=*/nullptr));
424 RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
425
426 // Add all of the inequality constraints.
427 constexpr double kInfinity = std::numeric_limits<double>::infinity();
428 cons.clear();
429 for (const int var_index : unique_var_indices) {
430 vars = {scip_resultant_var, scip_variables[var_index]};
431 vals = {1, -1};
432 if (gen_cst.has_min_constraint()) {
433 RETURN_IF_ERROR(add_lin_constraint(absl::StrCat("_ineq_", var_index),
434 -kInfinity, 0.0));
435 } else {
436 RETURN_IF_ERROR(add_lin_constraint(absl::StrCat("_ineq_", var_index), 0.0,
437 kInfinity));
438 }
439 }
440 if (minmax.has_constant()) {
441 vars = {scip_resultant_var};
442 vals = {1};
443 if (gen_cst.has_min_constraint()) {
444 RETURN_IF_ERROR(add_lin_constraint(absl::StrCat("_ineq_constant"),
445 -kInfinity, minmax.constant()));
446 } else {
447 RETURN_IF_ERROR(add_lin_constraint(absl::StrCat("_ineq_constant"),
448 minmax.constant(), kInfinity));
449 }
450 }
451 for (SCIP_CONS* scip_cons : cons) {
452 scip_constraints->push_back(scip_cons);
453 RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, scip_cons));
454 }
455 return absl::OkStatus();
456}
457
458absl::Status AddQuadraticObjective(const MPQuadraticObjective& quadobj,
459 SCIP* scip,
460 std::vector<SCIP_VAR*>* scip_variables,
461 std::vector<SCIP_CONS*>* scip_constraints) {
462 CHECK(scip != nullptr);
463 CHECK(scip_variables != nullptr);
464 CHECK(scip_constraints != nullptr);
465
466 constexpr double kInfinity = std::numeric_limits<double>::infinity();
467
468 const int size = quadobj.coefficient_size();
469 if (size == 0) return absl::OkStatus();
470
471 // SCIP supports quadratic objectives by adding a quadratic constraint. We
472 // need to create an extra variable to hold this quadratic objective.
473 scip_variables->push_back(nullptr);
474 RETURN_IF_SCIP_ERROR(SCIPcreateVarBasic(scip, /*var=*/&scip_variables->back(),
475 /*name=*/"quadobj",
476 /*lb=*/-kInfinity, /*ub=*/kInfinity,
477 /*obj=*/1,
478 /*vartype=*/SCIP_VARTYPE_CONTINUOUS));
479 RETURN_IF_SCIP_ERROR(SCIPaddVar(scip, scip_variables->back()));
480
481 scip_constraints->push_back(nullptr);
482 SCIP_VAR* linvars[1] = {scip_variables->back()};
483 double lincoefs[1] = {-1};
484 std::vector<SCIP_VAR*> quadvars1(size, nullptr);
485 std::vector<SCIP_VAR*> quadvars2(size, nullptr);
486 std::vector<double> quadcoefs(size, 0);
487 for (int i = 0; i < size; ++i) {
488 quadvars1[i] = scip_variables->at(quadobj.qvar1_index(i));
489 quadvars2[i] = scip_variables->at(quadobj.qvar2_index(i));
490 quadcoefs[i] = quadobj.coefficient(i);
491 }
492 RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicQuadratic(
493 scip, /*cons=*/&scip_constraints->back(), /*name=*/"quadobj",
494 /*nlinvars=*/1, /*linvars=*/linvars, /*lincoefs=*/lincoefs,
495 /*nquadterms=*/size, /*quadvars1=*/quadvars1.data(),
496 /*quadvars2=*/quadvars2.data(), /*quadcoefs=*/quadcoefs.data(),
497 /*lhs=*/0, /*rhs=*/0));
498 RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, scip_constraints->back()));
499
500 return absl::OkStatus();
501}
502
503absl::Status AddSolutionHint(const MPModelProto& model, SCIP* scip,
504 const std::vector<SCIP_VAR*>& scip_variables) {
505 CHECK(scip != nullptr);
506 if (!model.has_solution_hint()) return absl::OkStatus();
507
508 const PartialVariableAssignment& solution_hint = model.solution_hint();
509 SCIP_SOL* solution;
510 bool is_solution_partial =
511 solution_hint.var_index_size() != model.variable_size();
512 if (is_solution_partial) {
514 SCIPcreatePartialSol(scip, /*sol=*/&solution, /*heur=*/nullptr));
515 } else {
517 SCIPcreateSol(scip, /*sol=*/&solution, /*heur=*/nullptr));
518 }
519
520 for (int i = 0; i < solution_hint.var_index_size(); ++i) {
521 RETURN_IF_SCIP_ERROR(SCIPsetSolVal(
522 scip, solution, scip_variables[solution_hint.var_index(i)],
523 solution_hint.var_value(i)));
524 }
525
526 SCIP_Bool is_stored;
527 RETURN_IF_SCIP_ERROR(SCIPaddSolFree(scip, &solution, &is_stored));
528
529 return absl::OkStatus();
530}
531
532} // namespace
533
534// Returns "" iff the model seems valid for SCIP, else returns a human-readable
535// error message. Assumes that FindErrorInMPModelProto(model) found no error.
536std::string FindErrorInMPModelForScip(const MPModelProto& model, SCIP* scip) {
537 CHECK(scip != nullptr);
538 const double infinity = SCIPinfinity(scip);
539
540 for (int v = 0; v < model.variable_size(); ++v) {
541 const MPVariableProto& variable = model.variable(v);
542 if (variable.lower_bound() >= infinity) {
543 return absl::StrFormat(
544 "Variable %i's lower bound is considered +infinity", v);
545 }
546 if (variable.upper_bound() <= -infinity) {
547 return absl::StrFormat(
548 "Variable %i's upper bound is considered -infinity", v);
549 }
550 const double coeff = variable.objective_coefficient();
551 if (coeff >= infinity || coeff <= -infinity) {
552 return absl::StrFormat(
553 "Variable %i's objective coefficient is considered infinite", v);
554 }
555 }
556
557 for (int c = 0; c < model.constraint_size(); ++c) {
558 const MPConstraintProto& cst = model.constraint(c);
559 if (cst.lower_bound() >= infinity) {
560 return absl::StrFormat(
561 "Constraint %d's lower_bound is considered +infinity", c);
562 }
563 if (cst.upper_bound() <= -infinity) {
564 return absl::StrFormat(
565 "Constraint %d's upper_bound is considered -infinity", c);
566 }
567 for (int i = 0; i < cst.coefficient_size(); ++i) {
568 if (std::abs(cst.coefficient(i)) >= infinity) {
569 return absl::StrFormat(
570 "Constraint %d's coefficient #%d is considered infinite", c, i);
571 }
572 }
573 }
574
575 for (int c = 0; c < model.general_constraint_size(); ++c) {
576 const MPGeneralConstraintProto& cst = model.general_constraint(c);
577 switch (cst.general_constraint_case()) {
578 case MPGeneralConstraintProto::kQuadraticConstraint:
579 if (cst.quadratic_constraint().lower_bound() >= infinity) {
580 return absl::StrFormat(
581 "Quadratic constraint %d's lower_bound is considered +infinity",
582 c);
583 }
584 if (cst.quadratic_constraint().upper_bound() <= -infinity) {
585 return absl::StrFormat(
586 "Quadratic constraint %d's upper_bound is considered -infinity",
587 c);
588 }
589 for (int i = 0; i < cst.quadratic_constraint().coefficient_size();
590 ++i) {
591 const double coefficient = cst.quadratic_constraint().coefficient(i);
592 if (coefficient >= infinity || coefficient <= -infinity) {
593 return absl::StrFormat(
594 "Quadratic constraint %d's linear coefficient #%d considered "
595 "infinite",
596 c, i);
597 }
598 }
599 for (int i = 0; i < cst.quadratic_constraint().qcoefficient_size();
600 ++i) {
601 const double qcoefficient =
602 cst.quadratic_constraint().qcoefficient(i);
603 if (qcoefficient >= infinity || qcoefficient <= -infinity) {
604 return absl::StrFormat(
605 "Quadratic constraint %d's quadratic coefficient #%d "
606 "considered infinite",
607 c, i);
608 }
609 }
610 break;
611 case MPGeneralConstraintProto::kMinConstraint:
612 if (cst.min_constraint().constant() >= infinity ||
613 cst.min_constraint().constant() <= -infinity) {
614 return absl::StrFormat(
615 "Min constraint %d's coefficient constant considered infinite",
616 c);
617 }
618 break;
619 case MPGeneralConstraintProto::kMaxConstraint:
620 if (cst.max_constraint().constant() >= infinity ||
621 cst.max_constraint().constant() <= -infinity) {
622 return absl::StrFormat(
623 "Max constraint %d's coefficient constant considered infinite",
624 c);
625 }
626 break;
627 default:
628 continue;
629 }
630 }
631
632 const MPQuadraticObjective& quad_obj = model.quadratic_objective();
633 for (int i = 0; i < quad_obj.coefficient_size(); ++i) {
634 if (std::abs(quad_obj.coefficient(i)) >= infinity) {
635 return absl::StrFormat(
636 "Quadratic objective term #%d's coefficient is considered infinite",
637 i);
638 }
639 }
640
641 if (model.has_solution_hint()) {
642 for (int i = 0; i < model.solution_hint().var_value_size(); ++i) {
643 const double value = model.solution_hint().var_value(i);
644 if (value >= infinity || value <= -infinity) {
645 return absl::StrFormat(
646 "Variable %i's solution hint is considered infinite",
647 model.solution_hint().var_index(i));
648 }
649 }
650 }
651
652 if (model.objective_offset() >= infinity ||
653 model.objective_offset() <= -infinity) {
654 return "Model's objective offset is considered infinite.";
655 }
656
657 return "";
658}
659
660absl::StatusOr<MPSolutionResponse> ScipSolveProto(
661 const MPModelRequest& request) {
662 MPSolutionResponse response;
663 const absl::optional<LazyMutableCopy<MPModelProto>> optional_model =
665 if (!optional_model) return response;
666 const MPModelProto& model = optional_model->get();
667 SCIP* scip = nullptr;
668 std::vector<SCIP_VAR*> scip_variables(model.variable_size(), nullptr);
669 std::vector<SCIP_CONS*> scip_constraints(
670 model.constraint_size() + model.general_constraint_size(), nullptr);
671
672 auto delete_scip_objects = [&]() -> absl::Status {
673 // Release all created pointers.
674 if (scip == nullptr) return absl::OkStatus();
675 for (SCIP_VAR* variable : scip_variables) {
676 if (variable != nullptr) {
677 RETURN_IF_SCIP_ERROR(SCIPreleaseVar(scip, &variable));
678 }
679 }
680 for (SCIP_CONS* constraint : scip_constraints) {
681 if (constraint != nullptr) {
682 RETURN_IF_SCIP_ERROR(SCIPreleaseCons(scip, &constraint));
683 }
684 }
685 RETURN_IF_SCIP_ERROR(SCIPfree(&scip));
686 return absl::OkStatus();
687 };
688
689 auto scip_deleter = absl::MakeCleanup([delete_scip_objects]() {
690 const absl::Status deleter_status = delete_scip_objects();
691 LOG_IF(DFATAL, !deleter_status.ok()) << deleter_status;
692 });
693
694 RETURN_IF_SCIP_ERROR(SCIPcreate(&scip));
695 RETURN_IF_SCIP_ERROR(SCIPincludeDefaultPlugins(scip));
696 const std::string scip_model_invalid_error =
698 if (!scip_model_invalid_error.empty()) {
699 response.set_status(MPSOLVER_MODEL_INVALID);
700 response.set_status_str(scip_model_invalid_error);
701 return response;
702 }
703
704 const auto parameters_status = LegacyScipSetSolverSpecificParameters(
705 request.solver_specific_parameters(), scip);
706 if (!parameters_status.ok()) {
707 response.set_status(MPSOLVER_MODEL_INVALID_SOLVER_PARAMETERS);
708 response.set_status_str(
709 std::string(parameters_status.message())); // NOLINT
710 return response;
711 }
712 // Default clock type. We use wall clock time because getting CPU user seconds
713 // involves calling times() which is very expensive.
714 // NOTE(user): Also, time limit based on CPU user seconds is *NOT* thread
715 // safe. We observed that different instances of SCIP running concurrently
716 // in different threads consume the time limit *together*. E.g., 2 threads
717 // running SCIP with time limit 10s each will both terminate after ~5s.
719 SCIPsetIntParam(scip, "timing/clocktype", SCIP_CLOCKTYPE_WALL));
720 if (request.solver_time_limit_seconds() > 0 &&
721 request.solver_time_limit_seconds() < 1e20) {
722 RETURN_IF_SCIP_ERROR(SCIPsetRealParam(scip, "limits/time",
723 request.solver_time_limit_seconds()));
724 }
725 SCIPsetMessagehdlrQuiet(scip, !request.enable_internal_solver_output());
726
727 RETURN_IF_SCIP_ERROR(SCIPcreateProbBasic(scip, model.name().c_str()));
728 if (model.maximize()) {
729 RETURN_IF_SCIP_ERROR(SCIPsetObjsense(scip, SCIP_OBJSENSE_MAXIMIZE));
730 }
731
732 for (int v = 0; v < model.variable_size(); ++v) {
733 const MPVariableProto& variable = model.variable(v);
734 RETURN_IF_SCIP_ERROR(SCIPcreateVarBasic(
735 scip, /*var=*/&scip_variables[v], /*name=*/variable.name().c_str(),
736 /*lb=*/variable.lower_bound(), /*ub=*/variable.upper_bound(),
737 /*obj=*/variable.objective_coefficient(),
738 /*vartype=*/variable.is_integer() ? SCIP_VARTYPE_INTEGER
739 : SCIP_VARTYPE_CONTINUOUS));
740 RETURN_IF_SCIP_ERROR(SCIPaddVar(scip, scip_variables[v]));
741 }
742
743 {
744 std::vector<SCIP_VAR*> ct_variables;
745 std::vector<double> ct_coefficients;
746 for (int c = 0; c < model.constraint_size(); ++c) {
747 const MPConstraintProto& constraint = model.constraint(c);
748 const int size = constraint.var_index_size();
749 ct_variables.resize(size, nullptr);
750 ct_coefficients.resize(size, 0);
751 for (int i = 0; i < size; ++i) {
752 ct_variables[i] = scip_variables[constraint.var_index(i)];
753 ct_coefficients[i] = constraint.coefficient(i);
754 }
755 RETURN_IF_SCIP_ERROR(SCIPcreateConsLinear(
756 scip, /*cons=*/&scip_constraints[c],
757 /*name=*/constraint.name().c_str(),
758 /*nvars=*/constraint.var_index_size(), /*vars=*/ct_variables.data(),
759 /*vals=*/ct_coefficients.data(),
760 /*lhs=*/constraint.lower_bound(), /*rhs=*/constraint.upper_bound(),
761 /*initial=*/!constraint.is_lazy(),
762 /*separate=*/true,
763 /*enforce=*/true,
764 /*check=*/true,
765 /*propagate=*/true,
766 /*local=*/false,
767 /*modifiable=*/false,
768 /*dynamic=*/false,
769 /*removable=*/constraint.is_lazy(),
770 /*stickingatnode=*/false));
771 RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, scip_constraints[c]));
772 }
773
774 // These extra arrays are used by quadratic constraints.
775 std::vector<SCIP_VAR*> ct_qvariables1;
776 std::vector<SCIP_VAR*> ct_qvariables2;
777 std::vector<double> ct_qcoefficients;
778 const int lincst_size = model.constraint_size();
779 for (int c = 0; c < model.general_constraint_size(); ++c) {
780 const MPGeneralConstraintProto& gen_cst = model.general_constraint(c);
781 switch (gen_cst.general_constraint_case()) {
782 case MPGeneralConstraintProto::kIndicatorConstraint: {
783 RETURN_IF_ERROR(AddIndicatorConstraint(
784 gen_cst, scip, &scip_constraints[lincst_size + c],
785 &scip_variables, &scip_constraints, &ct_variables,
786 &ct_coefficients));
787 break;
788 }
789 case MPGeneralConstraintProto::kSosConstraint: {
790 RETURN_IF_ERROR(AddSosConstraint(gen_cst, scip_variables, scip,
791 &scip_constraints[lincst_size + c],
792 &ct_variables, &ct_coefficients));
793 break;
794 }
795 case MPGeneralConstraintProto::kQuadraticConstraint: {
796 RETURN_IF_ERROR(AddQuadraticConstraint(
797 gen_cst, scip_variables, scip, &scip_constraints[lincst_size + c],
798 &ct_variables, &ct_coefficients, &ct_qvariables1, &ct_qvariables2,
799 &ct_qcoefficients));
800 break;
801 }
802 case MPGeneralConstraintProto::kAbsConstraint: {
803 RETURN_IF_ERROR(AddAbsConstraint(gen_cst, scip_variables, scip,
804 &scip_constraints[lincst_size + c]));
805 break;
806 }
807 case MPGeneralConstraintProto::kAndConstraint: {
808 RETURN_IF_ERROR(AddAndConstraint(gen_cst, scip_variables, scip,
809 &scip_constraints[lincst_size + c],
810 &ct_variables));
811 break;
812 }
813 case MPGeneralConstraintProto::kOrConstraint: {
814 RETURN_IF_ERROR(AddOrConstraint(gen_cst, scip_variables, scip,
815 &scip_constraints[lincst_size + c],
816 &ct_variables));
817 break;
818 }
819 case MPGeneralConstraintProto::kMinConstraint:
820 case MPGeneralConstraintProto::kMaxConstraint: {
821 RETURN_IF_ERROR(AddMinMaxConstraint(
822 gen_cst, scip_variables, scip, &scip_constraints[lincst_size + c],
823 &scip_constraints, &ct_variables));
824 break;
825 }
826 default:
827 return absl::UnimplementedError(
828 absl::StrFormat("General constraints of type %i not supported.",
829 gen_cst.general_constraint_case()));
830 }
831 }
832 }
833
834 if (model.has_quadratic_objective()) {
835 RETURN_IF_ERROR(AddQuadraticObjective(model.quadratic_objective(), scip,
836 &scip_variables, &scip_constraints));
837 }
838 RETURN_IF_SCIP_ERROR(SCIPaddOrigObjoffset(scip, model.objective_offset()));
839 RETURN_IF_ERROR(AddSolutionHint(model, scip, scip_variables));
840
841 if (!absl::GetFlag(FLAGS_scip_proto_solver_output_cip_file).empty()) {
842 SCIPwriteOrigProblem(
843 scip, absl::GetFlag(FLAGS_scip_proto_solver_output_cip_file).c_str(),
844 nullptr, true);
845 }
846 const absl::Time time_before = absl::Now();
847 UserTimer user_timer;
848 user_timer.Start();
849
850 RETURN_IF_SCIP_ERROR(SCIPsolve(scip));
851
852 const absl::Duration solving_duration = absl::Now() - time_before;
853 user_timer.Stop();
854 VLOG(1) << "Finished solving in ScipSolveProto(), walltime = "
855 << solving_duration << ", usertime = " << user_timer.GetDuration();
856
857 response.mutable_solve_info()->set_solve_wall_time_seconds(
858 absl::ToDoubleSeconds(solving_duration));
859 response.mutable_solve_info()->set_solve_user_time_seconds(
860 absl::ToDoubleSeconds(user_timer.GetDuration()));
861
862 const int solution_count =
863 std::min(SCIPgetNSols(scip),
864 std::min(request.populate_additional_solutions_up_to(),
866 1);
867 if (solution_count > 0) {
868 // can't make 'scip_solution' const, as SCIPxxx does not offer const
869 // parameter functions.
870 auto scip_solution_to_repeated_field = [&](SCIP_SOL* scip_solution) {
871 google::protobuf::RepeatedField<double> variable_value;
872 variable_value.Reserve(model.variable_size());
873 for (int v = 0; v < model.variable_size(); ++v) {
874 double value = SCIPgetSolVal(scip, scip_solution, scip_variables[v]);
875 if (model.variable(v).is_integer()) {
876 value = std::round(value);
877 }
878 variable_value.AddAlreadyReserved(value);
879 }
880 return variable_value;
881 };
882
883 // NOTE(user): As of SCIP 8.0.0, getting the pointer to all
884 // solutions is as fast as getting the pointer to the best solution.
885 SCIP_SOL** const scip_solutions = SCIPgetSols(scip);
886 response.set_objective_value(SCIPgetSolOrigObj(scip, scip_solutions[0]));
887 response.set_best_objective_bound(SCIPgetDualbound(scip));
888 *response.mutable_variable_value() =
889 scip_solution_to_repeated_field(scip_solutions[0]);
890 for (int i = 1; i < solution_count; ++i) {
891 MPSolution* solution = response.add_additional_solutions();
892 solution->set_objective_value(SCIPgetSolOrigObj(scip, scip_solutions[i]));
893 *solution->mutable_variable_value() =
894 scip_solution_to_repeated_field(scip_solutions[i]);
895 }
896 }
897
898 const SCIP_STATUS scip_status = SCIPgetStatus(scip);
899 switch (scip_status) {
900 case SCIP_STATUS_OPTIMAL:
901 response.set_status(MPSOLVER_OPTIMAL);
902 break;
903 case SCIP_STATUS_GAPLIMIT:
904 // To be consistent with the other solvers.
905 response.set_status(MPSOLVER_OPTIMAL);
906 break;
907 case SCIP_STATUS_INFORUNBD:
908 // NOTE(user): After looking at the SCIP code on 2019-06-14, it seems
909 // that this will mostly happen for INFEASIBLE problems in practice.
910 // Since most (all?) users shouldn't have their application behave very
911 // differently upon INFEASIBLE or UNBOUNDED, the potential error that we
912 // are making here seems reasonable (and not worth a LOG, unless in
913 // debug mode).
914 DLOG(INFO) << "SCIP solve returned SCIP_STATUS_INFORUNBD, which we treat "
915 "as INFEASIBLE even though it may mean UNBOUNDED.";
916 response.set_status_str(
917 "The model may actually be unbounded: SCIP returned "
918 "SCIP_STATUS_INFORUNBD");
919 ABSL_FALLTHROUGH_INTENDED;
920 case SCIP_STATUS_INFEASIBLE:
921 response.set_status(MPSOLVER_INFEASIBLE);
922 break;
923 case SCIP_STATUS_UNBOUNDED:
924 response.set_status(MPSOLVER_UNBOUNDED);
925 break;
926 default:
927 if (solution_count > 0) {
928 response.set_status(MPSOLVER_FEASIBLE);
929 } else {
930 response.set_status(MPSOLVER_NOT_SOLVED);
931 response.set_status_str(absl::StrFormat("SCIP status code %d",
932 static_cast<int>(scip_status)));
933 }
934 break;
935 }
936
937 VLOG(1) << "ScipSolveProto() status="
938 << MPSolverResponseStatus_Name(response.status()) << ".";
939 return response;
940}
941
942} // namespace operations_research
943
944#endif // #if defined(USE_SCIP)
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define LOG_IF(severity, condition)
Definition: base/logging.h:479
#define DLOG(severity)
Definition: base/logging.h:881
#define CHECK(condition)
Definition: base/logging.h:495
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:703
#define VLOG(verboselevel)
Definition: base/logging.h:984
#define RETURN_IF_ERROR(expr)
void Start()
Definition: timer.h:31
void Stop()
Definition: timer.h:39
absl::Duration GetDuration() const
Definition: timer.h:48
SharedResponseManager * response
const std::string name
int64_t value
double upper_bound
double lower_bound
GRBmodel * model
const int INFO
Definition: log_severity.h:31
absl::Cleanup< absl::decay_t< Callback > > MakeCleanup(Callback &&callback)
Definition: cleanup.h:125
const double kInfinity
Definition: lp_types.h:84
Collection of objects used to extend the Constraint Solver library.
std::string FindErrorInMPModelForScip(const MPModelProto &model, SCIP *scip)
absl::StatusOr< MPSolutionResponse > ScipSolveProto(const MPModelRequest &request)
absl::Status LegacyScipSetSolverSpecificParameters(const std::string &parameters, SCIP *scip)
absl::optional< LazyMutableCopy< MPModelProto > > ExtractValidMPModelOrPopulateResponseStatus(const MPModelRequest &request, MPSolutionResponse *response)
If the model is valid and non-empty, returns it (possibly after extracting the model_delta).
int64_t coefficient
#define RETURN_IF_SCIP_ERROR(x)
ABSL_FLAG(std::string, scip_proto_solver_output_cip_file, "", "If given, saves the generated CIP file here. Useful for " "reporting bugs to SCIP.")
const double coeff