OR-Tools  9.2
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"
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
55 void* data;
56};
57
58namespace operations_research {
59
60namespace {
61int ScipNumVars(SCIP* scip) { return SCIPgetNOrigVars(scip); }
62
63SCIP_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
233extern "C" {
236static 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
246static 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
255static 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
283static 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
311static 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
341static 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}
358static 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}
389static 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
408namespace operations_research {
409namespace 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
435void 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)
int64_t max
Definition: alldiff_cst.cc:140
#define CHECK(condition)
Definition: base/logging.h:495
#define CHECK_OK(x)
Definition: base/logging.h:44
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:894
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:893
#define LOG(severity)
Definition: base/logging.h:420
#define VLOG(verboselevel)
Definition: base/logging.h:983
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 > SeparateIntegerSolution(const ScipConstraintHandlerContext &context, void *constraint)=0
virtual std::vector< CallbackRangeConstraint > SeparateFractionalSolution(const ScipConstraintHandlerContext &context, void *constraint)=0
IntVar * var
Definition: expr_array.cc:1874
int64_t coef
Definition: expr_array.cc:1875
GurobiMPCallbackContext * context
const int ERROR
Definition: log_severity.h:32
RowIndex row
Definition: markowitz.cc:182
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