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 <string>
20#include <utility>
21#include <vector>
22
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"
42
44
45namespace operations_research {
46namespace math_opt {
47namespace {
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
52constexpr int kNumGurobiEvents = 9;
53constexpr int kGrbOk = 0;
54constexpr double kInf = std::numeric_limits<double>::infinity();
55
56template <int where>
57constexpr int CheckedGuroibWhere() {
58 static_assert(where >= 0 && where < kNumGurobiEvents);
59 return where;
60}
61
62inline 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
86absl::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
95SparseDoubleVectorProto 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
111class 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.
206absl::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.
214absl::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
348absl::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,
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
395std::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
404absl::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
#define LOG(severity)
Definition: base/logging.h:417
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
struct _GRBenv GRBenv
Definition: environment.h:25
#define GRB_CB_MSG_STRING
Definition: environment.h:361
#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
struct _GRBmodel GRBmodel
Definition: environment.h:24
#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_MIPSOL_OBJ
Definition: environment.h:347
#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)
GRBmodel * model
GurobiMPCallbackContext * context
int where
const int FATAL
Definition: log_severity.h:32
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
std::vector< bool > EventToGurobiWhere(const absl::flat_hash_set< CallbackEventProto > &events)
absl::Status GurobiCallbackImpl(GRBmodel *grb_model, void *cbdata, int where, const GurobiCallbackInput &callback_input, MessageCallbackData &message_callback_data)
absl::Status GurobiCallbackImplFlush(const GurobiCallbackInput &callback_input, MessageCallbackData &message_callback_data)
Collection of objects used to extend the Constraint Solver library.
std::function< int(void *cbdata, int lazylen, const int *lazyind, const double *lazyval, char lazysense, double lazyrhs)> GRBcblazy
Definition: environment.cc:159
std::function< int(void *cbdata, int where, int what, void *resultP)> GRBcbget
Definition: environment.cc:148
std::function< void(GRBmodel *model)> GRBterminate
Definition: environment.cc:350
std::function< const char *(GRBenv *env)> GRBgeterrormsg
Definition: environment.cc:427
std::function< GRBenv *(GRBmodel *model)> GRBgetenv
Definition: environment.cc:420
std::function< int(void *cbdata, const double *solution, double *objvalP)> GRBcbsolution
Definition: environment.cc:153
std::function< int(void *cbdata, int cutlen, const int *cutind, const double *cutval, char cutsense, double cutrhs)> GRBcbcut
Definition: environment.cc:156
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:55
#define RETURN_IF_ERROR(expr)
Definition: status_macros.h:29