-
-
Notifications
You must be signed in to change notification settings - Fork 395
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WIP: bridge backend of Model if needed
- Loading branch information
Showing
13 changed files
with
370 additions
and
272 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -236,59 +236,61 @@ mutable struct Model <: AbstractModel | |
end | ||
|
||
""" | ||
Model(; caching_mode::MOIU.CachingOptimizerMode=MOIU.AUTOMATIC) | ||
Model(optimizer_factory = nothing; bridge_constraints::Bool = false,) | ||
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`. Use [`set_optimizer`](@ref) to set the optimizer before | ||
calling [`optimize!`](@ref). | ||
""" | ||
function Model(; | ||
caching_mode::MOIU.CachingOptimizerMode = MOIU.AUTOMATIC, | ||
solver = nothing, | ||
) | ||
if solver !== nothing | ||
error( | ||
"The solver= keyword is no longer available in JuMP 0.19 and " * | ||
"later. See the JuMP documentation " * | ||
"(https://jump.dev/JuMP.jl/latest/) for latest syntax.", | ||
) | ||
end | ||
universal_fallback = MOIU.UniversalFallback(MOIU.Model{Float64}()) | ||
caching_opt = MOIU.CachingOptimizer(universal_fallback, caching_mode) | ||
return direct_model(caching_opt) | ||
end | ||
Construct a JuMP model with a `MOI.Utilities.CachingOptimizer` backend. | ||
""" | ||
Model(optimizer_factory; | ||
caching_mode::MOIU.CachingOptimizerMode=MOIU.AUTOMATIC, | ||
bridge_constraints::Bool=true) | ||
See [`set_optimizer`](@ref) for a description of the arguments. | ||
## Examples | ||
Return a new JuMP model with the provided optimizer and bridge settings. This | ||
function is equivalent to: | ||
Create a model with no backing optimizer. Call [`set_optimizer`](@ref) later to | ||
add an optimizer prior to calling [`optimize!`](@ref). | ||
```julia | ||
model = Model() | ||
set_optimizer(model, optimizer_factory, | ||
bridge_constraints=bridge_constraints) | ||
return model | ||
model = Model() | ||
``` | ||
See [`set_optimizer`](@ref) for the description of the `optimizer_factory` and | ||
`bridge_constraints` arguments. | ||
## Examples | ||
Pass a `.Optimizer` object from a supported package: | ||
```julia | ||
model = Model(GLPK.Optimizer) | ||
``` | ||
The following creates a model with the optimizer set to `Ipopt`: | ||
Use [`optimizer_with-attributes`](@ref) to initialize the model with the | ||
provided attributes: | ||
```julia | ||
model = Model(Ipopt.Optimizer) | ||
model = Model( | ||
optimizer_with_attributes(GLPK.Optimizer, "loglevel" => 0), | ||
) | ||
``` | ||
Create an anonymous function to pass positional arguments to the optimizer: | ||
```julia | ||
model = Model() do | ||
AmplNLWriter.Optimizer(Bonmin_jll.amplexe) | ||
end | ||
``` | ||
Pass `bridge_constraints = true` to intialize the model with bridges. Normally, | ||
bridges will be added only if necessary. Adding them here can have performance | ||
benefits if you know that your model will use the bridges. | ||
```julia | ||
model = Model(SCS.Optimizer; bridge_constraints = true) | ||
``` | ||
""" | ||
function Model(optimizer_factory; bridge_constraints::Bool = true, kwargs...) | ||
model = Model(; kwargs...) | ||
set_optimizer( | ||
model, | ||
optimizer_factory, | ||
bridge_constraints = bridge_constraints, | ||
function Model(optimizer_factory = nothing; bridge_constraints::Bool = false) | ||
model = direct_model( | ||
MOI.Utilities.CachingOptimizer( | ||
MOIU.UniversalFallback(MOIU.Model{Float64}()), | ||
MOI.Utilities.AUTOMATIC, | ||
), | ||
) | ||
if optimizer_factory !== nothing | ||
set_optimizer( | ||
model, | ||
optimizer_factory; | ||
bridge_constraints = bridge_constraints, | ||
) | ||
end | ||
return model | ||
end | ||
|
||
|
@@ -432,6 +434,66 @@ end | |
unsafe_backend(model::MOIB.LazyBridgeOptimizer) = unsafe_backend(model.model) | ||
unsafe_backend(model::MOI.ModelLike) = model | ||
|
||
_needs_bridges(::MOI.ModelLike) = true | ||
_needs_bridges(::MOI.Bridges.LazyBridgeOptimizer) = false | ||
_needs_bridges(::Nothing) = false | ||
|
||
""" | ||
TODO(odow): replace with `MOI.Utilities.CachingOptimizer` when updating to | ||
[email protected]. | ||
This function works around an unnecessary `@assert MOI.is_valid(model_cache)` | ||
check when creating a CachingOptimizer. | ||
""" | ||
function _CachingOptimizer( | ||
model_cache::A, | ||
optimizer::B, | ||
) where {A<:MOI.ModelLike,B<:MOI.AbstractOptimizer} | ||
return MOI.Utilities.CachingOptimizer{B,A}( | ||
optimizer, | ||
model_cache, | ||
MOI.Utilities.EMPTY_OPTIMIZER, | ||
MOI.Utilities.AUTOMATIC, | ||
MOI.Utilities.IndexMap(), | ||
MOI.Utilities.IndexMap(), | ||
) | ||
end | ||
|
||
function _add_bridges_if_needed( | ||
model::Model, | ||
backend::MOI.Utilities.CachingOptimizer, | ||
) | ||
if !_needs_bridges(backend.optimizer) | ||
return false | ||
end | ||
optimizer = backend.optimizer | ||
if !MOI.Utilities.supports_default_copy_to(backend.optimizer, false) | ||
optimizer = Utilities.CachingOptimizer( | ||
Utilities.UniversalFallback(Utilities.Model{Float64}()), | ||
backend.optimizer, | ||
) | ||
end | ||
bridge = MOI.Bridges.full_bridge_optimizer(optimizer, Float64) | ||
# We don't have to worry about adding `model.bridge_types` here, because | ||
# calling `add_bridge` will force a `LazyBridgeOptimizer` backend. | ||
model.moi_backend = _CachingOptimizer(backend.model_cache, bridge) | ||
MOIU.reset_optimizer(model) | ||
return true | ||
end | ||
|
||
_add_bridges_if_needed(::Model, ::MOI.ModelLike) = false | ||
|
||
""" | ||
_add_bridges_if_needed(model::Model) | ||
Add a `MOI.Bridges.LazyBridgeOptimizer` to the backend of `model` if one does | ||
not already exist. Returns `true` if a new `MOI.Bridges.LazyBridgeOptimizer` is | ||
added, and `false` otherwise. | ||
""" | ||
function _add_bridges_if_needed(model::Model) | ||
return _add_bridges_if_needed(model, backend(model)) | ||
end | ||
|
||
""" | ||
moi_mode(model::MOI.ModelLike) | ||
|
@@ -458,16 +520,6 @@ function mode(model::Model) | |
return moi_mode(backend(model)) | ||
end | ||
|
||
""" | ||
moi_bridge_constraints(model::MOI.ModelLike) | ||
Return `true` if `model` will bridge constraints. | ||
""" | ||
moi_bridge_constraints(model::MOI.ModelLike) = false | ||
function moi_bridge_constraints(model::MOIU.CachingOptimizer) | ||
return model.optimizer isa MOI.Bridges.LazyBridgeOptimizer | ||
end | ||
|
||
# Internal function. | ||
function _try_get_solver_name(model_like) | ||
try | ||
|
@@ -500,44 +552,24 @@ function solver_name(model::Model) | |
end | ||
end | ||
|
||
""" | ||
bridge_constraints(model::Model) | ||
# No optimizer is attached, the bridge will be added when one is attached | ||
_moi_add_bridge(::Nothing, ::Type{<:MOI.Bridges.AbstractBridge}) = nothing | ||
|
||
When in direct mode, return `false`. | ||
When in manual or automatic mode, return a `Bool` indicating whether the | ||
optimizer is set and unsupported constraints are automatically bridged | ||
to equivalent supported constraints when an appropriate transformation is | ||
available. | ||
""" | ||
function bridge_constraints(model::Model) | ||
# The type of `backend(model)` is not type-stable, so we use a function | ||
# barrier (`moi_bridge_constraints`) to improve performance. | ||
return moi_bridge_constraints(backend(model)) | ||
end | ||
|
||
function _moi_add_bridge( | ||
model::Nothing, | ||
BridgeType::Type{<:MOI.Bridges.AbstractBridge}, | ||
) | ||
# No optimizer is attached, the bridge will be added when one is attached | ||
return | ||
end | ||
function _moi_add_bridge( | ||
model::MOI.ModelLike, | ||
BridgeType::Type{<:MOI.Bridges.AbstractBridge}, | ||
) | ||
function _moi_add_bridge(::MOI.ModelLike, ::Type{<:MOI.Bridges.AbstractBridge}) | ||
return error( | ||
"Cannot add bridge if `bridge_constraints` was set to `false` in the", | ||
" `Model` constructor.", | ||
"`In order to add a bridge, you must pass `bridge_constraints=true` " * | ||
"to `Model`, i.e., `Model(optimizer; bridge_constraints = true)`.", | ||
) | ||
end | ||
|
||
function _moi_add_bridge( | ||
bridge_opt::MOI.Bridges.LazyBridgeOptimizer, | ||
BridgeType::Type{<:MOI.Bridges.AbstractBridge}, | ||
) | ||
MOI.Bridges.add_bridge(bridge_opt, BridgeType{Float64}) | ||
return | ||
end | ||
|
||
function _moi_add_bridge( | ||
caching_opt::MOIU.CachingOptimizer, | ||
BridgeType::Type{<:MOI.Bridges.AbstractBridge}, | ||
|
@@ -547,8 +579,10 @@ function _moi_add_bridge( | |
end | ||
|
||
""" | ||
add_bridge(model::Model, | ||
BridgeType::Type{<:MOI.Bridges.AbstractBridge}) | ||
add_bridge( | ||
model::Model, | ||
BridgeType::Type{<:MOI.Bridges.AbstractBridge}, | ||
) | ||
Add `BridgeType` to the list of bridges that can be used to transform | ||
unsupported constraints into an equivalent formulation using only constraints | ||
|
@@ -559,6 +593,8 @@ function add_bridge( | |
BridgeType::Type{<:MOI.Bridges.AbstractBridge}, | ||
) | ||
push!(model.bridge_types, BridgeType) | ||
# Make sure we force a `LazyBridgeOptimizer` backend! | ||
_add_bridges_if_needed(model) | ||
# The type of `backend(model)` is not type-stable, so we use a function | ||
# barrier (`_moi_add_bridge`) to improve performance. | ||
_moi_add_bridge(JuMP.backend(model), BridgeType) | ||
|
@@ -606,8 +642,9 @@ end | |
|
||
function _moi_print_bridge_graph(::IO, ::MOI.ModelLike) | ||
return error( | ||
"Cannot print bridge graph if `bridge_constraints` was set to " * | ||
"`false` in the `Model` constructor.", | ||
"`In order to print the bridge graph, you must pass " * | ||
"`bridge_constraints=true` to `Model`, i.e., " * | ||
"`Model(optimizer; bridge_constraints = true)`.", | ||
) | ||
end | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.