Solvers

Interacting with solvers

A JuMP model keeps a MathOptInterface (MOI) backend of type MOI.ModelLike internally that stores the optimization problem and acts as the optimization solver. We call it an MOI backend and not optimizer as it can also be a wrapper around an optimization file format such as MPS that writes the JuMP model in a file. JuMP can be viewed as a lightweight user-friendly layer on top of the MOI backend:

While this allows JuMP to be a thin wrapper on top of the solver API, as mentioned in the last point above, this seems rather demanding on the solver. Indeed, while some solvers support incremental building of the model and modifications before and after solve, other solvers only support the model being copied at once before solve. Moreover it seems to require all solvers to implement all possible reformulations independently which seems both very ambitious and might generate a lot of duplicated code.

These apparent limitations are in fact addressed at the MOI level in a manner that is completely transparent to JuMP. While the MOI API may seem very demanding, it allows MOI models to be a succession of lightweight MOI layers that fill the gap between JuMP requirements and the solver capabilities.

JuMP models can be created in three different modes: Automatic, Manual and Direct.

Automatic and Manual modes

In Automatic and Manual modes, two MOI layers are automatically applied to the optimizer:

See the MOI documentation for more details on these two MOI layers.

To attach an optimizer to a JuMP model, JuMP needs to create a new empty optimizer instance. New optimizer instances can be obtained using an OptimizerFactory that can be created using the with_optimizer function:

JuMP.with_optimizerFunction.
with_optimizer(constructor, args...; kwargs...)

Return an OptimizerFactory that creates optimizers using the constructor constructor with positional arguments args and keyword arguments kwargs.

Examples

The following returns an optimizer factory that creates IpoptOptimizers using the constructor call IpoptOptimizer(print_level=0):

with_optimizer(IpoptOptimizer, print_level=0)
source

The factory can be provided either at model construction time or at JuMP.optimize! time:

JuMP.optimize!Function.
function optimize!(model::Model,
                   optimizer_factory::Union{Nothing, OptimizerFactory}=nothing;
                   ignore_optimize_hook=(model.optimize_hook === nothing))

Optimize the model. If optimizer_factory is not nothing, it first set the optimizer to a new one created using the optimizer factory.

source

New JuMP models are created using the Model constructor:

JuMP.ModelMethod.
Model(; caching_mode::MOIU.CachingOptimizerMode=MOIU.Automatic,
        bridge_constraints::Bool=true)

Return a new JuMP model without any optimizer; the model is stored the model in a cache. The mode of the CachingOptimizer storing this cache is caching_mode. The optimizer can be set later in the JuMP.optimize! call. If bridge_constraints is true, constraints that are not supported by the optimizer are automatically bridged to equivalent supported constraints when an appropriate is defined in the MathOptInterface.Bridges module or is defined in another module and is explicitely added.

source
JuMP.ModelMethod.
Model(optimizer_factory::OptimizerFactory;
      caching_mode::MOIU.CachingOptimizerMode=MOIU.Automatic,
      bridge_constraints::Bool=true)

Return a new JuMP model using the optimizer factory optimizer_factory to create the optimizer. The optimizer factory can be created by the with_optimizer function.

Examples

The following creates a model using the optimizer IpoptOptimizer(print_level=0):

model = JuMP.Model(with_optimizer(IpoptOptimizer, print_level=0))
source

TODO: how to control the caching optimizer states

Direct mode

JuMP models can be created in Direct mode using the JuMP.direct_model function.

JuMP.direct_modelFunction.
direct_model(backend::MOI.ModelLike)

Return a new JuMP model using backend to store the model and solve it. As opposed to the Model constructor, no cache of the model is stored outside of backend and no bridges are automatically applied to backend. The absence of cache reduces the memory footprint but it is important to bear in mind the following implications of creating models using this direct mode:

  • When backend does not support an operation such as adding variables/constraints after solver or modifying constraints, an error is thrown. With models created using the Model constructor, such situations can be dealt with by storing the modifications in a cache and loading them into the optimizer when JuMP.optimize! is called.
  • No constraint bridging is supported by default.
  • The optimizer used cannot be changed the model is constructed.
  • The model created cannot be copied.
source

TODO: How to set parameters (solver specific and generic). Status codes. Accessing the result. How to accurately measure the solve time.