Skip to content

Commit

Permalink
Merge 3548801 into c27c7fc
Browse files Browse the repository at this point in the history
  • Loading branch information
blegat committed Jul 30, 2018
2 parents c27c7fc + 3548801 commit cd0c152
Show file tree
Hide file tree
Showing 6 changed files with 341 additions and 134 deletions.
84 changes: 82 additions & 2 deletions docs/src/solvers.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,87 @@
Interacting with solvers
========================

TODO: Describe the connection between JuMP and solvers. Automatic vs. Manual
mode. CachingOptimizer. How to set/change solvers. How to set parameters (solver
A JuMP model keeps a [MathOptInterface (MOI)](https://github.com/JuliaOpt/MathOptInterface.jl)
*backend* 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:

* JuMP does not maintain any copy of the model outside this MOI backend.
* JuMP variable (resp. constraint) references are simple structures containing
both a reference to the JuMP model and the MOI index of the variable (resp.
constraint).
* JuMP gives the constraints to the MOI backend in the form provided by the user
without doing any automatic reformulation.
* variables additions, constraints additions/modifications and objective
modifications are directly applied to the MOI backend thus expecting the
backend to support such modifications.

While this allows JuMP API to 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:

* `CachingOptimizer`: maintains a cache of the model so that when the optimizer
does not support an incremental change to the model, the optimizer's internal
model can be discarded and restored from the cache just before optimization.
The `CachingOptimizer` has two different modes: Automatic and Manual
corresponding to the two JuMP modes with the same names.
* `LazyBridgeOptimizer`: when a constraint added is not supported by the
optimizer, it tries transform the constraint into an equivalent form,
possibly adding new variables and constraints that are supported by the
optimizer. The applied transformations are selected among known recipes
which are called bridges. A few default bridges are defined in MOI but new
ones can be defined and added to the `LazyBridgeOptimizer` used by JuMP.

See the [MOI documentation](http://www.juliaopt.org/MathOptInterface.jl/stable/)
for more details on these two MOI layers.

To create a fresh new JuMP model (or a fresh new copy of a JuMP model), JuMP
needs to create a new empty optimizer instance. New optimizer instances can
be obtained using a factory that can be created using the
[`with_optimizer`](@ref) function:
```@docs
with_optimizer
```

The factory can be set to the JuMP model using the [`JuMP.setoptimizer`](@ref)
function:
```@docs
JuMP.setoptimizer
```

New JuMP models are created using the [`Model`](@ref) constructor:
```@docs
Model()
Model(::JuMP.OptimizerFactory)
```

## Direct mode

JuMP models can be created in Direct mode using the [`JuMP.direct_model`](@ref)
function.
```@docs
JuMP.direct_model
```

TODO: How to set parameters (solver
specific and generic). Status codes. Accessing the result.
How to accurately measure the solve time.
193 changes: 151 additions & 42 deletions src/JuMP.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ using .Derivatives
export
# Objects
Model, VariableRef, Norm, AffExpr, QuadExpr,
with_optimizer,
# LinearConstraint, QuadConstraint, SDConstraint,
NonlinearConstraint,
ConstraintRef,
Expand Down Expand Up @@ -77,6 +78,50 @@ const MOIBIN = MOICON{MOI.SingleVariable,MOI.ZeroOne}

@MOIU.model JuMPMOIModel (ZeroOne, Integer) (EqualTo, GreaterThan, LessThan, Interval) (Zeros, Nonnegatives, Nonpositives, SecondOrderCone, RotatedSecondOrderCone, GeometricMeanCone, PositiveSemidefiniteConeTriangle, PositiveSemidefiniteConeSquare, RootDetConeTriangle, RootDetConeSquare, LogDetConeTriangle, LogDetConeSquare) () (SingleVariable,) (ScalarAffineFunction,ScalarQuadraticFunction) (VectorOfVariables,) (VectorAffineFunction,)

"""
OptimizerFactory
User-friendly closure that creates new MOI models. New `OptimizerFactory`s are
created with [`with_optimizer`](@ref) and new models are created from the
factory with [`create_model`](@ref).
"""
struct OptimizerFactory
# The constructor can be
# * `Function`: a function, or
# * `DataType`: a type, or
# * `UnionAll`: a type with missing parameters.
constructor::Union{Function, DataType, UnionAll}
args::Tuple
kwargs # type changes from Julia v0.6 to v0.7 so we leave it untyped for now
end

"""
with_optimizer(constructor::Type, args...; kwargs...)
Return a factory that creates optimizers using the constructor `constructor`
with positional arguments `args` and keyword arguments `kwargs`.
## Examples
The following returns a factory that creates `IpoptOptimizer`s using the
constructor call `IpoptOptimizer(print_level=0)`:
```julia
with_optimizer(IpoptOptimizer, print_level=0)
```
"""
function with_optimizer(constructor::Type, args...; kwargs...)
return OptimizerFactory(constructor, args, kwargs)
end

"""
create_model(factory::OptimizerFactory)
Creates a new model with the factory `factory`.
"""
function create_model(factory::OptimizerFactory)
return factory.constructor(factory.args...; factory.kwargs...)
end

###############################################################################
# Model

Expand Down Expand Up @@ -107,6 +152,12 @@ mutable struct Model <: AbstractModel

customnames::Vector

# OptimizerFactory used to create a new optimizer, it is kept as it might be needed
# again if the user requests a copy of the model using `Base.copy`.
# In Manual and Automatic mode: OptimizerFactory used to create the optimizer or
# Nothing if it has not already been set
# In Direct mode: Nothing
factory::Union{Nothing, OptimizerFactory}
# In Manual and Automatic modes, LazyBridgeOptimizer{CachingOptimizer}.
# In Direct mode, will hold an AbstractOptimizer.
moibackend::MOI.AbstractOptimizer
Expand All @@ -123,50 +174,108 @@ mutable struct Model <: AbstractModel
# Enable extensions to attach arbitrary information to a JuMP model by
# using an extension-specific symbol as a key.
ext::Dict{Symbol, Any}
end

# Default constructor.
function Model(;
mode::ModelMode=Automatic,
backend=nothing,
optimizer=nothing,
bridge_constraints=true)
model = new()
model.variabletolowerbound = Dict{MOIVAR, MOILB}()
model.variabletoupperbound = Dict{MOIVAR, MOIUB}()
model.variabletofix = Dict{MOIVAR, MOIFIX}()
model.variabletointegrality = Dict{MOIVAR, MOIINT}()
model.variabletozeroone = Dict{MOIVAR, MOIBIN}()
model.customnames = VariableRef[]
if backend != nothing
# TODO: It would make more sense to not force users to specify
# Direct mode if they also provide a backend.
@assert mode == Direct
@assert optimizer === nothing
@assert MOI.isempty(backend)
model.moibackend = backend
else
@assert mode != Direct
universal_fallback = MOIU.UniversalFallback(JuMPMOIModel{Float64}())
caching_mode = (mode == Automatic) ? MOIU.Automatic : MOIU.Manual
caching_opt = MOIU.CachingOptimizer(universal_fallback,
caching_mode)
if bridge_constraints
model.moibackend = MOI.Bridges.fullbridgeoptimizer(caching_opt,
Float64)
else
model.moibackend = caching_opt
end
if optimizer !== nothing
MOIU.resetoptimizer!(model, optimizer)
end
end
model.optimizehook = nothing
model.nlpdata = nothing
model.objdict = Dict{Symbol, Any}()
model.operator_counter = 0
model.ext = Dict{Symbol, Any}()
return model
"""
Model(factory::Union{Nothing, OptimizerFactory}, moibackend::MOI.ModelLike)
Return a new JuMP model with factory `factory` and MOI backend `moibackend`.
This constructor is a low-level constructor used by [`Model()`](@ref),
[`Model(::OptimizerFactory)`](@ref) and [`direct_model`](@ref).
"""
function Model(factory::Union{Nothing, OptimizerFactory}, moibackend::MOI.ModelLike)
@assert MOI.isempty(moibackend)
return Model(Dict{MOIVAR, MOILB}(),
Dict{MOIVAR, MOIUB}(),
Dict{MOIVAR, MOIFIX}(),
Dict{MOIVAR, MOIINT}(),
Dict{MOIVAR, MOIBIN}(),
VariableRef[],
factory,
moibackend,
nothing,
nothing,
Dict{Symbol, Any}(),
0,
Dict{Symbol, Any}())
end

"""
Model(; caching_mode::MOIU.CachingOptimizerMode=MOIU.Automatic,
bridge_constraints::Bool=true)
Return a new JuMP model without any optimizer storing the model in a cache.
The mode of the `CachingOptimizer` storing this cache is `caching_mode`.
The optimizer can be set later with [`set_optimizer`](@ref). 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.
"""
function Model(; caching_mode::MOIU.CachingOptimizerMode=MOIU.Automatic,
bridge_constraints::Bool=true)
universal_fallback = MOIU.UniversalFallback(JuMPMOIModel{Float64}())
caching_opt = MOIU.CachingOptimizer(universal_fallback,
caching_mode)
if bridge_constraints
backend = MOI.Bridges.fullbridgeoptimizer(caching_opt,
Float64)
else
backend = caching_opt
end
return Model(nothing, backend)
end

"""
Model(factory::OptimizerFactory;
caching_mode::MOIU.CachingOptimizerMode=MOIU.Automatic,
bridge_constraints::Bool=true)
Return a new JuMP model using the factory `factory` to create the optimizer.
This is equivalent to calling `Model` with the same keyword arguments and then
calling [`set_optimizer`](@ref) on the created model with the `factory`. The
factory can be created by the [`with_optimizer`](@ref) function.
## Examples
The following creates a model using the optimizer
`IpoptOptimizer(print_level=0)`:
```julia
model = JuMP.Model(with_optimizer(IpoptOptimizer, print_level=0))
```
"""
function Model(factory::OptimizerFactory; kwargs...)
model = Model(; kwargs...)
set_optimizer(model, factory)
return model
end

"""
direct_model(backend::MOI.ModelLike)
Return a new JuMP model using `backend` to store the model and solve it. As
opposed to the [`Model`](@ref) 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 importnat 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`](@ref) constructor, such
situations can be dealt with modifying the cache only and copying the model
cache once `JuMP.optimize` is called.
* When `backend` does not support a constraint type, the constraint is not
automatically bridged to constraints supported by `backend`.
* The optimizer used cannot be changed. With models created using the
[`Model`](@ref) constuctor, the variable and constraint indices used
are the indices corresponding to the cached model so the optimizer can be
changed but in direct mode, changing the backend would render all variable
and constraint references invalid are their internal indices corresponds to
the previous backend.
* The model created cannot be copied.
"""
function direct_model(backend::MOI.ModelLike)
return Model(nothing, backend)
end

# In Automatic and Manual mode, `model.moibackend` is either directly the
Expand Down
22 changes: 22 additions & 0 deletions src/optimizerinterface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,28 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""
set_optimizer(model::Model, factory::OptimizerFactory)
Sets the optimizer of the model `model` as the optimizers created by the
factory `factory`. The factory can be created by the [`with_optimizer`](@ref)
function.
## Examples
The following sets the optimizer of `model` to be
`IpoptOptimizer(print_level=0)`:
```julia
set_optimizer(model, with_optimizer(IpoptOptimizer, print_level=0))
```
"""
function set_optimizer(model::Model, factory::OptimizerFactory)
@assert mode(model) != Direct
model.factory = factory
optimizer = create_model(factory)
MOIU.resetoptimizer!(model, optimizer)
end

# These methods directly map to CachingOptimizer methods.
# They cannot be called in Direct mode.
function MOIU.resetoptimizer!(model::Model, optimizer::MOI.AbstractOptimizer)
Expand Down

0 comments on commit cd0c152

Please sign in to comment.