OR-Tools  9.2
gurobi_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
15
16#include <cstdint>
17#include <functional>
18#include <limits>
19#include <optional>
20#include <string>
21#include <utility>
22#include <vector>
23
25#include "absl/container/flat_hash_set.h"
26#include "absl/status/status.h"
27#include "absl/status/statusor.h"
28#include "absl/strings/str_cat.h"
29#include "absl/time/clock.h"
30#include "absl/time/time.h"
31#include "absl/types/span.h"
33#include "ortools/math_opt/callback.pb.h"
38#include "ortools/math_opt/solution.pb.h"
40#include "ortools/math_opt/sparse_containers.pb.h"
43
45
46namespace operations_research {
47namespace math_opt {
48namespace {
49
50// The number of possible values for "where" that Gurobi's callbacks can stop
51// at, see the table here:
52// https://www.gurobi.com/documentation/9.1/refman/cb_codes.html
53constexpr int kNumGurobiEvents = 9;
54constexpr int kGrbOk = 0;
55constexpr double kInf = std::numeric_limits<double>::infinity();
56
57template <int where>
58constexpr int CheckedGuroibWhere() {
59 static_assert(where >= 0 && where < kNumGurobiEvents);
60 return where;
61}
62
63inline int GurobiEvent(CallbackEventProto event) {
64 switch (event) {
65 case CALLBACK_EVENT_PRESOLVE:
66 return CheckedGuroibWhere<GRB_CB_PRESOLVE>();
67 case CALLBACK_EVENT_SIMPLEX:
68 return CheckedGuroibWhere<GRB_CB_SIMPLEX>();
69 case CALLBACK_EVENT_MIP:
70 return CheckedGuroibWhere<GRB_CB_MIP>();
71 case CALLBACK_EVENT_MIP_SOLUTION:
72 return CheckedGuroibWhere<GRB_CB_MIPSOL>();
73 case CALLBACK_EVENT_MIP_NODE:
74 return CheckedGuroibWhere<GRB_CB_MIPNODE>();
75 case CALLBACK_EVENT_BARRIER:
76 return CheckedGuroibWhere<GRB_CB_BARRIER>();
77 case CALLBACK_EVENT_UNSPECIFIED:
78 default:
79 LOG(FATAL) << "Unexpected callback event: " << event;
80 }
81}
82
83SparseDoubleVectorProto ApplyFilter(
84 const std::vector<double>& grb_solution,
86 const SparseVectorFilterProto& filter) {
87 SparseVectorFilterPredicate predicate(filter);
88 SparseDoubleVectorProto result;
89 for (const auto [id, grb_index] : var_ids) {
90 const double val = grb_solution[grb_index];
91 if (predicate.AcceptsAndUpdate(id, val)) {
92 result.add_ids(id);
93 result.add_values(val);
94 }
95 }
96 return result;
97}
98
99absl::StatusOr<int64_t> CbGetInt64(const Gurobi::CallbackContext& context,
100 int what) {
101 ASSIGN_OR_RETURN(const double result, context.CbGetDouble(what));
102 int64_t result64 = static_cast<int64_t>(result);
103 if (result != static_cast<double>(result64)) {
104 return absl::InternalError(
105 absl::StrCat("Error converting double attribute: ", what,
106 "with value: ", result, " to int64_t exactly."));
107 }
108 return result64;
109}
110
111absl::StatusOr<bool> CbGetBool(const Gurobi::CallbackContext& context,
112 int what) {
113 ASSIGN_OR_RETURN(const int result, context.CbGetInt(what));
114 bool result_bool = static_cast<bool>(result);
115 if (result != static_cast<int>(result_bool)) {
116 return absl::InternalError(
117 absl::StrCat("Error converting int attribute: ", what,
118 "with value: ", result, " to bool exactly."));
119 }
120 return result_bool;
121}
122
123// Invokes setter on a non-error value in statusor or returns the error.
124//
125// Requires that statusor is a StatusOr<T> and setter(T) is a function.
126#define MO_SET_OR_RET(setter, statusor) \
127 do { \
128 auto eval_status_or = statusor; \
129 RETURN_IF_ERROR(eval_status_or.status()) << __FILE__ << ":" << __LINE__; \
130 setter(*std::move(eval_status_or)); \
131 } while (false)
132
133// Sets the CallbackDataProto.runtime field using the difference between the
134// current wall clock time and the start time of the solve.
135absl::Status SetRuntime(const GurobiCallbackInput& callback_input,
136 CallbackDataProto& callback_data) {
137 return util_time::EncodeGoogleApiProto(absl::Now() - callback_input.start,
138 callback_data.mutable_runtime());
139}
140
141// Returns the data for the next callback. Returns nullopt if no callback is
142// needed.
143absl::StatusOr<std::optional<CallbackDataProto>> CreateCallbackDataProto(
144 const Gurobi::CallbackContext& c, const GurobiCallbackInput& callback_input,
145 MessageCallbackData& message_callback_data) {
146 CallbackDataProto callback_data;
147
148 // Query information from Gurobi.
149 switch (c.where()) {
150 case GRB_CB_PRESOLVE: {
151 callback_data.set_event(CALLBACK_EVENT_PRESOLVE);
152 CallbackDataProto::PresolveStats* const s =
153 callback_data.mutable_presolve_stats();
154 MO_SET_OR_RET(s->set_removed_variables, c.CbGetInt(GRB_CB_PRE_COLDEL));
155 MO_SET_OR_RET(s->set_removed_constraints, c.CbGetInt(GRB_CB_PRE_ROWDEL));
156 MO_SET_OR_RET(s->set_bound_changes, c.CbGetInt(GRB_CB_PRE_BNDCHG));
157 MO_SET_OR_RET(s->set_coefficient_changes, c.CbGetInt(GRB_CB_PRE_COECHG));
158 break;
159 }
160 case GRB_CB_SIMPLEX: {
161 callback_data.set_event(CALLBACK_EVENT_SIMPLEX);
162 CallbackDataProto::SimplexStats* const s =
163 callback_data.mutable_simplex_stats();
164 MO_SET_OR_RET(s->set_iteration_count, CbGetInt64(c, GRB_CB_SPX_ITRCNT));
165 MO_SET_OR_RET(s->set_is_pertubated, CbGetBool(c, GRB_CB_SPX_ISPERT));
166 MO_SET_OR_RET(s->set_objective_value, c.CbGetDouble(GRB_CB_SPX_OBJVAL));
167 MO_SET_OR_RET(s->set_primal_infeasibility,
168 c.CbGetDouble(GRB_CB_SPX_PRIMINF));
169 MO_SET_OR_RET(s->set_dual_infeasibility,
170 c.CbGetDouble(GRB_CB_SPX_DUALINF));
171 break;
172 }
173 case GRB_CB_BARRIER: {
174 callback_data.set_event(CALLBACK_EVENT_BARRIER);
175 CallbackDataProto::BarrierStats* const s =
176 callback_data.mutable_barrier_stats();
177 MO_SET_OR_RET(s->set_iteration_count, c.CbGetInt(GRB_CB_BARRIER_ITRCNT));
178 MO_SET_OR_RET(s->set_primal_objective,
179 c.CbGetDouble(GRB_CB_BARRIER_PRIMOBJ));
180 MO_SET_OR_RET(s->set_dual_objective,
181 c.CbGetDouble(GRB_CB_BARRIER_DUALOBJ));
182 MO_SET_OR_RET(s->set_primal_infeasibility,
183 c.CbGetDouble(GRB_CB_BARRIER_PRIMINF));
184 MO_SET_OR_RET(s->set_dual_infeasibility,
185 c.CbGetDouble(GRB_CB_BARRIER_DUALINF));
186 MO_SET_OR_RET(s->set_complementarity,
187 c.CbGetDouble(GRB_CB_BARRIER_COMPL));
188 break;
189 }
190 case GRB_CB_MIP: {
191 callback_data.set_event(CALLBACK_EVENT_MIP);
192 CallbackDataProto::MipStats* const s = callback_data.mutable_mip_stats();
193 MO_SET_OR_RET(s->set_primal_bound, c.CbGetDouble(GRB_CB_MIP_OBJBST));
194 MO_SET_OR_RET(s->set_dual_bound, c.CbGetDouble(GRB_CB_MIP_OBJBND));
195 MO_SET_OR_RET(s->set_explored_nodes, CbGetInt64(c, GRB_CB_MIP_NODCNT));
196 MO_SET_OR_RET(s->set_open_nodes, CbGetInt64(c, GRB_CB_MIP_NODLFT));
197 MO_SET_OR_RET(s->set_simplex_iterations,
198 CbGetInt64(c, GRB_CB_MIP_ITRCNT));
199 MO_SET_OR_RET(s->set_number_of_solutions_found,
200 c.CbGetInt(GRB_CB_MIP_SOLCNT));
201 MO_SET_OR_RET(s->set_cutting_planes_in_lp, c.CbGetInt(GRB_CB_MIP_CUTCNT));
202
203 break;
204 }
205 case GRB_CB_MIPSOL: {
206 callback_data.set_event(CALLBACK_EVENT_MIP_SOLUTION);
207 CallbackDataProto::MipStats* const s = callback_data.mutable_mip_stats();
208 MO_SET_OR_RET(s->set_primal_bound, c.CbGetDouble(GRB_CB_MIPSOL_OBJBST));
209 MO_SET_OR_RET(s->set_dual_bound, c.CbGetDouble(GRB_CB_MIPSOL_OBJBND));
210 MO_SET_OR_RET(s->set_explored_nodes, CbGetInt64(c, GRB_CB_MIPSOL_NODCNT));
211 MO_SET_OR_RET(s->set_number_of_solutions_found,
212 c.CbGetInt(GRB_CB_MIPSOL_SOLCNT));
213 std::vector<double> var_values(callback_input.num_gurobi_vars);
215 c.CbGetDoubleArray(GRB_CB_MIPSOL_SOL, absl::MakeSpan(var_values)))
216 << "Error reading solution at event MIP_SOLUTION";
217 *callback_data.mutable_primal_solution_vector() =
218 ApplyFilter(var_values, callback_input.variable_ids,
219 callback_input.mip_solution_filter);
220 break;
221 }
222 case GRB_CB_MIPNODE: {
223 callback_data.set_event(CALLBACK_EVENT_MIP_NODE);
224 CallbackDataProto::MipStats* const s = callback_data.mutable_mip_stats();
225 MO_SET_OR_RET(s->set_primal_bound, c.CbGetDouble(GRB_CB_MIPNODE_OBJBST));
226 MO_SET_OR_RET(s->set_dual_bound, c.CbGetDouble(GRB_CB_MIPNODE_OBJBND));
227 MO_SET_OR_RET(s->set_explored_nodes,
228 CbGetInt64(c, GRB_CB_MIPNODE_NODCNT));
229
230 MO_SET_OR_RET(s->set_number_of_solutions_found,
231 c.CbGetInt(GRB_CB_MIPNODE_SOLCNT));
232 const absl::StatusOr<int> grb_status = c.CbGetInt(GRB_CB_MIPNODE_STATUS);
233 RETURN_IF_ERROR(grb_status.status())
234 << "Error reading solution status at event MIP_NODE";
235 if (*grb_status == GRB_OPTIMAL) {
236 std::vector<double> var_values(callback_input.num_gurobi_vars);
238 c.CbGetDoubleArray(GRB_CB_MIPNODE_REL, absl::MakeSpan(var_values)))
239 << "Error reading solution at event MIP_NODE";
240 *callback_data.mutable_primal_solution_vector() =
241 ApplyFilter(var_values, callback_input.variable_ids,
242 callback_input.mip_node_filter);
243 // Note: Gurobi does not offer an objective value for the LP relaxation.
244 }
245 break;
246 }
247 default:
248 LOG(FATAL) << "Unknown gurobi callback code " << c.where();
249 }
250
251 RETURN_IF_ERROR(SetRuntime(callback_input, callback_data))
252 << "Error encoding runtime at callback event: " << c.where();
253
254 return callback_data;
255}
256
257#undef MO_SET_OR_RET
258
259absl::Status ApplyResult(const Gurobi::CallbackContext& context,
260 const GurobiCallbackInput& callback_input,
261 const CallbackResultProto& result,
262 SolveInterrupter& local_interrupter) {
263 for (const CallbackResultProto::GeneratedLinearConstraint& cut :
264 result.cuts()) {
265 std::vector<int> gurobi_vars;
266 gurobi_vars.reserve(cut.linear_expression().ids_size());
267 for (const int64_t id : cut.linear_expression().ids()) {
268 gurobi_vars.push_back(callback_input.variable_ids.at(id));
269 }
270 std::vector<std::pair<char, double>> sense_bound_pairs;
271 if (cut.lower_bound() == cut.upper_bound()) {
272 sense_bound_pairs.emplace_back(GRB_EQUAL, cut.upper_bound());
273 } else {
274 if (cut.upper_bound() < kInf) {
275 sense_bound_pairs.emplace_back(GRB_LESS_EQUAL, cut.upper_bound());
276 }
277 if (cut.lower_bound() > -kInf) {
278 sense_bound_pairs.emplace_back(GRB_GREATER_EQUAL, cut.lower_bound());
279 }
280 }
281 for (const auto [sense, bound] : sense_bound_pairs) {
282 if (cut.is_lazy()) {
284 gurobi_vars, cut.linear_expression().values(), sense, bound));
285 } else {
287 gurobi_vars, cut.linear_expression().values(), sense, bound));
288 }
289 }
290 }
291 for (const SparseDoubleVectorProto& solution_vector :
292 result.suggested_solutions()) {
293 // TODO(b/175829773): we cannot fill in auxiliary variables from range
294 // constraints.
295 std::vector<double> gurobi_var_values(callback_input.num_gurobi_vars,
297 for (const auto [id, value] : MakeView(solution_vector)) {
298 gurobi_var_values[callback_input.variable_ids.at(id)] = value;
299 }
300 RETURN_IF_ERROR(context.CbSolution(gurobi_var_values).status());
301 }
302
303 if (result.terminate()) {
304 local_interrupter.Interrupt();
305 return absl::OkStatus();
306 }
307 return absl::OkStatus();
308}
309
310} // namespace
311
312std::vector<bool> EventToGurobiWhere(
313 const absl::flat_hash_set<CallbackEventProto>& events) {
314 std::vector<bool> result(kNumGurobiEvents);
315 for (const auto event : events) {
316 result[GurobiEvent(event)] = true;
317 }
318 return result;
319}
320
322 const GurobiCallbackInput& callback_input,
323 MessageCallbackData& message_callback_data,
324 SolveInterrupter* const local_interrupter) {
325 // Gurobi 9 ignores early calls to GRBterminate(). For example calling
326 // GRBterminate() in the first call of a MESSAGE callback only will not
327 // interrupt the solve. The rationale is that it is likely Gurobi resets its
328 // own internal "terminated" flag at the beginning of the solve but do make
329 // some callbacks calls first.
330 //
331 // Hence here we make sure to call GRBterminate() for every event once the
332 // interrupter has been triggered. This in particular includes POLLING which
333 // is regularly emitted by Gurobi during the solve.
334 if (local_interrupter != nullptr && local_interrupter->IsInterrupted()) {
335 context.gurobi()->Terminate();
336 }
337
338 // The POLLING event is a way for interactive applications that uses Gurobi
339 // but don't want to deal with threading to regain some kind of interactivity
340 // while a long solve is running by being called back from time to time. No
341 // data can be retrieved from this event. This event if thus not wrapped by
342 // MathOpt.
343 if (context.where() == GRB_CB_POLLING) {
344 return absl::OkStatus();
345 }
346 if (context.where() == GRB_CB_MESSAGE) {
347 if (callback_input.message_cb) {
348 const absl::StatusOr<std::string> msg = context.CbGetMessage();
349 RETURN_IF_ERROR(msg.status())
350 << "Error getting message string in callback";
351 const std::vector<std::string> lines = message_callback_data.Parse(*msg);
352 if (!lines.empty()) {
353 callback_input.message_cb(lines);
354 }
355 }
356 return absl::OkStatus();
357 }
358
359 if (callback_input.user_cb == nullptr ||
360 !callback_input.events[context.where()]) {
361 return absl::OkStatus();
362 }
363 // At this point we know we have a user callback, thus we must have a local
364 // interrupter to deal with termination.
365 CHECK(local_interrupter != nullptr);
366
368 const std::optional<CallbackDataProto> callback_data,
369 CreateCallbackDataProto(context, callback_input, message_callback_data));
370 if (!callback_data) {
371 return absl::OkStatus();
372 }
373 const absl::StatusOr<CallbackResultProto> result =
374 callback_input.user_cb(*callback_data);
375 if (!result.ok()) {
376 local_interrupter->Interrupt();
377 return result.status();
378 }
380 ApplyResult(context, callback_input, *result, *local_interrupter));
381 return absl::OkStatus();
382}
383
385 MessageCallbackData& message_callback_data) {
386 const std::vector<std::string> lines = message_callback_data.Flush();
387 if (lines.empty()) {
388 return;
389 }
390
391 // Here we know that message_callback_data has only been filled-in if
392 // message_cb was not nullptr. Hence it is safe to make this call without
393 // testing.
394 callback_input.message_cb(lines);
395}
396
397} // namespace math_opt
398} // namespace operations_research
#define CHECK(condition)
Definition: base/logging.h:495
#define LOG(severity)
Definition: base/logging.h:420
std::vector< std::string > Parse(std::string_view message)
int64_t value
#define GRB_CB_MIP_NODLFT
Definition: environment.h:343
#define GRB_CB_SPX_ISPERT
Definition: environment.h:337
#define GRB_CB_MIPSOL
Definition: environment.h:324
#define GRB_CB_SPX_OBJVAL
Definition: environment.h:334
#define GRB_CB_BARRIER
Definition: environment.h:327
#define GRB_CB_PRE_COECHG
Definition: environment.h:332
#define GRB_GREATER_EQUAL
Definition: environment.h:97
#define GRB_CB_MIPSOL_OBJBST
Definition: environment.h:348
#define GRB_CB_MIP_ITRCNT
Definition: environment.h:344
#define GRB_CB_MIPSOL_SOLCNT
Definition: environment.h:351
#define GRB_CB_SPX_PRIMINF
Definition: environment.h:335
#define GRB_OPTIMAL
Definition: environment.h:457
#define GRB_CB_MIPNODE_REL
Definition: environment.h:354
#define GRB_CB_MIPNODE_OBJBND
Definition: environment.h:356
#define GRB_CB_SIMPLEX
Definition: environment.h:322
#define GRB_CB_MIPNODE_OBJBST
Definition: environment.h:355
#define GRB_CB_SPX_ITRCNT
Definition: environment.h:333
#define GRB_CB_BARRIER_COMPL
Definition: environment.h:368
#define GRB_CB_MIPSOL_NODCNT
Definition: environment.h:350
#define GRB_CB_MIPNODE_STATUS
Definition: environment.h:353
#define GRB_CB_PRE_ROWDEL
Definition: environment.h:329
#define GRB_CB_BARRIER_PRIMOBJ
Definition: environment.h:364
#define GRB_CB_PRESOLVE
Definition: environment.h:321
#define GRB_CB_MIPNODE_SOLCNT
Definition: environment.h:358
#define GRB_CB_MIP
Definition: environment.h:323
#define GRB_CB_SPX_DUALINF
Definition: environment.h:336
#define GRB_EQUAL
Definition: environment.h:98
#define GRB_CB_MIP_OBJBST
Definition: environment.h:338
#define GRB_CB_MIPNODE
Definition: environment.h:325
#define GRB_CB_POLLING
Definition: environment.h:320
#define GRB_CB_BARRIER_DUALINF
Definition: environment.h:367
#define GRB_CB_BARRIER_ITRCNT
Definition: environment.h:363
#define GRB_CB_MESSAGE
Definition: environment.h:326
#define GRB_CB_MIP_CUTCNT
Definition: environment.h:342
#define GRB_LESS_EQUAL
Definition: environment.h:96
#define GRB_CB_BARRIER_DUALOBJ
Definition: environment.h:365
#define GRB_CB_MIP_SOLCNT
Definition: environment.h:341
#define GRB_CB_PRE_COLDEL
Definition: environment.h:328
#define GRB_CB_MIP_NODCNT
Definition: environment.h:340
#define GRB_UNDEFINED
Definition: environment.h:109
#define GRB_CB_MIPSOL_SOL
Definition: environment.h:346
#define GRB_CB_MIPSOL_OBJBND
Definition: environment.h:349
#define GRB_CB_MIP_OBJBND
Definition: environment.h:339
#define GRB_CB_BARRIER_PRIMINF
Definition: environment.h:366
#define GRB_CB_PRE_BNDCHG
Definition: environment.h:331
#define GRB_CB_MIPNODE_NODCNT
Definition: environment.h:357
#define MO_SET_OR_RET(setter, statusor)
GurobiMPCallbackContext * context
int where
const int FATAL
Definition: log_severity.h:32
void GurobiCallbackImplFlush(const GurobiCallbackInput &callback_input, MessageCallbackData &message_callback_data)
absl::Status GurobiCallbackImpl(const Gurobi::CallbackContext &context, const GurobiCallbackInput &callback_input, MessageCallbackData &message_callback_data, SolveInterrupter *const local_interrupter)
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
std::vector< bool > EventToGurobiWhere(const absl::flat_hash_set< CallbackEventProto > &events)
Collection of objects used to extend the Constraint Solver library.
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
Definition: protoutil.h:27
int64_t bound
#define ASSIGN_OR_RETURN(lhs, rexpr)
Definition: status_macros.h:48
#define RETURN_IF_ERROR(expr)
Definition: status_macros.h:29
SolverInterface::MessageCallback message_cb