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