Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
scip_callback.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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"
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
55struct SCIP_ConshdlrData {
56 std::unique_ptr<operations_research::internal::ScipCallbackRunner> runner;
57};
58
59struct SCIP_ConsData {
60 void* data;
61};
62
63namespace operations_research {
64
65namespace {
66int ScipNumVars(SCIP* scip) { return SCIPgetNOrigVars(scip); }
67
68SCIP_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
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
238extern "C" {
241static 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
251static 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
260static 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
288static 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
316static 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
346static 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}
363static 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}
394static 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
413namespace operations_research {
414namespace 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
440void 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)
const absl::flat_hash_map< const MPVariable *, double > & terms() const
const LinearExpr & linear_expr() const
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)
A value of nullptr for solution means to use the current LP solution.
virtual std::vector< CallbackRangeConstraint > SeparateFractionalSolution(const ScipConstraintHandlerContext &context, void *constraint)=0
virtual std::vector< CallbackRangeConstraint > SeparateIntegerSolution(const ScipConstraintHandlerContext &context, void *constraint)=0
virtual bool IntegerSolutionFeasible(const ScipConstraintHandlerContext &context, void *constraint)=0
IntVar * var
int64_t coef
GurobiMPCallbackContext * context
RowIndex row
Definition markowitz.cc:186
double solution
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)
In SWIG mode, we don't want anything besides these top-level includes.
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)
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)
int var_index
Definition search.cc:3268
std::unique_ptr< operations_research::internal::ScipCallbackRunner > runner
absl::Span< SCIP_CONS * > useful_constraints
ScipConstraintHandlerContext context
absl::Span< SCIP_CONS * > unlikely_useful_constraints
internal::ScipCallbackRunner * callback_runner
CallbackSetup(SCIP *scip, SCIP_CONSHDLR *scip_handler, SCIP_CONS **conss, int nconss, int nusefulconss, SCIP_SOL *sol, bool is_pseudo_solution)
bool needs_constraints
See CONSHDLR_NEEDSCONS in SCIP documentation above.
std::string name
See CONSHDLR_NAME in SCIP documentation above.
int eager_frequency
See CONSHDLR_EAGERFREQ in SCIP documentation above.
std::string description
See CONSHDLR_DESC in SCIP documentation above.
int separation_frequency
See CONSHDLR_SEPAFREQ in the SCIP documentation above.