Skip to content

Define is_copyable and is_set_by_optimize #509

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

Merged
merged 7 commits into from
Aug 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs/src/apireference.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,24 @@ AbstractVariableAttribute
AbstractConstraintAttribute
```

Attributes can be set in different ways:

* it is either set when the model is created like [`SolverName`](@ref) and
[`RawSolver`](@ref),
* or explicitly when the model is copied like [`ObjectiveSense`](@ref),
* or implicitly, e.g., [`NumberOfVariables`](@ref) is implicitly set by
[`add_variable`](@ref) and [`ConstraintFunction`](@ref) is implicitly set by
[`add_constraint`](@ref).
* or it is set to contain the result of the optimization during
[`optimize!`](@ref) like [`VariablePrimal`](@ref).

The following functions allow to distinguish between some of these different
categories:
```@docs
is_set_by_optimize
is_copyable
```

Functions for getting and setting attributes.

```@docs
Expand Down
2 changes: 1 addition & 1 deletion src/Bridges/bridgeoptimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ function MOI.get(b::AbstractBridgeOptimizer,
attr::MOI.AbstractConstraintAttribute,
ci::CI)
if is_bridged(b, typeof(ci))
if MOIU.is_result_attribute(attr)
if MOI.is_set_by_optimize(attr)
MOI.get(b, attr, bridge(b, ci))
else
MOI.get(b.bridged, attr, ci)
Expand Down
45 changes: 3 additions & 42 deletions src/Utilities/cachingoptimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -428,47 +428,8 @@ function MOI.supports(m::CachingOptimizer, attr::MOI.AbstractModelAttribute)
(m.state == NoOptimizer || MOI.supports(m.optimizer, attr))
end

"""
is_result_attribute(::Union{MOI.AbstractModelAttribute,
MOI.AbstractVariableAttribute,
MOI.AbstractConstraintAttribute})

Return a `Bool` indicating whether the value of the attribute is determined
during [`MOI.optimize!`](@ref) hence is part of the result of the optimization.

## Important note when defining new attributes

This function returns `false` by default so it should be implemented for all
result attributes.
"""
function is_result_attribute end

function is_result_attribute(::Union{MOI.AbstractModelAttribute,
MOI.AbstractVariableAttribute,
MOI.AbstractConstraintAttribute})
return false
end
function is_result_attribute(::Union{MOI.ObjectiveValue,
MOI.ObjectiveBound,
MOI.RelativeGap,
MOI.SolveTime,
MOI.SimplexIterations,
MOI.BarrierIterations,
MOI.NodeCount,
MOI.RawSolver,
MOI.ResultCount,
MOI.TerminationStatus,
MOI.PrimalStatus,
MOI.DualStatus,
MOI.VariablePrimal,
MOI.VariableBasisStatus,
MOI.ConstraintPrimal,
MOI.ConstraintDual,
MOI.ConstraintBasisStatus})
return true
end
function MOI.get(model::CachingOptimizer, attr::MOI.AbstractModelAttribute)
if is_result_attribute(attr)
if MOI.is_set_by_optimize(attr)
return attribute_value_map(model.optimizer_to_model_map,
MOI.get(model.optimizer, attr))
else
Expand All @@ -479,7 +440,7 @@ function MOI.get(model::CachingOptimizer,
attr::Union{MOI.AbstractVariableAttribute,
MOI.AbstractConstraintAttribute},
index::MOI.Index)
if is_result_attribute(attr)
if MOI.is_set_by_optimize(attr)
return attribute_value_map(model.optimizer_to_model_map,
MOI.get(model.optimizer, attr,
model.model_to_optimizer_map[index]))
Expand All @@ -491,7 +452,7 @@ function MOI.get(model::CachingOptimizer,
attr::Union{MOI.AbstractVariableAttribute,
MOI.AbstractConstraintAttribute},
indices::Vector{<:MOI.Index})
if is_result_attribute(attr)
if MOI.is_set_by_optimize(attr)
return attribute_value_map(model.optimizer_to_model_map,
MOI.get(model.optimizer, attr,
map(index -> model.model_to_optimizer_map[index],
Expand Down
1 change: 1 addition & 0 deletions src/Utilities/copy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ end

function _pass_attributes(dest::MOI.ModelLike, src::MOI.ModelLike, copy_names::Bool, idxmap::IndexMap, attrs, canargs, getargs, setargs, passattr!::Function=MOI.set)
for attr in attrs
@assert MOI.is_copyable(attr)
if (copy_names || !(attr isa MOI.Name || attr isa MOI.VariableName || attr isa MOI.ConstraintName))
passattr!(dest, attr, setargs..., attribute_value_map(idxmap, MOI.get(src, attr, getargs...)))
end
Expand Down
117 changes: 112 additions & 5 deletions src/attributes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,31 @@ Return a `Bool` indicating whether `model` supports the constraint attribute

For all four methods, if the attribute is only not supported in specific
circumstances, it should still return `true`.

Note that `supports` is only defined for attributes for which
[`is_copyable`](@ref) returns `true` as other attributes do not appear in the
list of attributes set obtained by `ListOf...AttributesSet`.
"""
function supports end
supports(::ModelLike, ::Union{AbstractModelAttribute, AbstractOptimizerAttribute}) = false
supports(::ModelLike, ::Union{AbstractVariableAttribute, AbstractConstraintAttribute}, ::Type{<:Index}) = false
function supports(::ModelLike, attr::Union{AbstractModelAttribute,
AbstractOptimizerAttribute})
if !is_copyable(attr)
throw(ArgumentError("`supports` is not defined for $attr, it is only" *
" defined for attributes such that `is_copyable`" *
" returns `true`."))
end
return false
end
function supports(::ModelLike, attr::Union{AbstractVariableAttribute,
AbstractConstraintAttribute},
::Type{<:Index})
if !is_copyable(attr)
throw(ArgumentError("`supports` is not defined for $attr, it is only" *
" defined for attributes such that `is_copyable`" *
" returns `true`."))
end
return false
end

"""
get(optimizer::AbstractOptimizer, attr::AbstractOptimizerAttribute)
Expand Down Expand Up @@ -294,7 +315,9 @@ struct SolverName <: AbstractOptimizerAttribute end
"""
ListOfModelAttributesSet()

A model attribute for the `Vector{AbstractModelAttribute}` of all model attributes that were set to the model.
A model attribute for the `Vector{AbstractModelAttribute}` of all model
attributes `attr` such that 1) `is_copyable(attr)` returns `true` and 2) the
attribute was set to the model.
"""
struct ListOfModelAttributesSet <: AbstractModelAttribute end

Expand Down Expand Up @@ -458,7 +481,9 @@ struct ResultCount <: AbstractModelAttribute end
"""
ListOfVariableAttributesSet()

A model attribute for the `Vector{AbstractVariableAttribute}` of all variable attributes that were set to the model.
A model attribute for the `Vector{AbstractVariableAttribute}` of all variable
attributes `attr` such that 1) `is_copyable(attr)` returns `true` and 2) the
attribute was set to variables.
"""
struct ListOfVariableAttributesSet <: AbstractModelAttribute end

Expand Down Expand Up @@ -514,7 +539,9 @@ Possible values are:
"""
ListOfConstraintAttributesSet{F, S}()

A model attribute for the `Vector{AbstractConstraintAttribute}` of all constraint attributes that were set to `F`-in-`S` constraints.
A model attribute for the `Vector{AbstractConstraintAttribute}` of all
constraint attributes `attr` such that 1) `is_copyable(attr)` returns `true` and
2) the attribute was set to `F`-in-`S` constraints.

## Note

Expand Down Expand Up @@ -705,3 +732,83 @@ struct DualStatus <: AbstractModelAttribute
N::Int
end
DualStatus() = DualStatus(1)

"""
is_set_by_optimize(::AnyAttribute)

Return a `Bool` indicating whether the value of the attribute is modified
during an [`optimize!`](@ref) call, that is, the attribute is used to query
the result of the optimization.

## Important note when defining new attributes

This function returns `false` by default so it should be implemented for
attributes that are modified by [`optimize!`](@ref).
"""
is_set_by_optimize(::AnyAttribute) = false
function is_set_by_optimize(::Union{ObjectiveValue,
ObjectiveBound,
RelativeGap,
SolveTime,
SimplexIterations,
BarrierIterations,
NodeCount,
RawSolver,
ResultCount,
TerminationStatus,
PrimalStatus,
DualStatus,
VariablePrimal,
VariableBasisStatus,
ConstraintPrimal,
ConstraintDual,
ConstraintBasisStatus})
return true
end

"""
is_copyable(::AnyAttribute)

Return a `Bool` indicating whether the value of the attribute may be copied
during [`copy_to`](@ref) using [`set`](@ref).

## Important note when defining new attributes

By default `is_copyable(attr)` returns `!is_set_by_optimize(attr)`. A specific
method should be defined for attibutes which are copied indirectly during
[`copy_to`](@ref). For instance, both `is_copyable` and
[`is_set_by_optimize`](@ref) return `false` for the following attributes:

* [`ListOfOptimizerAttributesSet`](@ref), [`ListOfModelAttributesSet`](@ref),
[`ListOfConstraintAttributesSet`](@ref) and
[`ListOfVariableAttributesSet`](@ref).
* [`SolverName`](@ref) and [`RawSolver`](@ref): these attributes cannot be set.
* [`NumberOfVariables`](@ref) and [`ListOfVariableIndices`](@ref): these
attributes are set indirectly by [`add_variable`](@ref) and
[`add_variables`](@ref).
* [`ObjectiveFunctionType`](@ref): this attribute is set indirectly when setting
the [`ObjectiveFunction`](@ref) attribute.
* [`NumberOfConstraints`](@ref), [`ListOfConstraintIndices`](@ref),
[`ListOfConstraints`](@ref), [`ConstraintFunction`](@ref) and
[`ConstraintSet`](@ref): these attributes are set indirectly by
[`add_constraint`](@ref) and [`add_constraints`](@ref).
"""
function is_copyable(attr::AnyAttribute)
return !is_set_by_optimize(attr)
end
function is_copyable(::Union{ListOfOptimizerAttributesSet,
ListOfModelAttributesSet,
ListOfConstraintAttributesSet,
ListOfVariableAttributesSet,
SolverName,
RawSolver,
NumberOfVariables,
ListOfVariableIndices,
NumberOfConstraints,
ObjectiveFunctionType,
ListOfConstraintIndices,
ListOfConstraints,
ConstraintFunction,
ConstraintSet})
return false
end
22 changes: 22 additions & 0 deletions test/attributes.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@testset "Attributes" begin
@testset "is_set_by_optimize" begin
@test MOI.is_set_by_optimize(MOI.TerminationStatus())
@test !MOI.is_set_by_optimize(MOI.ConstraintSet())
@test !MOI.is_set_by_optimize(MOI.ObjectiveSense())
end
@testset "is_copyable" begin
@test !MOI.is_copyable(MOI.TerminationStatus())
@test !MOI.is_copyable(MOI.ConstraintSet())
@test MOI.is_copyable(MOI.ObjectiveSense())
end
@testset "supports" begin
model = DummyModel()
@test_throws ArgumentError MOI.supports(model, MOI.TerminationStatus())
@test_throws ArgumentError begin
MOI.supports(model, MOI.ConstraintSet(),
MOI.ConstraintIndex{MOI.SingleVariable,
MOI.EqualTo{Float64}})
end
@test MOI.supports(model, MOI.ObjectiveSense())
end
end
2 changes: 0 additions & 2 deletions test/cachingoptimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@
@test MOI.get(m, MOIU.AttributeFromModelCache(MOI.ObjectiveSense())) == MOI.MaxSense
@test MOI.get(m, MOIU.AttributeFromOptimizer(MOI.ObjectiveSense())) == MOI.MaxSense

@test !MOI.supports(m, MOI.NumberOfVariables())

@test MOI.supports(m, MOIU.AttributeFromOptimizer(MOIU.MockModelAttribute()))
MOI.set(m, MOIU.AttributeFromOptimizer(MOIU.MockModelAttribute()), 10)
@test MOI.get(m, MOIU.AttributeFromOptimizer(MOIU.MockModelAttribute())) == 10
Expand Down
10 changes: 10 additions & 0 deletions test/dummy.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
struct DummyModel <: MOI.ModelLike
end
MOI.supports(::DummyModel, ::MOI.ObjectiveSense) = true
MOI.supports(::DummyModel, ::MOI.ConstraintPrimalStart,
::Type{<:MOI.ConstraintIndex}) = true
MOI.supports_constraint(::DummyModel, ::Type{MOI.SingleVariable},
::Type{MOI.EqualTo{Float64}}) = true
MOI.supports_constraint(::DummyModel, ::Type{MOI.VectorOfVariables},
::Type{MOI.Zeros}) = true

10 changes: 0 additions & 10 deletions test/errors.jl
Original file line number Diff line number Diff line change
@@ -1,13 +1,3 @@
struct DummyModel <: MOI.ModelLike
end
MOI.supports(::DummyModel, ::MOI.ObjectiveSense) = true
MOI.supports(::DummyModel, ::MOI.ConstraintPrimalStart,
::Type{<:MOI.ConstraintIndex}) = true
MOI.supports_constraint(::DummyModel, ::Type{MOI.SingleVariable},
::Type{MOI.EqualTo{Float64}}) = true
MOI.supports_constraint(::DummyModel, ::Type{MOI.VectorOfVariables},
::Type{MOI.Zeros}) = true

@testset "Fallbacks for `set` methods" begin
model = DummyModel()

Expand Down
3 changes: 3 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ using Compat.Test

# Tests for solvers are located in MOI.Test.

include("dummy.jl")

# MOI tests not relying on any submodule
@testset "MOI" begin
include("isbits.jl")
include("isapprox.jl")
include("interval.jl")
include("errors.jl")
include("attributes.jl")
end

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