Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
gscip_solver_constraint_handler.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
15
16#include <utility>
17#include <vector>
18
19#include "absl/status/status.h"
20#include "absl/status/statusor.h"
21#include "absl/time/clock.h"
22#include "absl/time/time.h"
25#include "ortools/gscip/gscip.h"
28#include "ortools/math_opt/callback.pb.h"
30#include "ortools/math_opt/sparse_containers.pb.h"
32#include "scip/type_var.h"
33
35namespace {
36
37// We set all priorities to -9'999'998, rather than the default of -1, so that
38// our callback only checks constraints after all the constraints that are part
39// of the model (e.g. linear constraints have enforcement priority -1'000'000).
40// We still want to run before the count solutions constraint handler, which is
41// -9'999'999. All the constraints appears to separate with priority >= 0, but
42// if we want to run last, we can still pick -9'999'998. E.g. see:
43// https://stackoverflow.com/questions/72921074/can-i-set-the-scip-constraint-handler-to-work-only-after-a-feasible-solution-is
44//
45// Note that these priorities are different from the GScip defaults in
46// gscip_constraint_handler.h. Because we are forcing SCIPs API to look more
47// like Gurobi's in MathOpt, the GScip defaults make less sense.
48GScipConstraintHandlerProperties MakeHandlerProperties() {
49 return {
50 .name = "GScipSolverConstraintHandler",
51 .description = "A single handler for all mathopt callbacks",
52 .enforcement_priority = -9'999'998,
53 .feasibility_check_priority = -9'999'998,
54 .separation_priority = -9'999'998,
55 };
56}
57} // namespace
58
60 if (user_callback == nullptr) {
61 return absl::OkStatus();
62 }
63 if (variables == nullptr) {
64 return absl::InternalError(
65 "GScipSolverConstraintData::variables must be set when "
66 "GScipSolverConstraintData::user_callback is not null");
67 }
68 if (variable_node_filter == nullptr) {
69 return absl::InternalError(
70 "GScipSolverConstraintData::variable_node_filter must be set when "
71 "GScipSolverConstraintData::variable_node_filter is not null");
72 }
73 if (variable_solution_filter == nullptr) {
74 return absl::InternalError(
75 "GScipSolverConstraintData::variable_solution_filter must be set when "
76 "GScipSolverConstraintData::user_callback is not null");
77 }
78 if (interrupter == nullptr) {
79 return absl::InternalError(
80 "GScipSolverConstraintData::interrupter must be set when "
81 "GScipSolverConstraintData::user_callback is not null");
82 }
83 return absl::OkStatus();
84}
85
87 const CallbackRegistrationProto& registration) {
88 for (const int event_int : registration.request_registration()) {
89 switch (static_cast<CallbackEventProto>(event_int)) {
90 case CALLBACK_EVENT_MIP_NODE:
91 run_at_nodes = true;
92 break;
93 case CALLBACK_EVENT_MIP_SOLUTION:
94 run_at_solutions = true;
95 break;
96 default:
97 break;
98 }
99 }
100 adds_cuts = registration.add_cuts();
101 adds_lazy_constraints = registration.add_lazy_constraints();
102}
103
106
107absl::StatusOr<GScipCallbackResult> GScipSolverConstraintHandler::EnforceLp(
109 const GScipSolverConstraintData& constraint_data,
110 bool solution_infeasible) {
111 RETURN_IF_ERROR(constraint_data.Validate());
112 if (!constraint_data.run_at_solutions ||
113 constraint_data.user_callback == nullptr) {
115 }
117 const CallbackDataProto cb_data,
118 MakeCbData(context, constraint_data, CALLBACK_EVENT_MIP_SOLUTION));
119
120 ASSIGN_OR_RETURN(const CallbackResultProto result,
121 constraint_data.user_callback(cb_data));
122 return ApplyCallback(result, context, constraint_data,
124}
125
126absl::StatusOr<bool> GScipSolverConstraintHandler::CheckIsFeasible(
127 GScipConstraintHandlerContext context,
128 const GScipSolverConstraintData& constraint_data,
129 const bool check_integrality, const bool check_lp_rows,
130 const bool print_reason, const bool check_completely) {
131 if (check_completely) {
132 return absl::InternalError(
133 "check_completely inside of CONSCHECK not supported. This is called "
134 "only if you have set some SCIP parameters manually, e.g. "
135 "display/allviols=TRUE");
136 }
137 RETURN_IF_ERROR(constraint_data.Validate());
138 if (!constraint_data.run_at_solutions ||
139 constraint_data.user_callback == nullptr) {
140 return true;
141 }
143 const CallbackDataProto cb_data,
144 MakeCbData(context, constraint_data, CALLBACK_EVENT_MIP_SOLUTION));
145 ASSIGN_OR_RETURN(const CallbackResultProto result,
146 constraint_data.user_callback(cb_data));
148 ApplyCallback(result, context, constraint_data,
150 return cb_result == GScipCallbackResult::kFeasible;
151}
152
153absl::StatusOr<GScipCallbackResult> GScipSolverConstraintHandler::SeparateLp(
154 GScipConstraintHandlerContext context,
155 const GScipSolverConstraintData& constraint_data) {
156 RETURN_IF_ERROR(constraint_data.Validate());
157 if (!constraint_data.run_at_nodes ||
158 constraint_data.user_callback == nullptr) {
160 }
162 const CallbackDataProto cb_data,
163 MakeCbData(context, constraint_data, CALLBACK_EVENT_MIP_NODE));
164 ASSIGN_OR_RETURN(const CallbackResultProto result,
165 constraint_data.user_callback(cb_data));
167 ApplyCallback(result, context, constraint_data,
169 if (cb_result == GScipCallbackResult::kFeasible) {
171 }
172 return cb_result;
173}
174
175absl::StatusOr<GScipCallbackResult>
176GScipSolverConstraintHandler::SeparateSolution(
177 GScipConstraintHandlerContext context,
178 const GScipSolverConstraintData& constraint_data) {
179 RETURN_IF_ERROR(constraint_data.Validate());
180 if (!constraint_data.run_at_solutions ||
181 constraint_data.user_callback == nullptr) {
183 }
185 const CallbackDataProto cb_data,
186 MakeCbData(context, constraint_data, CALLBACK_EVENT_MIP_SOLUTION));
187 ASSIGN_OR_RETURN(const CallbackResultProto result,
188 constraint_data.user_callback(cb_data));
190 ApplyCallback(result, context, constraint_data,
192 if (cb_result == GScipCallbackResult::kFeasible) {
194 }
195 return cb_result;
196}
197
198absl::StatusOr<CallbackDataProto> GScipSolverConstraintHandler::MakeCbData(
199 GScipConstraintHandlerContext& context,
200 const GScipSolverConstraintData& constraint_data,
201 const CallbackEventProto event) {
202 if (event != CALLBACK_EVENT_MIP_NODE &&
203 event != CALLBACK_EVENT_MIP_SOLUTION) {
205 << "Only events MIP_NODE and MIP_SOLUTION are supported, but was "
206 "invoked on event: "
207 << ProtoEnumToString(event);
208 }
209 CallbackDataProto cb_data;
210 cb_data.set_event(event);
211 const SparseVectorFilterProto* filter =
212 event == CALLBACK_EVENT_MIP_NODE
213 ? constraint_data.variable_node_filter
214 : constraint_data.variable_solution_filter;
215 auto& var_values = *cb_data.mutable_primal_solution_vector();
216 SparseVectorFilterPredicate predicate(*filter);
217 for (const auto [var_id, scip_var] : *constraint_data.variables) {
218 const double value = context.VariableValue(scip_var);
219 if (predicate.AcceptsAndUpdate(var_id, value)) {
220 var_values.add_ids(var_id);
221 var_values.add_values(value);
222 }
223 }
224 const GScipCallbackStats& stats = context.stats();
225 CallbackDataProto::MipStats& cb_stats = *cb_data.mutable_mip_stats();
226 cb_stats.set_primal_bound(stats.primal_bound);
227 cb_stats.set_dual_bound(stats.dual_bound);
228 cb_stats.set_explored_nodes(stats.num_processed_nodes_total);
229 cb_stats.set_open_nodes(stats.num_nodes_left);
230 // TODO(b/314630175): maybe this should include diving/probing iterations
231 // and strong branching iterations as well, see SCIPgetNDivingLPIterations
232 // and SCIPgetNStrongbranchLPIterations
233 cb_stats.set_simplex_iterations(stats.primal_simplex_iterations +
234 stats.dual_simplex_iterations);
235 cb_stats.set_number_of_solutions_found(stats.num_solutions_found);
236 cb_stats.set_cutting_planes_in_lp(stats.num_cuts_in_lp);
237 const absl::Duration elapsed = absl::Now() - constraint_data.solve_start_time;
238 ASSIGN_OR_RETURN(*cb_data.mutable_runtime(),
240 return cb_data;
241}
242
243absl::StatusOr<GScipCallbackResult> GScipSolverConstraintHandler::ApplyCallback(
244 const CallbackResultProto& result, GScipConstraintHandlerContext& context,
245 const GScipSolverConstraintData& constraint_data,
246 ConstraintHandlerCallbackType scip_cb_type) {
247 if (!result.suggested_solutions().empty()) {
248 return absl::UnimplementedError(
249 "suggested solution is not yet implemented for SCIP callbacks in "
250 "MathOpt");
251 }
253 for (const CallbackResultProto::GeneratedLinearConstraint& cut :
254 result.cuts()) {
255 GScipLinearRange scip_constraint{.lower_bound = cut.lower_bound(),
256 .upper_bound = cut.upper_bound()};
257 for (int i = 0; i < cut.linear_expression().ids_size(); ++i) {
258 scip_constraint.variables.push_back(
259 constraint_data.variables->at(cut.linear_expression().ids(i)));
260 scip_constraint.coefficients.push_back(cut.linear_expression().values(i));
261 }
262 if (cut.is_lazy()) {
263 RETURN_IF_ERROR(context.AddLazyLinearConstraint(scip_constraint, ""));
265 cb_result, GScipCallbackResult::kConstraintAdded, scip_cb_type);
266 } else {
267 ASSIGN_OR_RETURN(const GScipCallbackResult cut_result,
268 context.AddCut(scip_constraint, ""));
269 cb_result =
270 MergeConstraintHandlerResults(cb_result, cut_result, scip_cb_type);
271 }
272 }
273 if (result.terminate()) {
274 // NOTE: we do not know what the current stage is, this is safer than
275 // calling SCIPinterruptSolve() directly.
276 constraint_data.interrupter->Interrupt();
277 }
278 return cb_result;
279}
280
281std::vector<std::pair<SCIP_VAR*, RoundingLockDirection>>
282GScipSolverConstraintHandler::RoundingLock(
283 GScip* gscip, const GScipSolverConstraintData& constraint_data,
284 const bool lock_type_is_model) {
285 // Warning: we do not call constraint_data.Validate() because this function
286 // cannot propagate status errors. As implemented, this function does not
287 // access the members of constraint_data checked by Validate().
288 const bool generates_constraints =
289 constraint_data.adds_cuts || constraint_data.adds_lazy_constraints;
290 if (constraint_data.user_callback == nullptr || !generates_constraints) {
291 return {};
292 }
293 std::vector<std::pair<SCIP_VAR*, RoundingLockDirection>> result;
294 for (SCIP_VAR* var : gscip->variables()) {
295 result.push_back({var, RoundingLockDirection::kBoth});
296 }
297 return result;
298}
299
300} // namespace operations_research::math_opt
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
int64_t value
IntVar * var
GurobiMPCallbackContext * context
An object oriented wrapper for quadratic constraints in ModelStorage.
Definition gurobi_isv.cc:28
std::string ProtoEnumToString(ProtoEnumType enum_value)
Definition proto_utils.h:50
GScipCallbackResult
Equivalent to type_result.h in SCIP.
GScipCallbackResult MergeConstraintHandlerResults(const GScipCallbackResult result1, const GScipCallbackResult result2, const ConstraintHandlerCallbackType callback_type)
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
Definition protoutil.h:27
StatusBuilder InternalErrorBuilder()
void SetWhenRunAndAdds(const CallbackRegistrationProto &registration)
const gtl::linked_hash_map< int64_t, SCIP_VAR * > * variables