Skip to content

Supports add constrained variable #1101

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 18 commits into from
Jun 29, 2020
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
4 changes: 3 additions & 1 deletion src/Bridges/lazy_bridge_optimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ end

function node(b::LazyBridgeOptimizer, S::Type{<:MOI.AbstractSet})
F = MOIU.variable_function_type(S)
if MOI.supports_constraint(b.model, F, S)
if (MOI.supports_constraint(b.model, F, S)
|| (S <: MOI.AbstractScalarSet && MOI.supports_add_constrained_variable(b.model, S))
|| (S <: MOI.AbstractVectorSet && MOI.supports_add_constrained_variables(b.model, S)))
return VariableNode(0)
end
variable_node = get(b.variable_node, (S,), nothing)
Expand Down
35 changes: 23 additions & 12 deletions src/Utilities/copy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -302,13 +302,15 @@ function default_copy_to(dest::MOI.ModelLike, src::MOI.ModelLike, copy_names::Bo

vis_src = MOI.get(src, MOI.ListOfVariableIndices())
constraint_types = MOI.get(src, MOI.ListOfConstraints())
single_variable_types = [S for (F, S) in constraint_types
if F == MOI.SingleVariable]
vector_of_variables_types = [S for (F, S) in constraint_types
if F == MOI.VectorOfVariables]
single_variable_types = Type{<:MOI.AbstractScalarSet}[]
vector_of_variables_types = Type{<:MOI.AbstractVectorSet}[]

# The `NLPBlock` assumes that the order of variables does not change (#849)
if MOI.NLPBlock() in MOI.get(src, MOI.ListOfModelAttributesSet())
single_variable_types = [S for (F, S) in constraint_types
if F == MOI.SingleVariable]
vector_of_variables_types = [S for (F, S) in constraint_types
if F == MOI.VectorOfVariables]
vector_of_variables_not_added = [
MOI.get(src, MOI.ListOfConstraintIndices{MOI.VectorOfVariables, S}())
for S in vector_of_variables_types
Expand All @@ -318,14 +320,23 @@ function default_copy_to(dest::MOI.ModelLike, src::MOI.ModelLike, copy_names::Bo
for S in single_variable_types
]
else
vector_of_variables_not_added = [
copy_vector_of_variables(dest, src, idxmap, S)
for S in vector_of_variables_types
]
single_variable_not_added = [
copy_single_variable(dest, src, idxmap, S)
for S in single_variable_types
]
# Order the copying of the variables by 1) their variable bridging cost 2) by starting with the vectors, as what was done before
# See issue #987
single_or_vector_variables_types = [(F, S) for (F, S) in constraint_types
if F == MOI.SingleVariable || F == MOI.VectorOfVariables]
sorted_by_cost = sortperm(single_or_vector_variables_types; by=((F, S),) -> (MOI.get(dest, MOI.VariableBridgingCost{S}()) - MOI.get(dest, MOI.ConstraintBridgingCost{F, S}()), F == MOI.SingleVariable))
vector_of_variables_not_added = Vector{Array{MOI.ConstraintIndex{MOI.VectorOfVariables, <:MOI.AbstractVectorSet}}}()
single_variable_not_added = Vector{Array{MOI.ConstraintIndex{MOI.SingleVariable, <:MOI.AbstractScalarSet}}}()
for i in sorted_by_cost
F, S = single_or_vector_variables_types[i]
if F == MOI.VectorOfVariables
push!(vector_of_variables_not_added, copy_vector_of_variables(dest, src, idxmap, S))
push!(vector_of_variables_types, S)
elseif F == MOI.SingleVariable
push!(single_variable_not_added, copy_single_variable(dest, src, idxmap, S))
push!(single_variable_types, S)
end
end
end

copy_free_variables(dest, idxmap, vis_src, MOI.add_variables)
Expand Down
20 changes: 18 additions & 2 deletions src/attributes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,9 @@ function get end
get(model::ModelLike, attr::AnyAttribute, idxs::Vector) = get.(model, attr, idxs)

function get(model::ModelLike, attr::AnyAttribute, args...)
throw(ArgumentError("ModelLike of type $(typeof(model)) does not support accessing the attribute $attr"))
get_fallback(model, attr, args...)
end
get_fallback(model::ModelLike, attr::AnyAttribute, args...) = throw(ArgumentError("ModelLike of type $(typeof(model)) does not support accessing the attribute $attr"))

"""
get!(output, model::ModelLike, args...)
Expand Down Expand Up @@ -1348,6 +1349,19 @@ end
DualStatus() = DualStatus(1)
_result_index_field(attr::DualStatus) = attr.N


# Cost of bridging constrained variable in S
struct VariableBridgingCost{S <: AbstractSet} <: AbstractModelAttribute
end
get_fallback(model::ModelLike, ::VariableBridgingCost{S}) where {S<:AbstractScalarSet} = supports_add_constrained_variable(model, S) ? 0.0 : Inf
get_fallback(model::ModelLike, ::VariableBridgingCost{S}) where {S<:AbstractVectorSet} = supports_add_constrained_variables(model, S) ? 0.0 : Inf

# Cost of bridging F-in-S constraints
struct ConstraintBridgingCost{F <: AbstractFunction, S <: AbstractSet} <: AbstractModelAttribute
end
get_fallback(model::ModelLike, ::ConstraintBridgingCost{F, S}) where {F<:AbstractFunction, S<:AbstractSet} = supports_constraint(model, F, S) ? 0.0 : Inf


"""
is_set_by_optimize(::AnyAttribute)

Expand Down Expand Up @@ -1427,6 +1441,8 @@ function is_copyable(::Union{ListOfOptimizerAttributesSet,
ListOfConstraintIndices,
ListOfConstraints,
ConstraintFunction,
ConstraintSet})
ConstraintSet,
VariableBridgingCost,
ConstraintBridgingCost})
return false
end
94 changes: 94 additions & 0 deletions test/Utilities/copy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,97 @@ end
@test dest.added_constrained[idxmap[vi].value]
end
end


abstract type AbstractConstrainedVariablesModel <: MOI.ModelLike end
mutable struct OrderConstrainedVariablesModel <: AbstractConstrainedVariablesModel
constraintIndices ::Array{MOI.ConstraintIndex}
inner ::MOIU.Model{Float64}
OrderConstrainedVariablesModel() = new(MOI.ConstraintIndex[], MOIU.Model{Float64}())
end
mutable struct ReverseOrderConstrainedVariablesModel <: AbstractConstrainedVariablesModel
constraintIndices ::Array{MOI.ConstraintIndex}
inner ::MOIU.Model{Float64}
ReverseOrderConstrainedVariablesModel() = new(MOI.ConstraintIndex[], MOIU.Model{Float64}())
end



MOI.add_variables(model::AbstractConstrainedVariablesModel, n) = MOI.add_variables(model.inner, n)
MOI.add_variable(model::AbstractConstrainedVariablesModel) = MOI.add_variable(model.inner)

function MOI.add_constraint(model::AbstractConstrainedVariablesModel, f::F, s::S) where {F<:MOI.AbstractFunction, S<:MOI.AbstractSet}
ci = MOI.add_constraint(model.inner, f, s)
push!(model.constraintIndices, ci)
return ci
end

function MOI.copy_to(dest::AbstractConstrainedVariablesModel, src::MOI.ModelLike; kws...)
MOIU.automatic_copy_to(dest, src; kws...)
end

MOIU.supports_default_copy_to(model::AbstractConstrainedVariablesModel, ::Bool) = true

function MOI.empty!(model::AbstractConstrainedVariablesModel)
model.constraintIndices = MOI.ConstraintIndex[]
MOI.empty!(model.inner)
end


MOI.supports_constraint(::OrderConstrainedVariablesModel, ::Type{MOI.VectorOfVariables}, ::Type{MOI.Nonnegatives}) = false
MOI.supports_add_constrained_variables(::OrderConstrainedVariablesModel, ::Type{MOI.Nonnegatives}) = true
MOI.supports_constraint(::OrderConstrainedVariablesModel, ::Type{MOI.VectorOfVariables}, ::Type{MOI.Nonnegatives}) = true
MOI.supports_add_constrained_variables(::OrderConstrainedVariablesModel, ::Type{MOI.Nonpositives}) = false

MOI.supports_constraint(::ReverseOrderConstrainedVariablesModel, ::Type{MOI.VectorOfVariables}, ::Type{MOI.Nonnegatives}) = true
MOI.supports_add_constrained_variables(::ReverseOrderConstrainedVariablesModel, ::Type{MOI.Nonnegatives}) = false
MOI.supports_constraint(::ReverseOrderConstrainedVariablesModel, ::Type{MOI.VectorOfVariables}, ::Type{MOI.Nonnegatives}) = false
MOI.supports_add_constrained_variables(::ReverseOrderConstrainedVariablesModel, ::Type{MOI.Nonpositives}) = true


MOI.supports_constraint(::OrderConstrainedVariablesModel, ::Type{MOI.SingleVariable}, ::Type{<:MOI.GreaterThan}) = true
MOI.supports_add_constrained_variable(::OrderConstrainedVariablesModel, ::Type{<:MOI.GreaterThan}) = false
MOI.supports_constraint(::OrderConstrainedVariablesModel, ::Type{MOI.SingleVariable}, ::Type{<:MOI.LessThan}) = false
MOI.supports_add_constrained_variable(::OrderConstrainedVariablesModel, ::Type{<:MOI.LessThan}) = true

MOI.supports_constraint(::ReverseOrderConstrainedVariablesModel, ::Type{MOI.SingleVariable}, ::Type{<:MOI.GreaterThan}) = false
MOI.supports_add_constrained_variable(::ReverseOrderConstrainedVariablesModel, ::Type{<:MOI.GreaterThan}) = true
MOI.supports_constraint(::ReverseOrderConstrainedVariablesModel, ::Type{MOI.SingleVariable}, ::Type{<:MOI.LessThan}) = true
MOI.supports_add_constrained_variable(::ReverseOrderConstrainedVariablesModel, ::Type{<:MOI.LessThan}) = false


@testset "Create variables using supports_add_constrained_variable(s) (#987)" begin
# With vectors
src = MOIU.Model{Float64}()
a, c1 = MOI.add_constrained_variables(src, MOI.Nonpositives(3))
c2 = MOI.add_constraint(src, a, MOI.Nonnegatives(3))


dest = OrderConstrainedVariablesModel()
index_map = MOI.copy_to(dest, src)
@test typeof(c1) == typeof(dest.constraintIndices[2])
@test typeof(c2) == typeof(dest.constraintIndices[1])

dest = ReverseOrderConstrainedVariablesModel()
index_map = MOI.copy_to(dest, src)
@test typeof(c1) == typeof(dest.constraintIndices[1])
@test typeof(c2) == typeof(dest.constraintIndices[2])



# With single variables
src = MOIU.Model{Float64}()
a, c1 = MOI.add_constrained_variable(src, MOI.GreaterThan{Float64}(5.0))
c2 = MOI.add_constraint(src, a, MOI.LessThan{Float64}(1.0))


dest = OrderConstrainedVariablesModel()
index_map = MOI.copy_to(dest, src)
@test typeof(c1) == typeof(dest.constraintIndices[2])
@test typeof(c2) == typeof(dest.constraintIndices[1])

dest = ReverseOrderConstrainedVariablesModel()
index_map = MOI.copy_to(dest, src)
@test typeof(c1) == typeof(dest.constraintIndices[1])
@test typeof(c2) == typeof(dest.constraintIndices[2])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, if you run the test with dest = full_bridge_optimizer(ReverseOrderConstrainedVariablesModel(), Float64) or dest = full_bridge_optimizer(OrderConstrainedVariablesModel(), Float64), I guess one of the two will fail as with the bridges they both support both constrained variables.
To fix this, we should implement the attribute for LazyBridgeOptimizer and return the bridge cost.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you meant here. Could you maybe write the corresponding test and I'd try to make it work?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean something like

dest = OrderConstrainedVariablesModel()
bridged_dest = MOI.Bridges.full_bridge_optimizer(dest, Float64)
index_map = MOI.copy_to(bridged_dest, src)
@test typeof(c1) == typeof(dest.constraintIndices[2])
@test typeof(c2) == typeof(dest.constraintIndices[1])

dest = ReverseOrderConstrainedVariablesModel()
bridged_dest = MOI.Bridges.full_bridge_optimizer(dest, Float64)
index_map = MOI.copy_to(bridged_dest, src)
@test typeof(c1) == typeof(dest.constraintIndices[1])
@test typeof(c2) == typeof(dest.constraintIndices[2])

Copy link
Contributor Author

@ilancoulon ilancoulon Jun 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By trying the following lines:

src = MOIU.Model{Float64}()
a, c1 = MOI.add_constrained_variables(src, MOI.Nonpositives(3))
c2 = MOI.add_constraint(src, a, MOI.Nonnegatives(3))
dest = OrderConstrainedVariablesModel()
bridged_dest = MOI.Bridges.full_bridge_optimizer(dest, Float64)
index_map = MOI.copy_to(bridged_dest, src)

I get an error stating MathOptInterface.UnsupportedConstraint{MathOptInterface.VectorOfVariables,MathOptInterface.Nonpositives}: MathOptInterface.VectorOfVariables-in-MathOptInterface.Nonpositives constraint is not supported by the model..
This is because no bridge of type ::Type{MathOptInterface.VectorOfVariables}, ::Type{MathOptInterface.Nonpositives} exists in the LazyBridgeOptimizer.

I must say I did not really grasp how the bridge types work, and I'm not sure how I should go from here. Maybe that is why the issue was not tagged as a good first issue 😅

I tried implementing the attributes with:

MOI.get(b::LazyBridgeOptimizer, c::MOI.VariableBridgingCost{S}) where S<:MOI.AbstractScalarSet = MOI.get(b.model, c)
MOI.get(b::LazyBridgeOptimizer, c::MOI.VariableBridgingCost{S}) where S<:MOI.AbstractVectorSet = MOI.get(b.model, c)
function MOI.get(b::LazyBridgeOptimizer, c::MOI.ConstraintBridgingCost{F,S}) where {S <: MOI.AbstractSet, F <: MOI.AbstractFunction}
    MOI.get(b.model, c)
end

But this did not work better, I think this shows how I don't actually understand how all of this works.

end