OR-Tools  9.6
scip_proto_solver.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 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 <algorithm>
19 #include <cmath>
20 #include <limits>
21 #include <memory>
22 #include <numeric>
23 #include <set>
24 #include <string>
25 #include <vector>
26 
27 #include "absl/status/status.h"
28 #include "absl/status/statusor.h"
29 #include "absl/strings/ascii.h"
30 #include "absl/strings/numbers.h"
31 #include "absl/strings/str_cat.h"
32 #include "absl/strings/str_format.h"
33 #include "absl/strings/str_split.h"
34 #include "absl/strings/string_view.h"
35 #include "absl/time/time.h"
36 #include "ortools/base/cleanup.h"
39 #include "ortools/base/timer.h"
41 #include "ortools/linear_solver/linear_solver.pb.h"
45 #include "scip/cons_disjunction.h"
46 #include "scip/cons_linear.h"
47 #include "scip/cons_quadratic.h"
48 #include "scip/pub_var.h"
49 #include "scip/scip.h"
50 #include "scip/scip_param.h"
51 #include "scip/scip_prob.h"
52 #include "scip/scip_var.h"
53 #include "scip/scipdefplugins.h"
54 #include "scip/set.h"
55 #include "scip/struct_paramset.h"
56 #include "scip/type_cons.h"
57 #include "scip/type_paramset.h"
58 #include "scip/type_var.h"
59 
60 ABSL_FLAG(std::string, scip_proto_solver_output_cip_file, "",
61  "If given, saves the generated CIP file here. Useful for "
62  "reporting bugs to SCIP.");
63 namespace operations_research {
64 namespace {
65 
66 // This function will create a new constraint if the indicator constraint has
67 // both a lower bound and an upper bound.
68 absl::Status AddIndicatorConstraint(const MPGeneralConstraintProto& gen_cst,
69  SCIP* scip, SCIP_CONS** scip_cst,
70  std::vector<SCIP_VAR*>* scip_variables,
71  std::vector<SCIP_CONS*>* scip_constraints,
72  std::vector<SCIP_VAR*>* tmp_variables,
73  std::vector<double>* tmp_coefficients) {
74  CHECK(scip != nullptr);
75  CHECK(scip_cst != nullptr);
76  CHECK(scip_variables != nullptr);
77  CHECK(scip_constraints != nullptr);
78  CHECK(tmp_variables != nullptr);
79  CHECK(tmp_coefficients != nullptr);
80  CHECK(gen_cst.has_indicator_constraint());
81  constexpr double kInfinity = std::numeric_limits<double>::infinity();
82 
83  const auto& ind = gen_cst.indicator_constraint();
84  if (!ind.has_constraint()) return absl::OkStatus();
85 
86  const MPConstraintProto& constraint = ind.constraint();
87  const int size = constraint.var_index_size();
88  tmp_variables->resize(size, nullptr);
89  tmp_coefficients->resize(size, 0);
90  for (int i = 0; i < size; ++i) {
91  (*tmp_variables)[i] = (*scip_variables)[constraint.var_index(i)];
92  (*tmp_coefficients)[i] = constraint.coefficient(i);
93  }
94 
95  SCIP_VAR* ind_var = (*scip_variables)[ind.var_index()];
96  if (ind.var_value() == 0) {
98  SCIPgetNegatedVar(scip, (*scip_variables)[ind.var_index()], &ind_var));
99  }
100 
101  if (ind.constraint().upper_bound() < kInfinity) {
102  RETURN_IF_SCIP_ERROR(SCIPcreateConsIndicator(
103  scip, scip_cst, gen_cst.name().c_str(), ind_var, size,
104  tmp_variables->data(), tmp_coefficients->data(),
105  ind.constraint().upper_bound(),
106  /*initial=*/!ind.constraint().is_lazy(),
107  /*separate=*/true,
108  /*enforce=*/true,
109  /*check=*/true,
110  /*propagate=*/true,
111  /*local=*/false,
112  /*dynamic=*/false,
113  /*removable=*/ind.constraint().is_lazy(),
114  /*stickingatnode=*/false));
115  RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
116  scip_constraints->push_back(nullptr);
117  scip_cst = &scip_constraints->back();
118  }
119  if (ind.constraint().lower_bound() > -kInfinity) {
120  for (int i = 0; i < size; ++i) {
121  (*tmp_coefficients)[i] *= -1;
122  }
123  RETURN_IF_SCIP_ERROR(SCIPcreateConsIndicator(
124  scip, scip_cst, gen_cst.name().c_str(), ind_var, size,
125  tmp_variables->data(), tmp_coefficients->data(),
126  -ind.constraint().lower_bound(),
127  /*initial=*/!ind.constraint().is_lazy(),
128  /*separate=*/true,
129  /*enforce=*/true,
130  /*check=*/true,
131  /*propagate=*/true,
132  /*local=*/false,
133  /*dynamic=*/false,
134  /*removable=*/ind.constraint().is_lazy(),
135  /*stickingatnode=*/false));
136  RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
137  }
138 
139  return absl::OkStatus();
140 }
141 
142 absl::Status AddSosConstraint(const MPGeneralConstraintProto& gen_cst,
143  const std::vector<SCIP_VAR*>& scip_variables,
144  SCIP* scip, SCIP_CONS** scip_cst,
145  std::vector<SCIP_VAR*>* tmp_variables,
146  std::vector<double>* tmp_weights) {
147  CHECK(scip != nullptr);
148  CHECK(scip_cst != nullptr);
149  CHECK(tmp_variables != nullptr);
150  CHECK(tmp_weights != nullptr);
151 
152  CHECK(gen_cst.has_sos_constraint());
153  const MPSosConstraint& sos_cst = gen_cst.sos_constraint();
154 
155  // SOS constraints of type N indicate at most N variables are non-zero.
156  // Constraints with N variables or less are valid, but useless. They also
157  // crash SCIP, so we skip them.
158  if (sos_cst.var_index_size() <= 1) return absl::OkStatus();
159  if (sos_cst.type() == MPSosConstraint::SOS2 &&
160  sos_cst.var_index_size() <= 2) {
161  return absl::OkStatus();
162  }
163 
164  tmp_variables->resize(sos_cst.var_index_size(), nullptr);
165  for (int v = 0; v < sos_cst.var_index_size(); ++v) {
166  (*tmp_variables)[v] = scip_variables[sos_cst.var_index(v)];
167  }
168  tmp_weights->resize(sos_cst.var_index_size(), 0);
169  if (sos_cst.weight_size() == sos_cst.var_index_size()) {
170  for (int w = 0; w < sos_cst.weight_size(); ++w) {
171  (*tmp_weights)[w] = sos_cst.weight(w);
172  }
173  } else {
174  // In theory, SCIP should accept empty weight arrays and use natural
175  // ordering, but in practice, this crashes their code.
176  std::iota(tmp_weights->begin(), tmp_weights->end(), 1);
177  }
178  switch (sos_cst.type()) {
179  case MPSosConstraint::SOS1_DEFAULT:
181  SCIPcreateConsBasicSOS1(scip,
182  /*cons=*/scip_cst,
183  /*name=*/gen_cst.name().c_str(),
184  /*nvars=*/sos_cst.var_index_size(),
185  /*vars=*/tmp_variables->data(),
186  /*weights=*/tmp_weights->data()));
187  break;
188  case MPSosConstraint::SOS2:
190  SCIPcreateConsBasicSOS2(scip,
191  /*cons=*/scip_cst,
192  /*name=*/gen_cst.name().c_str(),
193  /*nvars=*/sos_cst.var_index_size(),
194  /*vars=*/tmp_variables->data(),
195  /*weights=*/tmp_weights->data()));
196  break;
197  }
198  RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
199  return absl::OkStatus();
200 }
201 
202 absl::Status AddQuadraticConstraint(
203  const MPGeneralConstraintProto& gen_cst,
204  const std::vector<SCIP_VAR*>& scip_variables, SCIP* scip,
205  SCIP_CONS** scip_cst, std::vector<SCIP_VAR*>* tmp_variables,
206  std::vector<double>* tmp_coefficients,
207  std::vector<SCIP_VAR*>* tmp_qvariables1,
208  std::vector<SCIP_VAR*>* tmp_qvariables2,
209  std::vector<double>* tmp_qcoefficients) {
210  CHECK(scip != nullptr);
211  CHECK(scip_cst != nullptr);
212  CHECK(tmp_variables != nullptr);
213  CHECK(tmp_coefficients != nullptr);
214  CHECK(tmp_qvariables1 != nullptr);
215  CHECK(tmp_qvariables2 != nullptr);
216  CHECK(tmp_qcoefficients != nullptr);
217 
218  CHECK(gen_cst.has_quadratic_constraint());
219  const MPQuadraticConstraint& quad_cst = gen_cst.quadratic_constraint();
220 
221  // Process linear part of the constraint.
222  const int lsize = quad_cst.var_index_size();
223  CHECK_EQ(quad_cst.coefficient_size(), lsize);
224  tmp_variables->resize(lsize, nullptr);
225  tmp_coefficients->resize(lsize, 0.0);
226  for (int i = 0; i < lsize; ++i) {
227  (*tmp_variables)[i] = scip_variables[quad_cst.var_index(i)];
228  (*tmp_coefficients)[i] = quad_cst.coefficient(i);
229  }
230 
231  // Process quadratic part of the constraint.
232  const int qsize = quad_cst.qvar1_index_size();
233  CHECK_EQ(quad_cst.qvar2_index_size(), qsize);
234  CHECK_EQ(quad_cst.qcoefficient_size(), qsize);
235  tmp_qvariables1->resize(qsize, nullptr);
236  tmp_qvariables2->resize(qsize, nullptr);
237  tmp_qcoefficients->resize(qsize, 0.0);
238  for (int i = 0; i < qsize; ++i) {
239  (*tmp_qvariables1)[i] = scip_variables[quad_cst.qvar1_index(i)];
240  (*tmp_qvariables2)[i] = scip_variables[quad_cst.qvar2_index(i)];
241  (*tmp_qcoefficients)[i] = quad_cst.qcoefficient(i);
242  }
243 
245  SCIPcreateConsBasicQuadratic(scip,
246  /*cons=*/scip_cst,
247  /*name=*/gen_cst.name().c_str(),
248  /*nlinvars=*/lsize,
249  /*linvars=*/tmp_variables->data(),
250  /*lincoefs=*/tmp_coefficients->data(),
251  /*nquadterms=*/qsize,
252  /*quadvars1=*/tmp_qvariables1->data(),
253  /*quadvars2=*/tmp_qvariables2->data(),
254  /*quadcoefs=*/tmp_qcoefficients->data(),
255  /*lhs=*/quad_cst.lower_bound(),
256  /*rhs=*/quad_cst.upper_bound()));
257  RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
258  return absl::OkStatus();
259 }
260 
261 // Models the constraint y = |x| as y >= 0 plus one disjunction constraint:
262 // y = x OR y = -x
263 absl::Status AddAbsConstraint(const MPGeneralConstraintProto& gen_cst,
264  const std::vector<SCIP_VAR*>& scip_variables,
265  SCIP* scip, SCIP_CONS** scip_cst) {
266  CHECK(scip != nullptr);
267  CHECK(scip_cst != nullptr);
268  CHECK(gen_cst.has_abs_constraint());
269  const auto& abs = gen_cst.abs_constraint();
270  SCIP_VAR* scip_var = scip_variables[abs.var_index()];
271  SCIP_VAR* scip_resultant_var = scip_variables[abs.resultant_var_index()];
272 
273  // Set the resultant variable's lower bound to zero if it's negative.
274  if (SCIPvarGetLbLocal(scip_resultant_var) < 0.0) {
275  RETURN_IF_SCIP_ERROR(SCIPchgVarLb(scip, scip_resultant_var, 0.0));
276  }
277 
278  std::vector<SCIP_VAR*> vars;
279  std::vector<double> vals;
280  std::vector<SCIP_CONS*> cons;
281  auto add_abs_constraint = [&](absl::string_view name_prefix) -> absl::Status {
282  SCIP_CONS* scip_cons = nullptr;
283  CHECK(vars.size() == vals.size());
284  const std::string name =
285  gen_cst.has_name() ? absl::StrCat(gen_cst.name(), name_prefix) : "";
286  RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicLinear(
287  scip, /*cons=*/&scip_cons,
288  /*name=*/name.c_str(), /*nvars=*/vars.size(), /*vars=*/vars.data(),
289  /*vals=*/vals.data(), /*lhs=*/0.0, /*rhs=*/0.0));
290  // Note that the constraints are, by design, not added into the model using
291  // SCIPaddCons.
292  cons.push_back(scip_cons);
293  return absl::OkStatus();
294  };
295 
296  // Create an intermediary constraint such that y = -x
297  vars = {scip_resultant_var, scip_var};
298  vals = {1, 1};
299  RETURN_IF_ERROR(add_abs_constraint("_neg"));
300 
301  // Create an intermediary constraint such that y = x
302  vals = {1, -1};
303  RETURN_IF_ERROR(add_abs_constraint("_pos"));
304 
305  // Activate at least one of the two above constraints.
306  const std::string name =
307  gen_cst.has_name() ? absl::StrCat(gen_cst.name(), "_disj") : "";
308  RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicDisjunction(
309  scip, /*cons=*/scip_cst, /*name=*/name.c_str(),
310  /*nconss=*/cons.size(), /*conss=*/cons.data(), /*relaxcons=*/nullptr));
311  RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
312 
313  return absl::OkStatus();
314 }
315 
316 absl::Status AddAndConstraint(const MPGeneralConstraintProto& gen_cst,
317  const std::vector<SCIP_VAR*>& scip_variables,
318  SCIP* scip, SCIP_CONS** scip_cst,
319  std::vector<SCIP_VAR*>* tmp_variables) {
320  CHECK(scip != nullptr);
321  CHECK(scip_cst != nullptr);
322  CHECK(tmp_variables != nullptr);
323  CHECK(gen_cst.has_and_constraint());
324  const auto& andcst = gen_cst.and_constraint();
325 
326  tmp_variables->resize(andcst.var_index_size(), nullptr);
327  for (int i = 0; i < andcst.var_index_size(); ++i) {
328  (*tmp_variables)[i] = scip_variables[andcst.var_index(i)];
329  }
330  RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicAnd(
331  scip, /*cons=*/scip_cst,
332  /*name=*/gen_cst.name().c_str(),
333  /*resvar=*/scip_variables[andcst.resultant_var_index()],
334  /*nvars=*/andcst.var_index_size(),
335  /*vars=*/tmp_variables->data()));
336  RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
337  return absl::OkStatus();
338 }
339 
340 absl::Status AddOrConstraint(const MPGeneralConstraintProto& gen_cst,
341  const std::vector<SCIP_VAR*>& scip_variables,
342  SCIP* scip, SCIP_CONS** scip_cst,
343  std::vector<SCIP_VAR*>* tmp_variables) {
344  CHECK(scip != nullptr);
345  CHECK(scip_cst != nullptr);
346  CHECK(tmp_variables != nullptr);
347  CHECK(gen_cst.has_or_constraint());
348  const auto& orcst = gen_cst.or_constraint();
349 
350  tmp_variables->resize(orcst.var_index_size(), nullptr);
351  for (int i = 0; i < orcst.var_index_size(); ++i) {
352  (*tmp_variables)[i] = scip_variables[orcst.var_index(i)];
353  }
354  RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicOr(
355  scip, /*cons=*/scip_cst,
356  /*name=*/gen_cst.name().c_str(),
357  /*resvar=*/scip_variables[orcst.resultant_var_index()],
358  /*nvars=*/orcst.var_index_size(),
359  /*vars=*/tmp_variables->data()));
360  RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
361  return absl::OkStatus();
362 }
363 
364 // Models the constraint y = min(x1, x2, ... xn, c) with c being a constant with
365 // - n + 1 constraints to ensure y <= min(x1, x2, ... xn, c)
366 // - one disjunction constraint among all of the possible y = x1, y = x2, ...
367 // y = xn, y = c constraints
368 // Does the equivalent thing for max (with y >= max(...) instead).
369 absl::Status AddMinMaxConstraint(const MPGeneralConstraintProto& gen_cst,
370  const std::vector<SCIP_VAR*>& scip_variables,
371  SCIP* scip, SCIP_CONS** scip_cst,
372  std::vector<SCIP_CONS*>* scip_constraints,
373  std::vector<SCIP_VAR*>* tmp_variables) {
374  CHECK(scip != nullptr);
375  CHECK(scip_cst != nullptr);
376  CHECK(tmp_variables != nullptr);
377  CHECK(gen_cst.has_min_constraint() || gen_cst.has_max_constraint());
378  const auto& minmax = gen_cst.has_min_constraint() ? gen_cst.min_constraint()
379  : gen_cst.max_constraint();
380  const std::set<int> unique_var_indices(minmax.var_index().begin(),
381  minmax.var_index().end());
382  SCIP_VAR* scip_resultant_var = scip_variables[minmax.resultant_var_index()];
383 
384  std::vector<SCIP_VAR*> vars;
385  std::vector<double> vals;
386  std::vector<SCIP_CONS*> cons;
387  auto add_lin_constraint = [&](absl::string_view name_prefix,
388  double lower_bound = 0.0,
389  double upper_bound = 0.0) -> absl::Status {
390  SCIP_CONS* scip_cons = nullptr;
391  CHECK(vars.size() == vals.size());
392  const std::string name =
393  gen_cst.has_name() ? absl::StrCat(gen_cst.name(), name_prefix) : "";
394  RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicLinear(
395  scip, /*cons=*/&scip_cons,
396  /*name=*/name.c_str(), /*nvars=*/vars.size(), /*vars=*/vars.data(),
397  /*vals=*/vals.data(), /*lhs=*/lower_bound, /*rhs=*/upper_bound));
398  // Note that the constraints are, by design, not added into the model using
399  // SCIPaddCons.
400  cons.push_back(scip_cons);
401  return absl::OkStatus();
402  };
403 
404  // Create intermediary constraints such that y = xi
405  for (const int var_index : unique_var_indices) {
406  vars = {scip_resultant_var, scip_variables[var_index]};
407  vals = {1, -1};
408  RETURN_IF_ERROR(add_lin_constraint(absl::StrCat("_", var_index)));
409  }
410 
411  // Create an intermediary constraint such that y = c
412  if (minmax.has_constant()) {
413  vars = {scip_resultant_var};
414  vals = {1};
416  add_lin_constraint("_constant", minmax.constant(), minmax.constant()));
417  }
418 
419  // Activate at least one of the above constraints.
420  const std::string name =
421  gen_cst.has_name() ? absl::StrCat(gen_cst.name(), "_disj") : "";
422  RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicDisjunction(
423  scip, /*cons=*/scip_cst, /*name=*/name.c_str(),
424  /*nconss=*/cons.size(), /*conss=*/cons.data(), /*relaxcons=*/nullptr));
425  RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, *scip_cst));
426 
427  // Add all of the inequality constraints.
428  constexpr double kInfinity = std::numeric_limits<double>::infinity();
429  cons.clear();
430  for (const int var_index : unique_var_indices) {
431  vars = {scip_resultant_var, scip_variables[var_index]};
432  vals = {1, -1};
433  if (gen_cst.has_min_constraint()) {
434  RETURN_IF_ERROR(add_lin_constraint(absl::StrCat("_ineq_", var_index),
435  -kInfinity, 0.0));
436  } else {
437  RETURN_IF_ERROR(add_lin_constraint(absl::StrCat("_ineq_", var_index), 0.0,
438  kInfinity));
439  }
440  }
441  if (minmax.has_constant()) {
442  vars = {scip_resultant_var};
443  vals = {1};
444  if (gen_cst.has_min_constraint()) {
445  RETURN_IF_ERROR(add_lin_constraint(absl::StrCat("_ineq_constant"),
446  -kInfinity, minmax.constant()));
447  } else {
448  RETURN_IF_ERROR(add_lin_constraint(absl::StrCat("_ineq_constant"),
449  minmax.constant(), kInfinity));
450  }
451  }
452  for (SCIP_CONS* scip_cons : cons) {
453  scip_constraints->push_back(scip_cons);
454  RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, scip_cons));
455  }
456  return absl::OkStatus();
457 }
458 
459 absl::Status AddQuadraticObjective(const MPQuadraticObjective& quadobj,
460  SCIP* scip,
461  std::vector<SCIP_VAR*>* scip_variables,
462  std::vector<SCIP_CONS*>* scip_constraints) {
463  CHECK(scip != nullptr);
464  CHECK(scip_variables != nullptr);
465  CHECK(scip_constraints != nullptr);
466 
467  constexpr double kInfinity = std::numeric_limits<double>::infinity();
468 
469  const int size = quadobj.coefficient_size();
470  if (size == 0) return absl::OkStatus();
471 
472  // SCIP supports quadratic objectives by adding a quadratic constraint. We
473  // need to create an extra variable to hold this quadratic objective.
474  scip_variables->push_back(nullptr);
475  RETURN_IF_SCIP_ERROR(SCIPcreateVarBasic(scip, /*var=*/&scip_variables->back(),
476  /*name=*/"quadobj",
477  /*lb=*/-kInfinity, /*ub=*/kInfinity,
478  /*obj=*/1,
479  /*vartype=*/SCIP_VARTYPE_CONTINUOUS));
480  RETURN_IF_SCIP_ERROR(SCIPaddVar(scip, scip_variables->back()));
481 
482  scip_constraints->push_back(nullptr);
483  SCIP_VAR* linvars[1] = {scip_variables->back()};
484  double lincoefs[1] = {-1};
485  std::vector<SCIP_VAR*> quadvars1(size, nullptr);
486  std::vector<SCIP_VAR*> quadvars2(size, nullptr);
487  std::vector<double> quadcoefs(size, 0);
488  for (int i = 0; i < size; ++i) {
489  quadvars1[i] = scip_variables->at(quadobj.qvar1_index(i));
490  quadvars2[i] = scip_variables->at(quadobj.qvar2_index(i));
491  quadcoefs[i] = quadobj.coefficient(i);
492  }
493  RETURN_IF_SCIP_ERROR(SCIPcreateConsBasicQuadratic(
494  scip, /*cons=*/&scip_constraints->back(), /*name=*/"quadobj",
495  /*nlinvars=*/1, /*linvars=*/linvars, /*lincoefs=*/lincoefs,
496  /*nquadterms=*/size, /*quadvars1=*/quadvars1.data(),
497  /*quadvars2=*/quadvars2.data(), /*quadcoefs=*/quadcoefs.data(),
498  /*lhs=*/0, /*rhs=*/0));
499  RETURN_IF_SCIP_ERROR(SCIPaddCons(scip, scip_constraints->back()));
500 
501  return absl::OkStatus();
502 }
503 
504 absl::Status AddSolutionHint(const MPModelProto& model, SCIP* scip,
505  const std::vector<SCIP_VAR*>& scip_variables) {
506  CHECK(scip != nullptr);
507  if (!model.has_solution_hint()) return absl::OkStatus();
508 
509  const PartialVariableAssignment& solution_hint = model.solution_hint();
510  SCIP_SOL* solution;
511  bool is_solution_partial =
512  solution_hint.var_index_size() != model.variable_size();
513  if (is_solution_partial) {
515  SCIPcreatePartialSol(scip, /*sol=*/&solution, /*heur=*/nullptr));
516  } else {
518  SCIPcreateSol(scip, /*sol=*/&solution, /*heur=*/nullptr));
519  }
520 
521  for (int i = 0; i < solution_hint.var_index_size(); ++i) {
522  RETURN_IF_SCIP_ERROR(SCIPsetSolVal(
523  scip, solution, scip_variables[solution_hint.var_index(i)],
524  solution_hint.var_value(i)));
525  }
526 
527  SCIP_Bool is_stored;
528  RETURN_IF_SCIP_ERROR(SCIPaddSolFree(scip, &solution, &is_stored));
529 
530  return absl::OkStatus();
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.
536 std::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 
660 absl::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 7.0.1, getting the pointer to all
884  // solutions is as fast as getting the pointer to the best solution.
885  // See scip/src/scip/scip_sol.c?l=2264&rcl=322332899.
886  SCIP_SOL** const scip_solutions = SCIPgetSols(scip);
887  response.set_objective_value(SCIPgetSolOrigObj(scip, scip_solutions[0]));
888  response.set_best_objective_bound(SCIPgetDualbound(scip));
889  *response.mutable_variable_value() =
890  scip_solution_to_repeated_field(scip_solutions[0]);
891  for (int i = 1; i < solution_count; ++i) {
892  MPSolution* solution = response.add_additional_solutions();
893  solution->set_objective_value(SCIPgetSolOrigObj(scip, scip_solutions[i]));
894  *solution->mutable_variable_value() =
895  scip_solution_to_repeated_field(scip_solutions[i]);
896  }
897  }
898 
899  const SCIP_STATUS scip_status = SCIPgetStatus(scip);
900  switch (scip_status) {
901  case SCIP_STATUS_OPTIMAL:
902  response.set_status(MPSOLVER_OPTIMAL);
903  break;
904  case SCIP_STATUS_GAPLIMIT:
905  // To be consistent with the other solvers.
906  response.set_status(MPSOLVER_OPTIMAL);
907  break;
908  case SCIP_STATUS_INFORUNBD:
909  // NOTE(user): After looking at the SCIP code on 2019-06-14, it seems
910  // that this will mostly happen for INFEASIBLE problems in practice.
911  // Since most (all?) users shouldn't have their application behave very
912  // differently upon INFEASIBLE or UNBOUNDED, the potential error that we
913  // are making here seems reasonable (and not worth a LOG, unless in
914  // debug mode).
915  DLOG(INFO) << "SCIP solve returned SCIP_STATUS_INFORUNBD, which we treat "
916  "as INFEASIBLE even though it may mean UNBOUNDED.";
917  response.set_status_str(
918  "The model may actually be unbounded: SCIP returned "
919  "SCIP_STATUS_INFORUNBD");
920  ABSL_FALLTHROUGH_INTENDED;
921  case SCIP_STATUS_INFEASIBLE:
922  response.set_status(MPSOLVER_INFEASIBLE);
923  break;
924  case SCIP_STATUS_UNBOUNDED:
925  response.set_status(MPSOLVER_UNBOUNDED);
926  break;
927  default:
928  if (solution_count > 0) {
929  response.set_status(MPSOLVER_FEASIBLE);
930  } else {
931  response.set_status(MPSOLVER_NOT_SOLVED);
932  response.set_status_str(absl::StrFormat("SCIP status code %d",
933  static_cast<int>(scip_status)));
934  }
935  break;
936  }
937 
938  VLOG(1) << "ScipSolveProto() status="
939  << MPSolverResponseStatus_Name(response.status()) << ".";
940  return response;
941 }
942 
943 } // namespace operations_research
944 
945 #endif // #if defined(USE_SCIP)
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#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
GRBmodel * model
absl::Cleanup< absl::decay_t< Callback > > MakeCleanup(Callback &&callback)
Definition: cleanup.h:125
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(absl::string_view parameters, SCIP *scip)
std::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).
IntVar * upper_bound
Definition: routing.cc:1087
IntVar * lower_bound
Definition: routing.cc:1086
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.")
constexpr double kInfinity
#define VLOG(verboselevel)
Definition: vlog.h:39