23 #include "absl/status/status.h"
24 #include "absl/strings/match.h"
25 #include "absl/strings/str_format.h"
37 DEFINE_int32(num_gurobi_threads, 4,
"Number of threads available for Gurobi.");
54 const MPModelRequest& request)
override;
57 void Write(
const std::string& filename)
override;
61 void Reset()
override;
75 const MPVariable*
const variable,
double new_value,
76 double old_value)
override;
105 bool IsLP()
const override {
return !mip_; }
106 bool IsMIP()
const override {
return mip_; }
113 int major, minor, technical;
115 return absl::StrFormat(
"Gurobi library version %d.%d.%d\n", major, minor,
128 LOG(DFATAL) <<
"ComputeExactConditionNumber not implemented for"
129 <<
" GUROBI_MIXED_INTEGER_PROGRAMMING";
134 LOG(DFATAL) <<
"ComputeExactConditionNumber not implemented for"
135 <<
" GUROBI_LINEAR_PROGRAMMING";
159 void SetRelativeMipGap(
double value)
override;
160 void SetPrimalTolerance(
double value)
override;
161 void SetDualTolerance(
double value)
override;
162 void SetPresolveMode(
int value)
override;
163 void SetScalingMode(
int value)
override;
164 void SetLpAlgorithm(
int value)
override;
166 bool ReadParameterFile(
const std::string& filename)
override;
167 std::string ValidFileExtensionForParameterFile()
const override;
170 int gurobi_basis_status)
const;
172 int gurobi_basis_status,
int constraint_index)
const;
174 void CheckedGurobiCall(
int err)
const;
176 int SolutionCount()
const;
181 int current_solution_index_;
183 bool update_branching_priorities_ =
false;
188 void CheckedGurobiCall(
int err,
GRBenv*
const env) {
189 CHECK_EQ(0, err) <<
"Fatal error with code " << err <<
", due to "
194 struct GurobiInternalCallbackContext {
200 class GurobiMPCallbackContext :
public MPCallbackContext {
202 GurobiMPCallbackContext(
GRBenv* env,
bool might_add_cuts,
203 bool might_add_lazy_constraints);
207 bool CanQueryVariableValues()
override;
208 double VariableValue(
const MPVariable* variable)
override;
209 void AddCut(
const LinearRange& cutting_plane)
override;
210 void AddLazyConstraint(
const LinearRange& lazy_constraint)
override;
211 double SuggestSolution(
212 const absl::flat_hash_map<const MPVariable*, double>& solution)
override;
213 int64 NumExploredNodes()
override;
217 void UpdateFromGurobiState(
218 const GurobiInternalCallbackContext& gurobi_internal_context);
224 template <
typename T>
226 const GurobiInternalCallbackContext& gurobi_internal_context,
228 void CheckedGurobiCall(
int gurobi_error_code)
const;
230 template <
typename GRBConstra
intFunction>
231 void AddGeneratedConstraint(
const LinearRange& linear_range,
232 GRBConstraintFunction grb_constraint_function);
237 int NumGurobiVariables()
const;
241 const bool might_add_cuts_;
242 const bool might_add_lazy_constraints_;
245 GurobiInternalCallbackContext current_gurobi_internal_callback_context_;
246 bool variable_values_extracted_ =
false;
247 std::vector<double> variable_values_;
250 void GurobiMPCallbackContext::CheckedGurobiCall(
int gurobi_error_code)
const {
251 ::operations_research::CheckedGurobiCall(gurobi_error_code, env_);
254 GurobiMPCallbackContext::GurobiMPCallbackContext(
255 GRBenv* env,
bool might_add_cuts,
bool might_add_lazy_constraints)
257 might_add_cuts_(might_add_cuts),
258 might_add_lazy_constraints_(might_add_lazy_constraints) {}
260 void GurobiMPCallbackContext::UpdateFromGurobiState(
261 const GurobiInternalCallbackContext& gurobi_internal_context) {
262 current_gurobi_internal_callback_context_ = gurobi_internal_context;
263 variable_values_extracted_ =
false;
266 int64 GurobiMPCallbackContext::NumExploredNodes() {
268 case MPCallbackEvent::kMipNode:
269 return static_cast<int64>(GurobiCallbackGet<double>(
271 case MPCallbackEvent::kMipSolution:
272 return static_cast<int64>(GurobiCallbackGet<double>(
275 LOG(FATAL) <<
"Node count is supported only for callback events MIP_NODE "
276 "and MIP_SOL, but was requested at: "
281 template <
typename T>
282 T GurobiMPCallbackContext::GurobiCallbackGet(
283 const GurobiInternalCallbackContext& gurobi_internal_context,
284 const int callback_code) {
287 GRBcbget(gurobi_internal_context.gurobi_internal_callback_data,
288 gurobi_internal_context.where, callback_code,
289 static_cast<void*
>(&result)));
294 switch (current_gurobi_internal_callback_context_.where) {
296 return MPCallbackEvent::kPolling;
298 return MPCallbackEvent::kPresolve;
300 return MPCallbackEvent::kSimplex;
302 return MPCallbackEvent::kMip;
304 return MPCallbackEvent::kMipSolution;
306 return MPCallbackEvent::kMipNode;
308 return MPCallbackEvent::kMessage;
310 return MPCallbackEvent::kBarrier;
315 LOG_FIRST_N(ERROR, 1) <<
"Gurobi callback at unknown where="
316 << current_gurobi_internal_callback_context_.where;
317 return MPCallbackEvent::kUnknown;
321 bool GurobiMPCallbackContext::CanQueryVariableValues() {
323 if (
where == MPCallbackEvent::kMipSolution) {
326 if (
where == MPCallbackEvent::kMipNode) {
327 const int gurobi_node_status = GurobiCallbackGet<int>(
334 double GurobiMPCallbackContext::VariableValue(
const MPVariable* variable) {
335 CHECK(variable !=
nullptr);
336 if (!variable_values_extracted_) {
338 CHECK(
where == MPCallbackEvent::kMipSolution ||
339 where == MPCallbackEvent::kMipNode)
340 <<
"You can only call VariableValue at "
341 <<
ToString(MPCallbackEvent::kMipSolution) <<
" or "
342 <<
ToString(MPCallbackEvent::kMipNode)
344 const int gurobi_get_var_param =
where == MPCallbackEvent::kMipNode
348 variable_values_.resize(NumGurobiVariables());
350 current_gurobi_internal_callback_context_.gurobi_internal_callback_data,
351 current_gurobi_internal_callback_context_.where, gurobi_get_var_param,
352 static_cast<void*
>(variable_values_.data())));
353 variable_values_extracted_ =
true;
355 return variable_values_[variable->index()];
358 template <
typename GRBConstra
intFunction>
359 void GurobiMPCallbackContext::AddGeneratedConstraint(
360 const LinearRange& linear_range,
361 GRBConstraintFunction grb_constraint_function) {
362 std::vector<int> variable_indices;
363 std::vector<double> variable_coefficients;
364 const int num_terms = linear_range.linear_expr().terms().size();
365 variable_indices.reserve(num_terms);
366 variable_coefficients.reserve(num_terms);
367 for (
const auto& var_coef_pair : linear_range.linear_expr().terms()) {
368 variable_indices.push_back(var_coef_pair.first->index());
369 variable_coefficients.push_back(var_coef_pair.second);
371 if (std::isfinite(linear_range.upper_bound())) {
372 CheckedGurobiCall(grb_constraint_function(
373 current_gurobi_internal_callback_context_.gurobi_internal_callback_data,
374 variable_indices.size(), variable_indices.data(),
376 linear_range.upper_bound()));
378 if (std::isfinite(linear_range.lower_bound())) {
379 CheckedGurobiCall(grb_constraint_function(
380 current_gurobi_internal_callback_context_.gurobi_internal_callback_data,
381 variable_indices.size(), variable_indices.data(),
383 linear_range.lower_bound()));
387 void GurobiMPCallbackContext::AddCut(
const LinearRange& cutting_plane) {
388 CHECK(might_add_cuts_);
390 CHECK(
where == MPCallbackEvent::kMipNode)
391 <<
"Cuts can only be added at MIP_NODE, tried to add cut at: "
393 AddGeneratedConstraint(cutting_plane,
GRBcbcut);
396 void GurobiMPCallbackContext::AddLazyConstraint(
397 const LinearRange& lazy_constraint) {
398 CHECK(might_add_lazy_constraints_);
400 CHECK(
where == MPCallbackEvent::kMipNode ||
401 where == MPCallbackEvent::kMipSolution)
402 <<
"Lazy constraints can only be added at MIP_NODE or MIP_SOL, tried to "
403 "add lazy constraint at: "
405 AddGeneratedConstraint(lazy_constraint,
GRBcblazy);
408 double GurobiMPCallbackContext::SuggestSolution(
409 const absl::flat_hash_map<const MPVariable*, double>& solution) {
411 CHECK(
where == MPCallbackEvent::kMipNode)
412 <<
"Feasible solutions can only be added at MIP_NODE, tried to add "
416 std::vector<double> full_solution(NumGurobiVariables(),
GRB_UNDEFINED);
417 for (
const auto& variable_value : solution) {
418 const MPVariable*
var = variable_value.first;
419 full_solution[
var->index()] = variable_value.second;
424 current_gurobi_internal_callback_context_.gurobi_internal_callback_data,
425 full_solution.data(), &objval));
430 int GurobiMPCallbackContext::NumGurobiVariables()
const {
431 int num_gurobi_variables = 0;
433 GRBgetintattr(current_gurobi_internal_callback_context_.model,
"NumVars",
434 &num_gurobi_variables));
435 return num_gurobi_variables;
438 struct MPCallbackWithGurobiContext {
446 int where,
void* raw_model_and_callback) {
447 MPCallbackWithGurobiContext*
const callback_with_context =
448 static_cast<MPCallbackWithGurobiContext*
>(raw_model_and_callback);
449 CHECK(callback_with_context !=
nullptr);
450 CHECK(callback_with_context->context !=
nullptr);
451 CHECK(callback_with_context->callback !=
nullptr);
452 GurobiInternalCallbackContext gurobi_internal_context{
454 callback_with_context->context->UpdateFromGurobiState(
455 gurobi_internal_context);
456 callback_with_context->callback->RunCallback(callback_with_context->context);
462 void GurobiInterface::CheckedGurobiCall(
int err)
const {
463 ::operations_research::CheckedGurobiCall(err, env_);
467 GurobiInterface::GurobiInterface(
MPSolver*
const solver,
bool mip)
472 current_solution_index_(0) {
566 double new_value,
double old_value) {
592 update_branching_priorities_ =
true;
601 return static_cast<int64>(iter);
611 LOG(DFATAL) <<
"Number of nodes only available for discrete problems.";
628 if (
solver_->variables_.empty() &&
solver_->constraints_.empty()) {
640 CheckedGurobiCall(error);
643 LOG(DFATAL) <<
"Best objective bound only available for discrete problems.";
649 int gurobi_basis_status)
const {
650 switch (gurobi_basis_status) {
660 LOG(DFATAL) <<
"Unknown GRB basis status.";
666 int gurobi_basis_status,
int constraint_index)
const {
667 switch (gurobi_basis_status) {
673 double tolerance = 0.0;
677 constraint_index, &slack));
680 constraint_index, &sense));
681 VLOG(4) <<
"constraint " << constraint_index <<
" , slack = " << slack
682 <<
" , sense = " << sense;
683 if (fabs(slack) <= tolerance) {
702 int optim_status = 0;
705 LOG(DFATAL) <<
"Basis status only available after a solution has "
710 LOG(DFATAL) <<
"Basis status only available for continuous problems.";
713 int gurobi_basis_status = 0;
716 return TransformGRBConstraintBasisStatus(gurobi_basis_status,
722 int optim_status = 0;
725 LOG(DFATAL) <<
"Basis status only available after a solution has "
730 LOG(DFATAL) <<
"Basis status only available for continuous problems.";
733 int gurobi_basis_status = 0;
735 variable_index, &gurobi_basis_status));
736 return TransformGRBVarBasisStatus(gurobi_basis_status);
745 const int total_num_vars =
solver_->variables_.size();
748 std::unique_ptr<double[]> obj_coeffs(
new double[num_new_variables]);
749 std::unique_ptr<double[]> lb(
new double[num_new_variables]);
750 std::unique_ptr<double[]> ub(
new double[num_new_variables]);
751 std::unique_ptr<char[]> ctype(
new char[num_new_variables]);
752 std::unique_ptr<const char*[]> colname(
new const char*[num_new_variables]);
754 for (
int j = 0; j < num_new_variables; ++j) {
760 if (!
var->name().empty()) {
761 colname[j] =
var->name().c_str();
763 obj_coeffs[j] =
solver_->objective_->GetCoefficient(
var);
766 CheckedGurobiCall(
GRBaddvars(model_, num_new_variables, 0,
nullptr,
nullptr,
767 nullptr, obj_coeffs.get(), lb.get(), ub.get(),
769 const_cast<char**
>(colname.get())));
779 int total_num_rows =
solver_->constraints_.size();
782 int max_row_length = 0;
787 if (
ct->coefficients_.size() > max_row_length) {
788 max_row_length =
ct->coefficients_.size();
792 max_row_length =
std::max(1, max_row_length);
793 std::unique_ptr<int[]> col_indices(
new int[max_row_length]);
794 std::unique_ptr<double[]> coeffs(
new double[max_row_length]);
800 const int size =
ct->coefficients_.size();
802 for (
const auto& entry :
ct->coefficients_) {
803 const int var_index = entry.first->index();
805 col_indices[
col] = var_index;
806 coeffs[
col] = entry.second;
810 ct->name().empty() ? nullptr :
const_cast<char*
>(
ct->name().c_str());
811 if (
ct->indicator_variable() !=
nullptr) {
812 if (
ct->lb() > -std::numeric_limits<double>::infinity()) {
814 model_,
name,
ct->indicator_variable()->index(),
815 ct->indicator_value(), size, col_indices.get(), coeffs.get(),
818 if (
ct->ub() < std::numeric_limits<double>::infinity() &&
819 ct->lb() !=
ct->ub()) {
821 model_,
name,
ct->indicator_variable()->index(),
822 ct->indicator_value(), size, col_indices.get(), coeffs.get(),
828 if (
ct->lb() ==
ct->ub()) {
829 CheckedGurobiCall(
GRBaddconstr(model_, size, col_indices.get(),
832 }
else if (
ct->lb() == -std::numeric_limits<double>::infinity()) {
833 CheckedGurobiCall(
GRBaddconstr(model_, size, col_indices.get(),
836 }
else if (
ct->ub() == std::numeric_limits<double>::infinity()) {
837 CheckedGurobiCall(
GRBaddconstr(model_, size, col_indices.get(),
842 coeffs.get(),
ct->lb(),
ct->ub(),
867 void GurobiInterface::SetRelativeMipGap(
double value) {
872 LOG(WARNING) <<
"The relative MIP gap is only available "
873 <<
"for discrete problems.";
883 void GurobiInterface::SetPrimalTolerance(
double value) {
894 void GurobiInterface::SetDualTolerance(
double value) {
899 void GurobiInterface::SetPresolveMode(
int value) {
918 void GurobiInterface::SetScalingMode(
int value) {
938 void GurobiInterface::SetLpAlgorithm(
int value) {
958 int GurobiInterface::SolutionCount()
const {
959 int solution_count = 0;
962 return solution_count;
986 VLOG(1) << absl::StrFormat(
"Model built in %s.",
990 for (
const std::pair<const MPVariable*, double>& p :
997 if (update_branching_priorities_) {
1001 var->index(),
var->branching_priority()));
1003 update_branching_priorities_ =
false;
1018 SetParameters(param);
1020 solver_->solver_specific_parameter_string_);
1022 std::unique_ptr<GurobiMPCallbackContext> gurobi_context;
1023 MPCallbackWithGurobiContext mp_callback_with_context;
1024 int gurobi_precrush = 0;
1025 int gurobi_lazy_constraint = 0;
1026 if (callback_ ==
nullptr) {
1029 gurobi_context = absl::make_unique<GurobiMPCallbackContext>(
1032 mp_callback_with_context.context = gurobi_context.get();
1033 mp_callback_with_context.callback = callback_;
1035 model_, CallbackImpl,
static_cast<void*
>(&mp_callback_with_context)));
1051 VLOG(1) << absl::StrFormat(
"Solved in %s.",
1056 int optimization_status = 0;
1059 VLOG(1) << absl::StrFormat(
"Solution status %d.\n", optimization_status);
1060 const int solution_count = SolutionCount();
1062 switch (optimization_status) {
1078 if (solution_count > 0) {
1089 current_solution_index_ = 0;
1091 const int total_num_rows =
solver_->constraints_.size();
1092 const int total_num_cols =
solver_->variables_.size();
1095 std::vector<double> variable_values(total_num_cols);
1099 model_,
GRB_DBL_ATTR_X, 0, total_num_cols, variable_values.data()));
1102 for (
int i = 0; i <
solver_->variables_.size(); ++i) {
1104 var->set_solution_value(variable_values[i]);
1105 VLOG(3) <<
var->name() <<
", value = " << variable_values[i];
1110 std::vector<double> reduced_costs(total_num_cols);
1113 for (
int i = 0; i <
solver_->variables_.size(); ++i) {
1115 var->set_reduced_cost(reduced_costs[i]);
1116 VLOG(4) <<
var->name() <<
", reduced cost = " << reduced_costs[i];
1121 std::vector<double> dual_values(total_num_rows);
1124 for (
int i = 0; i <
solver_->constraints_.size(); ++i) {
1126 ct->set_dual_value(dual_values[i]);
1127 VLOG(4) <<
"row " <<
ct->index()
1128 <<
", dual value = " << dual_values[i];
1140 const MPModelRequest& request) {
1142 if (status_or.ok())
return status_or.value();
1145 if (absl::IsUnimplemented(status_or.status()))
return absl::nullopt;
1147 if (request.enable_internal_solver_output()) {
1148 LOG(INFO) <<
"Invalid Gurobi status: " << status_or.status();
1152 response.set_status_str(status_or.status().ToString());
1158 if (!mip_)
return false;
1165 if (current_solution_index_ + 1 >= SolutionCount()) {
1168 current_solution_index_++;
1170 const int total_num_cols =
solver_->variables_.size();
1171 std::vector<double> variable_values(total_num_cols);
1179 total_num_cols, variable_values.data()));
1180 for (
int i = 0; i <
solver_->variables_.size(); ++i) {
1182 var->set_solution_value(variable_values[i]);
1196 VLOG(1) <<
"Writing Gurobi model file \"" << filename <<
"\".";
1197 const int status =
GRBwrite(model_, filename.c_str());
1203 bool GurobiInterface::ReadParameterFile(
const std::string& filename) {
1208 std::string GurobiInterface::ValidFileExtensionForParameterFile()
const {
1218 callback_ = mp_callback;