OR-Tools  9.1
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 <string>
20 #include <utility>
21 #include <vector>
22 
23 #include "ortools/base/logging.h"
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/optional.h"
31 #include "absl/types/span.h"
33 #include "ortools/math_opt/callback.pb.h"
37 #include "ortools/math_opt/solution.pb.h"
39 #include "ortools/math_opt/sparse_containers.pb.h"
41 #include "ortools/base/protoutil.h"
42 
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 int kGrbOk = 0;
54 constexpr double kInf = std::numeric_limits<double>::infinity();
55 
56 template <int where>
57 constexpr int CheckedGuroibWhere() {
58  static_assert(where >= 0 && where < kNumGurobiEvents);
59  return where;
60 }
61 
62 inline int GurobiEvent(CallbackEventProto event) {
63  switch (event) {
64  case CALLBACK_EVENT_POLLING:
65  return CheckedGuroibWhere<GRB_CB_POLLING>();
66  case CALLBACK_EVENT_PRESOLVE:
67  return CheckedGuroibWhere<GRB_CB_PRESOLVE>();
68  case CALLBACK_EVENT_SIMPLEX:
69  return CheckedGuroibWhere<GRB_CB_SIMPLEX>();
70  case CALLBACK_EVENT_MIP:
71  return CheckedGuroibWhere<GRB_CB_MIP>();
72  case CALLBACK_EVENT_MIP_SOLUTION:
73  return CheckedGuroibWhere<GRB_CB_MIPSOL>();
74  case CALLBACK_EVENT_MIP_NODE:
75  return CheckedGuroibWhere<GRB_CB_MIPNODE>();
76  case CALLBACK_EVENT_BARRIER:
77  return CheckedGuroibWhere<GRB_CB_BARRIER>();
78  case CALLBACK_EVENT_MESSAGE:
79  return CheckedGuroibWhere<GRB_CB_MESSAGE>();
80  case CALLBACK_EVENT_UNSPECIFIED:
81  default:
82  LOG(FATAL) << "Unexpected callback event: " << event;
83  }
84 }
85 
86 absl::Status GurobiStatus(GRBmodel* model, int error_code) {
87  if (error_code == kGrbOk) {
88  return absl::OkStatus();
89  }
90  GRBenv* const env = GRBgetenv(model);
91  return absl::InternalError(
92  absl::StrCat("Gurobi error ", error_code, ": ", GRBgeterrormsg(env)));
93 }
94 
95 SparseDoubleVectorProto ApplyFilter(
96  const std::vector<double>& grb_solution,
98  const SparseVectorFilterProto& filter) {
99  SparseVectorFilterPredicate predicate(filter);
100  SparseDoubleVectorProto result;
101  for (const auto [id, grb_index] : var_ids) {
102  const double val = grb_solution[grb_index];
103  if (predicate.AcceptsAndUpdate(id, val)) {
104  result.add_ids(id);
105  result.add_values(val);
106  }
107  }
108  return result;
109 }
110 
111 class GurobiCallbackContext {
112  public:
113  GurobiCallbackContext(GRBmodel* model, void* cbdata, int where)
114  : model_(model), cbdata_(cbdata), where_(where) {}
115 
116  absl::StatusOr<int> get_int(int what) const {
117  int result;
118  RETURN_IF_ERROR(AsStatus(GRBcbget(cbdata_, where_, what, &result)));
119  return result;
120  }
121 
122  absl::StatusOr<double> get_double(int what) const {
123  double result;
124  RETURN_IF_ERROR(AsStatus(GRBcbget(cbdata_, where_, what, &result)));
125  return result;
126  }
127 
128  absl::StatusOr<int64_t> get_int64(int what) const {
129  double result;
130  RETURN_IF_ERROR(AsStatus(GRBcbget(cbdata_, where_, what, &result)));
131  int64_t result64 = static_cast<int64_t>(result);
132  if (result != static_cast<double>(result64)) {
133  return absl::InternalError(
134  absl::StrCat("Error converting double attribute: ", what,
135  "with value: ", result, " to int64_t exactly."));
136  }
137  return result64;
138  }
139 
140  absl::StatusOr<bool> get_bool(int what) const {
141  int result;
142  RETURN_IF_ERROR(AsStatus(GRBcbget(cbdata_, where_, what, &result)));
143  bool result_bool = static_cast<bool>(result);
144  if (result != static_cast<int>(result_bool)) {
145  return absl::InternalError(
146  absl::StrCat("Error converting int attribute: ", what,
147  "with value: ", result, " to bool exactly."));
148  }
149  return result_bool;
150  }
151 
152  absl::StatusOr<std::string> get_string(int what) const {
153  char* result;
154  RETURN_IF_ERROR(AsStatus(GRBcbget(cbdata_, where_, what, &result)));
155  return result;
156  }
157 
158  // The output argument doubles_out will be modified, it is the callers
159  // responsibility to ensure that it is large enough.
160  absl::Status get_doubles(int what, absl::Span<double> doubles_out) const {
161  double* const first = doubles_out.data();
162  RETURN_IF_ERROR(AsStatus(GRBcbget(cbdata_, where_, what, first)));
163  return absl::OkStatus();
164  }
165 
166  GRBmodel* grb_model() const { return model_; }
167  int where() const { return where_; }
168 
169  absl::Status AddConstraint(absl::Span<const int> vars,
170  absl::Span<const double> coefs, char sense,
171  double rhs, bool is_lazy) const {
172  auto cut_fn = is_lazy ? &GRBcblazy : &GRBcbcut;
173  return AsStatus((*cut_fn)(cbdata_, vars.size(), vars.begin(), coefs.begin(),
174  sense, rhs));
175  }
176 
177  absl::StatusOr<double> SuggestSolution(absl::Span<const double> coefs) const {
178  double obj_value;
180  AsStatus(GRBcbsolution(cbdata_, coefs.begin(), &obj_value)));
181  return obj_value;
182  }
183 
184  private:
185  absl::Status AsStatus(int error_code) const {
186  return GurobiStatus(model_, error_code);
187  }
188 
189  GRBmodel* const model_;
190  void* const cbdata_;
191  const int where_;
192 };
193 
194 // Invokes setter on a non-error value in statusor or returns the error.
195 //
196 // Requires that statusor is a StatusOr<T> and setter(T) is a function.
197 #define MO_SET_OR_RET(setter, statusor) \
198  do { \
199  auto eval_status_or = statusor; \
200  RETURN_IF_ERROR(eval_status_or.status()) << __FILE__ << ":" << __LINE__; \
201  setter(*std::move(eval_status_or)); \
202  } while (false)
203 
204 // Sets the CallbackDataProto.runtime field using the difference between the
205 // current wall clock time and the start time of the solve.
206 absl::Status SetRuntime(const GurobiCallbackInput& callback_input,
207  CallbackDataProto& callback_data) {
208  return util_time::EncodeGoogleApiProto(absl::Now() - callback_input.start,
209  callback_data.mutable_runtime());
210 }
211 
212 // Returns the data for the next callback. Returns nullopt if no callback is
213 // needed.
214 absl::StatusOr<absl::optional<CallbackDataProto>> CreateCallbackDataProto(
215  const GurobiCallbackContext& c, const GurobiCallbackInput& callback_input,
216  MessageCallbackData& message_callback_data) {
217  CallbackDataProto callback_data;
218 
219  // Query information from Gurobi.
220  switch (c.where()) {
221  case GRB_CB_POLLING: {
222  callback_data.set_event(CALLBACK_EVENT_POLLING);
223  break;
224  }
225  case GRB_CB_PRESOLVE: {
226  callback_data.set_event(CALLBACK_EVENT_PRESOLVE);
227  CallbackDataProto::PresolveStats* const s =
228  callback_data.mutable_presolve_stats();
229  MO_SET_OR_RET(s->set_removed_variables, c.get_int(GRB_CB_PRE_COLDEL));
230  MO_SET_OR_RET(s->set_removed_constraints, c.get_int(GRB_CB_PRE_ROWDEL));
231  MO_SET_OR_RET(s->set_bound_changes, c.get_int(GRB_CB_PRE_BNDCHG));
232  MO_SET_OR_RET(s->set_coefficient_changes, c.get_int(GRB_CB_PRE_COECHG));
233  break;
234  }
235  case GRB_CB_SIMPLEX: {
236  callback_data.set_event(CALLBACK_EVENT_SIMPLEX);
237  CallbackDataProto::SimplexStats* const s =
238  callback_data.mutable_simplex_stats();
239  MO_SET_OR_RET(s->set_iteration_count, c.get_int64(GRB_CB_SPX_ITRCNT));
240  MO_SET_OR_RET(s->set_is_pertubated, c.get_bool(GRB_CB_SPX_ISPERT));
241  MO_SET_OR_RET(s->set_objective_value, c.get_double(GRB_CB_SPX_OBJVAL));
242  MO_SET_OR_RET(s->set_primal_infeasibility,
243  c.get_double(GRB_CB_SPX_PRIMINF));
244  MO_SET_OR_RET(s->set_dual_infeasibility,
245  c.get_double(GRB_CB_SPX_DUALINF));
246  break;
247  }
248  case GRB_CB_BARRIER: {
249  callback_data.set_event(CALLBACK_EVENT_BARRIER);
250  CallbackDataProto::BarrierStats* const s =
251  callback_data.mutable_barrier_stats();
252  MO_SET_OR_RET(s->set_iteration_count, c.get_int(GRB_CB_BARRIER_ITRCNT));
253  MO_SET_OR_RET(s->set_primal_objective,
254  c.get_double(GRB_CB_BARRIER_PRIMOBJ));
255  MO_SET_OR_RET(s->set_dual_objective,
256  c.get_double(GRB_CB_BARRIER_DUALOBJ));
257  MO_SET_OR_RET(s->set_primal_infeasibility,
258  c.get_double(GRB_CB_BARRIER_PRIMINF));
259  MO_SET_OR_RET(s->set_dual_infeasibility,
260  c.get_double(GRB_CB_BARRIER_DUALINF));
261  MO_SET_OR_RET(s->set_complementarity, c.get_double(GRB_CB_BARRIER_COMPL));
262  break;
263  }
264  case GRB_CB_MESSAGE: {
265  const absl::StatusOr<std::string> msg = c.get_string(GRB_CB_MSG_STRING);
266  RETURN_IF_ERROR(msg.status())
267  << "Error getting message string in callback";
268  absl::optional<CallbackDataProto> message_data =
269  message_callback_data.Parse(*msg);
270  if (!message_data) {
271  // We don't generate any callback when there is no message.
272  return absl::nullopt;
273  }
274  callback_data = std::move(*message_data);
275  break;
276  }
277  case GRB_CB_MIP: {
278  callback_data.set_event(CALLBACK_EVENT_MIP);
279  CallbackDataProto::MipStats* const s = callback_data.mutable_mip_stats();
280  MO_SET_OR_RET(s->set_primal_bound, c.get_double(GRB_CB_MIP_OBJBST));
281  MO_SET_OR_RET(s->set_dual_bound, c.get_double(GRB_CB_MIP_OBJBND));
282  MO_SET_OR_RET(s->set_explored_nodes, c.get_int64(GRB_CB_MIP_NODCNT));
283  MO_SET_OR_RET(s->set_open_nodes, c.get_int64(GRB_CB_MIP_NODLFT));
284  MO_SET_OR_RET(s->set_simplex_iterations, c.get_int64(GRB_CB_MIP_ITRCNT));
285  MO_SET_OR_RET(s->set_number_of_solutions_found,
286  c.get_int(GRB_CB_MIP_SOLCNT));
287  MO_SET_OR_RET(s->set_cutting_planes_in_lp, c.get_int(GRB_CB_MIP_CUTCNT));
288 
289  break;
290  }
291  case GRB_CB_MIPSOL: {
292  callback_data.set_event(CALLBACK_EVENT_MIP_SOLUTION);
293  CallbackDataProto::MipStats* const s = callback_data.mutable_mip_stats();
294  MO_SET_OR_RET(s->set_primal_bound, c.get_double(GRB_CB_MIPSOL_OBJBST));
295  MO_SET_OR_RET(s->set_dual_bound, c.get_double(GRB_CB_MIPSOL_OBJBND));
296  MO_SET_OR_RET(s->set_explored_nodes, c.get_int64(GRB_CB_MIPSOL_NODCNT));
297  MO_SET_OR_RET(s->set_number_of_solutions_found,
298  c.get_int(GRB_CB_MIPSOL_SOLCNT));
299  std::vector<double> var_values(callback_input.num_gurobi_vars);
301  c.get_doubles(GRB_CB_MIPSOL_SOL, absl::MakeSpan(var_values)))
302  << "Error reading solution at event MIP_SOLUTION";
303  PrimalSolutionProto* const solution =
304  callback_data.mutable_primal_solution();
305  *solution->mutable_variable_values() =
306  ApplyFilter(var_values, callback_input.variable_ids,
307  callback_input.mip_solution_filter);
308  MO_SET_OR_RET(solution->set_objective_value,
309  c.get_double(GRB_CB_MIPSOL_OBJ));
310  break;
311  }
312  case GRB_CB_MIPNODE: {
313  callback_data.set_event(CALLBACK_EVENT_MIP_NODE);
314  CallbackDataProto::MipStats* const s = callback_data.mutable_mip_stats();
315  MO_SET_OR_RET(s->set_primal_bound, c.get_double(GRB_CB_MIPNODE_OBJBST));
316  MO_SET_OR_RET(s->set_dual_bound, c.get_double(GRB_CB_MIPNODE_OBJBND));
317  MO_SET_OR_RET(s->set_explored_nodes, c.get_int64(GRB_CB_MIPNODE_NODCNT));
318 
319  MO_SET_OR_RET(s->set_number_of_solutions_found,
320  c.get_int(GRB_CB_MIPNODE_SOLCNT));
321  const absl::StatusOr<int> grb_status = c.get_int(GRB_CB_MIPNODE_STATUS);
322  RETURN_IF_ERROR(grb_status.status())
323  << "Error reading solution status at event MIP_NODE";
324  if (*grb_status == GRB_OPTIMAL) {
325  std::vector<double> var_values(callback_input.num_gurobi_vars);
327  c.get_doubles(GRB_CB_MIPNODE_REL, absl::MakeSpan(var_values)))
328  << "Error reading solution at event MIP_NODE";
329  *callback_data.mutable_primal_solution()->mutable_variable_values() =
330  ApplyFilter(var_values, callback_input.variable_ids,
331  callback_input.mip_node_filter);
332  // Note: Gurobi does not offer an objective value for the LP relaxation.
333  }
334  break;
335  }
336  default:
337  LOG(FATAL) << "Unknown gurobi callback code " << c.where();
338  }
339 
340  RETURN_IF_ERROR(SetRuntime(callback_input, callback_data))
341  << "Error encoding runtime at callback event: " << c.where();
342 
343  return callback_data;
344 }
345 
346 #undef MO_SET_OR_RET
347 
348 absl::Status ApplyResult(const GurobiCallbackContext& context,
349  const GurobiCallbackInput& callback_input,
350  const CallbackResultProto& result) {
351  for (const CallbackResultProto::GeneratedLinearConstraint& cut :
352  result.cuts()) {
353  std::vector<int> gurobi_vars;
354  gurobi_vars.reserve(cut.linear_expression().ids_size());
355  for (const int64_t id : cut.linear_expression().ids()) {
356  gurobi_vars.push_back(callback_input.variable_ids.at(id));
357  }
358  std::vector<std::pair<char, double>> sense_bound_pairs;
359  if (cut.lower_bound() == cut.upper_bound()) {
360  sense_bound_pairs.emplace_back(GRB_EQUAL, cut.upper_bound());
361  } else {
362  if (cut.upper_bound() < kInf) {
363  sense_bound_pairs.emplace_back(GRB_LESS_EQUAL, cut.upper_bound());
364  }
365  if (cut.lower_bound() > -kInf) {
366  sense_bound_pairs.emplace_back(GRB_GREATER_EQUAL, cut.lower_bound());
367  }
368  }
369  for (const auto [sense, bound] : sense_bound_pairs) {
370  RETURN_IF_ERROR(context.AddConstraint(gurobi_vars,
371  cut.linear_expression().values(),
372  sense, bound, cut.is_lazy()));
373  }
374  }
375  for (const PrimalSolutionProto& solution : result.suggested_solution()) {
376  // TODO(b/175829773): we cannot fill in auxiliary variables from range
377  // constraints.
378  std::vector<double> gurobi_var_values(callback_input.num_gurobi_vars,
379  GRB_UNDEFINED);
380  for (const auto [id, value] : MakeView(solution.variable_values())) {
381  gurobi_var_values[callback_input.variable_ids.at(id)] = value;
382  }
383  RETURN_IF_ERROR(context.SuggestSolution(gurobi_var_values).status());
384  }
385 
386  if (result.terminate()) {
387  GRBterminate(context.grb_model());
388  return absl::OkStatus();
389  }
390  return absl::OkStatus();
391 }
392 
393 } // namespace
394 
395 std::vector<bool> EventToGurobiWhere(
396  const absl::flat_hash_set<CallbackEventProto>& events) {
397  std::vector<bool> result(kNumGurobiEvents);
398  for (const auto event : events) {
399  result[GurobiEvent(event)] = true;
400  }
401  return result;
402 }
403 
404 absl::Status GurobiCallbackImpl(GRBmodel* grb_model, void* cbdata, int where,
405  const GurobiCallbackInput& callback_input,
406  MessageCallbackData& message_callback_data) {
407  if (callback_input.user_cb == nullptr || !callback_input.events[where]) {
408  return absl::OkStatus();
409  }
410  const GurobiCallbackContext cb_context(grb_model, cbdata, where);
411  ASSIGN_OR_RETURN(const absl::optional<CallbackDataProto> callback_data,
412  CreateCallbackDataProto(cb_context, callback_input,
413  message_callback_data));
414  if (!callback_data) {
415  return absl::OkStatus();
416  }
417  const absl::StatusOr<CallbackResultProto> result =
418  callback_input.user_cb(*callback_data);
419  if (!result.ok()) {
420  GRBterminate(grb_model);
421  return result.status();
422  }
423  RETURN_IF_ERROR(ApplyResult(cb_context, callback_input, *result));
424  return absl::OkStatus();
425 }
426 
428  const GurobiCallbackInput& callback_input,
429  MessageCallbackData& message_callback_data) {
430  absl::optional<CallbackDataProto> callback_data =
431  message_callback_data.Flush();
432  if (!callback_data) {
433  return absl::OkStatus();
434  }
435 
436  RETURN_IF_ERROR(SetRuntime(callback_input, *callback_data))
437  << "Error encoding runtime when flushing the remaining callbacks";
438 
439  // No need to terminate here, we are already done. On top of that we are after
440  // the solve, so nothing in the CallbackResultProto matters.
441  return callback_input.user_cb(*callback_data).status();
442 }
443 
444 } // namespace math_opt
445 } // namespace operations_research
std::function< const char *(GRBenv *env)> GRBgeterrormsg
Definition: environment.cc:426
#define GRB_CB_MIPSOL
Definition: environment.h:324
int64_t bound
#define GRB_CB_PRESOLVE
Definition: environment.h:321
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
#define GRB_CB_MIPNODE_SOLCNT
Definition: environment.h:358
#define GRB_CB_PRE_COECHG
Definition: environment.h:332
#define GRB_GREATER_EQUAL
Definition: environment.h:97
#define GRB_CB_MIPNODE_OBJBND
Definition: environment.h:356
#define GRB_CB_MIPSOL_OBJ
Definition: environment.h:347
const int FATAL
Definition: log_severity.h:32
#define GRB_CB_PRE_BNDCHG
Definition: environment.h:331
#define GRB_CB_MIP_CUTCNT
Definition: environment.h:342
#define GRB_CB_BARRIER_DUALOBJ
Definition: environment.h:365
std::function< GRBenv *(GRBmodel *model)> GRBgetenv
Definition: environment.cc:419
#define GRB_CB_MIP_NODCNT
Definition: environment.h:340
struct _GRBmodel GRBmodel
Definition: environment.h:24
std::function< int(void *cbdata, int lazylen, const int *lazyind, const double *lazyval, char lazysense, double lazyrhs)> GRBcblazy
Definition: environment.cc:158
#define LOG(severity)
Definition: base/logging.h:416
GRBmodel * model
#define GRB_CB_SPX_DUALINF
Definition: environment.h:336
#define GRB_CB_BARRIER
Definition: environment.h:327
#define GRB_CB_MESSAGE
Definition: environment.h:326
#define GRB_CB_BARRIER_DUALINF
Definition: environment.h:367
std::vector< bool > EventToGurobiWhere(const absl::flat_hash_set< CallbackEventProto > &events)
#define GRB_CB_MIPNODE_NODCNT
Definition: environment.h:357
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
Definition: protoutil.h:27
#define GRB_CB_PRE_ROWDEL
Definition: environment.h:329
#define GRB_CB_MIPSOL_NODCNT
Definition: environment.h:350
#define GRB_CB_MIP_ITRCNT
Definition: environment.h:344
#define GRB_CB_SPX_ISPERT
Definition: environment.h:337
absl::Status GurobiCallbackImplFlush(const GurobiCallbackInput &callback_input, MessageCallbackData &message_callback_data)
#define GRB_CB_POLLING
Definition: environment.h:320
#define GRB_CB_BARRIER_COMPL
Definition: environment.h:368
std::function< int(void *cbdata, const double *solution, double *objvalP)> GRBcbsolution
Definition: environment.cc:152
std::function< int(void *cbdata, int cutlen, const int *cutind, const double *cutval, char cutsense, double cutrhs)> GRBcbcut
Definition: environment.cc:155
#define GRB_UNDEFINED
Definition: environment.h:109
struct _GRBenv GRBenv
Definition: environment.h:25
#define GRB_CB_MIP_NODLFT
Definition: environment.h:343
#define GRB_CB_SPX_ITRCNT
Definition: environment.h:333
#define GRB_CB_MIPSOL_OBJBND
Definition: environment.h:349
std::function< int(void *cbdata, int where, int what, void *resultP)> GRBcbget
Definition: environment.cc:147
std::function< void(GRBmodel *model)> GRBterminate
Definition: environment.cc:349
#define GRB_CB_BARRIER_PRIMINF
Definition: environment.h:366
#define GRB_CB_MIP_SOLCNT
Definition: environment.h:341
int where
#define GRB_CB_MSG_STRING
Definition: environment.h:361
#define GRB_CB_SIMPLEX
Definition: environment.h:322
#define GRB_CB_MIP_OBJBST
Definition: environment.h:338
absl::Status GurobiCallbackImpl(GRBmodel *grb_model, void *cbdata, int where, const GurobiCallbackInput &callback_input, MessageCallbackData &message_callback_data)
#define GRB_CB_PRE_COLDEL
Definition: environment.h:328
#define GRB_CB_MIPSOL_OBJBST
Definition: environment.h:348
#define GRB_CB_BARRIER_ITRCNT
Definition: environment.h:363
#define GRB_CB_MIPNODE_STATUS
Definition: environment.h:353
#define GRB_LESS_EQUAL
Definition: environment.h:96
#define GRB_CB_MIPSOL_SOL
Definition: environment.h:346
#define MO_SET_OR_RET(setter, statusor)
#define GRB_CB_MIPNODE_OBJBST
Definition: environment.h:355
#define GRB_CB_SPX_OBJVAL
Definition: environment.h:334
#define GRB_EQUAL
Definition: environment.h:98
Collection of objects used to extend the Constraint Solver library.
#define RETURN_IF_ERROR(expr)
Definition: status_macros.h:29
#define GRB_CB_BARRIER_PRIMOBJ
Definition: environment.h:364
#define GRB_CB_MIPNODE_REL
Definition: environment.h:354
#define GRB_CB_MIP_OBJBND
Definition: environment.h:339
GurobiMPCallbackContext * context
#define GRB_CB_MIPNODE
Definition: environment.h:325
int64_t value
#define GRB_OPTIMAL
Definition: environment.h:457
#define GRB_CB_MIP
Definition: environment.h:323
#define ASSIGN_OR_RETURN(lhs, rexpr)
Definition: status_macros.h:55
#define GRB_CB_MIPSOL_SOLCNT
Definition: environment.h:351
#define GRB_CB_SPX_PRIMINF
Definition: environment.h:335