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