54 #include "absl/status/status.h"
55 #include "absl/strings/match.h"
56 #include "absl/strings/str_format.h"
68 "Number of threads available for Gurobi.");
85 const MPModelRequest& request)
override;
88 void Write(
const std::string& filename)
override;
92 void Reset()
override;
106 const MPVariable*
const variable,
double new_value,
107 double old_value)
override;
133 bool IsLP()
const override {
return !mip_; }
134 bool IsMIP()
const override {
return mip_; }
141 int major, minor, technical;
143 return absl::StrFormat(
"Gurobi library version %d.%d.%d\n", major, minor,
156 LOG(DFATAL) <<
"ComputeExactConditionNumber not implemented for"
157 <<
" GUROBI_MIXED_INTEGER_PROGRAMMING";
162 LOG(DFATAL) <<
"ComputeExactConditionNumber not implemented for"
163 <<
" GUROBI_LINEAR_PROGRAMMING";
197 bool SetSolverSpecificParametersAsString(
200 void SetRelativeMipGap(
double value)
override;
201 void SetPrimalTolerance(
double value)
override;
202 void SetDualTolerance(
double value)
override;
203 void SetPresolveMode(
int value)
override;
204 void SetScalingMode(
int value)
override;
205 void SetLpAlgorithm(
int value)
override;
207 bool ReadParameterFile(
const std::string& filename)
override;
208 std::string ValidFileExtensionForParameterFile()
const override;
211 int gurobi_basis_status)
const;
213 int gurobi_basis_status,
int constraint_index)
const;
216 bool ModelIsNonincremental()
const;
218 void SetIntAttr(
const char*
name,
int value);
219 int GetIntAttr(
const char*
name)
const;
220 void SetDoubleAttr(
const char*
name,
double value);
221 double GetDoubleAttr(
const char*
name)
const;
223 int GetIntAttrElement(
const char*
name,
int index)
const;
224 void SetDoubleAttrElement(
const char*
name,
int index,
double value);
225 double GetDoubleAttrElement(
const char*
name,
int index)
const;
226 std::vector<double> GetDoubleAttrArray(
const char*
name,
int elements);
228 char GetCharAttrElement(
const char*
name,
int index)
const;
230 void CheckedGurobiCall(
int err)
const;
232 int SolutionCount()
const;
237 int current_solution_index_;
239 bool update_branching_priorities_ =
false;
245 std::vector<int> mp_var_to_gurobi_var_;
250 std::vector<int> mp_cons_to_gurobi_linear_cons_;
252 int num_gurobi_vars_ = 0;
255 int num_gurobi_linear_cons_ = 0;
257 bool had_nonincremental_change_ =
false;
262 void CheckedGurobiCall(
int err,
GRBenv*
const env) {
263 CHECK_EQ(0, err) <<
"Fatal error with code " << err <<
", due to "
268 struct GurobiInternalCallbackContext {
274 class GurobiMPCallbackContext :
public MPCallbackContext {
276 GurobiMPCallbackContext(
GRBenv* env,
277 const std::vector<int>* mp_var_to_gurobi_var,
278 int num_gurobi_vars,
bool might_add_cuts,
279 bool might_add_lazy_constraints);
283 bool CanQueryVariableValues()
override;
284 double VariableValue(
const MPVariable* variable)
override;
285 void AddCut(
const LinearRange& cutting_plane)
override;
286 void AddLazyConstraint(
const LinearRange& lazy_constraint)
override;
287 double SuggestSolution(
288 const absl::flat_hash_map<const MPVariable*, double>& solution)
override;
289 int64 NumExploredNodes()
override;
293 void UpdateFromGurobiState(
294 const GurobiInternalCallbackContext& gurobi_internal_context);
300 template <
typename T>
302 const GurobiInternalCallbackContext& gurobi_internal_context,
304 void CheckedGurobiCall(
int gurobi_error_code)
const;
306 template <
typename GRBConstra
intFunction>
307 void AddGeneratedConstraint(
const LinearRange& linear_range,
308 GRBConstraintFunction grb_constraint_function);
311 const std::vector<int>*
const mp_var_to_gurobi_var_;
312 const int num_gurobi_vars_;
314 const bool might_add_cuts_;
315 const bool might_add_lazy_constraints_;
318 GurobiInternalCallbackContext current_gurobi_internal_callback_context_;
319 bool variable_values_extracted_ =
false;
320 std::vector<double> gurobi_variable_values_;
323 void GurobiMPCallbackContext::CheckedGurobiCall(
int gurobi_error_code)
const {
324 ::operations_research::CheckedGurobiCall(gurobi_error_code, env_);
327 GurobiMPCallbackContext::GurobiMPCallbackContext(
328 GRBenv* env,
const std::vector<int>* mp_var_to_gurobi_var,
329 int num_gurobi_vars,
bool might_add_cuts,
bool might_add_lazy_constraints)
332 num_gurobi_vars_(num_gurobi_vars),
333 might_add_cuts_(might_add_cuts),
334 might_add_lazy_constraints_(might_add_lazy_constraints) {}
336 void GurobiMPCallbackContext::UpdateFromGurobiState(
337 const GurobiInternalCallbackContext& gurobi_internal_context) {
338 current_gurobi_internal_callback_context_ = gurobi_internal_context;
339 variable_values_extracted_ =
false;
342 int64 GurobiMPCallbackContext::NumExploredNodes() {
344 case MPCallbackEvent::kMipNode:
345 return static_cast<int64>(GurobiCallbackGet<double>(
347 case MPCallbackEvent::kMipSolution:
348 return static_cast<int64>(GurobiCallbackGet<double>(
351 LOG(
FATAL) <<
"Node count is supported only for callback events MIP_NODE "
352 "and MIP_SOL, but was requested at: "
357 template <
typename T>
358 T GurobiMPCallbackContext::GurobiCallbackGet(
359 const GurobiInternalCallbackContext& gurobi_internal_context,
360 const int callback_code) {
363 GRBcbget(gurobi_internal_context.gurobi_internal_callback_data,
364 gurobi_internal_context.where, callback_code,
365 static_cast<void*
>(&result)));
370 switch (current_gurobi_internal_callback_context_.where) {
372 return MPCallbackEvent::kPolling;
374 return MPCallbackEvent::kPresolve;
376 return MPCallbackEvent::kSimplex;
378 return MPCallbackEvent::kMip;
380 return MPCallbackEvent::kMipSolution;
382 return MPCallbackEvent::kMipNode;
384 return MPCallbackEvent::kMessage;
386 return MPCallbackEvent::kBarrier;
392 << current_gurobi_internal_callback_context_.where;
393 return MPCallbackEvent::kUnknown;
397 bool GurobiMPCallbackContext::CanQueryVariableValues() {
399 if (
where == MPCallbackEvent::kMipSolution) {
402 if (
where == MPCallbackEvent::kMipNode) {
403 const int gurobi_node_status = GurobiCallbackGet<int>(
410 double GurobiMPCallbackContext::VariableValue(
const MPVariable* variable) {
411 CHECK(variable !=
nullptr);
412 if (!variable_values_extracted_) {
414 CHECK(
where == MPCallbackEvent::kMipSolution ||
415 where == MPCallbackEvent::kMipNode)
416 <<
"You can only call VariableValue at "
417 <<
ToString(MPCallbackEvent::kMipSolution) <<
" or "
418 <<
ToString(MPCallbackEvent::kMipNode)
420 const int gurobi_get_var_param =
where == MPCallbackEvent::kMipNode
424 gurobi_variable_values_.resize(num_gurobi_vars_);
426 current_gurobi_internal_callback_context_.gurobi_internal_callback_data,
427 current_gurobi_internal_callback_context_.where, gurobi_get_var_param,
428 static_cast<void*
>(gurobi_variable_values_.data())));
429 variable_values_extracted_ =
true;
431 return gurobi_variable_values_[mp_var_to_gurobi_var_->at(variable->index())];
434 template <
typename GRBConstra
intFunction>
435 void GurobiMPCallbackContext::AddGeneratedConstraint(
436 const LinearRange& linear_range,
437 GRBConstraintFunction grb_constraint_function) {
438 std::vector<int> variable_indices;
439 std::vector<double> variable_coefficients;
440 const int num_terms = linear_range.linear_expr().terms().size();
441 variable_indices.reserve(num_terms);
442 variable_coefficients.reserve(num_terms);
443 for (
const auto& var_coef_pair : linear_range.linear_expr().terms()) {
444 variable_indices.push_back(
445 mp_var_to_gurobi_var_->at(var_coef_pair.first->index()));
446 variable_coefficients.push_back(var_coef_pair.second);
448 if (std::isfinite(linear_range.upper_bound())) {
449 CheckedGurobiCall(grb_constraint_function(
450 current_gurobi_internal_callback_context_.gurobi_internal_callback_data,
451 variable_indices.size(), variable_indices.data(),
453 linear_range.upper_bound()));
455 if (std::isfinite(linear_range.lower_bound())) {
456 CheckedGurobiCall(grb_constraint_function(
457 current_gurobi_internal_callback_context_.gurobi_internal_callback_data,
458 variable_indices.size(), variable_indices.data(),
460 linear_range.lower_bound()));
464 void GurobiMPCallbackContext::AddCut(
const LinearRange& cutting_plane) {
465 CHECK(might_add_cuts_);
468 <<
"Cuts can only be added at MIP_NODE, tried to add cut at: "
470 AddGeneratedConstraint(cutting_plane,
GRBcbcut);
473 void GurobiMPCallbackContext::AddLazyConstraint(
474 const LinearRange& lazy_constraint) {
475 CHECK(might_add_lazy_constraints_);
478 where == MPCallbackEvent::kMipSolution)
479 <<
"Lazy constraints can only be added at MIP_NODE or MIP_SOL, tried to "
480 "add lazy constraint at: "
482 AddGeneratedConstraint(lazy_constraint,
GRBcblazy);
485 double GurobiMPCallbackContext::SuggestSolution(
486 const absl::flat_hash_map<const MPVariable*, double>& solution) {
489 <<
"Feasible solutions can only be added at MIP_NODE, tried to add "
493 std::vector<double> full_solution(num_gurobi_vars_,
GRB_UNDEFINED);
494 for (
const auto& variable_value : solution) {
495 const MPVariable*
var = variable_value.first;
496 full_solution[mp_var_to_gurobi_var_->at(
var->index())] =
497 variable_value.second;
502 current_gurobi_internal_callback_context_.gurobi_internal_callback_data,
503 full_solution.data(), &objval));
508 struct MPCallbackWithGurobiContext {
516 int where,
void* raw_model_and_callback) {
517 MPCallbackWithGurobiContext*
const callback_with_context =
518 static_cast<MPCallbackWithGurobiContext*
>(raw_model_and_callback);
519 CHECK(callback_with_context !=
nullptr);
520 CHECK(callback_with_context->context !=
nullptr);
521 CHECK(callback_with_context->callback !=
nullptr);
522 GurobiInternalCallbackContext gurobi_internal_context{
524 callback_with_context->context->UpdateFromGurobiState(
525 gurobi_internal_context);
526 callback_with_context->callback->RunCallback(callback_with_context->context);
532 void GurobiInterface::CheckedGurobiCall(
int err)
const {
533 ::operations_research::CheckedGurobiCall(err, env_);
536 void GurobiInterface::SetIntAttr(
const char*
name,
int value) {
540 int GurobiInterface::GetIntAttr(
const char*
name)
const {
546 void GurobiInterface::SetDoubleAttr(
const char*
name,
double value) {
550 double GurobiInterface::GetDoubleAttr(
const char*
name)
const {
556 void GurobiInterface::SetIntAttrElement(
const char*
name,
int index,
561 int GurobiInterface::GetIntAttrElement(
const char*
name,
int index)
const {
567 void GurobiInterface::SetDoubleAttrElement(
const char*
name,
int index,
571 double GurobiInterface::GetDoubleAttrElement(
const char*
name,
578 std::vector<double> GurobiInterface::GetDoubleAttrArray(
const char*
name,
580 std::vector<double> results(elements);
586 void GurobiInterface::SetCharAttrElement(
const char*
name,
int index,
590 char GurobiInterface::GetCharAttrElement(
const char*
name,
int index)
const {
597 GurobiInterface::GurobiInterface(
MPSolver*
const solver,
bool mip)
602 current_solution_index_(0) {
613 absl::GetFlag(FLAGS_num_gurobi_threads)));
633 mp_var_to_gurobi_var_.clear();
634 mp_cons_to_gurobi_linear_cons_.clear();
635 num_gurobi_vars_ = 0;
636 num_gurobi_linear_cons_ = 0;
637 had_nonincremental_change_ =
false;
648 SetDoubleAttrElement(
GRB_DBL_ATTR_LB, mp_var_to_gurobi_var_.at(var_index),
650 SetDoubleAttrElement(
GRB_DBL_ATTR_UB, mp_var_to_gurobi_var_.at(var_index),
676 had_nonincremental_change_ =
true;
693 had_nonincremental_change_ =
true;
704 double new_value,
double old_value) {
709 int grb_var = mp_var_to_gurobi_var_.at(variable->
index());
710 int grb_cons = mp_cons_to_gurobi_linear_cons_.at(constraint->
index());
712 had_nonincremental_change_ =
true;
717 GRBchgcoeffs(model_, 1, &grb_cons, &grb_var, &new_value));
725 had_nonincremental_change_ =
true;
737 mp_var_to_gurobi_var_.at(variable->
index()),
746 if (!had_nonincremental_change_) {
755 if (!had_nonincremental_change_) {
757 for (
const auto& entry :
solver_->objective_->coefficients_) {
766 update_branching_priorities_ =
true;
775 return static_cast<int64>(iter);
783 LOG(DFATAL) <<
"Number of nodes only available for discrete problems.";
789 int gurobi_basis_status)
const {
790 switch (gurobi_basis_status) {
800 LOG(DFATAL) <<
"Unknown GRB basis status.";
806 int gurobi_basis_status,
int constraint_index)
const {
807 const int grb_index = mp_cons_to_gurobi_linear_cons_.at(constraint_index);
809 LOG(DFATAL) <<
"Basis status not available for nonlinear constraints.";
812 switch (gurobi_basis_status) {
817 double tolerance = 0.0;
822 VLOG(4) <<
"constraint " << constraint_index <<
" , slack = " << slack
823 <<
" , sense = " << sense;
824 if (fabs(slack) <= tolerance) {
845 LOG(DFATAL) <<
"Basis status only available after a solution has "
850 LOG(DFATAL) <<
"Basis status only available for continuous problems.";
853 const int grb_index = mp_cons_to_gurobi_linear_cons_.at(constraint_index);
855 LOG(DFATAL) <<
"Basis status not available for nonlinear constraints.";
858 const int gurobi_basis_status =
860 return TransformGRBConstraintBasisStatus(gurobi_basis_status,
868 LOG(DFATAL) <<
"Basis status only available after a solution has "
873 LOG(DFATAL) <<
"Basis status only available for continuous problems.";
876 const int grb_index = mp_var_to_gurobi_var_.at(variable_index);
877 const int gurobi_basis_status =
879 return TransformGRBVarBasisStatus(gurobi_basis_status);
884 const int total_num_vars =
solver_->variables_.size();
896 var->name().empty() ?
nullptr :
var->name().c_str()));
897 mp_var_to_gurobi_var_.push_back(num_gurobi_vars_++);
901 std::vector<int> grb_cons_ind;
902 std::vector<int> grb_var_ind;
903 std::vector<double>
coef;
910 const int grb_ct_idx = mp_cons_to_gurobi_linear_cons_.at(
ct->index());
912 DCHECK(
ct->indicator_variable() ==
nullptr);
913 for (
const auto& entry :
ct->coefficients_) {
914 const int var_index = entry.first->index();
918 grb_cons_ind.push_back(grb_ct_idx);
919 grb_var_ind.push_back(mp_var_to_gurobi_var_.at(var_index));
920 coef.push_back(entry.second);
924 if (!grb_cons_ind.empty()) {
925 CheckedGurobiCall(
GRBchgcoeffs(model_, grb_cons_ind.size(),
926 grb_cons_ind.data(), grb_var_ind.data(),
935 int total_num_rows =
solver_->constraints_.size();
941 const int size =
ct->coefficients_.size();
942 std::vector<int> grb_vars;
943 std::vector<double> coefs;
944 grb_vars.reserve(size);
946 for (
const auto& entry :
ct->coefficients_) {
947 const int var_index = entry.first->index();
949 grb_vars.push_back(mp_var_to_gurobi_var_.at(var_index));
950 coefs.push_back(entry.second);
953 ct->name().empty() ? nullptr :
const_cast<char*
>(
ct->name().c_str());
954 if (
ct->indicator_variable() !=
nullptr) {
955 const int grb_ind_var =
956 mp_var_to_gurobi_var_.at(
ct->indicator_variable()->index());
957 if (
ct->lb() > -std::numeric_limits<double>::infinity()) {
959 model_,
name, grb_ind_var,
ct->indicator_value(), size,
960 grb_vars.data(), coefs.data(),
963 if (
ct->ub() < std::numeric_limits<double>::infinity() &&
964 ct->lb() !=
ct->ub()) {
966 model_,
name, grb_ind_var,
ct->indicator_value(), size,
969 mp_cons_to_gurobi_linear_cons_.push_back(-1);
973 if (
ct->lb() ==
ct->ub()) {
974 CheckedGurobiCall(
GRBaddconstr(model_, size, grb_vars.data(),
977 }
else if (
ct->lb() == -std::numeric_limits<double>::infinity()) {
978 CheckedGurobiCall(
GRBaddconstr(model_, size, grb_vars.data(),
981 }
else if (
ct->ub() == std::numeric_limits<double>::infinity()) {
982 CheckedGurobiCall(
GRBaddconstr(model_, size, grb_vars.data(),
987 coefs.data(),
ct->lb(),
ct->ub(),
993 mp_cons_to_gurobi_linear_cons_.push_back(num_gurobi_linear_cons_++);
1015 bool GurobiInterface::SetSolverSpecificParametersAsString(
1020 void GurobiInterface::SetRelativeMipGap(
double value) {
1025 LOG(
WARNING) <<
"The relative MIP gap is only available "
1026 <<
"for discrete problems.";
1036 void GurobiInterface::SetPrimalTolerance(
double value) {
1047 void GurobiInterface::SetDualTolerance(
double value) {
1052 void GurobiInterface::SetPresolveMode(
int value) {
1071 void GurobiInterface::SetScalingMode(
int value) {
1091 void GurobiInterface::SetLpAlgorithm(
int value) {
1111 int GurobiInterface::SolutionCount()
const {
1115 bool GurobiInterface::ModelIsNonincremental()
const {
1117 if (c->indicator_variable() !=
nullptr) {
1130 ModelIsNonincremental() || had_nonincremental_change_) {
1141 VLOG(1) << absl::StrFormat(
"Model built in %s.",
1145 for (
const std::pair<const MPVariable*, double>& p :
1148 mp_var_to_gurobi_var_.at(p.first->index()), p.second);
1152 if (update_branching_priorities_) {
1155 mp_var_to_gurobi_var_.at(
var->index()),
1156 var->branching_priority());
1158 update_branching_priorities_ =
false;
1173 SetParameters(param);
1175 solver_->solver_specific_parameter_string_);
1177 std::unique_ptr<GurobiMPCallbackContext> gurobi_context;
1178 MPCallbackWithGurobiContext mp_callback_with_context;
1179 int gurobi_precrush = 0;
1180 int gurobi_lazy_constraint = 0;
1181 if (callback_ ==
nullptr) {
1184 gurobi_context = absl::make_unique<GurobiMPCallbackContext>(
1185 env_, &mp_var_to_gurobi_var_, num_gurobi_vars_,
1187 mp_callback_with_context.context = gurobi_context.get();
1188 mp_callback_with_context.callback = callback_;
1190 model_, CallbackImpl,
static_cast<void*
>(&mp_callback_with_context)));
1206 VLOG(1) << absl::StrFormat(
"Solved in %s.",
1212 VLOG(1) << absl::StrFormat(
"Solution status %d.\n", optimization_status);
1213 const int solution_count = SolutionCount();
1215 switch (optimization_status) {
1231 if (solution_count > 0) {
1245 <<
"Best objective bound is not available, error=" << error
1252 current_solution_index_ = 0;
1258 const std::vector<double> grb_variable_values =
1260 for (
int i = 0; i <
solver_->variables_.size(); ++i) {
1262 const double val = grb_variable_values.at(mp_var_to_gurobi_var_.at(i));
1263 var->set_solution_value(val);
1264 VLOG(3) <<
var->name() <<
", value = " << val;
1269 const std::vector<double> grb_reduced_costs =
1271 for (
int i = 0; i <
solver_->variables_.size(); ++i) {
1273 const double rc = grb_reduced_costs.at(mp_var_to_gurobi_var_.at(i));
1274 var->set_reduced_cost(rc);
1275 VLOG(4) <<
var->name() <<
", reduced cost = " << rc;
1280 std::vector<double> grb_dual_values =
1282 for (
int i = 0; i <
solver_->constraints_.size(); ++i) {
1284 const double dual_value =
1285 grb_dual_values.at(mp_cons_to_gurobi_linear_cons_.at(i));
1286 ct->set_dual_value(dual_value);
1287 VLOG(4) <<
"row " <<
ct->index() <<
", dual value = " << dual_value;
1299 const MPModelRequest& request) {
1303 if (status_or.ok())
return status_or.value();
1306 if (absl::IsUnimplemented(status_or.status()))
return absl::nullopt;
1308 if (request.enable_internal_solver_output()) {
1309 LOG(
INFO) <<
"Invalid Gurobi status: " << status_or.status();
1313 response.set_status_str(status_or.status().ToString());
1319 if (!mip_)
return false;
1326 if (current_solution_index_ + 1 >= SolutionCount()) {
1329 current_solution_index_++;
1335 const std::vector<double> grb_variable_values =
1338 for (
int i = 0; i <
solver_->variables_.size(); ++i) {
1340 var->set_solution_value(
1341 grb_variable_values.at(mp_var_to_gurobi_var_.at(i)));
1355 VLOG(1) <<
"Writing Gurobi model file \"" << filename <<
"\".";
1356 const int status =
GRBwrite(model_, filename.c_str());
1362 bool GurobiInterface::ReadParameterFile(
const std::string& filename) {
1367 std::string GurobiInterface::ValidFileExtensionForParameterFile()
const {
1376 callback_ = mp_callback;