Skip to content

Commit d9186f9

Browse files
authored
[Utilities] add with_cache_type to instantiate (#2097)
This is useful when creating optimizers which do not support the incremental interface, or which only partially implement it. This change is motivated by some recent examples in which it was necessary for the user to hard-code the caching logic to work-around the lack of deletion in a solver, and the caching logic is confusing to even advanced users. See: * https://discourse.julialang.org/t/jump-scip-error/93958 * jump-dev/MultiObjectiveAlgorithms.jl#46
1 parent dfeac45 commit d9186f9

File tree

2 files changed

+98
-20
lines changed

2 files changed

+98
-20
lines changed

src/instantiate.jl

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -125,36 +125,68 @@ end
125125
"""
126126
instantiate(
127127
optimizer_constructor,
128-
with_bridge_type::Union{Nothing, Type} = nothing,
128+
with_cache_type::Union{Nothing,Type} = nothing,
129+
with_bridge_type::Union{Nothing,Type} = nothing,
129130
)
130131
131-
Creates an instance of optimizer by either:
132+
Create an instance of an optimizer by either:
133+
132134
* calling `optimizer_constructor.optimizer_constructor()` and setting the
133135
parameters in `optimizer_constructor.params` if `optimizer_constructor` is a
134136
[`OptimizerWithAttributes`](@ref)
135137
* calling `optimizer_constructor()` if `optimizer_constructor` is callable.
136138
137-
If `with_bridge_type` is not `nothing`, it enables all the bridges defined in
138-
the MathOptInterface.Bridges submodule with coefficient type `with_bridge_type`.
139+
## with_cache_type
140+
141+
If `with_cache_type` is not `nothing`, then the optimizer is wrapped in a
142+
[`Utilities.CachingOptimizer`](@ref) to store a cache of the model. This is most
143+
useful if the optimizer you are constructing does not support the incremental
144+
interface (see [`supports_incremental_interface`](@ref)).
145+
146+
## with_bridge_type
147+
148+
If `with_bridge_type` is not `nothing`, the optimizer is wrapped in a
149+
[`Bridges.full_bridge_optimizer`](@ref), enabling all the bridges defined in
150+
the MOI.Bridges submodule with coefficient type `with_bridge_type`.
151+
152+
In addition, if the optimizer created by `optimizer_constructor` does not
153+
support the incremental interface (see [`supports_incremental_interface`](@ref)),
154+
then, irrespective of `with_cache_type`, the optimizer is wrapped in a
155+
[`Utilities.CachingOptimizer`](@ref) to store a cache of the bridged model.
139156
140-
If the optimizer created by `optimizer_constructor` does not support loading the
141-
problem incrementally (see [`supports_incremental_interface`](@ref)), then a
142-
[`Utilities.CachingOptimizer`](@ref) is added to store a cache of the bridged
143-
model.
157+
If `with_cache_type` and `with_bridge_type` are both not `nothing`, then they
158+
must be the same type.
144159
"""
145160
function instantiate(
146161
(@nospecialize optimizer_constructor);
147162
with_bridge_type::Union{Nothing,Type} = nothing,
163+
with_cache_type::Union{Nothing,Type} = nothing,
148164
)
165+
if with_bridge_type !== nothing && with_cache_type !== nothing
166+
if with_bridge_type != with_cache_type
167+
error(
168+
"If both provided, `with_bridge_type` and `with_cache_type` " *
169+
"must be the same type. Got " *
170+
"`with_bridge_type = $with_bridge_type` and " *
171+
"`with_cache_type = $with_cache_type`",
172+
)
173+
end
174+
end
149175
optimizer = _instantiate_and_check(optimizer_constructor)
150176
if with_bridge_type === nothing
151-
return optimizer
152-
end
153-
if !supports_incremental_interface(optimizer)
154-
cache = default_cache(optimizer, with_bridge_type)
155-
optimizer = Utilities.CachingOptimizer(cache, optimizer)
177+
if with_cache_type === nothing
178+
return optimizer
179+
end
180+
cache = default_cache(optimizer, with_cache_type)
181+
return Utilities.CachingOptimizer(cache, optimizer)
182+
else
183+
if with_cache_type !== nothing ||
184+
!supports_incremental_interface(optimizer)
185+
cache = default_cache(optimizer, with_bridge_type)
186+
optimizer = Utilities.CachingOptimizer(cache, optimizer)
187+
end
188+
return Bridges.full_bridge_optimizer(optimizer, with_bridge_type)
156189
end
157-
return Bridges.full_bridge_optimizer(optimizer, with_bridge_type)
158190
end
159191

160192
"""

test/instantiate.jl

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,20 @@ module TestInstantiate
99
using Test
1010
import MathOptInterface as MOI
1111

12+
function runtests()
13+
for name in names(@__MODULE__; all = true)
14+
if startswith("$name", "test_")
15+
@testset "$(name)" begin
16+
getfield(@__MODULE__, name)()
17+
end
18+
end
19+
end
20+
end
21+
1222
struct DummyOptimizer <: MOI.AbstractOptimizer end
23+
1324
MOI.is_empty(::DummyOptimizer) = true
25+
1426
function MOI.default_cache(::DummyOptimizer, ::Type{T}) where {T}
1527
return MOI.Utilities.Model{T}()
1628
end
@@ -110,14 +122,48 @@ function test_get_set_Optimizer_with_attributes()
110122
return
111123
end
112124

113-
function runtests()
114-
for name in names(@__MODULE__; all = true)
115-
if startswith("$name", "test_")
116-
@testset "$(name)" begin
117-
getfield(@__MODULE__, name)()
118-
end
125+
function test_instantiate_with_cache_type()
126+
function f(::Type{T}) where {T}
127+
return MOI.Utilities.MockOptimizer(
128+
MOI.Utilities.UniversalFallback(MOI.Utilities.Model{T}()),
129+
)
130+
end
131+
function inner_type(::Type{T}) where {T}
132+
return MOI.Utilities.UniversalFallback{MOI.Utilities.Model{T}}
133+
end
134+
function mock_type(::Type{T}) where {T}
135+
return MOI.Utilities.MockOptimizer{inner_type(T),Float64}
136+
end
137+
for T in (Int, Float64)
138+
mock, inner = mock_type(T), inner_type(T)
139+
# Check with no arguments provided
140+
model = MOI.instantiate(() -> f(T))
141+
@test typeof(model) == mock
142+
# Check with only with_bridge_type
143+
model = MOI.instantiate(() -> f(T); with_bridge_type = T)
144+
@test typeof(model) == MOI.Bridges.LazyBridgeOptimizer{mock}
145+
# Check with only with_cache_type
146+
model = MOI.instantiate(() -> f(T); with_cache_type = T)
147+
@test typeof(model) == MOI.Utilities.CachingOptimizer{mock,inner}
148+
# Check with both with_cache_type and with_bridge_type
149+
model = MOI.instantiate(; with_cache_type = T, with_bridge_type = T) do
150+
return f(T)
119151
end
152+
@test typeof(model) == MOI.Bridges.LazyBridgeOptimizer{
153+
MOI.Utilities.CachingOptimizer{mock,inner},
154+
}
120155
end
156+
@test_throws(
157+
ErrorException(
158+
"If both provided, `with_bridge_type` and `with_cache_type` must " *
159+
"be the same type. Got `with_bridge_type = $Float64` and " *
160+
"`with_cache_type = $Int`",
161+
),
162+
MOI.instantiate(; with_cache_type = Int, with_bridge_type = Float64) do
163+
return f(Int)
164+
end
165+
)
166+
return
121167
end
122168

123169
end

0 commit comments

Comments
 (0)