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