Skip to content

Commit 7c7f38b

Browse files
authored
Merge pull request #509 from JuliaOpt/bl/attibutescategories
Define is_copyable and is_set_by_optimize
2 parents c8e597e + 846d463 commit 7c7f38b

File tree

10 files changed

+170
-60
lines changed

10 files changed

+170
-60
lines changed

docs/src/apireference.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,24 @@ AbstractVariableAttribute
1717
AbstractConstraintAttribute
1818
```
1919

20+
Attributes can be set in different ways:
21+
22+
* it is either set when the model is created like [`SolverName`](@ref) and
23+
[`RawSolver`](@ref),
24+
* or explicitly when the model is copied like [`ObjectiveSense`](@ref),
25+
* or implicitly, e.g., [`NumberOfVariables`](@ref) is implicitly set by
26+
[`add_variable`](@ref) and [`ConstraintFunction`](@ref) is implicitly set by
27+
[`add_constraint`](@ref).
28+
* or it is set to contain the result of the optimization during
29+
[`optimize!`](@ref) like [`VariablePrimal`](@ref).
30+
31+
The following functions allow to distinguish between some of these different
32+
categories:
33+
```@docs
34+
is_set_by_optimize
35+
is_copyable
36+
```
37+
2038
Functions for getting and setting attributes.
2139

2240
```@docs

src/Bridges/bridgeoptimizer.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ function MOI.get(b::AbstractBridgeOptimizer,
210210
attr::MOI.AbstractConstraintAttribute,
211211
ci::CI)
212212
if is_bridged(b, typeof(ci))
213-
if MOIU.is_result_attribute(attr)
213+
if MOI.is_set_by_optimize(attr)
214214
MOI.get(b, attr, bridge(b, ci))
215215
else
216216
MOI.get(b.bridged, attr, ci)

src/Utilities/cachingoptimizer.jl

Lines changed: 3 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -428,47 +428,8 @@ function MOI.supports(m::CachingOptimizer, attr::MOI.AbstractModelAttribute)
428428
(m.state == NoOptimizer || MOI.supports(m.optimizer, attr))
429429
end
430430

431-
"""
432-
is_result_attribute(::Union{MOI.AbstractModelAttribute,
433-
MOI.AbstractVariableAttribute,
434-
MOI.AbstractConstraintAttribute})
435-
436-
Return a `Bool` indicating whether the value of the attribute is determined
437-
during [`MOI.optimize!`](@ref) hence is part of the result of the optimization.
438-
439-
## Important note when defining new attributes
440-
441-
This function returns `false` by default so it should be implemented for all
442-
result attributes.
443-
"""
444-
function is_result_attribute end
445-
446-
function is_result_attribute(::Union{MOI.AbstractModelAttribute,
447-
MOI.AbstractVariableAttribute,
448-
MOI.AbstractConstraintAttribute})
449-
return false
450-
end
451-
function is_result_attribute(::Union{MOI.ObjectiveValue,
452-
MOI.ObjectiveBound,
453-
MOI.RelativeGap,
454-
MOI.SolveTime,
455-
MOI.SimplexIterations,
456-
MOI.BarrierIterations,
457-
MOI.NodeCount,
458-
MOI.RawSolver,
459-
MOI.ResultCount,
460-
MOI.TerminationStatus,
461-
MOI.PrimalStatus,
462-
MOI.DualStatus,
463-
MOI.VariablePrimal,
464-
MOI.VariableBasisStatus,
465-
MOI.ConstraintPrimal,
466-
MOI.ConstraintDual,
467-
MOI.ConstraintBasisStatus})
468-
return true
469-
end
470431
function MOI.get(model::CachingOptimizer, attr::MOI.AbstractModelAttribute)
471-
if is_result_attribute(attr)
432+
if MOI.is_set_by_optimize(attr)
472433
return attribute_value_map(model.optimizer_to_model_map,
473434
MOI.get(model.optimizer, attr))
474435
else
@@ -479,7 +440,7 @@ function MOI.get(model::CachingOptimizer,
479440
attr::Union{MOI.AbstractVariableAttribute,
480441
MOI.AbstractConstraintAttribute},
481442
index::MOI.Index)
482-
if is_result_attribute(attr)
443+
if MOI.is_set_by_optimize(attr)
483444
return attribute_value_map(model.optimizer_to_model_map,
484445
MOI.get(model.optimizer, attr,
485446
model.model_to_optimizer_map[index]))
@@ -491,7 +452,7 @@ function MOI.get(model::CachingOptimizer,
491452
attr::Union{MOI.AbstractVariableAttribute,
492453
MOI.AbstractConstraintAttribute},
493454
indices::Vector{<:MOI.Index})
494-
if is_result_attribute(attr)
455+
if MOI.is_set_by_optimize(attr)
495456
return attribute_value_map(model.optimizer_to_model_map,
496457
MOI.get(model.optimizer, attr,
497458
map(index -> model.model_to_optimizer_map[index],

src/Utilities/copy.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ end
5454

5555
function _pass_attributes(dest::MOI.ModelLike, src::MOI.ModelLike, copy_names::Bool, idxmap::IndexMap, attrs, canargs, getargs, setargs, passattr!::Function=MOI.set)
5656
for attr in attrs
57+
@assert MOI.is_copyable(attr)
5758
if (copy_names || !(attr isa MOI.Name || attr isa MOI.VariableName || attr isa MOI.ConstraintName))
5859
passattr!(dest, attr, setargs..., attribute_value_map(idxmap, MOI.get(src, attr, getargs...)))
5960
end

src/attributes.jl

Lines changed: 112 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,31 @@ Return a `Bool` indicating whether `model` supports the constraint attribute
100100
101101
For all four methods, if the attribute is only not supported in specific
102102
circumstances, it should still return `true`.
103+
104+
Note that `supports` is only defined for attributes for which
105+
[`is_copyable`](@ref) returns `true` as other attributes do not appear in the
106+
list of attributes set obtained by `ListOf...AttributesSet`.
103107
"""
104108
function supports end
105-
supports(::ModelLike, ::Union{AbstractModelAttribute, AbstractOptimizerAttribute}) = false
106-
supports(::ModelLike, ::Union{AbstractVariableAttribute, AbstractConstraintAttribute}, ::Type{<:Index}) = false
109+
function supports(::ModelLike, attr::Union{AbstractModelAttribute,
110+
AbstractOptimizerAttribute})
111+
if !is_copyable(attr)
112+
throw(ArgumentError("`supports` is not defined for $attr, it is only" *
113+
" defined for attributes such that `is_copyable`" *
114+
" returns `true`."))
115+
end
116+
return false
117+
end
118+
function supports(::ModelLike, attr::Union{AbstractVariableAttribute,
119+
AbstractConstraintAttribute},
120+
::Type{<:Index})
121+
if !is_copyable(attr)
122+
throw(ArgumentError("`supports` is not defined for $attr, it is only" *
123+
" defined for attributes such that `is_copyable`" *
124+
" returns `true`."))
125+
end
126+
return false
127+
end
107128

108129
"""
109130
get(optimizer::AbstractOptimizer, attr::AbstractOptimizerAttribute)
@@ -294,7 +315,9 @@ struct SolverName <: AbstractOptimizerAttribute end
294315
"""
295316
ListOfModelAttributesSet()
296317
297-
A model attribute for the `Vector{AbstractModelAttribute}` of all model attributes that were set to the model.
318+
A model attribute for the `Vector{AbstractModelAttribute}` of all model
319+
attributes `attr` such that 1) `is_copyable(attr)` returns `true` and 2) the
320+
attribute was set to the model.
298321
"""
299322
struct ListOfModelAttributesSet <: AbstractModelAttribute end
300323

@@ -458,7 +481,9 @@ struct ResultCount <: AbstractModelAttribute end
458481
"""
459482
ListOfVariableAttributesSet()
460483
461-
A model attribute for the `Vector{AbstractVariableAttribute}` of all variable attributes that were set to the model.
484+
A model attribute for the `Vector{AbstractVariableAttribute}` of all variable
485+
attributes `attr` such that 1) `is_copyable(attr)` returns `true` and 2) the
486+
attribute was set to variables.
462487
"""
463488
struct ListOfVariableAttributesSet <: AbstractModelAttribute end
464489

@@ -514,7 +539,9 @@ Possible values are:
514539
"""
515540
ListOfConstraintAttributesSet{F, S}()
516541
517-
A model attribute for the `Vector{AbstractConstraintAttribute}` of all constraint attributes that were set to `F`-in-`S` constraints.
542+
A model attribute for the `Vector{AbstractConstraintAttribute}` of all
543+
constraint attributes `attr` such that 1) `is_copyable(attr)` returns `true` and
544+
2) the attribute was set to `F`-in-`S` constraints.
518545
519546
## Note
520547
@@ -705,3 +732,83 @@ struct DualStatus <: AbstractModelAttribute
705732
N::Int
706733
end
707734
DualStatus() = DualStatus(1)
735+
736+
"""
737+
is_set_by_optimize(::AnyAttribute)
738+
739+
Return a `Bool` indicating whether the value of the attribute is modified
740+
during an [`optimize!`](@ref) call, that is, the attribute is used to query
741+
the result of the optimization.
742+
743+
## Important note when defining new attributes
744+
745+
This function returns `false` by default so it should be implemented for
746+
attributes that are modified by [`optimize!`](@ref).
747+
"""
748+
is_set_by_optimize(::AnyAttribute) = false
749+
function is_set_by_optimize(::Union{ObjectiveValue,
750+
ObjectiveBound,
751+
RelativeGap,
752+
SolveTime,
753+
SimplexIterations,
754+
BarrierIterations,
755+
NodeCount,
756+
RawSolver,
757+
ResultCount,
758+
TerminationStatus,
759+
PrimalStatus,
760+
DualStatus,
761+
VariablePrimal,
762+
VariableBasisStatus,
763+
ConstraintPrimal,
764+
ConstraintDual,
765+
ConstraintBasisStatus})
766+
return true
767+
end
768+
769+
"""
770+
is_copyable(::AnyAttribute)
771+
772+
Return a `Bool` indicating whether the value of the attribute may be copied
773+
during [`copy_to`](@ref) using [`set`](@ref).
774+
775+
## Important note when defining new attributes
776+
777+
By default `is_copyable(attr)` returns `!is_set_by_optimize(attr)`. A specific
778+
method should be defined for attibutes which are copied indirectly during
779+
[`copy_to`](@ref). For instance, both `is_copyable` and
780+
[`is_set_by_optimize`](@ref) return `false` for the following attributes:
781+
782+
* [`ListOfOptimizerAttributesSet`](@ref), [`ListOfModelAttributesSet`](@ref),
783+
[`ListOfConstraintAttributesSet`](@ref) and
784+
[`ListOfVariableAttributesSet`](@ref).
785+
* [`SolverName`](@ref) and [`RawSolver`](@ref): these attributes cannot be set.
786+
* [`NumberOfVariables`](@ref) and [`ListOfVariableIndices`](@ref): these
787+
attributes are set indirectly by [`add_variable`](@ref) and
788+
[`add_variables`](@ref).
789+
* [`ObjectiveFunctionType`](@ref): this attribute is set indirectly when setting
790+
the [`ObjectiveFunction`](@ref) attribute.
791+
* [`NumberOfConstraints`](@ref), [`ListOfConstraintIndices`](@ref),
792+
[`ListOfConstraints`](@ref), [`ConstraintFunction`](@ref) and
793+
[`ConstraintSet`](@ref): these attributes are set indirectly by
794+
[`add_constraint`](@ref) and [`add_constraints`](@ref).
795+
"""
796+
function is_copyable(attr::AnyAttribute)
797+
return !is_set_by_optimize(attr)
798+
end
799+
function is_copyable(::Union{ListOfOptimizerAttributesSet,
800+
ListOfModelAttributesSet,
801+
ListOfConstraintAttributesSet,
802+
ListOfVariableAttributesSet,
803+
SolverName,
804+
RawSolver,
805+
NumberOfVariables,
806+
ListOfVariableIndices,
807+
NumberOfConstraints,
808+
ObjectiveFunctionType,
809+
ListOfConstraintIndices,
810+
ListOfConstraints,
811+
ConstraintFunction,
812+
ConstraintSet})
813+
return false
814+
end

test/attributes.jl

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
@testset "Attributes" begin
2+
@testset "is_set_by_optimize" begin
3+
@test MOI.is_set_by_optimize(MOI.TerminationStatus())
4+
@test !MOI.is_set_by_optimize(MOI.ConstraintSet())
5+
@test !MOI.is_set_by_optimize(MOI.ObjectiveSense())
6+
end
7+
@testset "is_copyable" begin
8+
@test !MOI.is_copyable(MOI.TerminationStatus())
9+
@test !MOI.is_copyable(MOI.ConstraintSet())
10+
@test MOI.is_copyable(MOI.ObjectiveSense())
11+
end
12+
@testset "supports" begin
13+
model = DummyModel()
14+
@test_throws ArgumentError MOI.supports(model, MOI.TerminationStatus())
15+
@test_throws ArgumentError begin
16+
MOI.supports(model, MOI.ConstraintSet(),
17+
MOI.ConstraintIndex{MOI.SingleVariable,
18+
MOI.EqualTo{Float64}})
19+
end
20+
@test MOI.supports(model, MOI.ObjectiveSense())
21+
end
22+
end

test/cachingoptimizer.jl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@
2929
@test MOI.get(m, MOIU.AttributeFromModelCache(MOI.ObjectiveSense())) == MOI.MaxSense
3030
@test MOI.get(m, MOIU.AttributeFromOptimizer(MOI.ObjectiveSense())) == MOI.MaxSense
3131

32-
@test !MOI.supports(m, MOI.NumberOfVariables())
33-
3432
@test MOI.supports(m, MOIU.AttributeFromOptimizer(MOIU.MockModelAttribute()))
3533
MOI.set(m, MOIU.AttributeFromOptimizer(MOIU.MockModelAttribute()), 10)
3634
@test MOI.get(m, MOIU.AttributeFromOptimizer(MOIU.MockModelAttribute())) == 10

test/dummy.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
struct DummyModel <: MOI.ModelLike
2+
end
3+
MOI.supports(::DummyModel, ::MOI.ObjectiveSense) = true
4+
MOI.supports(::DummyModel, ::MOI.ConstraintPrimalStart,
5+
::Type{<:MOI.ConstraintIndex}) = true
6+
MOI.supports_constraint(::DummyModel, ::Type{MOI.SingleVariable},
7+
::Type{MOI.EqualTo{Float64}}) = true
8+
MOI.supports_constraint(::DummyModel, ::Type{MOI.VectorOfVariables},
9+
::Type{MOI.Zeros}) = true
10+

test/errors.jl

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,3 @@
1-
struct DummyModel <: MOI.ModelLike
2-
end
3-
MOI.supports(::DummyModel, ::MOI.ObjectiveSense) = true
4-
MOI.supports(::DummyModel, ::MOI.ConstraintPrimalStart,
5-
::Type{<:MOI.ConstraintIndex}) = true
6-
MOI.supports_constraint(::DummyModel, ::Type{MOI.SingleVariable},
7-
::Type{MOI.EqualTo{Float64}}) = true
8-
MOI.supports_constraint(::DummyModel, ::Type{MOI.VectorOfVariables},
9-
::Type{MOI.Zeros}) = true
10-
111
@testset "Fallbacks for `set` methods" begin
122
model = DummyModel()
133

test/runtests.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ using Compat.Test
99

1010
# Tests for solvers are located in MOI.Test.
1111

12+
include("dummy.jl")
13+
1214
# MOI tests not relying on any submodule
1315
@testset "MOI" begin
1416
include("isbits.jl")
1517
include("isapprox.jl")
1618
include("interval.jl")
1719
include("errors.jl")
20+
include("attributes.jl")
1821
end
1922

2023
# Needed by test spread over several files, defining it here make it easier to comment out tests

0 commit comments

Comments
 (0)