diff --git a/docs/src/apimanual.md b/docs/src/apimanual.md index dffff62af1..8d715ed347 100644 --- a/docs/src/apimanual.md +++ b/docs/src/apimanual.md @@ -919,9 +919,9 @@ MOI.set(model, MyPackage.PrintLevel(), 0) ### Implementing copy Avoid storing extra copies of the problem when possible. This means that solver -wrappers should not use `CachingOptimizer` as part of the wrapper. Instead, do -one of the following to load the problem (assuming the solver wrapper type is -called `Optimizer`): +wrappers should not use [`Utilities.CachingOptimizer`](@ref) as part of the wrapper. +Instead, do one of the following to load the problem (assuming the solver +wrapper type is called `Optimizer`): * If the solver supports loading the problem incrementally, implement [`add_variable`](@ref), [`add_constraint`](@ref) for supported constraints and diff --git a/docs/src/apireference.md b/docs/src/apireference.md index 650f19cc7d..47729a37c2 100644 --- a/docs/src/apireference.md +++ b/docs/src/apireference.md @@ -547,6 +547,58 @@ Utilities.load Utilities.load_constraint ``` +### Caching optimizer + +Some solvers do not support incremental definition of optimization +models. Nevertheless, you are still able to build incrementally an optimization +model with such solvers. MathOptInterface provides a utility, +[`Utilities.CachingOptimizer`](@ref), that will store in a [`ModelLike`](@ref) +the optimization model during its incremental definition. Once the +model is completely defined, the `CachingOptimizer` specifies all problem +information to the underlying solver, all at once. + +The function [`Utilities.state`](@ref) allows to query the state +of the optimizer cached inside a `CachingOptimizer`. The state +could be: +* `NO_OPTIMIZER`, if no optimizer is attached; +* `EMPTY_OPTIMIZER`, if the attached optimizer is empty; +* `ATTACHED_OPTIMIZER`, if the attached optimizer is synchronized with the + cached model defined in `CachingOptimizer`. + +The following methods modify the state of the attached optimizer: +* [`Utilities.attach_optimizer`](@ref) attachs a new `optimizer` + to a `cached_optimizer` with state `EMPTY_OPTIMIZER`. + The state of `cached_optimizer` is set to `ATTACHED_OPTIMIZER` after the call. +* [`Utilities.drop_optimizer`](@ref) drops the underlying `optimizer` + from `cached_optimizer`, without emptying it. The state of `cached_optimizer` + is set to `NO_OPTIMIZER` after the call. +* [`Utilities.reset_optimizer`](@ref) empties `optimizer` inside + `cached_optimizer`, without droping it. The state of `cached_optimizer` + is set to `EMPTY_OPTIMIZER` after the call. + +The way to operate a `CachingOptimizer` depends whether the mode +is set to `AUTOMATIC` or to `MANUAL`. +* In `MANUAL` mode, the state of the `CachingOptimizer` changes only + if the methods [`Utilities.attach_optimizer`](@ref), + [`Utilities.reset_optimizer`](@ref) or [`Utilities.drop_optimizer`](@ref) + are being called. Any unattended operation results in an error. +* In `AUTOMATIC` mode, the state of the `CachingOptimizer` changes when + necessary. Any modification not supported by the solver (e.g. dropping + a constraint) results in a drop to the state `EMPTY_OPTIMIZER`. + +When calling [`Utilities.attach_optimizer`](@ref), the `CachingOptimizer` copies +the cached model to the optimizer with [`MathOptInterface.copy_to`](@ref). +We refer to [Implementing copy](@ref) for more details. + +```@docs +Utilities.CachingOptimizer +Utilities.attach_optimizer +Utilities.reset_optimizer +Utilities.drop_optimizer +Utilities.state +Utilities.mode +``` + ## Benchmarks Functions to help benchmark the performance of solver wrappers. See diff --git a/src/Benchmarks/Benchmarks.jl b/src/Benchmarks/Benchmarks.jl index f126106320..9efa017405 100644 --- a/src/Benchmarks/Benchmarks.jl +++ b/src/Benchmarks/Benchmarks.jl @@ -22,7 +22,6 @@ Use `exclude` to exclude a subset of benchmarks. suite() do GLPK.Optimizer() end - suite(exclude = [r"delete"]) do Gurobi.Optimizer(OutputFlag=0) end diff --git a/src/Utilities/cachingoptimizer.jl b/src/Utilities/cachingoptimizer.jl index 919bf0e75d..4d32e93cd2 100644 --- a/src/Utilities/cachingoptimizer.jl +++ b/src/Utilities/cachingoptimizer.jl @@ -12,14 +12,22 @@ construction and modification even when the optimizer doesn't. A `CachingOptimizer` may be in one of three possible states (`CachingOptimizerState`): -- `NO_OPTIMIZER`: The CachingOptimizer does not have any optimizer. -- `EMPTY_OPTIMIZER`: The CachingOptimizer an empty optimizer. The optimizer is not synchronized with the cached model. -- `ATTACHED_OPTIMIZER`: The CachingOptimizer a optimizer, and it is synchronized with the cached model. +* `NO_OPTIMIZER`: The CachingOptimizer does not have any optimizer. +* `EMPTY_OPTIMIZER`: The CachingOptimizer has an empty optimizer. + The optimizer is not synchronized with the cached model. +* `ATTACHED_OPTIMIZER`: The CachingOptimizer has an optimizer, and it is synchronized with the cached model. A `CachingOptimizer` has two modes of operation (`CachingOptimizerMode`): -- `MANUAL`: The only methods that change the state of the `CachingOptimizer` are [`reset_optimizer`](@ref), [`drop_optimizer`](@ref), and [`attach_optimizer`](@ref). Attempting to perform an operation in the incorrect state results in an error. -- `AUTOMATIC`: The `CachingOptimizer` changes its state when necessary. For example, `optimize!` will automatically call `attach_optimizer` (an optimizer must have been previously set). Attempting to add a constraint or perform a modification not supported by the optimizer results in a drop to `EMPTY_OPTIMIZER` mode. +* `MANUAL`: The only methods that change the state of the `CachingOptimizer` + are [`Utilities.reset_optimizer`](@ref), [`Utilities.drop_optimizer`](@ref), + and [`Utilities.attach_optimizer`](@ref). + Attempting to perform an operation in the incorrect state results in an error. +* `AUTOMATIC`: The `CachingOptimizer` changes its state when necessary. For + example, `optimize!` will automatically call `attach_optimizer` (an + optimizer must have been previously set). Attempting to add a constraint or + perform a modification not supported by the optimizer results in a drop to + `EMPTY_OPTIMIZER` mode. """ mutable struct CachingOptimizer{OptimizerType, ModelType<:MOI.ModelLike} <: MOI.AbstractOptimizer optimizer::Union{Nothing, OptimizerType} @@ -41,7 +49,7 @@ end CachingOptimizer(model_cache::MOI.ModelLike, optimizer::AbstractOptimizer) Creates an `CachingOptimizer` in `AUTOMATIC` mode, with the optimizer `optimizer`. -The model_cache manager returned behaves like an `AbstractOptimizer` as long as no +The `model_cache` manager returned behaves like an `AbstractOptimizer` as long as no `CachingOptimizer`-specific functions (e.g. `reset_optimizer`) are called on it. The type of the optimizer returned is `CachingOptimizer{typeof(optimizer), typeof(model_cache)}` so it does not support the function @@ -59,14 +67,14 @@ end """ state(m::CachingOptimizer)::CachingOptimizerState -Returns the state of the CachingOptimizer `m`. See [`CachingOptimizer`](@ref). +Returns the state of the CachingOptimizer `m`. See [`Utilities.CachingOptimizer`](@ref). """ state(m::CachingOptimizer) = m.state """ mode(m::CachingOptimizer)::CachingOptimizerMode -Returns the operating mode of the CachingOptimizer `m`. See [`CachingOptimizer`](@ref). +Returns the operating mode of the CachingOptimizer `m`. See [`Utilities.CachingOptimizer`](@ref). """ mode(m::CachingOptimizer) = m.mode @@ -121,7 +129,7 @@ end Attaches the optimizer to `model`, copying all model data into it. Can be called only from the `EMPTY_OPTIMIZER` state. If the copy succeeds, the `CachingOptimizer` will be in state `ATTACHED_OPTIMIZER` after the call, -otherwise an error is thrown; see [`copy_to`](@ref) for more details on which +otherwise an error is thrown; see [`MathOptInterface.copy_to`](@ref) for more details on which errors can be thrown. """ function attach_optimizer(model::CachingOptimizer) @@ -387,7 +395,10 @@ function MOI.set(m::CachingOptimizer, attr::MOI.AbstractModelAttribute, value) MOI.set(m.model_cache, attr, value) end -function MOI.set(m::CachingOptimizer, attr::Union{MOI.AbstractVariableAttribute,MOI.AbstractConstraintAttribute}, index::MOI.Index, value) +function MOI.set(m::CachingOptimizer, + attr::Union{MOI.AbstractVariableAttribute, + MOI.AbstractConstraintAttribute}, + index::MOI.Index, value) if m.state == ATTACHED_OPTIMIZER optimizer_index = m.model_to_optimizer_map[index] optimizer_value = attribute_value_map(m.model_to_optimizer_map, value)