CPLEX.jl icon indicating copy to clipboard operation
CPLEX.jl copied to clipboard

Installing CPLEX.jl without cplex, as a placeholder / "empty wrapper"

Open cfe316 opened this issue 2 years ago • 7 comments

I'm helping to develop a package (https://github.com/genXProject/genX) that can use CPLEX (through JuMP), and can also use other solvers. Not all users will have access to commercial solvers (and neither does our test server).

The Gurobi.jl package optionally looks for an environment variable GUROBI_JL_SKIP_LIB_CHECK which allows it to be installed (but not used of course) on systems without Gurobi.

Would it be possible to have a similar interface for CPLEX.jl? Otherwise, is there a recommended workaround? We're admittedly somewhat new to julia package management.

cfe316 avatar Aug 16 '22 16:08 cfe316

There's no work-around, but I guess there's precedence with Gurobi so we could add CPLEX_JL_SKIP_LIB_CHECK.

I'm usually pretty skeptical of packages which try to install CPLEX/Gurobi if the user doesn't have a license.

The correct work-around is to setup your project so that the user can pass in the solver they want to use, and to use an open-source solver like HiGHS as the default.

odow avatar Aug 16 '22 21:08 odow

Related:

  • https://github.com/jump-dev/HiGHS.jl/issues/122
  • https://github.com/jump-dev/HiGHS.jl/issues/118

I don't think configuring solvers like this is a good idea: https://github.com/GenXProject/GenX/blob/main/src/configure_solver/configure_cplex.jl

It means that your project installs a whole bunch of solvers that aren't actually used: https://github.com/GenXProject/GenX/blob/66cc78a4133a947af8a001d6fb923aeb193e83cc/Project.toml#L9-L10

Just provide one good default (HiGHS), and document how you can use something else.

cc @sambuddhac

odow avatar Aug 16 '22 21:08 odow

I feel that this feature is though very interesting to enable features that are not common with other solvers. In a repo, I am working on a more efficient procedure that exploits benders decomposition.

Currently, I am trying to use as a workaround a try-catch approach to enable CPLEX only when it is installed in the environment, without adding it as a dependency in the toml.

davide-f avatar Sep 02 '22 11:09 davide-f

Davide, does your approach work? Would you be able to share the code?

cfe316 avatar Sep 06 '22 14:09 cfe316

Davide, does your approach work? Would you be able to share the code?

I'll release the complete package soon. However, the principle is as follows:

  • Import CPLEX package in a try-catch-end block:
try
    using CPLEX
catch e
    @warn "Impossible to import CPLEX package, features may be limited"
end
  • Where the CPLEX features are needed, place those features in another catch-try. In my case, I had a function like:
   function add_feature(::Any, options)
       @warn "Feature not implemented yet or not available"
   end

   try
       function add_feature(::CPLEX.Optimizer, options)
           # do stuff
       end
   catch e
       @warn "Feature by CPLEX not available"
   end

I tested this in an environment where CPLEX was installed, yet the and it actually works; however a warning by Julia is usually prompted to notify that the package does not have CPLEX as a dependency. That is annoying but it works

davide-f avatar Sep 06 '22 14:09 davide-f

Thanks @cfe316 , @cfe316 , and @odow .... looking forward to the release and meanwhile, trying this approach out for GenX

sambuddhac avatar Sep 06 '22 15:09 sambuddhac

The code exactly is the follows;

try

    """
        Add notations for CPLEX backend
    """
    function add_notations!(model, ::Type{CPLEX.Optimizer})

        variable_classification = get_annotations(model)

        num_variables = sum(length(it) for it in values(variable_classification))
        if num_variables != JuMP.num_variables(model)
            @warn "Annotation for $num_variables out of the total $(JuMP.num_variables(model)) variables"
        end
        indices, annotations = CPLEX.CPXINT[], CPLEX.CPXLONG[]
        for (key, value) in variable_classification
            indices_value = map(x->CPLEX.CPXINT(x.index.value-1), value)
            append!(indices, indices_value)
            append!(annotations, fill(CPLEX.CPXLONG(CPLEX.CPX_BENDERS_MASTERVALUE + key), length(indices_value)))
        end
        cplex = JuMP.backend(model)
        index_p = Ref{CPLEX.CPXINT}()
        CPLEX.CPXnewlongannotation(
            cplex.env,
            cplex.lp,
            CPLEX.CPX_BENDERS_ANNOTATION,
            CPLEX.CPX_BENDERS_MASTERVALUE,
        )
        CPLEX.CPXgetlongannotationindex(
            cplex.env,
            cplex.lp,
            CPLEX.CPX_BENDERS_ANNOTATION,
            index_p,
        )
        CPLEX.CPXsetlongannotations(
            cplex.env,
            cplex.lp,
            index_p[],
            CPLEX.CPX_ANNOTATIONOBJ_COL,
            length(indices),
            indices,
            annotations,
        )
        return
    end
catch e
    @warn "Notation by CPLEX are not enabled"
end

You just need to adapt the get_annotations(model) function to use the code

davide-f avatar Sep 06 '22 15:09 davide-f

Julia 1.9 has an upcoming "weakdeps" feature that should support functionality like this. It'll allow you to write code that gets loaded only if two packages are installed.

Closing because we won't be enabling CPLEX.jl to be installed without a working version of CPLEX.

odow avatar Jan 22 '23 00:01 odow