CPLEX.jl

Build Status codecov

CPLEX.jl is a wrapper for the IBM® ILOG® CPLEX® Optimization Studio.

CPLEX.jl has two components:

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.

Getting help

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.

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, 22.1, or 22.2.

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 PkgPkg.add("CPLEX")
Note

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, CPLEXmodel = 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:

List of supported variable types:

List of supported constraint types:

List of supported model attributes:

Options

Options match those of the C API in the CPLEX documentation.

Set options using JuMP.set_attribute:

using JuMP, CPLEXmodel = Model(CPLEX.Optimizer)set_attribute(model, "CPX_PARAM_EPINT", 1e-8)

Callbacks

CPLEX.jl provides a solver-specific callback to CPLEX:

using JuMP, CPLEX, Testmodel = 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)    endendMOI.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, CPLEXfunction 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,    )    returnend# Problemfunction 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)")endfunction 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