Skip to content

Commit f29f182

Browse files
authored
Merge pull request #1008 from JuliaOpt/bl/instantiate
Add OptimizerWithAttributes
2 parents 5df5ec9 + 65b379d commit f29f182

5 files changed

Lines changed: 179 additions & 0 deletions

File tree

docs/src/apireference.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ ListOfConstraintAttributesSet
120120
```@docs
121121
AbstractOptimizer
122122
optimize!
123+
OptimizerWithAttributes
124+
instantiate
123125
```
124126

125127
List of optimizers attributes

src/MathOptInterface.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,6 @@ include("Bridges/Bridges.jl") # MOI.Bridges
126126
include("Benchmarks/Benchmarks.jl")
127127
include("FileFormats/FileFormats.jl")
128128

129+
include("instantiate.jl")
130+
129131
end

src/instantiate.jl

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
"""
2+
struct OptimizerWithAttributes
3+
optimizer_constructor
4+
params::Vector{Pair{AbstractOptimizerAttribute,<:Any}}
5+
end
6+
7+
Object grouping an optimizer constructor and a list of optimizer attributes.
8+
Instances are created with [`instantiate`](@ref).
9+
"""
10+
struct OptimizerWithAttributes
11+
# Function that takes zero arguments and returns a new optimizer.
12+
# The type of the function could be
13+
# * `Function`: a function, or
14+
# * `DataType`: a type, or
15+
# * `UnionAll`: a type with missing parameters.
16+
optimizer_constructor
17+
params::Vector{Pair{AbstractOptimizerAttribute,Any}}
18+
end
19+
20+
_to_param(param::Pair{<:AbstractOptimizerAttribute}) = param
21+
_to_param(param::Pair{String}) = RawParameter(param.first) => param.second
22+
function _to_param(param::Pair)
23+
error("Expected an optimizer attribute or a string, got `$(param.first)` which is a `$(typeof(param.first))`.")
24+
end
25+
26+
"""
27+
OptimizerWithAttributes(optimizer_constructor, params::Pair...)
28+
29+
Create an [`OptimizerWithAttributes`](@ref) with the parameters `params`.
30+
"""
31+
function OptimizerWithAttributes(optimizer_constructor, args::Vararg{Pair, N}) where N
32+
if !applicable(optimizer_constructor)
33+
error(_INSTANTIATE_NOT_CALLABLE_MESSAGE)
34+
end
35+
params = Pair{AbstractOptimizerAttribute,Any}[_to_param(arg) for arg in args]
36+
return OptimizerWithAttributes(optimizer_constructor, params)
37+
end
38+
39+
const _INSTANTIATE_NOT_CALLABLE_MESSAGE =
40+
"The provided `optimizer_constructor` is invalid. It must be callable with zero " *
41+
"arguments. For example, \"Ipopt.Optimizer\" or " *
42+
"\"() -> ECOS.Optimizer()\". It should not be an instantiated optimizer " *
43+
"like \"Ipopt.Optimizer()\" or \"ECOS.Optimizer()\". " *
44+
"(Note the difference in parentheses!)"
45+
46+
"""
47+
_instantiate_and_check(optimizer_constructor)
48+
49+
Create an instance of optimizer by calling `optimizer_constructor`.
50+
Then check that the type returned is an empty [`AbstractOptimizer`](@ref).
51+
"""
52+
function _instantiate_and_check(optimizer_constructor)
53+
if !applicable(optimizer_constructor)
54+
error(_INSTANTIATE_NOT_CALLABLE_MESSAGE)
55+
end
56+
optimizer = optimizer_constructor()
57+
if !isa(optimizer, AbstractOptimizer)
58+
error("The provided `optimizer_constructor` returned an object of type " *
59+
"$(typeof(optimizer)). Expected a " *
60+
"MathOptInterface.AbstractOptimizer.")
61+
end
62+
if !is_empty(optimizer)
63+
error("The provided `optimizer_constructor` returned a non-empty optimizer.")
64+
end
65+
return optimizer
66+
end
67+
68+
"""
69+
_instantiate_and_check(optimizer_constructor::OptimizerWithAttributes)
70+
71+
Create an instance of optimizer represented by [`OptimizerWithAttributes`](@ref).
72+
Then check that the type returned is an empty [`AbstractOptimizer`](@ref).
73+
"""
74+
function _instantiate_and_check(optimizer_constructor::OptimizerWithAttributes)
75+
optimizer = _instantiate_and_check(optimizer_constructor.optimizer_constructor)
76+
for param in optimizer_constructor.params
77+
set(optimizer, param.first, param.second)
78+
end
79+
return optimizer
80+
end
81+
82+
"""
83+
instantiate(optimizer_constructor,
84+
with_bridge_type::Union{Nothing, Type}=nothing,
85+
with_names::Bool=false)
86+
87+
Creates an instance of optimizer either by calling
88+
`optimizer_constructor.optimizer_constructor()` and setting the parameters in
89+
`optimizer_constructor.params` if `optimizer_constructor` is a
90+
[`OptimizerWithAttributes`](@ref) or by calling `optimizer_constructor()`
91+
if `optimizer_constructor` is callable.
92+
93+
If `with_bridge_type` is not `nothing`, it enables all the bridges defined in
94+
the MathOptInterface.Bridges submodule with coefficient type
95+
`with_bridge_type`.
96+
97+
If the optimizer created by `optimizer_constructor` does not support loading the
98+
problem incrementally or does not support names and `with_names` is `true` (see
99+
[`Utilities.supports_default_copy_to`](@ref)) then a
100+
[`Utilities.CachingOptimizer`](@ref) is added to store a cache of the bridged
101+
model.
102+
Hence set `with_names` to `true` if names might be set.
103+
"""
104+
function instantiate(
105+
optimizer_constructor; with_bridge_type::Union{Nothing, Type}=nothing,
106+
with_names::Bool=false)
107+
optimizer = _instantiate_and_check(optimizer_constructor)
108+
if with_bridge_type === nothing
109+
return optimizer
110+
end
111+
if !Utilities.supports_default_copy_to(optimizer, with_names)
112+
universal_fallback = Utilities.UniversalFallback(Utilities.Model{with_bridge_type}())
113+
optimizer = Utilities.CachingOptimizer(universal_fallback, optimizer)
114+
end
115+
return Bridges.full_bridge_optimizer(optimizer, with_bridge_type)
116+
end

test/instantiate.jl

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using Test
2+
3+
using MathOptInterface
4+
const MOI = MathOptInterface
5+
const MOIU = MathOptInterface.Utilities
6+
7+
struct DummyOptimizer <: MOI.AbstractOptimizer end
8+
MOI.is_empty(::DummyOptimizer) = true
9+
10+
@testset "Instantiate with $T" for T in [Float64, Int]
11+
f() = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{T}()))
12+
optimizer_constructor = MOI.OptimizerWithAttributes(f, MOI.Silent() => true, "a" => 1, "b" => 2)
13+
optimizer = MOI.instantiate(optimizer_constructor)
14+
@test optimizer isa MOIU.MockOptimizer{MOIU.UniversalFallback{MOIU.Model{T}}}
15+
@test MOI.get(optimizer, MOI.Silent())
16+
for with_names in [true, false]
17+
optimizer = MOI.instantiate(optimizer_constructor, with_bridge_type=T, with_names=with_names)
18+
@test optimizer isa MOI.Bridges.LazyBridgeOptimizer{MOIU.MockOptimizer{MOIU.UniversalFallback{MOIU.Model{T}}}}
19+
@test MOI.get(optimizer, MOI.Silent())
20+
@test MOI.get(optimizer, MOI.RawParameter("a")) == 1
21+
@test MOI.get(optimizer, MOI.RawParameter("b")) == 2
22+
end
23+
24+
optimizer_constructor = MOI.OptimizerWithAttributes(DummyOptimizer, [])
25+
optimizer = MOI.instantiate(optimizer_constructor)
26+
@test optimizer isa DummyOptimizer
27+
for with_names in [true, false]
28+
optimizer = MOI.instantiate(optimizer_constructor, with_bridge_type=T, with_names=with_names)
29+
@test optimizer isa MOI.Bridges.LazyBridgeOptimizer{MOIU.CachingOptimizer{DummyOptimizer, MOIU.UniversalFallback{MOIU.Model{T}}}}
30+
end
31+
32+
err = ErrorException("The provided `optimizer_constructor` returned a non-empty optimizer.")
33+
function g()
34+
model = f()
35+
MOI.add_variable(model)
36+
return model
37+
end
38+
@test_throws err MOI.instantiate(g)
39+
@test_throws err MOI.instantiate(g, with_bridge_type=T)
40+
optimizer_constructor = MOI.OptimizerWithAttributes(g)
41+
@test_throws err MOI.instantiate(optimizer_constructor)
42+
@test_throws err MOI.instantiate(optimizer_constructor, with_bridge_type=T)
43+
44+
err = ErrorException(MOI._INSTANTIATE_NOT_CALLABLE_MESSAGE)
45+
@test_throws err MOI.OptimizerWithAttributes(1)
46+
@test_throws err MOI.OptimizerWithAttributes(1, MOI.Silent() => true)
47+
@test_throws err MOI.instantiate(1)
48+
@test_throws err MOI.instantiate(1, with_bridge_type=T)
49+
50+
err = ErrorException("The provided `optimizer_constructor` returned an object of type " *
51+
"$Int. Expected a MathOptInterface.AbstractOptimizer.")
52+
h() = 1
53+
@test_throws err MOI.instantiate(h)
54+
@test_throws err MOI.instantiate(h, with_bridge_type=T)
55+
optimizer_constructor = MOI.OptimizerWithAttributes(h)
56+
@test_throws err MOI.instantiate(optimizer_constructor)
57+
@test_throws err MOI.instantiate(optimizer_constructor, with_bridge_type=T)
58+
end

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ include("dummy.jl")
2222
include("functions.jl")
2323
include("sets.jl")
2424
include("attributes.jl")
25+
include("instantiate.jl")
2526
end
2627

2728
# Utilities submodule tests

0 commit comments

Comments
 (0)