Gurobi.jl is a wrapper for the Gurobi Optimizer.
It has two components:
- a thin wrapper around the complete C API
- an interface to MathOptInterface
This wrapper is maintained by the JuMP community with help from Gurobi.
If you encounter a problem with this interface, please either open an issue in
this repository directly or create a topic in the Julia Discourse
with the gurobi
tag.
If you encounter a problem with the Gurobi solver, please post in Gurobi’s Community Forum, or if you are a commercial customer, please contact Gurobi directly through the Gurobi Help Center.
Gurobi.jl
is licensed under the MIT License.
The underlying solver is a closed-source commercial product for which you must obtain a license.
Free Gurobi licenses are available for academics and students.
First, obtain a license of Gurobi and install Gurobi solver.
Then, set the GUROBI_HOME
environment variable as appropriate and run
Pkg.add("Gurobi")
:
# On Windows, this might be
ENV["GUROBI_HOME"] = "C:\\Program Files\\gurobi1000\\win64"
# ... or perhaps ...
ENV["GUROBI_HOME"] = "C:\\gurobi1000\\win64"
# On Mac, this might be
ENV["GUROBI_HOME"] = "/Library/gurobi1000/mac64"
import Pkg
Pkg.add("Gurobi")
Note: your path may differ. Check which folder you installed Gurobi in, and update the path accordingly.
By default, building Gurobi.jl will fail if the Gurobi library is not found.
This may not be desirable in certain cases, for example when part of a package's
test suite uses Gurobi as an optional test dependency, but Gurobi cannot be
installed on a CI server running the test suite. To support this use case, the
GUROBI_JL_SKIP_LIB_CHECK
environment variable may be set (to any value) to
make Gurobi.jl installable (but not usable).
To use Gurobi with JuMP, use Gurobi.Optimizer
:
using JuMP, Gurobi
model = Model(Gurobi.Optimizer)
set_attribute(model, "TimeLimit", 100)
set_attribute(model, "Presolve", 0)
The Gurobi optimizer supports the following constraints and attributes.
List of supported objective functions:
MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}
MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}
MOI.ObjectiveFunction{MOI.VariableIndex}
MOI.ObjectiveFunction{MOI.VectorAffineFunction{Float64}}
List of supported variable types:
List of supported constraint types:
MOI.ScalarAffineFunction{Float64}
inMOI.EqualTo{Float64}
MOI.ScalarAffineFunction{Float64}
inMOI.GreaterThan{Float64}
MOI.ScalarAffineFunction{Float64}
inMOI.LessThan{Float64}
MOI.ScalarQuadraticFunction{Float64}
inMOI.EqualTo{Float64}
MOI.ScalarQuadraticFunction{Float64}
inMOI.GreaterThan{Float64}
MOI.ScalarQuadraticFunction{Float64}
inMOI.LessThan{Float64}
MOI.VariableIndex
inMOI.EqualTo{Float64}
MOI.VariableIndex
inMOI.GreaterThan{Float64}
MOI.VariableIndex
inMOI.Integer
MOI.VariableIndex
inMOI.Interval{Float64}
MOI.VariableIndex
inMOI.LessThan{Float64}
MOI.VariableIndex
inMOI.Semicontinuous{Float64}
MOI.VariableIndex
inMOI.Semiinteger{Float64}
MOI.VariableIndex
inMOI.ZeroOne
MOI.VectorOfVariables
inMOI.SOS1{Float64}
MOI.VectorOfVariables
inMOI.SOS2{Float64}
MOI.VectorOfVariables
inMOI.SecondOrderCone
List of supported model attributes:
MOI.HeuristicCallback()
MOI.LazyConstraintCallback()
MOI.Name()
MOI.ObjectiveSense()
MOI.UserCutCallback()
See the Gurobi Documentation for a list and description of allowable parameters.
The C API can be accessed via Gurobi.GRBxx
functions, where the names and
arguments are identical to the C API.
See the Gurobi documentation for details.
As general rules when converting from Julia to C:
- When Gurobi requires the column index of a variable
x
, useGurobi.c_column(model, x)
- When Gurobi requires a
Ptr{T}
that holds one element, likedouble *
, use aRef{T}()
. - When Gurobi requries a
Ptr{T}
that holds multiple elements, use aVector{T}
. - When Gurobi requires a
double
, useCdouble
- When Gurobi requires an
int
, useCint
- When Gurobi requires a
NULL
, useC_NULL
For example:
julia> import MathOptInterface as MOI
julia> using Gurobi
julia> model = Gurobi.Optimizer();
julia> x = MOI.add_variable(model)
MOI.VariableIndex(1)
julia> x_col = Gurobi.c_column(model, x)
0
julia> GRBupdatemodel(model)
0
julia> pValue = Ref{Cdouble}(NaN)
Base.RefValue{Float64}(NaN)
julia> GRBgetdblattrelement(model, "LB", x_col, pValue)
0
julia> pValue[]
-1.0e100
julia> GRBsetdblattrelement(model, "LB", x_col, 1.5)
0
julia> GRBupdatemodel(model)
0
julia> GRBgetdblattrelement(model, "LB", x_col, pValue)
0
julia> pValue[]
1.5
When using this package via other packages such as JuMP.jl, the default behavior is to obtain a new Gurobi license token every time a model is created. If you are using Gurobi in a setting where the number of concurrent Gurobi uses is limited (for example, "Single-Use" or "Floating-Use" licenses), you might instead prefer to obtain a single license token that is shared by all models that your program solves.
You can do this by passing a Gurobi.Env()
object as the first parameter to
Gurobi.Optimizer
. For example:
using JuMP, Gurobi
const GRB_ENV = Gurobi.Env()
model_1 = Model(() -> Gurobi.Optimizer(GRB_ENV))
# The solvers can have different options too
model_2 = direct_model(Gurobi.Optimizer(GRB_ENV))
set_attribute(model_2, "OutputFlag", 0)
If you create a module with a Gurobi.Env
as a module-level constant, use an
__init__
function to ensure that a new environment is created each time the
module is loaded:
module MyModule
import Gurobi
const GRB_ENV_REF = Ref{Gurobi.Env}()
function __init__()
global GRB_ENV_REF
GRB_ENV_REF[] = Gurobi.Env()
return
end
# Note the need for GRB_ENV_REF[] not GRB_ENV_REF
create_optimizer() = Gurobi.Optimizer(GRB_ENV_REF[])
end
Get and set Gurobi-specific variable, constraint, and model attributes as follows:
using JuMP, Gurobi
model = direct_model(Gurobi.Optimizer())
@variable(model, x >= 0)
@constraint(model, c, 2x >= 1)
@objective(model, Min, x)
MOI.set(model, Gurobi.ConstraintAttribute("Lazy"), c, 2)
optimize!(model)
MOI.get(model, Gurobi.VariableAttribute("LB"), x) # Returns 0.0
MOI.get(model, Gurobi.ModelAttribute("NumConstrs")) # Returns 1
A complete list of supported Gurobi attributes can be found in their online documentation.
Here is an example using Gurobi's solver-specific callbacks.
using JuMP, Gurobi, Test
model = direct_model(Gurobi.Optimizer())
@variable(model, 0 <= x <= 2.5, Int)
@variable(model, 0 <= y <= 2.5, Int)
@objective(model, Max, y)
cb_calls = Cint[]
function my_callback_function(cb_data, cb_where::Cint)
# You can reference variables outside the function as normal
push!(cb_calls, cb_where)
# You can select where the callback is run
if cb_where != GRB_CB_MIPSOL && cb_where != GRB_CB_MIPNODE
return
end
# You can query a callback attribute using GRBcbget
if cb_where == GRB_CB_MIPNODE
resultP = Ref{Cint}()
GRBcbget(cb_data, cb_where, GRB_CB_MIPNODE_STATUS, resultP)
if resultP[] != GRB_OPTIMAL
return # Solution is something other than optimal.
end
end
# Before querying `callback_value`, you must call:
Gurobi.load_callback_variable_primal(cb_data, cb_where)
x_val = callback_value(cb_data, x)
y_val = callback_value(cb_data, y)
# You can submit solver-independent MathOptInterface attributes such as
# lazy constraints, user-cuts, and heuristic solutions.
if y_val - x_val > 1 + 1e-6
con = @build_constraint(y - x <= 1)
MOI.submit(model, MOI.LazyConstraint(cb_data), con)
elseif y_val + x_val > 3 + 1e-6
con = @build_constraint(y + x <= 3)
MOI.submit(model, MOI.LazyConstraint(cb_data), con)
end
if rand() < 0.1
# You can terminate the callback as follows:
GRBterminate(backend(model))
end
return
end
# You _must_ set this parameter if using lazy constraints.
MOI.set(model, MOI.RawOptimizerAttribute("LazyConstraints"), 1)
MOI.set(model, Gurobi.CallbackFunction(), my_callback_function)
optimize!(model)
@test termination_status(model) == MOI.OPTIMAL
@test primal_status(model) == MOI.FEASIBLE_POINT
@test value(x) == 1
@test value(y) == 2
See the Gurobi documentation
for other information that can be queried with GRBcbget
.
Gurobi's API works differently than most solvers. Any changes to the model are
not applied immediately, but instead go sit in a internal buffer (making any
modifications appear to be instantaneous) waiting for a call to GRBupdatemodel
(where the work is done).
This leads to a common performance pitfall that has the following message as its main symptom:
Warning: excessive time spent in model updates. Consider calling update less frequently.
This often means the JuMP program was structured in such a way that Gurobi.jl
ends up calling GRBupdatemodel
in each iteration of a loop.
Usually, it is possible (and easy) to restructure the JuMP program in a way it stays ssolver-agnostic and has a close-to-ideal performance with Gurobi.
To guide such restructuring it is good to keep in mind the following bits of information:
GRBupdatemodel
is only called if changes were done since lastGRBupdatemodel
(that is, if the internal buffer is not empty).GRBupdatemodel
is called whenJuMP.optimize!
is called, but this often is not the source of the problem.GRBupdatemodel
may be called when any model attribute is queried, even if that specific attribute was not changed. This often the source of the problem.
The worst-case scenario is, therefore, a loop of modify-query-modify-query, even if what is being modified and what is being queried are two completely distinct things.
As an example, instead of:
model = Model(Gurobi.Optimizer)
@variable(model, x[1:100] >= 0)
for i in 1:100
set_upper_bound(x[i], i)
# `GRBupdatemodel` called on each iteration of this loop.
println(lower_bound(x[i]))
end
do
model = Model(Gurobi.Optimizer)
@variable(model, x[1:100] >= 0)
# All modifications are done before any queries.
for i in 1:100
set_upper_bound(x[i], i)
end
for i in 1:100
# Only the first `lower_bound` query may trigger an `GRBupdatemodel`.
println(lower_bound(x[i]))
end
You need to set the NonConvex parameter:
model = Model(Gurobi.Optimizer)
set_optimizer_attribute(model, "NonConvex", 2)
Make sure that your license is correct for your Gurobi version. See the Gurobi documentation for details.
Once you are sure that the license and Gurobi versions match, re-install Gurobi.jl by running:
import Pkg
Pkg.build("Gurobi")