Files
ortools-clone/ortools/linear_solver/gurobi_interface.cc
2025-07-18 08:11:03 +00:00

1419 lines
53 KiB
C++

// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Gurobi backend to MPSolver.
//
// Implementation Notes:
//
// Incrementalism (last updated June 29, 2020): For solving both LPs and MIPs,
// Gurobi attempts to reuse information from previous solves, potentially
// giving a faster solve time. MPSolver supports this for the following problem
// modification types:
// * Adding a variable,
// * Adding a linear constraint,
// * Updating a variable bound,
// * Updating an objective coefficient or the objective offset (note that in
// Gurobi 7.5 LP solver, there is a bug if you update only the objective
// offset and nothing else).
// * Updating a coefficient in the constraint matrix.
// * Updating the type of variable (integer, continuous)
// * Changing the optimization direction.
// Updates of the following types will force a resolve from scratch:
// * Updating the upper or lower bounds of a linear constraint. Note that in
// MPSolver's model, this includes updating the sense (le, ge, eq, range) of
// a linear constraint.
// * Clearing a constraint
// Any model containing indicator constraints is considered "non-incremental"
// and will always solve from scratch.
//
// The above limitations are largely due MPSolver and this file, not Gurobi.
//
// TODO(user): the interactions between callbacks and incrementalism are
// poorly tested, proceed with caution.
#include <algorithm>
#include <atomic>
#include <cmath>
#include <cstdint>
#include <iostream>
#include <limits>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include "absl/base/attributes.h"
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/flags/flag.h"
#include "absl/log/check.h"
#include "absl/log/die_if_null.h"
#include "absl/status/status.h"
#include "absl/strings/str_format.h"
#include "absl/synchronization/mutex.h"
#include "absl/time/time.h"
#include "ortools/base/logging.h"
#include "ortools/base/timer.h"
#include "ortools/linear_solver/gurobi_util.h"
#include "ortools/linear_solver/linear_solver.h"
#include "ortools/linear_solver/linear_solver_callback.h"
#include "ortools/linear_solver/proto_solver/gurobi_proto_solver.h"
#include "ortools/linear_solver/proto_solver/proto_utils.h"
#include "ortools/third_party_solvers/gurobi_environment.h"
#include "ortools/util/lazy_mutable_copy.h"
#include "ortools/util/time_limit.h"
ABSL_FLAG(int, num_gurobi_threads, 0,
"Number of threads available for Gurobi.");
namespace operations_research {
class GurobiInterface : public MPSolverInterface {
public:
// Constructor that takes a name for the underlying GRB solver.
explicit GurobiInterface(MPSolver* solver, bool mip);
~GurobiInterface() override;
// Sets the optimization direction (min/max).
void SetOptimizationDirection(bool maximize) override;
// ----- Solve -----
// Solves the problem using the parameter values specified.
MPSolver::ResultStatus Solve(const MPSolverParameters& param) override;
// ----- Directly solve proto is supported without interrupt ---
bool SupportsDirectlySolveProto(std::atomic<bool>* interrupt) const override {
return interrupt == nullptr;
}
MPSolutionResponse DirectlySolveProto(LazyMutableCopy<MPModelRequest> request,
std::atomic<bool>* interrupt) override {
DCHECK_EQ(interrupt, nullptr);
const bool log_error = request->enable_internal_solver_output();
// Here we reuse the Gurobi environment to support single-use license that
// forbids creating a second environment if one already exists.
return ConvertStatusOrMPSolutionResponse(
log_error, GurobiSolveProto(std::move(request), global_env_));
}
// Writes the model.
void Write(const std::string& filename) override;
// ----- Model modifications and extraction -----
// Resets extracted model
void Reset() override;
// Modifies bounds.
void SetVariableBounds(int var_index, double lb, double ub) override;
void SetVariableInteger(int var_index, bool integer) override;
void SetConstraintBounds(int row_index, double lb, double ub) override;
// Adds Constraint incrementally.
void AddRowConstraint(MPConstraint* ct) override;
bool AddIndicatorConstraint(MPConstraint* ct) override;
// Adds variable incrementally.
void AddVariable(MPVariable* var) override;
// Changes a coefficient in a constraint.
void SetCoefficient(MPConstraint* constraint, const MPVariable* variable,
double new_value, double old_value) override;
// Clears a constraint from all its terms.
void ClearConstraint(MPConstraint* constraint) override;
// Changes a coefficient in the linear objective
void SetObjectiveCoefficient(const MPVariable* variable,
double coefficient) override;
// Changes the constant term in the linear objective.
void SetObjectiveOffset(double value) override;
// Clears the objective from all its terms.
void ClearObjective() override;
void BranchingPriorityChangedForVariable(int var_index) override;
// ------ Query statistics on the solution and the solve ------
// Number of simplex or interior-point iterations
int64_t iterations() const override;
// Number of branch-and-bound nodes. Only available for discrete problems.
int64_t nodes() const override;
// Returns the basis status of a row.
MPSolver::BasisStatus row_status(int constraint_index) const override;
// Returns the basis status of a column.
MPSolver::BasisStatus column_status(int variable_index) const override;
// ----- Misc -----
// Queries problem type.
bool IsContinuous() const override { return IsLP(); }
bool IsLP() const override { return !mip_; }
bool IsMIP() const override { return mip_; }
void ExtractNewVariables() override;
void ExtractNewConstraints() override;
void ExtractObjective() override;
std::string SolverVersion() const override {
int major, minor, technical;
GRBversion(&major, &minor, &technical);
return absl::StrFormat("Gurobi library version %d.%d.%d\n", major, minor,
technical);
}
bool InterruptSolve() override {
const absl::MutexLock lock(&hold_interruptions_mutex_);
if (model_ != nullptr) GRBterminate(model_);
return true;
}
void* underlying_solver() override { return reinterpret_cast<void*>(model_); }
double ComputeExactConditionNumber() const override {
if (!IsContinuous()) {
LOG(DFATAL) << "ComputeExactConditionNumber not implemented for"
<< " GUROBI_MIXED_INTEGER_PROGRAMMING";
return 0.0;
}
// TODO(user): Not yet working.
LOG(DFATAL) << "ComputeExactConditionNumber not implemented for"
<< " GUROBI_LINEAR_PROGRAMMING";
return 0.0;
// double cond = 0.0;
// const int status = GRBgetdblattr(model_, GRB_DBL_ATTR_KAPPA, &cond);
// if (0 == status) {
// return cond;
// } else {
// LOG(DFATAL) << "Condition number only available for "
// << "continuous problems";
// return 0.0;
// }
}
// Iterates through the solutions in Gurobi's solution pool.
bool NextSolution() override;
void SetCallback(MPCallback* mp_callback) override;
bool SupportsCallbacks() const override { return true; }
private:
// Sets all parameters in the underlying solver.
void SetParameters(const MPSolverParameters& param) override;
// Sets solver-specific parameters (avoiding using files). The previous
// implementations supported multi-line strings of the form:
// parameter_i value_i\n
// We extend support for strings of the form:
// parameter1=value1,....,parametern=valuen
// or for strings of the form:
// parameter1 value1, ... ,parametern valuen
// which are easier to set in the command line.
// This implementations relies on SetSolverSpecificParameters, which has the
// extra benefit of unifying the way we handle specific parameters for both
// proto-based solves and for MPModel solves.
bool SetSolverSpecificParametersAsString(
const std::string& parameters) override;
// Sets each parameter in the underlying solver.
void SetRelativeMipGap(double value) override;
void SetPrimalTolerance(double value) override;
void SetDualTolerance(double value) override;
void SetPresolveMode(int value) override;
void SetScalingMode(int value) override;
void SetLpAlgorithm(int value) override;
MPSolver::BasisStatus TransformGRBVarBasisStatus(
int gurobi_basis_status) const;
MPSolver::BasisStatus TransformGRBConstraintBasisStatus(
int gurobi_basis_status, int constraint_index) const;
// See the implementation note at the top of file on incrementalism.
bool ModelIsNonincremental() const;
void SetIntAttr(const char* name, int value);
int GetIntAttr(const char* name) const;
void SetDoubleAttr(const char* name, double value);
double GetDoubleAttr(const char* name) const;
void SetIntAttrElement(const char* name, int index, int value);
int GetIntAttrElement(const char* name, int index) const;
void SetDoubleAttrElement(const char* name, int index, double value);
double GetDoubleAttrElement(const char* name, int index) const;
std::vector<double> GetDoubleAttrArray(const char* name, int elements);
void SetCharAttrElement(const char* name, int index, char value);
char GetCharAttrElement(const char* name, int index) const;
void CheckedGurobiCall(int err) const;
int SolutionCount() const;
GRBmodel* model_;
// The environment used to create model_. Note that once the model is created,
// it used a copy of the environment, accessible via GRBgetenv(model_), which
// you should use for setting parameters. Use global_env_ only to create a new
// model or for GRBgeterrormsg().
GRBenv* global_env_;
bool mip_;
int current_solution_index_;
MPCallback* callback_ = nullptr;
bool update_branching_priorities_ = false;
// Has length equal to the number of MPVariables in
// MPSolverInterface::solver_. Values are the index of the corresponding
// Gurobi variable. Note that Gurobi may have additional auxiliary variables
// not represented by MPVariables, such as those created by two-sided range
// constraints.
std::vector<int> mp_var_to_gurobi_var_;
// Has length equal to the number of MPConstraints in
// MPSolverInterface::solver_. Values are the index of the corresponding
// linear (or range) constraint in Gurobi, or -1 if no such constraint exists
// (e.g. for indicator constraints).
std::vector<int> mp_cons_to_gurobi_linear_cons_;
// Should match the Gurobi model after it is updated.
int num_gurobi_vars_ = 0;
// Should match the Gurobi model after it is updated.
// NOTE(user): indicator constraints are not counted below.
int num_gurobi_linear_cons_ = 0;
// See the implementation note at the top of file on incrementalism.
bool had_nonincremental_change_ = false;
// Mutex is held to prevent InterruptSolve() to call GRBterminate() when
// model_ is not completely built. It also prevents model_ to be changed
// during the execution of GRBterminate().
mutable absl::Mutex hold_interruptions_mutex_;
};
namespace {
constexpr int kGurobiOkCode = 0;
void CheckedGurobiCall(int err, GRBenv* const env) {
CHECK_EQ(kGurobiOkCode, err)
<< "Fatal error with code " << err << ", due to " << GRBgeterrormsg(env);
}
// For interacting directly with the Gurobi C API for callbacks.
struct GurobiInternalCallbackContext {
GRBmodel* model;
void* gurobi_internal_callback_data;
int where;
};
class GurobiMPCallbackContext : public MPCallbackContext {
public:
GurobiMPCallbackContext(GRBenv* env,
const std::vector<int>* mp_var_to_gurobi_var,
int num_gurobi_vars, bool might_add_cuts,
bool might_add_lazy_constraints);
// Implementation of the interface.
MPCallbackEvent Event() override;
bool CanQueryVariableValues() override;
double VariableValue(const MPVariable* variable) override;
void AddCut(const LinearRange& cutting_plane) override;
void AddLazyConstraint(const LinearRange& lazy_constraint) override;
double SuggestSolution(
const absl::flat_hash_map<const MPVariable*, double>& solution) override;
int64_t NumExploredNodes() override;
// Call this method to update the internal state of the callback context
// before passing it to MPCallback::RunCallback().
void UpdateFromGurobiState(
const GurobiInternalCallbackContext& gurobi_internal_context);
private:
// Wraps GRBcbget(), used to query the state of the solver. See
// http://www.gurobi.com/documentation/8.0/refman/callback_codes.html#sec:CallbackCodes
// for callback_code values.
template <typename T>
T GurobiCallbackGet(
const GurobiInternalCallbackContext& gurobi_internal_context,
int callback_code);
void CheckedGurobiCall(int gurobi_error_code) const;
template <typename GRBConstraintFunction>
void AddGeneratedConstraint(const LinearRange& linear_range,
GRBConstraintFunction grb_constraint_function);
GRBenv* const env_;
const std::vector<int>* const mp_var_to_gurobi_var_;
const int num_gurobi_vars_;
const bool might_add_cuts_;
const bool might_add_lazy_constraints_;
// Stateful, updated before each call to the callback.
GurobiInternalCallbackContext current_gurobi_internal_callback_context_;
bool variable_values_extracted_ = false;
std::vector<double> gurobi_variable_values_;
};
void GurobiMPCallbackContext::CheckedGurobiCall(int gurobi_error_code) const {
::operations_research::CheckedGurobiCall(gurobi_error_code, env_);
}
GurobiMPCallbackContext::GurobiMPCallbackContext(
GRBenv* env, const std::vector<int>* mp_var_to_gurobi_var,
int num_gurobi_vars, bool might_add_cuts, bool might_add_lazy_constraints)
: env_(ABSL_DIE_IF_NULL(env)),
mp_var_to_gurobi_var_(ABSL_DIE_IF_NULL(mp_var_to_gurobi_var)),
num_gurobi_vars_(num_gurobi_vars),
might_add_cuts_(might_add_cuts),
might_add_lazy_constraints_(might_add_lazy_constraints) {}
void GurobiMPCallbackContext::UpdateFromGurobiState(
const GurobiInternalCallbackContext& gurobi_internal_context) {
current_gurobi_internal_callback_context_ = gurobi_internal_context;
variable_values_extracted_ = false;
}
int64_t GurobiMPCallbackContext::NumExploredNodes() {
switch (Event()) {
case MPCallbackEvent::kMipNode:
return static_cast<int64_t>(GurobiCallbackGet<double>(
current_gurobi_internal_callback_context_, GRB_CB_MIPNODE_NODCNT));
case MPCallbackEvent::kMipSolution:
return static_cast<int64_t>(GurobiCallbackGet<double>(
current_gurobi_internal_callback_context_, GRB_CB_MIPSOL_NODCNT));
default:
LOG(FATAL) << "Node count is supported only for callback events MIP_NODE "
"and MIP_SOL, but was requested at: "
<< ToString(Event());
}
}
template <typename T>
T GurobiMPCallbackContext::GurobiCallbackGet(
const GurobiInternalCallbackContext& gurobi_internal_context,
const int callback_code) {
T result = 0;
CheckedGurobiCall(
GRBcbget(gurobi_internal_context.gurobi_internal_callback_data,
gurobi_internal_context.where, callback_code,
static_cast<void*>(&result)));
return result;
}
MPCallbackEvent GurobiMPCallbackContext::Event() {
switch (current_gurobi_internal_callback_context_.where) {
case GRB_CB_POLLING:
return MPCallbackEvent::kPolling;
case GRB_CB_PRESOLVE:
return MPCallbackEvent::kPresolve;
case GRB_CB_SIMPLEX:
return MPCallbackEvent::kSimplex;
case GRB_CB_MIP:
return MPCallbackEvent::kMip;
case GRB_CB_MIPSOL:
return MPCallbackEvent::kMipSolution;
case GRB_CB_MIPNODE:
return MPCallbackEvent::kMipNode;
case GRB_CB_MESSAGE:
return MPCallbackEvent::kMessage;
case GRB_CB_BARRIER:
return MPCallbackEvent::kBarrier;
// TODO(b/112427356): in Gurobi 8.0, there is a new callback location.
// case GRB_CB_MULTIOBJ:
// return MPCallbackEvent::kMultiObj;
default:
LOG_FIRST_N(ERROR, 1) << "Gurobi callback at unknown where="
<< current_gurobi_internal_callback_context_.where;
return MPCallbackEvent::kUnknown;
}
}
bool GurobiMPCallbackContext::CanQueryVariableValues() {
const MPCallbackEvent where = Event();
if (where == MPCallbackEvent::kMipSolution) {
return true;
}
if (where == MPCallbackEvent::kMipNode) {
const int gurobi_node_status = GurobiCallbackGet<int>(
current_gurobi_internal_callback_context_, GRB_CB_MIPNODE_STATUS);
return gurobi_node_status == GRB_OPTIMAL;
}
return false;
}
double GurobiMPCallbackContext::VariableValue(const MPVariable* variable) {
CHECK(variable != nullptr);
if (!variable_values_extracted_) {
const MPCallbackEvent where = Event();
CHECK(where == MPCallbackEvent::kMipSolution ||
where == MPCallbackEvent::kMipNode)
<< "You can only call VariableValue at "
<< ToString(MPCallbackEvent::kMipSolution) << " or "
<< ToString(MPCallbackEvent::kMipNode)
<< " but called from: " << ToString(where);
const int gurobi_get_var_param = where == MPCallbackEvent::kMipNode
? GRB_CB_MIPNODE_REL
: GRB_CB_MIPSOL_SOL;
gurobi_variable_values_.resize(num_gurobi_vars_);
CheckedGurobiCall(GRBcbget(
current_gurobi_internal_callback_context_.gurobi_internal_callback_data,
current_gurobi_internal_callback_context_.where, gurobi_get_var_param,
static_cast<void*>(gurobi_variable_values_.data())));
variable_values_extracted_ = true;
}
return gurobi_variable_values_[mp_var_to_gurobi_var_->at(variable->index())];
}
template <typename GRBConstraintFunction>
void GurobiMPCallbackContext::AddGeneratedConstraint(
const LinearRange& linear_range,
GRBConstraintFunction grb_constraint_function) {
std::vector<int> variable_indices;
std::vector<double> variable_coefficients;
const int num_terms = linear_range.linear_expr().terms().size();
variable_indices.reserve(num_terms);
variable_coefficients.reserve(num_terms);
for (const auto& var_coef_pair : linear_range.linear_expr().terms()) {
variable_indices.push_back(
mp_var_to_gurobi_var_->at(var_coef_pair.first->index()));
variable_coefficients.push_back(var_coef_pair.second);
}
if (std::isfinite(linear_range.upper_bound())) {
CheckedGurobiCall(grb_constraint_function(
current_gurobi_internal_callback_context_.gurobi_internal_callback_data,
variable_indices.size(), variable_indices.data(),
variable_coefficients.data(), GRB_LESS_EQUAL,
linear_range.upper_bound()));
}
if (std::isfinite(linear_range.lower_bound())) {
CheckedGurobiCall(grb_constraint_function(
current_gurobi_internal_callback_context_.gurobi_internal_callback_data,
variable_indices.size(), variable_indices.data(),
variable_coefficients.data(), GRB_GREATER_EQUAL,
linear_range.lower_bound()));
}
}
void GurobiMPCallbackContext::AddCut(const LinearRange& cutting_plane) {
CHECK(might_add_cuts_);
const MPCallbackEvent where = Event();
CHECK(where == MPCallbackEvent::kMipNode)
<< "Cuts can only be added at MIP_NODE, tried to add cut at: "
<< ToString(where);
AddGeneratedConstraint(cutting_plane, GRBcbcut);
}
void GurobiMPCallbackContext::AddLazyConstraint(
const LinearRange& lazy_constraint) {
CHECK(might_add_lazy_constraints_);
const MPCallbackEvent where = Event();
CHECK(where == MPCallbackEvent::kMipNode ||
where == MPCallbackEvent::kMipSolution)
<< "Lazy constraints can only be added at MIP_NODE or MIP_SOL, tried to "
"add lazy constraint at: "
<< ToString(where);
AddGeneratedConstraint(lazy_constraint, GRBcblazy);
}
double GurobiMPCallbackContext::SuggestSolution(
const absl::flat_hash_map<const MPVariable*, double>& solution) {
const MPCallbackEvent where = Event();
CHECK(where == MPCallbackEvent::kMipNode)
<< "Feasible solutions can only be added at MIP_NODE, tried to add "
"solution at: "
<< ToString(where);
std::vector<double> full_solution(num_gurobi_vars_, GRB_UNDEFINED);
for (const auto& variable_value : solution) {
const MPVariable* var = variable_value.first;
full_solution[mp_var_to_gurobi_var_->at(var->index())] =
variable_value.second;
}
double objval;
CheckedGurobiCall(GRBcbsolution(
current_gurobi_internal_callback_context_.gurobi_internal_callback_data,
full_solution.data(), &objval));
return objval;
}
struct MPCallbackWithGurobiContext {
GurobiMPCallbackContext* context;
MPCallback* callback;
};
// NOTE(user): This function must have this exact API, because we are passing
// it to Gurobi as a callback.
#if defined(_MSC_VER)
#define GUROBI_STDCALL __stdcall
#else
#define GUROBI_STDCALL
#endif
int GUROBI_STDCALL CallbackImpl(GRBmodel* model,
void* gurobi_internal_callback_data, int where,
void* raw_model_and_callback) {
MPCallbackWithGurobiContext* const callback_with_context =
static_cast<MPCallbackWithGurobiContext*>(raw_model_and_callback);
CHECK(callback_with_context != nullptr);
CHECK(callback_with_context->context != nullptr);
CHECK(callback_with_context->callback != nullptr);
GurobiInternalCallbackContext gurobi_internal_context{
model, gurobi_internal_callback_data, where};
callback_with_context->context->UpdateFromGurobiState(
gurobi_internal_context);
callback_with_context->callback->RunCallback(callback_with_context->context);
return 0;
}
} // namespace
void GurobiInterface::CheckedGurobiCall(int err) const {
::operations_research::CheckedGurobiCall(err, global_env_);
}
void GurobiInterface::SetIntAttr(const char* name, int value) {
CheckedGurobiCall(GRBsetintattr(model_, name, value));
}
int GurobiInterface::GetIntAttr(const char* name) const {
int value;
CheckedGurobiCall(GRBgetintattr(model_, name, &value));
return value;
}
void GurobiInterface::SetDoubleAttr(const char* name, double value) {
CheckedGurobiCall(GRBsetdblattr(model_, name, value));
}
double GurobiInterface::GetDoubleAttr(const char* name) const {
double value;
CheckedGurobiCall(GRBgetdblattr(model_, name, &value));
return value;
}
void GurobiInterface::SetIntAttrElement(const char* name, int index,
int value) {
CheckedGurobiCall(GRBsetintattrelement(model_, name, index, value));
}
int GurobiInterface::GetIntAttrElement(const char* name, int index) const {
int value;
CheckedGurobiCall(GRBgetintattrelement(model_, name, index, &value));
return value;
}
void GurobiInterface::SetDoubleAttrElement(const char* name, int index,
double value) {
CheckedGurobiCall(GRBsetdblattrelement(model_, name, index, value));
}
double GurobiInterface::GetDoubleAttrElement(const char* name,
int index) const {
double value;
CheckedGurobiCall(GRBgetdblattrelement(model_, name, index, &value));
return value;
}
std::vector<double> GurobiInterface::GetDoubleAttrArray(const char* name,
int elements) {
std::vector<double> results(elements);
CheckedGurobiCall(
GRBgetdblattrarray(model_, name, 0, elements, results.data()));
return results;
}
void GurobiInterface::SetCharAttrElement(const char* name, int index,
char value) {
CheckedGurobiCall(GRBsetcharattrelement(model_, name, index, value));
}
char GurobiInterface::GetCharAttrElement(const char* name, int index) const {
char value;
CheckedGurobiCall(GRBgetcharattrelement(model_, name, index, &value));
return value;
}
// Creates a LP/MIP instance with the specified name and minimization objective.
GurobiInterface::GurobiInterface(MPSolver* const solver, bool mip)
: MPSolverInterface(solver),
model_(nullptr),
global_env_(nullptr),
mip_(mip),
current_solution_index_(0) {
global_env_ = GetGurobiEnv().value();
CheckedGurobiCall(GRBnewmodel(global_env_, &model_, solver_->name_.c_str(),
0, // numvars
nullptr, // obj
nullptr, // lb
nullptr, // ub
nullptr, // vtype
nullptr)); // varnanes
SetIntAttr(GRB_INT_ATTR_MODELSENSE, maximize_ ? GRB_MAXIMIZE : GRB_MINIMIZE);
CheckedGurobiCall(
GRBsetintparam(GRBgetenv(model_), GRB_INT_PAR_OUTPUTFLAG, 0));
CheckedGurobiCall(GRBsetintparam(GRBgetenv(model_), GRB_INT_PAR_THREADS,
absl::GetFlag(FLAGS_num_gurobi_threads)));
}
GurobiInterface::~GurobiInterface() {
CheckedGurobiCall(GRBfreemodel(model_));
GRBfreeenv(global_env_);
}
// ------ Model modifications and extraction -----
void GurobiInterface::Reset() {
// We hold calls to GRBterminate() until the new model_ is ready.
const absl::MutexLock lock(&hold_interruptions_mutex_);
GRBmodel* old_model = model_;
CheckedGurobiCall(GRBnewmodel(global_env_, &model_, solver_->name_.c_str(),
0, // numvars
nullptr, // obj
nullptr, // lb
nullptr, // ub
nullptr, // vtype
nullptr)); // varnames
// Copy all existing parameters from the previous model to the new one. This
// ensures that if a user calls multiple times
// SetSolverSpecificParametersAsString() and then Reset() is called, we still
// take into account all parameters.
//
// The current code only reapplies the parameters stored in
// solver_specific_parameter_string_ at the start of the solve; other
// parameters set by previous calls are only kept in the Gurobi model.
//
// TODO - b/328604189: Fix logging issue upstream, switch to a different API
// for copying parameters, or avoid calling Reset() in more places.
CheckedGurobiCall(GRBcopyparams(GRBgetenv(model_), GRBgetenv(old_model)));
CheckedGurobiCall(GRBfreemodel(old_model));
old_model = nullptr;
ResetExtractionInformation();
mp_var_to_gurobi_var_.clear();
mp_cons_to_gurobi_linear_cons_.clear();
num_gurobi_vars_ = 0;
num_gurobi_linear_cons_ = 0;
had_nonincremental_change_ = false;
}
void GurobiInterface::SetOptimizationDirection(bool maximize) {
InvalidateSolutionSynchronization();
SetIntAttr(GRB_INT_ATTR_MODELSENSE, maximize_ ? GRB_MAXIMIZE : GRB_MINIMIZE);
}
void GurobiInterface::SetVariableBounds(int var_index, double lb, double ub) {
InvalidateSolutionSynchronization();
if (!had_nonincremental_change_ && variable_is_extracted(var_index)) {
SetDoubleAttrElement(GRB_DBL_ATTR_LB, mp_var_to_gurobi_var_.at(var_index),
lb);
SetDoubleAttrElement(GRB_DBL_ATTR_UB, mp_var_to_gurobi_var_.at(var_index),
ub);
} else {
sync_status_ = MUST_RELOAD;
}
}
void GurobiInterface::SetVariableInteger(int index, bool integer) {
InvalidateSolutionSynchronization();
if (!had_nonincremental_change_ && variable_is_extracted(index)) {
char type_var;
if (integer) {
type_var = GRB_INTEGER;
} else {
type_var = GRB_CONTINUOUS;
}
SetCharAttrElement(GRB_CHAR_ATTR_VTYPE, mp_var_to_gurobi_var_.at(index),
type_var);
} else {
sync_status_ = MUST_RELOAD;
}
}
void GurobiInterface::SetConstraintBounds(int index, double lb, double ub) {
sync_status_ = MUST_RELOAD;
if (constraint_is_extracted(index)) {
had_nonincremental_change_ = true;
}
// TODO(user): this is nontrivial to make incremental:
// 1. Make sure it is a linear constraint (not an indicator or indicator
// range constraint).
// 2. Check if the sense of the constraint changes. If it was previously a
// range constraint, we can do nothing, and if it becomes a range
// constraint, we can do nothing. We could support range constraints if
// we tracked the auxiliary variable that is added with range
// constraints.
}
void GurobiInterface::AddRowConstraint(MPConstraint* const ct) {
sync_status_ = MUST_RELOAD;
}
bool GurobiInterface::AddIndicatorConstraint(MPConstraint* const ct) {
had_nonincremental_change_ = true;
sync_status_ = MUST_RELOAD;
return !IsContinuous();
}
void GurobiInterface::AddVariable(MPVariable* const var) {
sync_status_ = MUST_RELOAD;
}
void GurobiInterface::SetCoefficient(MPConstraint* const constraint,
const MPVariable* const variable,
double new_value, double old_value) {
InvalidateSolutionSynchronization();
if (!had_nonincremental_change_ && variable_is_extracted(variable->index()) &&
constraint_is_extracted(constraint->index())) {
// Cannot be const, GRBchgcoeffs needs non-const pointer.
int grb_var = mp_var_to_gurobi_var_.at(variable->index());
int grb_cons = mp_cons_to_gurobi_linear_cons_.at(constraint->index());
if (grb_cons < 0) {
had_nonincremental_change_ = true;
sync_status_ = MUST_RELOAD;
} else {
// TODO(user): investigate if this has bad performance.
CheckedGurobiCall(
GRBchgcoeffs(model_, 1, &grb_cons, &grb_var, &new_value));
}
} else {
sync_status_ = MUST_RELOAD;
}
}
void GurobiInterface::ClearConstraint(MPConstraint* const constraint) {
had_nonincremental_change_ = true;
sync_status_ = MUST_RELOAD;
// TODO(user): this is difficult to make incremental, like
// SetConstraintBounds(), because of the auxiliary Gurobi variables that
// range constraints introduce.
}
void GurobiInterface::SetObjectiveCoefficient(const MPVariable* const variable,
double coefficient) {
InvalidateSolutionSynchronization();
if (!had_nonincremental_change_ && variable_is_extracted(variable->index())) {
SetDoubleAttrElement(GRB_DBL_ATTR_OBJ,
mp_var_to_gurobi_var_.at(variable->index()),
coefficient);
} else {
sync_status_ = MUST_RELOAD;
}
}
void GurobiInterface::SetObjectiveOffset(double value) {
InvalidateSolutionSynchronization();
if (!had_nonincremental_change_) {
SetDoubleAttr(GRB_DBL_ATTR_OBJCON, value);
} else {
sync_status_ = MUST_RELOAD;
}
}
void GurobiInterface::ClearObjective() {
InvalidateSolutionSynchronization();
if (!had_nonincremental_change_) {
SetObjectiveOffset(0.0);
for (const auto& entry : solver_->objective_->coefficients_) {
SetObjectiveCoefficient(entry.first, 0.0);
}
} else {
sync_status_ = MUST_RELOAD;
}
}
void GurobiInterface::BranchingPriorityChangedForVariable(int var_index) {
update_branching_priorities_ = true;
}
// ------ Query statistics on the solution and the solve ------
int64_t GurobiInterface::iterations() const {
double iter;
if (!CheckSolutionIsSynchronized()) return kUnknownNumberOfIterations;
CheckedGurobiCall(GRBgetdblattr(model_, GRB_DBL_ATTR_ITERCOUNT, &iter));
return static_cast<int64_t>(iter);
}
int64_t GurobiInterface::nodes() const {
if (mip_) {
if (!CheckSolutionIsSynchronized()) return kUnknownNumberOfNodes;
return static_cast<int64_t>(GetDoubleAttr(GRB_DBL_ATTR_NODECOUNT));
} else {
LOG(DFATAL) << "Number of nodes only available for discrete problems.";
return kUnknownNumberOfNodes;
}
}
MPSolver::BasisStatus GurobiInterface::TransformGRBVarBasisStatus(
int gurobi_basis_status) const {
switch (gurobi_basis_status) {
case GRB_BASIC:
return MPSolver::BASIC;
case GRB_NONBASIC_LOWER:
return MPSolver::AT_LOWER_BOUND;
case GRB_NONBASIC_UPPER:
return MPSolver::AT_UPPER_BOUND;
case GRB_SUPERBASIC:
return MPSolver::FREE;
default:
LOG(DFATAL) << "Unknown GRB basis status.";
return MPSolver::FREE;
}
}
MPSolver::BasisStatus GurobiInterface::TransformGRBConstraintBasisStatus(
int gurobi_basis_status, int constraint_index) const {
const int grb_index = mp_cons_to_gurobi_linear_cons_.at(constraint_index);
if (grb_index < 0) {
LOG(DFATAL) << "Basis status not available for nonlinear constraints.";
return MPSolver::FREE;
}
switch (gurobi_basis_status) {
case GRB_BASIC:
return MPSolver::BASIC;
default: {
// Non basic.
double tolerance = 0.0;
CheckedGurobiCall(GRBgetdblparam(GRBgetenv(model_),
GRB_DBL_PAR_FEASIBILITYTOL, &tolerance));
const double slack = GetDoubleAttrElement(GRB_DBL_ATTR_SLACK, grb_index);
const char sense = GetCharAttrElement(GRB_CHAR_ATTR_SENSE, grb_index);
VLOG(4) << "constraint " << constraint_index << " , slack = " << slack
<< " , sense = " << sense;
if (fabs(slack) <= tolerance) {
switch (sense) {
case GRB_EQUAL:
case GRB_LESS_EQUAL:
return MPSolver::AT_UPPER_BOUND;
case GRB_GREATER_EQUAL:
return MPSolver::AT_LOWER_BOUND;
default:
return MPSolver::FREE;
}
} else {
return MPSolver::FREE;
}
}
}
}
// Returns the basis status of a row.
MPSolver::BasisStatus GurobiInterface::row_status(int constraint_index) const {
const int optim_status = GetIntAttr(GRB_INT_ATTR_STATUS);
if (optim_status != GRB_OPTIMAL && optim_status != GRB_SUBOPTIMAL) {
LOG(DFATAL) << "Basis status only available after a solution has "
<< "been found.";
return MPSolver::FREE;
}
if (mip_) {
LOG(DFATAL) << "Basis status only available for continuous problems.";
return MPSolver::FREE;
}
const int grb_index = mp_cons_to_gurobi_linear_cons_.at(constraint_index);
if (grb_index < 0) {
LOG(DFATAL) << "Basis status not available for nonlinear constraints.";
return MPSolver::FREE;
}
const int gurobi_basis_status =
GetIntAttrElement(GRB_INT_ATTR_CBASIS, grb_index);
return TransformGRBConstraintBasisStatus(gurobi_basis_status,
constraint_index);
}
// Returns the basis status of a column.
MPSolver::BasisStatus GurobiInterface::column_status(int variable_index) const {
const int optim_status = GetIntAttr(GRB_INT_ATTR_STATUS);
if (optim_status != GRB_OPTIMAL && optim_status != GRB_SUBOPTIMAL) {
LOG(DFATAL) << "Basis status only available after a solution has "
<< "been found.";
return MPSolver::FREE;
}
if (mip_) {
LOG(DFATAL) << "Basis status only available for continuous problems.";
return MPSolver::FREE;
}
const int grb_index = mp_var_to_gurobi_var_.at(variable_index);
const int gurobi_basis_status =
GetIntAttrElement(GRB_INT_ATTR_VBASIS, grb_index);
return TransformGRBVarBasisStatus(gurobi_basis_status);
}
// Extracts new variables.
void GurobiInterface::ExtractNewVariables() {
const int total_num_vars = solver_->variables_.size();
if (total_num_vars > last_variable_index_) {
// Define new variables.
for (int j = last_variable_index_; j < total_num_vars; ++j) {
const MPVariable* const var = solver_->variables_.at(j);
set_variable_as_extracted(var->index(), true);
CheckedGurobiCall(GRBaddvar(
model_, 0, // numnz
nullptr, // vind
nullptr, // vval
solver_->objective_->GetCoefficient(var), var->lb(), var->ub(),
var->integer() && mip_ ? GRB_INTEGER : GRB_CONTINUOUS,
var->name().empty() ? nullptr : var->name().c_str()));
mp_var_to_gurobi_var_.push_back(num_gurobi_vars_++);
}
CheckedGurobiCall(GRBupdatemodel(model_));
// Add new variables to existing constraints.
std::vector<int> grb_cons_ind;
std::vector<int> grb_var_ind;
std::vector<double> coef;
for (int i = 0; i < last_constraint_index_; ++i) {
// If there was a nonincremental change/the model is not incremental (e.g.
// there is an indicator constraint), we should never enter this loop, as
// last_variable_index_ will be reset to zero before ExtractNewVariables()
// is called.
MPConstraint* const ct = solver_->constraints_[i];
const int grb_ct_idx = mp_cons_to_gurobi_linear_cons_.at(ct->index());
DCHECK_GE(grb_ct_idx, 0);
DCHECK(ct->indicator_variable() == nullptr);
for (const auto& entry : ct->coefficients_) {
const int var_index = entry.first->index();
DCHECK(variable_is_extracted(var_index));
if (var_index >= last_variable_index_) {
grb_cons_ind.push_back(grb_ct_idx);
grb_var_ind.push_back(mp_var_to_gurobi_var_.at(var_index));
coef.push_back(entry.second);
}
}
}
if (!grb_cons_ind.empty()) {
CheckedGurobiCall(GRBchgcoeffs(model_, grb_cons_ind.size(),
grb_cons_ind.data(), grb_var_ind.data(),
coef.data()));
}
}
CheckedGurobiCall(GRBupdatemodel(model_));
DCHECK_EQ(GetIntAttr(GRB_INT_ATTR_NUMVARS), num_gurobi_vars_);
}
void GurobiInterface::ExtractNewConstraints() {
int total_num_rows = solver_->constraints_.size();
if (last_constraint_index_ < total_num_rows) {
// Add each new constraint.
for (int row = last_constraint_index_; row < total_num_rows; ++row) {
MPConstraint* const ct = solver_->constraints_[row];
set_constraint_as_extracted(row, true);
const int size = ct->coefficients_.size();
std::vector<int> grb_vars;
std::vector<double> coefs;
grb_vars.reserve(size);
coefs.reserve(size);
for (const auto& entry : ct->coefficients_) {
const int var_index = entry.first->index();
CHECK(variable_is_extracted(var_index));
grb_vars.push_back(mp_var_to_gurobi_var_.at(var_index));
coefs.push_back(entry.second);
}
char* const name =
ct->name().empty() ? nullptr : const_cast<char*>(ct->name().c_str());
if (ct->indicator_variable() != nullptr) {
const int grb_ind_var =
mp_var_to_gurobi_var_.at(ct->indicator_variable()->index());
if (ct->lb() > -std::numeric_limits<double>::infinity()) {
CheckedGurobiCall(GRBaddgenconstrIndicator(
model_, name, grb_ind_var, ct->indicator_value(), size,
grb_vars.data(), coefs.data(),
ct->ub() == ct->lb() ? GRB_EQUAL : GRB_GREATER_EQUAL, ct->lb()));
}
if (ct->ub() < std::numeric_limits<double>::infinity() &&
ct->lb() != ct->ub()) {
CheckedGurobiCall(GRBaddgenconstrIndicator(
model_, name, grb_ind_var, ct->indicator_value(), size,
grb_vars.data(), coefs.data(), GRB_LESS_EQUAL, ct->ub()));
}
mp_cons_to_gurobi_linear_cons_.push_back(-1);
} else {
// Using GRBaddrangeconstr for constraints that don't require it adds
// a slack which is not always removed by presolve.
if (ct->lb() == ct->ub()) {
CheckedGurobiCall(GRBaddconstr(model_, size, grb_vars.data(),
coefs.data(), GRB_EQUAL, ct->lb(),
name));
} else if (ct->lb() == -std::numeric_limits<double>::infinity()) {
CheckedGurobiCall(GRBaddconstr(model_, size, grb_vars.data(),
coefs.data(), GRB_LESS_EQUAL, ct->ub(),
name));
} else if (ct->ub() == std::numeric_limits<double>::infinity()) {
CheckedGurobiCall(GRBaddconstr(model_, size, grb_vars.data(),
coefs.data(), GRB_GREATER_EQUAL,
ct->lb(), name));
} else {
CheckedGurobiCall(GRBaddrangeconstr(model_, size, grb_vars.data(),
coefs.data(), ct->lb(), ct->ub(),
name));
// NOTE(user): range constraints implicitly add an extra variable
// to the model.
num_gurobi_vars_++;
}
mp_cons_to_gurobi_linear_cons_.push_back(num_gurobi_linear_cons_++);
}
}
}
CheckedGurobiCall(GRBupdatemodel(model_));
DCHECK_EQ(GetIntAttr(GRB_INT_ATTR_NUMCONSTRS), num_gurobi_linear_cons_);
}
void GurobiInterface::ExtractObjective() {
SetIntAttr(GRB_INT_ATTR_MODELSENSE, maximize_ ? GRB_MAXIMIZE : GRB_MINIMIZE);
SetDoubleAttr(GRB_DBL_ATTR_OBJCON, solver_->Objective().offset());
}
// ------ Parameters -----
void GurobiInterface::SetParameters(const MPSolverParameters& param) {
SetCommonParameters(param);
if (mip_) {
SetMIPParameters(param);
}
}
bool GurobiInterface::SetSolverSpecificParametersAsString(
const std::string& parameters) {
return SetSolverSpecificParameters(parameters, GRBgetenv(model_)).ok();
}
void GurobiInterface::SetRelativeMipGap(double value) {
if (mip_) {
CheckedGurobiCall(
GRBsetdblparam(GRBgetenv(model_), GRB_DBL_PAR_MIPGAP, value));
} else {
LOG(WARNING) << "The relative MIP gap is only available "
<< "for discrete problems.";
}
}
// Gurobi has two different types of primal tolerance (feasibility tolerance):
// constraint and integrality. We need to set them both.
// See:
// http://www.gurobi.com/documentation/6.0/refman/feasibilitytol.html
// and
// http://www.gurobi.com/documentation/6.0/refman/intfeastol.html
void GurobiInterface::SetPrimalTolerance(double value) {
CheckedGurobiCall(
GRBsetdblparam(GRBgetenv(model_), GRB_DBL_PAR_FEASIBILITYTOL, value));
CheckedGurobiCall(
GRBsetdblparam(GRBgetenv(model_), GRB_DBL_PAR_INTFEASTOL, value));
}
// As opposed to primal (feasibility) tolerance, the dual (optimality) tolerance
// applies only to the reduced costs in the improving direction.
// See:
// http://www.gurobi.com/documentation/6.0/refman/optimalitytol.html
void GurobiInterface::SetDualTolerance(double value) {
CheckedGurobiCall(
GRBsetdblparam(GRBgetenv(model_), GRB_DBL_PAR_OPTIMALITYTOL, value));
}
void GurobiInterface::SetPresolveMode(int value) {
switch (value) {
case MPSolverParameters::PRESOLVE_OFF: {
CheckedGurobiCall(
GRBsetintparam(GRBgetenv(model_), GRB_INT_PAR_PRESOLVE, false));
break;
}
case MPSolverParameters::PRESOLVE_ON: {
CheckedGurobiCall(
GRBsetintparam(GRBgetenv(model_), GRB_INT_PAR_PRESOLVE, true));
break;
}
default: {
SetIntegerParamToUnsupportedValue(MPSolverParameters::PRESOLVE, value);
}
}
}
// Sets the scaling mode.
void GurobiInterface::SetScalingMode(int value) {
switch (value) {
case MPSolverParameters::SCALING_OFF:
CheckedGurobiCall(
GRBsetintparam(GRBgetenv(model_), GRB_INT_PAR_SCALEFLAG, false));
break;
case MPSolverParameters::SCALING_ON:
CheckedGurobiCall(
GRBsetintparam(GRBgetenv(model_), GRB_INT_PAR_SCALEFLAG, true));
CheckedGurobiCall(
GRBsetdblparam(GRBgetenv(model_), GRB_DBL_PAR_OBJSCALE, 0.0));
break;
default:
// Leave the parameters untouched.
break;
}
}
// Sets the LP algorithm : primal, dual or barrier. Note that GRB
// offers automatic selection
void GurobiInterface::SetLpAlgorithm(int value) {
switch (value) {
case MPSolverParameters::DUAL:
CheckedGurobiCall(GRBsetintparam(GRBgetenv(model_), GRB_INT_PAR_METHOD,
GRB_METHOD_DUAL));
break;
case MPSolverParameters::PRIMAL:
CheckedGurobiCall(GRBsetintparam(GRBgetenv(model_), GRB_INT_PAR_METHOD,
GRB_METHOD_PRIMAL));
break;
case MPSolverParameters::BARRIER:
CheckedGurobiCall(GRBsetintparam(GRBgetenv(model_), GRB_INT_PAR_METHOD,
GRB_METHOD_BARRIER));
break;
default:
SetIntegerParamToUnsupportedValue(MPSolverParameters::LP_ALGORITHM,
value);
}
}
int GurobiInterface::SolutionCount() const {
return GetIntAttr(GRB_INT_ATTR_SOLCOUNT);
}
bool GurobiInterface::ModelIsNonincremental() const {
for (const MPConstraint* c : solver_->constraints()) {
if (c->indicator_variable() != nullptr) {
return true;
}
}
return false;
}
MPSolver::ResultStatus GurobiInterface::Solve(const MPSolverParameters& param) {
WallTimer timer;
timer.Start();
if (param.GetIntegerParam(MPSolverParameters::INCREMENTALITY) ==
MPSolverParameters::INCREMENTALITY_OFF ||
ModelIsNonincremental() || had_nonincremental_change_) {
Reset();
}
// Set log level.
CheckedGurobiCall(
GRBsetintparam(GRBgetenv(model_), GRB_INT_PAR_OUTPUTFLAG, !quiet_));
ExtractModel();
// Sync solver.
CheckedGurobiCall(GRBupdatemodel(model_));
VLOG(1) << absl::StrFormat("Model built in %s.",
absl::FormatDuration(timer.GetDuration()));
// Set solution hints if any.
for (const std::pair<const MPVariable*, double>& p :
solver_->solution_hint_) {
SetDoubleAttrElement(GRB_DBL_ATTR_START,
mp_var_to_gurobi_var_.at(p.first->index()), p.second);
}
// Pass branching priority annotations if at least one has been updated.
if (update_branching_priorities_) {
for (const MPVariable* var : solver_->variables_) {
SetIntAttrElement(GRB_INT_ATTR_BRANCHPRIORITY,
mp_var_to_gurobi_var_.at(var->index()),
var->branching_priority());
}
update_branching_priorities_ = false;
}
// Time limit.
if (solver_->time_limit() != 0) {
VLOG(1) << "Setting time limit = " << solver_->time_limit() << " ms.";
CheckedGurobiCall(GRBsetdblparam(GRBgetenv(model_), GRB_DBL_PAR_TIMELIMIT,
solver_->time_limit_in_secs()));
}
// We first set our internal MPSolverParameters from 'param' and then set
// any user-specified internal solver parameters via
// solver_specific_parameter_string_.
// Default MPSolverParameters can override custom parameters (for example for
// presolving) and therefore we apply MPSolverParameters first.
SetParameters(param);
solver_->SetSolverSpecificParametersAsString(
solver_->solver_specific_parameter_string_);
std::unique_ptr<GurobiMPCallbackContext> gurobi_context;
MPCallbackWithGurobiContext mp_callback_with_context;
int gurobi_precrush = 0;
int gurobi_lazy_constraint = 0;
if (callback_ == nullptr) {
CheckedGurobiCall(GRBsetcallbackfunc(model_, nullptr, nullptr));
} else {
gurobi_context = std::make_unique<GurobiMPCallbackContext>(
global_env_, &mp_var_to_gurobi_var_, num_gurobi_vars_,
callback_->might_add_cuts(), callback_->might_add_lazy_constraints());
mp_callback_with_context.context = gurobi_context.get();
mp_callback_with_context.callback = callback_;
CheckedGurobiCall(GRBsetcallbackfunc(
model_, CallbackImpl, static_cast<void*>(&mp_callback_with_context)));
gurobi_precrush = callback_->might_add_cuts();
gurobi_lazy_constraint = callback_->might_add_lazy_constraints();
}
CheckedGurobiCall(
GRBsetintparam(GRBgetenv(model_), GRB_INT_PAR_PRECRUSH, gurobi_precrush));
CheckedGurobiCall(GRBsetintparam(
GRBgetenv(model_), GRB_INT_PAR_LAZYCONSTRAINTS, gurobi_lazy_constraint));
// Logs all parameters not at default values in the model environment.
if (!quiet()) {
std::cout << GurobiParamInfoForLogging(GRBgetenv(model_),
/*one_liner_output=*/true);
}
// Solve
timer.Restart();
const int status = GRBoptimize(model_);
if (status) {
VLOG(1) << "Failed to optimize MIP." << GRBgeterrormsg(global_env_);
} else {
VLOG(1) << absl::StrFormat("Solved in %s.",
absl::FormatDuration(timer.GetDuration()));
}
// Get the status.
const int optimization_status = GetIntAttr(GRB_INT_ATTR_STATUS);
VLOG(1) << absl::StrFormat("Solution status %d.\n", optimization_status);
const int solution_count = SolutionCount();
switch (optimization_status) {
case GRB_OPTIMAL:
result_status_ = MPSolver::OPTIMAL;
break;
case GRB_INFEASIBLE:
result_status_ = MPSolver::INFEASIBLE;
break;
case GRB_UNBOUNDED:
result_status_ = MPSolver::UNBOUNDED;
break;
case GRB_INF_OR_UNBD:
// TODO(user): We could introduce our own "infeasible or
// unbounded" status.
result_status_ = MPSolver::INFEASIBLE;
break;
default: {
if (solution_count > 0) {
result_status_ = MPSolver::FEASIBLE;
} else {
result_status_ = MPSolver::NOT_SOLVED;
}
break;
}
}
if (IsMIP() && (result_status_ != MPSolver::UNBOUNDED &&
result_status_ != MPSolver::INFEASIBLE)) {
const int error =
GRBgetdblattr(model_, GRB_DBL_ATTR_OBJBOUND, &best_objective_bound_);
LOG_IF(WARNING, error != 0)
<< "Best objective bound is not available, error=" << error
<< ", message=" << GRBgeterrormsg(global_env_);
VLOG(1) << "best bound = " << best_objective_bound_;
}
if (solution_count > 0 && (result_status_ == MPSolver::FEASIBLE ||
result_status_ == MPSolver::OPTIMAL)) {
current_solution_index_ = 0;
// Get the results.
objective_value_ = GetDoubleAttr(GRB_DBL_ATTR_OBJVAL);
VLOG(1) << "objective = " << objective_value_;
{
const std::vector<double> grb_variable_values =
GetDoubleAttrArray(GRB_DBL_ATTR_X, num_gurobi_vars_);
for (int i = 0; i < solver_->variables_.size(); ++i) {
MPVariable* const var = solver_->variables_[i];
const double val = grb_variable_values.at(mp_var_to_gurobi_var_.at(i));
var->set_solution_value(val);
VLOG(3) << var->name() << ", value = " << val;
}
}
if (!mip_) {
{
const std::vector<double> grb_reduced_costs =
GetDoubleAttrArray(GRB_DBL_ATTR_RC, num_gurobi_vars_);
for (int i = 0; i < solver_->variables_.size(); ++i) {
MPVariable* const var = solver_->variables_[i];
const double rc = grb_reduced_costs.at(mp_var_to_gurobi_var_.at(i));
var->set_reduced_cost(rc);
VLOG(4) << var->name() << ", reduced cost = " << rc;
}
}
{
std::vector<double> grb_dual_values =
GetDoubleAttrArray(GRB_DBL_ATTR_PI, num_gurobi_linear_cons_);
for (int i = 0; i < solver_->constraints_.size(); ++i) {
MPConstraint* const ct = solver_->constraints_[i];
const double dual_value =
grb_dual_values.at(mp_cons_to_gurobi_linear_cons_.at(i));
ct->set_dual_value(dual_value);
VLOG(4) << "row " << ct->index() << ", dual value = " << dual_value;
}
}
}
}
sync_status_ = SOLUTION_SYNCHRONIZED;
GRBresetparams(GRBgetenv(model_));
return result_status_;
}
bool GurobiInterface::NextSolution() {
// Next solution only supported for MIP
if (!mip_) return false;
// Make sure we have successfully solved the problem and not modified it.
if (!CheckSolutionIsSynchronizedAndExists()) {
return false;
}
// Check if we are out of solutions.
if (current_solution_index_ + 1 >= SolutionCount()) {
return false;
}
current_solution_index_++;
CheckedGurobiCall(GRBsetintparam(
GRBgetenv(model_), GRB_INT_PAR_SOLUTIONNUMBER, current_solution_index_));
objective_value_ = GetDoubleAttr(GRB_DBL_ATTR_POOLOBJVAL);
const std::vector<double> grb_variable_values =
GetDoubleAttrArray(GRB_DBL_ATTR_XN, num_gurobi_vars_);
for (int i = 0; i < solver_->variables_.size(); ++i) {
MPVariable* const var = solver_->variables_[i];
var->set_solution_value(
grb_variable_values.at(mp_var_to_gurobi_var_.at(i)));
}
// TODO(user): This reset may not be necessary, investigate.
GRBresetparams(GRBgetenv(model_));
return true;
}
void GurobiInterface::Write(const std::string& filename) {
if (sync_status_ == MUST_RELOAD) {
Reset();
}
ExtractModel();
// Sync solver.
CheckedGurobiCall(GRBupdatemodel(model_));
VLOG(1) << "Writing Gurobi model file \"" << filename << "\".";
const int status = GRBwrite(model_, filename.c_str());
if (status) {
LOG(WARNING) << "Failed to write MIP." << GRBgeterrormsg(global_env_);
}
}
MPSolverInterface* BuildGurobiInterface(bool mip, MPSolver* const solver) {
return new GurobiInterface(solver, mip);
}
void GurobiInterface::SetCallback(MPCallback* mp_callback) {
callback_ = mp_callback;
}
} // namespace operations_research