Use Cbc with Parametron? (ERROR: MathOptInterface.SetAttributeNotAllowed)

I am trying to adapt the first Parametron README.md example to use Cbc because I’ll need binary variables. However, the example does not work out of the box with Cbc.Optimizer(). I’ve worked through some of the errors about unsupported constraint types by replacing the vectorized constraints with scalar versions. However, I’m stuck at this last error:

using Parametron
using Cbc
using Random, LinearAlgebra

optimizer = Cbc.Optimizer()
model = Model(optimizer)

n = 8; m = 2

x = [Variable(model) for _ = 1 : n]
A = Parameter(rand!, zeros(n, n), model)
b = Parameter(rand!, zeros(n), model)
C = Parameter(rand!, zeros(m, n), model)
d = Parameter(zeros(m), model) do d
    rand!(d)
    d .*= 2
end

# Minimize absolute errors instead of squared error to use an LP:
residual = [Variable(model) for _=1:n]
for i in 1:n
    @constraint(model, residual[i] >= dot(A[i,:], x) - b[i])
    @constraint(model, residual[i] >= b[i] - dot(A[i,:], x))
end

@objective(model, Minimize, sum(residual))

for i in 1:m
    @constraint(model, dot(C[i,:], x) == d[i])
end

solve!(model)

throws

ERROR: MathOptInterface.SetAttributeNotAllowed{MathOptInterface.ConstraintFunction}: Setting attribute MathOptInterface.ConstraintFunction() cannot be performed. You may want to use a `CachingOptimizer` in `AUTOMATIC` mode or you may need to call `reset_optimizer` before doing this operation if the `CachingOptimizer` is in `MANUAL` mode.
Stacktrace:
 [1] #throw_set_error_fallback#18(::MathOptInterface.SetAttributeNotAllowed{MathOptInterface.ConstraintFunction}, ::Function, ::Cbc.Optimizer, ::MathOptInterface.ConstraintFunction, ::MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.GreaterThan{Float64}}, ::MathOptInterface.ScalarAffineFunction{Float64}) at /Users/ifiske/.julia/packages/MathOptInterface/C3lip/src/attributes.jl:684
 [2] throw_set_error_fallback(::Cbc.Optimizer, ::MathOptInterface.ConstraintFunction, ::MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.GreaterThan{Float64}}, ::MathOptInterface.ScalarAffineFunction{Float64}) at /Users/ifiske/.julia/packages/MathOptInterface/C3lip/src/attributes.jl:684
 [3] set(::Cbc.Optimizer, ::MathOptInterface.ConstraintFunction, ::MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64},MathOptInterface.GreaterThan{Float64}}, ::MathOptInterface.ScalarAffineFunction{Float64}) at /Users/ifiske/.julia/packages/MathOptInterface/C3lip/src/attributes.jl:295
 [4] update! at /Users/ifiske/.julia/packages/Parametron/5crnA/src/moi_interop.jl:171 [inlined]
 [5] update!(::Parametron.Constraints{Float64}, ::Cbc.Optimizer, ::Array{MathOptInterface.VariableIndex,1}) at /Users/ifiske/.julia/packages/Parametron/5crnA/src/moi_interop.jl:241
 [6] update!(::Model{Float64,Cbc.Optimizer}) at /Users/ifiske/.julia/packages/Parametron/5crnA/src/model.jl:141
 [7] solve!(::Model{Float64,Cbc.Optimizer}) at /Users/ifiske/.julia/packages/Parametron/5crnA/src/model.jl:156
 [8] top-level scope at none:0

I’ve tried figuring out the CachingOptimizer with Parametron, but continue to get other errors. Also, would Parametron keep it’s performance benefits if we use an intermediate layer of a CachingOptimizer?

Thanks!
Ian

1 Like

Cbc’s interface supports very few types of modifications, and even discourages re-using the solver for multiple solves (https://github.com/coin-or/Cbc/blob/18849d535127b4cf2d31c94bfa0e70d725afd01c/Cbc/src/Cbc_C_Interface.h#L556), so a cache of the problem is required on the Julia side. CachingOptimizer is the solution that JuMP uses for this, and it should be usable by Parametron as well.

Given the presence of binary variables, it’s doubtful that there are performance benefits from more efficient model generation. The bottleneck is likely to be the solve itself.

1 Like

In general, no. What will happen is that Parametron modifies an intermediate model in the CachingOptimizer. When you then call solve!, the CachingOptimizer will initialize the actual optimizer basically from scratch using that intermediate model.

GLPK does support constraint modification. Also Gurobi, if you have a license.

1 Like

Thank you for the responses. So for an MPC algorithm, it seems like there is still some potential for a Parametron speed-up over JuMP. At each iteration, with JuMP, the algorithm would

  1. Create a fresh CachingOptimizer (a new Julia-level representation along with the fresh JuMP Model)
  2. Send this off to the Cbc layer to be solved.
  3. Solve in Cbc

whereas Parametron would

  1. update the existing CachingOptimizer (maybe faster than generating fresh?)
  2. Send this off to the Cbc layer to be solved.
  3. Solve in Cbc

I’ve looked into using JuMP’s constraint modification methods, but without the concept of a “parameter”, it’s difficult to translate updated data → updated expressions → updated constraint coefficients and/or RHS. Ideally, I’d build the JuMP and Parametron versions and compare the performance, though not sure I’ll have time to do so. Will keep you posted if I do such an experiment.

Also, I just noticed https://github.com/JuliaOpt/MathOptInterface.jl/pull/777. That helps me with getting the CachingOptimizer working :slight_smile:.

I don’t have access to Gurobi at the moment, and historically Cbc has dramatically outperformed GLPK for me, but I might experiment with it again if the trade-off of model-building vs solve-time becomes competitive.

Potentially, yes, but as Miles said you’d have to be solving tiny mixed-integer problems in order for setup time to dominate (solve time without warm start).

1 Like

Got-it. Thanks. Will stick with JuMP initially then, assuming that solve-time will dominate.

Yep, that’s probably the right strategy.