CPLEX.jl
CPLEX.jl is a wrapper for the IBM® ILOG® CPLEX® Optimization Studio.
CPLEX.jl has two components:
- a thin wrapper around the complete C API
- an interface to MathOptInterface
The C API can be accessed via CPLEX.CPXxx
functions, where the names and arguments are identical to the C API. See the CPLEX documentation for details.
Affiliation
This wrapper is maintained by the JuMP community and is not officially supported by IBM. However, we thank IBM for providing us with a CPLEX license to test CPLEX.jl
on GitHub. If you are a commercial customer interested in official support for CPLEX in Julia, let them know.
License
CPLEX.jl
is licensed under the MIT License.
The underlying solver is a closed-source commercial product for which you must purchase a license.
Free CPLEX licenses are available for academics and students.
Installation
CPLEX.jl requires CPLEX version 12.10, 20.1, or 22.1.
First, obtain a license of CPLEX and install CPLEX solver, following the instructions on IBM's website.
Once installed, set the CPLEX_STUDIO_BINARIES
environment variable as appropriate and run Pkg.add("CPLEX")
. For example:
# On Windows, this might be:
ENV["CPLEX_STUDIO_BINARIES"] = "C:\\Program Files\\CPLEX_Studio1210\\cplex\\bin\\x86-64_win\\"
# On OSX, this might be:
ENV["CPLEX_STUDIO_BINARIES"] = "/Applications/CPLEX_Studio1210/cplex/bin/x86-64_osx/"
# On Unix, this might be:
ENV["CPLEX_STUDIO_BINARIES"] = "/opt/CPLEX_Studio1210/cplex/bin/x86-64_linux/"
import Pkg
Pkg.add("CPLEX")
The exact path may differ. Check which folder you installed CPLEX in, and update the path accordingly.
Use with JuMP
Use CPLEX.jl
with JuMP as follows:
using JuMP, CPLEX
model = Model(CPLEX.Optimizer)
set_attribute(model, "CPX_PARAM_EPINT", 1e-8)
MathOptInterface API
The CPLEX 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.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.ConflictStatus()
MOI.HeuristicCallback()
MOI.LazyConstraintCallback()
MOI.Name()
MOI.ObjectiveSense()
MOI.UserCutCallback()
Options
Options match those of the C API in the CPLEX documentation.
Set options using JuMP.set_attribute
:
using JuMP, CPLEX
model = Model(CPLEX.Optimizer)
set_attribute(model, "CPX_PARAM_EPINT", 1e-8)
Callbacks
CPLEX.jl provides a solver-specific callback to CPLEX:
using JuMP, CPLEX, Test
model = direct_model(CPLEX.Optimizer())
set_silent(model)
# This is very, very important!!! Only use callbacks in single-threaded mode.
MOI.set(model, MOI.NumberOfThreads(), 1)
@variable(model, 0 <= x <= 2.5, Int)
@variable(model, 0 <= y <= 2.5, Int)
@objective(model, Max, y)
cb_calls = Clong[]
function my_callback_function(cb_data::CPLEX.CallbackContext, context_id::Clong)
# You can reference variables outside the function as normal
push!(cb_calls, context_id)
# You can select where the callback is run
if context_id != CPX_CALLBACKCONTEXT_CANDIDATE
return
end
ispoint_p = Ref{Cint}()
ret = CPXcallbackcandidateispoint(cb_data, ispoint_p)
if ret != 0 || ispoint_p[] == 0
return # No candidate point available or error
end
# You can query CALLBACKINFO items
valueP = Ref{Cdouble}()
ret = CPXcallbackgetinfodbl(cb_data, CPXCALLBACKINFO_BEST_BND, valueP)
@info "Best bound is currently: $(valueP[])"
# As well as any other C API
x_p = Vector{Cdouble}(undef, 2)
obj_p = Ref{Cdouble}()
ret = CPXcallbackgetincumbent(cb_data, x_p, 0, 1, obj_p)
if ret == 0
@info "Objective incumbent is: $(obj_p[])"
@info "Incumbent solution is: $(x_p)"
# Use CPLEX.column to map between variable references and the 1-based
# column.
x_col = CPLEX.column(cb_data, index(x))
@info "x = $(x_p[x_col])"
else
# Unable to query incumbent.
end
# Before querying `callback_value`, you must call:
CPLEX.load_callback_variable_primal(cb_data, context_id)
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
end
MOI.set(model, CPLEX.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
Annotations for automatic Benders' decomposition
Here is an example of using the annotation feature for automatic Benders' decomposition:
using JuMP, CPLEX
function add_annotation(
model::JuMP.Model,
variable_classification::Dict;
all_variables::Bool = true,
)
num_variables = sum(length(it) for it in values(variable_classification))
if all_variables
@assert num_variables == JuMP.num_variables(model)
end
indices, annotations = CPXINT[], CPXLONG[]
for (key, value) in variable_classification
for variable_ref in value
push!(indices, variable_ref.index.value - 1)
push!(annotations, CPX_BENDERS_MASTERVALUE + key)
end
end
cplex = backend(model)
index_p = Ref{CPXINT}()
CPXnewlongannotation(
cplex.env,
cplex.lp,
CPX_BENDERS_ANNOTATION,
CPX_BENDERS_MASTERVALUE,
)
CPXgetlongannotationindex(
cplex.env,
cplex.lp,
CPX_BENDERS_ANNOTATION,
index_p,
)
CPXsetlongannotations(
cplex.env,
cplex.lp,
index_p[],
CPX_ANNOTATIONOBJ_COL,
length(indices),
indices,
annotations,
)
return
end
# Problem
function illustrate_full_annotation()
c_1, c_2 = [1, 4], [2, 3]
dim_x, dim_y = length(c_1), length(c_2)
b = [-2; -3]
A_1, A_2 = [1 -3; -1 -3], [1 -2; -1 -1]
model = JuMP.direct_model(CPLEX.Optimizer())
set_optimizer_attribute(model, "CPXPARAM_Benders_Strategy", 1)
@variable(model, x[1:dim_x] >= 0, Bin)
@variable(model, y[1:dim_y] >= 0)
variable_classification = Dict(0 => [x[1], x[2]], 1 => [y[1], y[2]])
@constraint(model, A_2 * y + A_1 * x .<= b)
@objective(model, Min, c_1' * x + c_2' * y)
add_annotation(model, variable_classification)
optimize!(model)
x_optimal = value.(x)
y_optimal = value.(y)
println("x: $(x_optimal), y: $(y_optimal)")
end
function illustrate_partial_annotation()
c_1, c_2 = [1, 4], [2, 3]
dim_x, dim_y = length(c_1), length(c_2)
b = [-2; -3]
A_1, A_2 = [1 -3; -1 -3], [1 -2; -1 -1]
model = JuMP.direct_model(CPLEX.Optimizer())
# Note that the "CPXPARAM_Benders_Strategy" has to be set to 2 if partial
# annotation is provided. If "CPXPARAM_Benders_Strategy" is set to 1, then
# the following error will be thrown:
# `CPLEX Error 2002: Invalid Benders decomposition.`
set_optimizer_attribute(model, "CPXPARAM_Benders_Strategy", 2)
@variable(model, x[1:dim_x] >= 0, Bin)
@variable(model, y[1:dim_y] >= 0)
variable_classification = Dict(0 => [x[1]], 1 => [y[1], y[2]])
@constraint(model, A_2 * y + A_1 * x .<= b)
@objective(model, Min, c_1' * x + c_2' * y)
add_annotation(model, variable_classification; all_variables = false)
optimize!(model)
x_optimal = value.(x)
y_optimal = value.(y)
println("x: $(x_optimal), y: $(y_optimal)")
end