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.
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.
If you need help, please ask a question on the JuMP community forum.
If you have a reproducible example of a bug, please open a GitHub issue.
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.
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")!!! note The exact path may differ. Check which folder you installed CPLEX in, and update the path accordingly.
Use CPLEX.jl with JuMP as follows:
using JuMP, CPLEX model =Model(CPLEX.Optimizer) set_attribute(model, "CPX_PARAM_EPINT", 1e-8)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.VariableIndexinMOI.EqualTo{Float64}MOI.VariableIndexinMOI.GreaterThan{Float64}MOI.VariableIndexinMOI.IntegerMOI.VariableIndexinMOI.Interval{Float64}MOI.VariableIndexinMOI.LessThan{Float64}MOI.VariableIndexinMOI.Semicontinuous{Float64}MOI.VariableIndexinMOI.Semiinteger{Float64}MOI.VariableIndexinMOI.ZeroOneMOI.VectorOfVariablesinMOI.SOS1{Float64}MOI.VectorOfVariablesinMOI.SOS2{Float64}MOI.VectorOfVariablesinMOI.SecondOrderConeMOI.VectorAffineFunctioninMOI.Indicator
List of supported model attributes:
MOI.ConflictStatus()MOI.HeuristicCallback()MOI.LazyConstraintCallback()MOI.Name()MOI.ObjectiveSense()MOI.UserCutCallback()
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)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[] functionmy_callback_function(cb_data::CPLEX.CallbackContext, context_id::Clong) # You can reference variables outside the function as normalpush!(cb_calls, context_id) # You can select where the callback is runif context_id != CPX_CALLBACKCONTEXT_CANDIDATE returnend ispoint_p =Ref{Cint}() ret =CPXcallbackcandidateispoint(cb_data, ispoint_p) if ret !=0|| ispoint_p[] ==0return# No candidate point available or errorend# 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) endend MOI.set(model, CPLEX.CallbackFunction(), my_callback_function) optimize!(model) @testtermination_status(model) == MOI.OPTIMAL @testprimal_status(model) == MOI.FEASIBLE_POINT @testvalue(x) ==1@testvalue(y) ==2Here is an example of using the annotation feature for automatic Benders' decomposition:
using JuMP, CPLEX functionadd_annotation( model::JuMP.Model, variable_classification::Dict; all_variables::Bool=true, ) num_variables =sum(length(it) for it invalues(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) endend 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, ) returnend# Problemfunctionillustrate_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)") endfunctionillustrate_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