Skip to content

Caches that are not caches #2862

@odow

Description

@odow

Consider:

julia> import ECOS

julia> import MathOptInterface

julia> model = MOI.instantiate(ECOS.Optimizer; with_cache_type = Float64)
MOIU.CachingOptimizer
├ state: EMPTY_OPTIMIZER
├ mode: AUTOMATIC
├ model_cache: MOIU.UniversalFallback{ECOS.OptimizerCache}
│ ├ ObjectiveSense: FEASIBILITY_SENSE
│ ├ ObjectiveFunctionType: MOI.ScalarAffineFunction{Float64}
│ ├ NumberOfVariables: 0
│ └ NumberOfConstraints: 0
└ optimizer: ECOS.Optimizer
  ├ ObjectiveSense: unknown
  ├ ObjectiveFunctionType: unknown
  ├ NumberOfVariables: unknown
  └ NumberOfConstraints: unknown

julia> x = MOI.add_variable(model)
MOI.VariableIndex(1)

julia> f = MOI.Utilities.vectorize([1.0 * x])
┌                              ┐
│0.0 + 1.0 MOI.VariableIndex(1)│
└                              ┘

julia> MOI.add_constraint(model, f, MOI.Nonnegatives(1))
MathOptInterface.ConstraintIndex{MathOptInterface.VectorAffineFunction{Float64}, MathOptInterface.Nonnegatives}(1)

julia> MOI.optimize!(model)

ECOS 2.0.8 - (C) embotech GmbH, Zurich Switzerland, 2012-15. Web: www.embotech.com/ECOS

It     pcost       dcost      gap   pres   dres    k/t    mu     step   sigma     IR    |   BT
 0  +0.000e+00  -0.000e+00  +1e+00  5e-01  5e-01  1e+00  1e+00    ---    ---    0  0  - |  -  - 
 1  +0.000e+00  -0.000e+00  +1e-02  4e-03  1e-02  1e-02  1e-02  0.9890  1e-04   0  0  1 |  0  0
 2  +0.000e+00  -0.000e+00  +1e-04  4e-05  1e-04  1e-04  1e-04  0.9890  1e-04   0  1  1 |  0  0
 3  +0.000e+00  -0.000e+00  +1e-06  5e-07  1e-06  1e-06  1e-06  0.9890  1e-04   0  1  1 |  0  0
 4  +0.000e+00  -0.000e+00  +2e-08  5e-09  2e-08  2e-08  2e-08  0.9890  1e-04   0  0  0 |  0  0
 5  +0.000e+00  -0.000e+00  +2e-10  6e-11  2e-10  2e-10  2e-10  0.9890  1e-04   0  0  0 |  0  0

OPTIMAL (within feastol=1.7e-10, reltol=nan, abstol=1.7e-10).
Runtime: 0.000077 seconds.


julia> MOI.add_constraint(model, f, MOI.Nonnegatives(1))
ERROR: MathOptInterface.AddConstraintNotAllowed{MathOptInterface.VectorAffineFunction{Float64}, MathOptInterface.Nonnegatives}:

## Cause

Adding `MathOptInterface.VectorAffineFunction{Float64}`-in-`MathOptInterface.Nonnegatives` constraints cannot be performed because:

MatrixOfConstraints does not allow modifications to be made to the model once
`MOI.Utilities.final_touch` has been called. This is called at the end of
`MOI.copy_to` and in `MOI.Utilities.attach_optimizer` (which is called by
`MOI.optimize!` in a `MOI.Utilities.CachingOptimizer`). In order to be able to
apply modifications to this model, you should add a layer
`MOI.Utilities.CachingOptimizer(MOI.Utilities.Model{Float64}(), model)`
where `model` is the current model. This will automatically empty `model` when
modifications are done after `MOI.Utilities.final_touch` is called and copy the
model again in `MOI.Utilities.attach_optimizer`.


## Fixing this error

An `MOI.NotAllowedError` error occurs when you have tried to do something that
is not implemented by the solver.

The most common way to fix this error is to wrap the optimizer in a
`MOI.Utilities.CachingOptimizer`.

For example, if you are using `JuMP.Model` or `JuMP.set_optimizer`, do:
```julia
model = JuMP.Model(optimizer; with_cache_type = Float64)
model = JuMP.GenericModel{T}(optimizer; with_cache_type = T)
JuMP.set_optimizer(model, optimizer; with_cache_type = Float64)
```
Similarly, if you are using `MOI.instantiate`, do:
```julia
model = MOI.instantiate(optimizer; with_cache_type = Float64)
```

Stacktrace:
 [1] add_constraint(model::MathOptInterface.Utilities.MatrixOfConstraints{…}, func::MathOptInterface.VectorAffineFunction{…}, set::MathOptInterface.Nonnegatives)
   @ MathOptInterface.Utilities ~/.julia/packages/MathOptInterface/zq9bo/src/Utilities/matrix_of_constraints.jl:403
 [2] add_constraint(model::ECOS.ZerosOrNot{…}, func::MathOptInterface.VectorAffineFunction{…}, set::MathOptInterface.Nonnegatives)
   @ MathOptInterface.Utilities ~/.julia/packages/MathOptInterface/zq9bo/src/Utilities/struct_of_constraints.jl:67
 [3] add_constraint
   @ ~/.julia/packages/MathOptInterface/zq9bo/src/Utilities/model.jl:325 [inlined]
 [4] add_constraint(uf::MathOptInterface.Utilities.UniversalFallback{…}, func::MathOptInterface.VectorAffineFunction{…}, set::MathOptInterface.Nonnegatives)
   @ MathOptInterface.Utilities ~/.julia/packages/MathOptInterface/zq9bo/src/Utilities/universalfallback.jl:851
 [5] add_constraint(m::MathOptInterface.Utilities.CachingOptimizer{…}, func::MathOptInterface.VectorAffineFunction{…}, set::MathOptInterface.Nonnegatives)
   @ MathOptInterface.Utilities ~/.julia/packages/MathOptInterface/zq9bo/src/Utilities/cachingoptimizer.jl:586
 [6] top-level scope
   @ REPL[112]:1
Some type information was truncated. Use `show(err)` to see complete types.

But I aaaaaam using a caching optimizer you say.

That's because our cache is a ECOS.OptimizerCache instead of a Utilities.Model.

That's intended behaviour because we optimised for JuMP, which has a top-level cache. It's problematic for Juniper which embeds it as a sub-solver: lanl-ansi/Juniper.jl#269

I guess the solution is for it to use an explicit cache.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions