ParametricOptInterface + Clarabel

I am trying to use POI with Clarabel. Based on the ParametricOptInterface.jl readme, the recommended usage is with direct_model. Here, I’ve just swapped HiGHS.jl for Clarabel.jl:

using JuMP, Clarabel
import ParametricOptInterface as POI

model = direct_model(POI.Optimizer(Clarabel.Optimizer()))
#Error showing value of type Model:
#
#SYSTEM (REPL): showing an error caused an error
#ERROR: MathOptInterface.GetAttributeNotAllowed{MathOptInterface.ObjectiveSense}:
#...

@variable(model, x)
# ERROR: MathOptInterface.AddVariableNotAllowed:

and with the other (old?) syntax, we get

using JuMP, Clarabel
import ParametricOptInterface as POI

model = Model(() -> POI.Optimizer(Clarabel.Optimizer()))
@variable(model, x)
@variable(model, p in Parameter(1.0))
@constraint(model, cons, x + p >= 3)
@objective(model, Min, 2x)
optimize!(model)
# ERROR: `copy_to` is not supported by the solver `ParametricOptInterface.Optimizer{Float64, Clarabel.MOIwrapper.Optimizer{Float64}}`. Did you mean to call `optimize!(dest::AbstractOptimizer, src::ModelLike)` instead?

So, (why) is Clarabel.jl not compatible with POI? Note both of those syntax work well for HiGHS.jl and Ipopt.jl.

Hi @klamike,

Another good question that gets at something much deeper.

The answer is that there are two ways to write an MOI interface:

  • incremental mode: where the solver supports building the model one variable and constraint at a time, supports deletion, and supports querying the various attributes of the model
  • copy-to mode: where the solver supports building (and perhaps solving) the model in a single function call, where it gets given the entire model as input and it returns a solution.

POI assumes that the solver supports the incremental interface. Clarabel implements the copy-to interface, which you can see with:

julia> using JuMP, Clarabel

julia> import ParametricOptInterface as POI

julia> MOI.supports_incremental_interface(Clarabel.Optimizer())
false

The solution is to wrap Clarabel in a caching optimizer, which implements the incremental interface:

julia> inner = MOI.instantiate(Clarabel.Optimizer; with_cache_type = Float64)
MOIU.CachingOptimizer
├ state: EMPTY_OPTIMIZER
├ mode: AUTOMATIC
├ model_cache: MOIU.UniversalFallback{MOIU.Model{Float64}}
│ ├ ObjectiveSense: FEASIBILITY_SENSE
│ ├ ObjectiveFunctionType: MOI.ScalarAffineFunction{Float64}
│ ├ NumberOfVariables: 0
│ └ NumberOfConstraints: 0
└ optimizer: Empty Clarabel - Optimizer

julia> model = direct_model(POI.Optimizer(inner))
A JuMP Model
├ mode: DIRECT
├ solver: Parametric Optimizer with Clarabel attached
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 0
├ num_constraints: 0
└ Names registered in the model: none

julia> @variable(model, x)
x

But then you have a cache, two copies of the problem, etc.

The real answer is that you probably shouldn’t use POI with Clarabel because Clarabel won’t benefit from efficiently modifying the problem inn-place. POI is really intended to be used with MIP solvers that can efficiently warm start after small parts of the problem have changed.

What problem are you trying to solve? Why Clarabel and why POI?

In the mean time: we should improve the documentation of POI to make it clear when it should be used.

2 Likes

Thanks for the quick response @odow!

I came across this since I’m starting to implement the DLL method using JuMP. In the most general case, the “Dual Completion” step is a (conic) solve, which has to be done many times during training. I implemented it for LP first where HiGHS and POI worked well. Then trying a conic problem I swapped Clarabel in and saw the error.

Actually, it turns out for the problem I’m looking at, those conic constraints are not even needed, so I am now deleting them from the model and using POI+HiGHS like before. But I appreciate the explanation :smiley:

I assume @mtanneau used JuMP for this. He can probably share more details.

It looks like he used Mosek (MosekTools.jl) which does implement the incremental interface.

Yes, I am generalizing/automating his implementation. The main idea is to automate the training of DLL models (for arbitrary primals) as much as possible, so (eventually) L2ODLL will detect the decomposition, generate the completion/projection functions, etc.

1 Like

Hi odow and klamike!

My implementation was limited to a handful of problem types, and I did re-write several things manually mainly because I didn’t know how to use other tools.

I know that klamike is very familiar with the underlying work and code, so I’ll dive right in, hopefully making some sense and being somewhat useful :crossed_fingers:

At the core of DLL is the ability to 1) project onto a cone and 2) differentiate through that projection. In theory, this can be done via JuMP + DiffOpt, as long as there’s a solver that supports the underlying cone. I hard-coded it because it was simple enough to do so and more efficient. Streamlining this particular step would go a long way in making the method more accessible.

The second step of DLL (called “dual completion” in the paper) consists in taking a partial (i.e. only a subset of variables are known) dual solution and recovering the remaining variables. In my implementation, this step was also hard-coded, again because the problems were “easy” enough. I expect that streamlining this would be more complicated, as it requires detecting some structure. I’m also not sure whether this was the scope of klamike’s original question :thinking:

1 Like