OR-Tools  9.1
scip_callback.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 <cstdint>
19 
20 #include "absl/strings/str_cat.h"
21 #include "absl/types/span.h"
22 #include "ortools/base/logging.h"
24 #include "scip/cons_linear.h"
25 #include "scip/def.h"
26 #include "scip/pub_cons.h"
27 #include "scip/scip.h"
28 #include "scip/scip_cons.h"
29 #include "scip/scip_cut.h"
30 #include "scip/scip_general.h"
31 #include "scip/scip_lp.h"
32 #include "scip/scip_param.h"
33 #include "scip/scip_prob.h"
34 #include "scip/scip_sol.h"
35 #include "scip/scip_solvingstats.h"
36 #include "scip/scip_tree.h"
37 #include "scip/scipdefplugins.h"
38 #include "scip/struct_cons.h"
39 #include "scip/struct_tree.h"
40 #include "scip/struct_var.h"
41 #include "scip/type_cons.h"
42 #include "scip/type_lp.h"
43 #include "scip/type_result.h"
44 #include "scip/type_retcode.h"
45 #include "scip/type_scip.h"
46 #include "scip/type_sol.h"
47 #include "scip/type_tree.h"
48 #include "scip/type_var.h"
49 
51  std::unique_ptr<operations_research::internal::ScipCallbackRunner> runner;
52 };
53 
54 struct SCIP_ConsData {
55  void* data;
56 };
57 
58 namespace operations_research {
59 
60 namespace {
61 int ScipNumVars(SCIP* scip) { return SCIPgetNOrigVars(scip); }
62 
63 SCIP_VAR* ScipGetVar(SCIP* scip, int var_index) {
64  DCHECK_GE(var_index, 0);
65  DCHECK_LT(var_index, ScipNumVars(scip));
66  return SCIPgetOrigVars(scip)[var_index];
67 }
68 
69 } // namespace
70 
72  SCIP* scip, SCIP_SOL* solution, bool is_pseudo_solution)
73  : scip_(scip),
74  solution_(solution),
75  is_pseudo_solution_(is_pseudo_solution) {}
76 
78  const MPVariable* variable) const {
79  return SCIPgetSolVal(scip_, solution_, ScipGetVar(scip_, variable->index()));
80 }
81 
83  return SCIPgetNNodes(scip_);
84 }
85 
87  return SCIPgetCurrentNode(scip_)->number;
88 }
89 
94 };
95 
97  const LinearRange& constraint) {
98  double a_times_x = 0;
99  for (const auto& coef_pair : constraint.linear_expr().terms()) {
100  a_times_x += coef_pair.second * context.VariableValue(coef_pair.first);
101  }
102  double violation = std::max(a_times_x - constraint.upper_bound(),
103  constraint.lower_bound() - a_times_x);
104  return violation > 0;
105 }
106 
107 // If any violated lazy constraint is found:
108 // returns kLazyConstraintAdded,
109 // else if any violated cutting plane is found:
110 // returns kCuttingPlaneAdded,
111 // else:
112 // returns kDidNotFind
115  absl::Span<SCIP_CONS*> constraints,
116  bool is_integral) {
118  SCIP* scip = context.scip();
119  for (SCIP_CONS* constraint : constraints) {
120  SCIP_CONSDATA* consdata = SCIPconsGetData(constraint);
121  CHECK(consdata != nullptr);
122  std::vector<CallbackRangeConstraint> user_suggested_constraints;
123  if (is_integral) {
124  user_suggested_constraints =
125  runner->SeparateIntegerSolution(context, consdata->data);
126  } else {
127  user_suggested_constraints =
128  runner->SeparateFractionalSolution(context, consdata->data);
129  }
130  int num_constraints_added = 0;
131  for (const CallbackRangeConstraint& user_suggested_constraint :
132  user_suggested_constraints) {
134  user_suggested_constraint.range)) {
135  continue;
136  }
137  num_constraints_added++;
138  // Two code paths, one for cuts, one for lazy constraints. Cuts first:
139  if (user_suggested_constraint.is_cut) {
140  SCIP_ROW* row = nullptr;
141  constexpr bool kModifiable = false;
142  constexpr bool kRemovable = true;
143  CHECK_OK(SCIP_TO_STATUS(SCIPcreateEmptyRowCons(
144  scip, &row, constraint, user_suggested_constraint.name.c_str(),
145  user_suggested_constraint.range.lower_bound(),
146  user_suggested_constraint.range.upper_bound(),
147  user_suggested_constraint.local, kModifiable, kRemovable)));
148  CHECK_OK(SCIP_TO_STATUS(SCIPcacheRowExtensions(scip, row)));
149  for (const auto& coef_pair :
150  user_suggested_constraint.range.linear_expr().terms()) {
151  // NOTE(user): the coefficients don't come out sorted. I don't
152  // think this matters.
153  SCIP_VAR* var = ScipGetVar(scip, coef_pair.first->index());
154  const double coef = coef_pair.second;
155  CHECK_OK(SCIP_TO_STATUS(SCIPaddVarToRow(scip, row, var, coef)));
156  }
157  CHECK_OK(SCIP_TO_STATUS(SCIPflushRowExtensions(scip, row)));
158  SCIP_Bool infeasible;
159  constexpr bool kForceCut = false;
160  CHECK_OK(SCIP_TO_STATUS(SCIPaddRow(scip, row, kForceCut, &infeasible)));
161  CHECK_OK(SCIP_TO_STATUS(SCIPreleaseRow(scip, &row)));
162  // TODO(user): when infeasible is true, it better to have the scip
163  // return status be cutoff instead of cutting plane added (e.g. see
164  // cs/scip/cons_knapsack.c). However, as we use
165  // SCIPaddRow(), it isn't clear this will even happen.
167  // NOTE(user): if we have already found a violated lazy constraint,
168  // we want to return kLazyConstraintAdded, not kCuttingPlaneAdded,
169  // see function contract.
171  }
172  } else {
173  // Lazy constraint path:
174  std::vector<SCIP_VAR*> vars;
175  std::vector<double> coefs;
176  for (const auto& coef_pair :
177  user_suggested_constraint.range.linear_expr().terms()) {
178  // NOTE(user): the coefficients don't come out sorted. I don't
179  // think this matters.
180  vars.push_back(ScipGetVar(scip, coef_pair.first->index()));
181  coefs.push_back(coef_pair.second);
182  }
183 
184  const int num_vars = vars.size();
185  SCIP_CONS* scip_cons;
186  // TODO(user): Maybe it is better to expose more of these options,
187  // potentially through user_suggested_constraint.
188  CHECK_OK(SCIP_TO_STATUS(SCIPcreateConsLinear(
189  scip, &scip_cons, user_suggested_constraint.name.c_str(), num_vars,
190  vars.data(), coefs.data(),
191  user_suggested_constraint.range.lower_bound(),
192  user_suggested_constraint.range.upper_bound(), /*initial=*/true,
193  /*separate=*/true, /*enforce=*/true, /*check=*/true,
194  /*propagate=*/true, /*local=*/user_suggested_constraint.local,
195  /*modifiable=*/false, /*dynamic=*/false, /*removable=*/true,
196  /*stickingatnode=*/false)));
197  if (user_suggested_constraint.local) {
198  CHECK_OK(SCIP_TO_STATUS(SCIPaddConsLocal(scip, scip_cons, nullptr)));
199  } else {
200  CHECK_OK(SCIP_TO_STATUS(SCIPaddCons(scip, scip_cons)));
201  }
202  CHECK_OK(SCIP_TO_STATUS(SCIPreleaseCons(scip, &scip_cons)));
204  }
205  }
206  }
207  return result;
208 }
209 
211  SCIP_CONSHDLRDATA* scip_handler_data;
214  absl::Span<SCIP_CONS*> useful_constraints;
215  absl::Span<SCIP_CONS*> unlikely_useful_constraints;
216 
217  CallbackSetup(SCIP* scip, SCIP_CONSHDLR* scip_handler, SCIP_CONS** conss,
218  int nconss, int nusefulconss, SCIP_SOL* sol,
219  bool is_pseudo_solution)
220  : scip_handler_data(SCIPconshdlrGetData(scip_handler)),
221  callback_runner(scip_handler_data->runner.get()),
222  context(scip, sol, is_pseudo_solution),
223  useful_constraints(absl::MakeSpan(conss, nusefulconss)),
225  absl::MakeSpan(conss, nconss).subspan(nusefulconss)) {
226  CHECK(scip_handler_data != nullptr);
227  CHECK(callback_runner != nullptr);
228  }
229 };
230 
231 } // namespace operations_research
232 
233 extern "C" {
236 static SCIP_DECL_CONSFREE(ConstraintHandlerFreeC) {
237  VLOG(3) << "FreeC";
238  CHECK(scip != nullptr);
239  SCIP_CONSHDLRDATA* scip_handler_data = SCIPconshdlrGetData(conshdlr);
240  CHECK(scip_handler_data != nullptr);
241  delete scip_handler_data;
242  SCIPconshdlrSetData(conshdlr, nullptr);
243  return SCIP_OKAY;
244 }
245 
246 static SCIP_DECL_CONSDELETE(ConstraintHandlerDeleteC) {
247  VLOG(3) << "DeleteC";
248  CHECK(consdata != nullptr);
249  CHECK(*consdata != nullptr);
250  delete *consdata;
251  cons->consdata = nullptr;
252  return SCIP_OKAY;
253 }
254 
255 static SCIP_DECL_CONSENFOLP(EnforceLpC) {
256  VLOG(3) << "EnforceC";
257  operations_research::CallbackSetup setup(scip, conshdlr, conss, nconss,
258  nusefulconss, nullptr, false);
261  setup.useful_constraints,
262  /*is_integral=*/true);
263  if (separation_result ==
265  separation_result = operations_research::RunSeparation(
267  /*is_integral=*/true);
268  }
269  switch (separation_result) {
271  *result = SCIP_CONSADDED;
272  break;
274  *result = SCIP_SEPARATED;
275  break;
277  *result = SCIP_FEASIBLE;
278  break;
279  }
280  return SCIP_OKAY;
281 }
282 
283 static SCIP_DECL_CONSSEPALP(SeparateLpC) {
284  VLOG(3) << "SeparateLpC";
285  operations_research::CallbackSetup setup(scip, conshdlr, conss, nconss,
286  nusefulconss, nullptr, false);
289  setup.useful_constraints,
290  /*is_integral=*/false);
291  if (separation_result ==
293  separation_result = operations_research::RunSeparation(
295  /*is_integral=*/false);
296  }
297  switch (separation_result) {
299  *result = SCIP_CONSADDED;
300  break;
302  *result = SCIP_SEPARATED;
303  break;
305  *result = SCIP_DIDNOTFIND;
306  break;
307  }
308  return SCIP_OKAY;
309 }
310 
311 static SCIP_DECL_CONSSEPASOL(SeparatePrimalSolutionC) {
312  VLOG(3) << "SeparatePrimalC";
313  operations_research::CallbackSetup setup(scip, conshdlr, conss, nconss,
314  nusefulconss, sol, false);
317  setup.useful_constraints,
318  /*is_integral=*/true);
319  if (separation_result ==
321  separation_result = operations_research::RunSeparation(
323  /*is_integral=*/true);
324  }
325  switch (separation_result) {
327  *result = SCIP_CONSADDED;
328  break;
330  LOG(ERROR) << "Cutting planes cannot be added on integer solutions, "
331  "treating as a constraint.";
332  *result = SCIP_CONSADDED;
333  break;
335  *result = SCIP_DIDNOTFIND;
336  break;
337  }
338  return SCIP_OKAY;
339 }
340 
341 static SCIP_DECL_CONSCHECK(CheckFeasibilityC) {
342  VLOG(3) << "CheckFeasibilityC";
343  operations_research::CallbackSetup setup(scip, conshdlr, conss, nconss,
344  nconss, sol, false);
345  // All constraints are "useful" for this callback.
346  for (SCIP_CONS* constraint : setup.useful_constraints) {
347  SCIP_CONSDATA* consdata = SCIPconsGetData(constraint);
348  CHECK(consdata != nullptr);
350  consdata->data)) {
351  *result = SCIP_INFEASIBLE;
352  return SCIP_OKAY;
353  }
354  }
355  *result = SCIP_FEASIBLE;
356  return SCIP_OKAY;
357 }
358 static SCIP_DECL_CONSENFOPS(EnforcePseudoSolutionC) {
359  VLOG(3) << "EnforcePseudoSolutionC";
360  // TODO(user): are we sure the pseudo solution is LP feasible? It seems like
361  // it doesn't need to be. The code in RunSeparation might assume this?
362  operations_research::CallbackSetup setup(scip, conshdlr, conss, nconss,
363  nusefulconss, nullptr, true);
366  setup.useful_constraints,
367  /*is_integral=*/false);
368  if (separation_result ==
370  separation_result = operations_research::RunSeparation(
372  /*is_integral=*/false);
373  }
374  switch (separation_result) {
376  *result = SCIP_CONSADDED;
377  break;
379  LOG(ERROR) << "Cutting planes cannot be added on pseudo solutions, "
380  "treating as a constraint.";
381  *result = SCIP_CONSADDED;
382  break;
384  *result = SCIP_FEASIBLE;
385  break;
386  }
387  return SCIP_OKAY;
388 }
389 static SCIP_DECL_CONSLOCK(VariableRoundingLockC) {
390  // In this callback, we need to say, for a constraint class and an instance of
391  // the constraint, for which variables could an {increase,decrease,either}
392  // affect feasibility. As a conservative overestimate, we say that any
393  // change in any variable could cause an infeasibility for any instance of
394  // any callback constraint.
395  // TODO(user): this could be a little better, but we would need to add
396  // another method to override on ScipConstraintHandler<ConstraintData>.
397 
398  const int num_vars = operations_research::ScipNumVars(scip);
399  for (int i = 0; i < num_vars; ++i) {
400  SCIP_VAR* var = operations_research::ScipGetVar(scip, i);
401  SCIP_CALL(SCIPaddVarLocksType(scip, var, locktype, nlockspos + nlocksneg,
402  nlockspos + nlocksneg));
403  }
404  return SCIP_OKAY;
405 }
406 }
407 
408 namespace operations_research {
409 namespace internal {
410 
412  const ScipConstraintHandlerDescription& description,
413  std::unique_ptr<ScipCallbackRunner> runner, SCIP* scip) {
414  SCIP_CONSHDLR* c_scip_handler;
415  SCIP_CONSHDLRDATA* scip_handler_data = new SCIP_CONSHDLRDATA;
416  scip_handler_data->runner = std::move(runner);
417 
418  CHECK_OK(SCIP_TO_STATUS(SCIPincludeConshdlrBasic(
419  scip, &c_scip_handler, description.name.c_str(),
420  description.description.c_str(), description.enforcement_priority,
421  description.feasibility_check_priority, description.eager_frequency,
422  description.needs_constraints, EnforceLpC, EnforcePseudoSolutionC,
423  CheckFeasibilityC, VariableRoundingLockC, scip_handler_data)));
424  CHECK(c_scip_handler != nullptr);
425  CHECK_OK(SCIP_TO_STATUS(SCIPsetConshdlrSepa(
426  scip, c_scip_handler, SeparateLpC, SeparatePrimalSolutionC,
427  description.separation_frequency, description.separation_priority,
428  /*delaysepa=*/false)));
430  SCIPsetConshdlrFree(scip, c_scip_handler, ConstraintHandlerFreeC)));
432  SCIPsetConshdlrDelete(scip, c_scip_handler, ConstraintHandlerDeleteC)));
433 }
434 
435 void AddCallbackConstraintImpl(SCIP* scip, const std::string& handler_name,
436  const std::string& constraint_name,
437  void* constraint_data,
438  const ScipCallbackConstraintOptions& options) {
439  SCIP_CONSHDLR* conshdlr = SCIPfindConshdlr(scip, handler_name.c_str());
440  CHECK(conshdlr != nullptr)
441  << "Constraint handler " << handler_name << " not registered with scip.";
442  SCIP_ConsData* consdata = new SCIP_ConsData;
443  consdata->data = constraint_data;
444  SCIP_CONS* constraint = nullptr;
445  CHECK_OK(SCIP_TO_STATUS(SCIPcreateCons(
446  scip, &constraint, constraint_name.c_str(), conshdlr, consdata,
447  options.initial, options.separate, options.enforce, options.check,
448  options.propagate, options.local, options.modifiable, options.dynamic,
449  options.removable, options.stickingatnodes)));
450  CHECK(constraint != nullptr);
451  CHECK_OK(SCIP_TO_STATUS(SCIPaddCons(scip, constraint)));
452  CHECK_OK(SCIP_TO_STATUS(SCIPreleaseCons(scip, &constraint)));
453 }
454 
455 } // namespace internal
456 } // namespace operations_research
457 #endif // #if defined(USE_SCIP)
static SCIP_DECL_CONSENFOPS(EnforcePseudoSolutionC)
#define CHECK(condition)
Definition: base/logging.h:491
SCIP_CONSHDLRDATA * scip_handler_data
void AddConstraintHandlerImpl(const ScipConstraintHandlerDescription &description, std::unique_ptr< ScipCallbackRunner > runner, SCIP *scip)
double VariableValue(const MPVariable *variable) const
static SCIP_DECL_CONSCHECK(CheckFeasibilityC)
#define CHECK_OK(x)
Definition: base/logging.h:42
#define VLOG(verboselevel)
Definition: base/logging.h:979
const int ERROR
Definition: log_severity.h:32
ScipSeparationResult RunSeparation(internal::ScipCallbackRunner *runner, const ScipConstraintHandlerContext &context, absl::Span< SCIP_CONS * > constraints, bool is_integral)
#define LOG(severity)
Definition: base/logging.h:416
internal::ScipCallbackRunner * callback_runner
#define SCIP_TO_STATUS(x)
CallbackSetup(SCIP *scip, SCIP_CONSHDLR *scip_handler, SCIP_CONS **conss, int nconss, int nusefulconss, SCIP_SOL *sol, bool is_pseudo_solution)
RowIndex row
Definition: markowitz.cc:182
int64_t coef
Definition: expr_array.cc:1875
The class for variables of a Mathematical Programming (MP) model.
int64_t max
Definition: alldiff_cst.cc:140
std::unique_ptr< operations_research::internal::ScipCallbackRunner > runner
Definition: cleanup.h:22
virtual std::vector< CallbackRangeConstraint > SeparateFractionalSolution(const ScipConstraintHandlerContext &context, void *constraint)=0
const absl::flat_hash_map< const MPVariable *, double > & terms() const
Definition: linear_expr.h:143
static SCIP_DECL_CONSLOCK(VariableRoundingLockC)
bool LinearConstraintIsViolated(const ScipConstraintHandlerContext &context, const LinearRange &constraint)
ScipConstraintHandlerContext context
virtual bool IntegerSolutionFeasible(const ScipConstraintHandlerContext &context, void *constraint)=0
static SCIP_DECL_CONSSEPASOL(SeparatePrimalSolutionC)
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:890
ScipConstraintHandlerContext(SCIP *scip, SCIP_SOL *solution, bool is_pseudo_solution)
int index() const
Returns the index of the variable in the MPSolver::variables_.
absl::Span< SCIP_CONS * > useful_constraints
static SCIP_DECL_CONSENFOLP(EnforceLpC)
Collection of objects used to extend the Constraint Solver library.
static SCIP_DECL_CONSFREE(ConstraintHandlerFreeC)
destructor of constraint handler to free user data (called when SCIP is exiting)
const LinearExpr & linear_expr() const
Definition: linear_expr.h:206
static SCIP_DECL_CONSSEPALP(SeparateLpC)
IntVar * var
Definition: expr_array.cc:1874
An expression of the form:
Definition: linear_expr.h:192
virtual std::vector< CallbackRangeConstraint > SeparateIntegerSolution(const ScipConstraintHandlerContext &context, void *constraint)=0
GurobiMPCallbackContext * context
static SCIP_DECL_CONSDELETE(ConstraintHandlerDeleteC)
absl::Span< SCIP_CONS * > unlikely_useful_constraints
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:889
void AddCallbackConstraintImpl(SCIP *scip, const std::string &handler_name, const std::string &constraint_name, void *constraint_data, const ScipCallbackConstraintOptions &options)