-
-
Notifications
You must be signed in to change notification settings - Fork 395
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
✨ Add support for solver at optimize with bridges #1626
Changes from 1 commit
87c558b
7af300d
40d74f1
ebb7e9d
564522c
c531a93
fe0ee72
de63d02
88be967
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
julia 0.6 | ||
MathOptInterface 0.6 0.7 | ||
MathOptInterface 0.6.3 0.7 | ||
ForwardDiff 0.5 0.11 | ||
Calculus | ||
DataStructures | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -134,8 +134,8 @@ end | |
# Model | ||
|
||
# Model has three modes: | ||
# 1) Automatic: moi_backend field holds a LazyBridgeOptimizer{CachingOptimizer} in Automatic mode. | ||
# 2) Manual: moi_backend field holds a LazyBridgeOptimizer{CachingOptimizer} in Manual mode. | ||
# 1) Automatic: moi_backend field holds a CachingOptimizer in Automatic mode. | ||
# 2) Manual: moi_backend field holds a CachingOptimizer in Manual mode. | ||
# 3) Direct: moi_backend field holds an AbstractOptimizer. No extra copy of the model is stored. The moi_backend must support add_constraint etc. | ||
# Methods to interact with the CachingOptimizer are defined in solverinterface.jl. | ||
@enum ModelMode Automatic Manual Direct | ||
|
@@ -157,7 +157,7 @@ mutable struct Model <: AbstractModel | |
variable_to_fix::Dict{MOIVAR, MOIFIX} | ||
variable_to_integrality::Dict{MOIVAR, MOIINT} | ||
variable_to_zero_one::Dict{MOIVAR, MOIBIN} | ||
# In Manual and Automatic modes, LazyBridgeOptimizer{CachingOptimizer}. | ||
# In Manual and Automatic modes, CachingOptimizer. | ||
# In Direct mode, will hold an AbstractOptimizer. | ||
moi_backend::MOI.AbstractOptimizer | ||
# Hook into a solve call...function of the form f(m::Model; kwargs...), | ||
|
@@ -184,11 +184,10 @@ a cache. The mode of the `CachingOptimizer` storing this cache is | |
`caching_mode`. The optimizer can be set later in the [`JuMP.optimize!`](@ref) | ||
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. | ||
an appropriate transformation 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, | ||
solver=nothing) | ||
if solver !== nothing | ||
error("The solver= keyword is no longer available in JuMP 0.19 and " * | ||
|
@@ -198,13 +197,7 @@ function Model(; caching_mode::MOIU.CachingOptimizerMode=MOIU.Automatic, | |
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 direct_model(backend) | ||
return direct_model(caching_opt) | ||
end | ||
|
||
""" | ||
|
@@ -224,10 +217,11 @@ The following creates a model using the optimizer | |
model = JuMP.Model(with_optimizer(IpoptOptimizer, print_level=0)) | ||
``` | ||
""" | ||
function Model(optimizer_factory::OptimizerFactory; kwargs...) | ||
function Model(optimizer_factory::OptimizerFactory; | ||
bridge_constraints::Bool=true, kwargs...) | ||
model = Model(; kwargs...) | ||
optimizer = optimizer_factory() | ||
MOIU.resetoptimizer!(model, optimizer) | ||
set_optimizer(model, optimizer_factory, | ||
bridge_constraints=bridge_constraints) | ||
return model | ||
end | ||
|
||
|
@@ -269,22 +263,6 @@ if VERSION >= v"0.7-" | |
end | ||
|
||
|
||
# In Automatic and Manual mode, `backend(model)` is either directly the | ||
# `CachingOptimizer` if `bridge_constraints=false` was passed in the constructor | ||
# or it is a `LazyBridgeOptimizer` and the `CachingOptimizer` is stored in the | ||
# `model` field | ||
function caching_optimizer(model::Model) | ||
if backend(model) isa MOIU.CachingOptimizer | ||
return backend(model) | ||
elseif (backend(model) isa | ||
MOI.Bridges.LazyBridgeOptimizer{<:MOIU.CachingOptimizer}) | ||
return backend(model).model | ||
else | ||
error("The function `caching_optimizer` cannot be called on a model " * | ||
"in `Direct` mode.") | ||
end | ||
end | ||
|
||
""" | ||
backend(model::Model) | ||
|
||
|
@@ -294,8 +272,7 @@ and whether there are any bridges in the model. | |
|
||
If JuMP is in direct mode (i.e., the model was created using [`JuMP.direct_model`](@ref)), | ||
the backend with be the optimizer passed to `direct_model`. If JuMP is in manual | ||
or automatic mode, the backend will either be a `MOI.Utilities.CachingOptimizer` | ||
or a `MOI.Bridges.LazyBridgeOptimizer`. | ||
or automatic mode, the backend is a `MOI.Utilities.CachingOptimizer`. | ||
|
||
This function should only be used by advanced users looking to access low-level | ||
MathOptInterface or solver-specific functionality. | ||
|
@@ -308,16 +285,34 @@ backend(model::Model) = model.moi_backend | |
Return mode (Direct, Automatic, Manual) of model. | ||
""" | ||
function mode(model::Model) | ||
if !(backend(model) isa MOI.Bridges.LazyBridgeOptimizer{<:MOIU.CachingOptimizer} || | ||
backend(model) isa MOIU.CachingOptimizer) | ||
if !(backend(model) isa MOIU.CachingOptimizer) | ||
return Direct | ||
elseif caching_optimizer(model).mode == MOIU.Automatic | ||
elseif backend(model).mode == MOIU.Automatic | ||
return Automatic | ||
else | ||
return Manual | ||
end | ||
end | ||
|
||
""" | ||
bridge_constraints(model::Model) | ||
|
||
Return a `Bool` indicating whether the model `model` is in manual or automatic | ||
mode, 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) | ||
caching_optimizer = backend(model) | ||
if caching_optimizer isa MOIU.CachingOptimizer | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I renamed it |
||
return caching_optimizer.optimizer isa MOI.Bridges.LazyBridgeOptimizer | ||
else | ||
# Direct mode | ||
return false | ||
end | ||
end | ||
|
||
|
||
""" | ||
num_variables(model::Model) | ||
|
||
|
@@ -439,17 +434,17 @@ function optimizer_index(v::VariableRef) | |
if mode(model) == Direct | ||
return index(v) | ||
else | ||
@assert caching_optimizer(model).state == MOIU.AttachedOptimizer | ||
return caching_optimizer(model).model_to_optimizer_map[index(v)] | ||
@assert backend(model).state == MOIU.AttachedOptimizer | ||
return backend(model).model_to_optimizer_map[index(v)] | ||
end | ||
end | ||
|
||
function optimizer_index(cr::ConstraintRef{Model}) | ||
if mode(cr.model) == Direct | ||
return index(cr) | ||
else | ||
@assert caching_optimizer(cr.model).state == MOIU.AttachedOptimizer | ||
return caching_optimizer(cr.model).model_to_optimizer_map[index(cr)] | ||
@assert backend(cr.model).state == MOIU.AttachedOptimizer | ||
return backend(cr.model).model_to_optimizer_map[index(cr)] | ||
end | ||
end | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -115,12 +115,12 @@ However, it is not convertible to a variable. | |
julia> JuMP.objective_function(model, JuMP.VariableRef) | ||
ERROR: InexactError: convert(MathOptInterface.SingleVariable, MathOptInterface.ScalarAffineFunction{Float64}(MathOptInterface.ScalarAffineTerm{Float64}[ScalarAffineTerm{Float64}(2.0, VariableIndex(1))], 1.0)) | ||
Stacktrace: | ||
[1] convert at /home/blegat/.julia/dev/MathOptInterface/src/functions.jl:393 [inlined] | ||
[2] get(::JuMP.JuMPMOIModel{Float64}, ::MathOptInterface.ObjectiveFunction{MathOptInterface.SingleVariable}) at /home/blegat/.julia/dev/MathOptInterface/src/Utilities/model.jl:259 | ||
[3] get at /home/blegat/.julia/dev/MathOptInterface/src/Utilities/universalfallback.jl:105 [inlined] | ||
[4] get at /home/blegat/.julia/dev/MathOptInterface/src/Utilities/cachingoptimizer.jl:436 [inlined] | ||
[1] convert at /home/blegat/.julia/dev/MathOptInterface/src/functions.jl:398 [inlined] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Surely this fails because we're not filtering out the path as well? Or does that happen somewhere else? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are filtering out this output so I was not required to update it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I missed the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I'm not mistaken, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The modifier |
||
[2] get(::JuMP.JuMPMOIModel{Float64}, ::MathOptInterface.ObjectiveFunction{MathOptInterface.SingleVariable}) at /home/blegat/.julia/dev/MathOptInterface/src/Utilities/model.jl:290 | ||
[3] get at /home/blegat/.julia/dev/MathOptInterface/src/Utilities/universalfallback.jl:114 [inlined] | ||
[4] get at /home/blegat/.julia/dev/MathOptInterface/src/Utilities/cachingoptimizer.jl:439 [inlined] | ||
[5] get(::MathOptInterface.Bridges.LazyBridgeOptimizer{MathOptInterface.Utilities.CachingOptimizer{MathOptInterface.AbstractOptimizer,MathOptInterface.Utilities.UniversalFallback{JuMP.JuMPMOIModel{Float64}}},MathOptInterface.Bridges.AllBridgedConstraints{Float64}}, ::MathOptInterface.ObjectiveFunction{MathOptInterface.SingleVariable}) at /home/blegat/.julia/dev/MathOptInterface/src/Bridges/bridgeoptimizer.jl:172 | ||
[6] objective_function(::Model, ::Type{VariableRef}) at /home/blegat/.julia/dev/JuMP/src/objective.jl:121 | ||
[6] objective_function(::Model, ::Type{VariableRef}) at /home/blegat/.julia/dev/JuMP/src/objective.jl:129 | ||
[7] top-level scope at none:0 | ||
``` | ||
""" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,28 +3,57 @@ | |
# 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/. | ||
|
||
function error_if_direct_mode(model::Model, func::Symbol) | ||
if mode(model) == Direct | ||
error("The `$func` function is not supported in Direct mode.") | ||
end | ||
end | ||
|
||
# These methods directly map to CachingOptimizer methods. | ||
# They cannot be called in Direct mode. | ||
function MOIU.resetoptimizer!(model::Model, optimizer::MOI.AbstractOptimizer) | ||
@assert mode(model) != Direct | ||
MOIU.resetoptimizer!(caching_optimizer(model), optimizer) | ||
function MOIU.resetoptimizer!(model::Model, optimizer::MOI.AbstractOptimizer, | ||
bridge_constraints::Bool=true) | ||
error_if_direct_mode(model, :resetoptimizer!) | ||
MOIU.resetoptimizer!(backend(model), optimizer) | ||
end | ||
|
||
function MOIU.resetoptimizer!(model::Model) | ||
@assert mode(model) != Direct | ||
MOIU.resetoptimizer!(caching_optimizer(model)) | ||
error_if_direct_mode(model, :resetoptimizer!) | ||
MOIU.resetoptimizer!(backend(model)) | ||
end | ||
|
||
function MOIU.dropoptimizer!(model::Model) | ||
error_if_direct_mode(model, :dropoptimizer!) | ||
@assert mode(model) != Direct | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The assert can be dropped now |
||
MOIU.dropoptimizer!(caching_optimizer(model)) | ||
MOIU.dropoptimizer!(backend(model)) | ||
end | ||
|
||
function MOIU.attachoptimizer!(model::Model) | ||
error_if_direct_mode(model, :attachoptimizer!) | ||
@assert mode(model) != Direct | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here |
||
MOIU.attachoptimizer!(caching_optimizer(model)) | ||
MOIU.attachoptimizer!(backend(model)) | ||
end | ||
|
||
function set_optimizer(model::Model, optimizer_factory::OptimizerFactory; | ||
bridge_constraints::Bool=true) | ||
error_if_direct_mode(model, :set_optimizer) | ||
optimizer = optimizer_factory() | ||
if bridge_constraints | ||
# The names are handled by the first caching optimizer. | ||
# If default_copy_to without names is supported, no need for a second | ||
# cache. | ||
if !MOIU.supports_default_copy_to(optimizer, false) | ||
if mode(model) == Manual | ||
# TODO figure out what to do in manual mode with the two caches | ||
error("Bridges in Manual mode with an optimizer not supporting `default_copy_to` is not supported yet") | ||
end | ||
universal_fallback = MOIU.UniversalFallback(JuMPMOIModel{Float64}()) | ||
optimizer = MOIU.CachingOptimizer(universal_fallback, optimizer) | ||
end | ||
optimizer = MOI.Bridges.fullbridgeoptimizer(optimizer, Float64) | ||
end | ||
MOIU.resetoptimizer!(model, optimizer) | ||
end | ||
|
||
""" | ||
optimize!(model::Model, | ||
|
@@ -55,6 +84,7 @@ JuMP.optimize!(model, with_optimizer(GLPK.Optimizer)) | |
""" | ||
function optimize!(model::Model, | ||
optimizer_factory::Union{Nothing, OptimizerFactory}=nothing; | ||
bridge_constraints::Bool=true, | ||
ignore_optimize_hook=(model.optimize_hook === nothing)) | ||
# The nlp_data is not kept in sync, so re-set it here. | ||
# TODO: Consider how to handle incremental solves. | ||
|
@@ -67,11 +97,11 @@ function optimize!(model::Model, | |
if mode(model) == Direct | ||
error("An optimizer factory cannot be provided at the `optimize` call in Direct mode.") | ||
end | ||
if MOIU.state(caching_optimizer(model)) != MOIU.NoOptimizer | ||
if MOIU.state(backend(model)) != MOIU.NoOptimizer | ||
error("An optimizer factory cannot both be provided in the `Model` constructor and at the `optimize` call.") | ||
end | ||
optimizer = optimizer_factory() | ||
MOIU.resetoptimizer!(model, optimizer) | ||
set_optimizer(model, optimizer_factory, | ||
bridge_constraints=bridge_constraints) | ||
MOIU.attachoptimizer!(model) | ||
end | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The wording is a bit confusing. Maybe split into cases. When in manual or automatic mode, returns X. When in direct mode, returns false.