From 2799c65344886efdf3782687f9720ba68875de7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 1 Aug 2019 18:09:42 +0200 Subject: [PATCH 1/3] Add constraint map --- src/Bridges/Constraint/Constraint.jl | 3 + src/Bridges/Constraint/map.jl | 285 ++++++++++++++++++++++++++ test/Bridges/Constraint/Constraint.jl | 4 + test/Bridges/Constraint/map.jl | 121 +++++++++++ 4 files changed, 413 insertions(+) create mode 100644 src/Bridges/Constraint/map.jl create mode 100644 test/Bridges/Constraint/map.jl diff --git a/src/Bridges/Constraint/Constraint.jl b/src/Bridges/Constraint/Constraint.jl index ae6db7fc77..ce21266c83 100644 --- a/src/Bridges/Constraint/Constraint.jl +++ b/src/Bridges/Constraint/Constraint.jl @@ -10,6 +10,9 @@ const CI = MOI.ConstraintIndex # Definition of a constraint bridge include("bridge.jl") +# Mapping between constraint indices and bridges +include("map.jl") + # Bridge optimizer bridging a specific constraint bridge include("single_bridge_optimizer.jl") diff --git a/src/Bridges/Constraint/map.jl b/src/Bridges/Constraint/map.jl new file mode 100644 index 0000000000..989f04c62b --- /dev/null +++ b/src/Bridges/Constraint/map.jl @@ -0,0 +1,285 @@ +""" + Map <: AbstractDict{MOI.VariableIndex, AbstractBridge} + +Mapping between bridged constraints and the bridge that bridged the constraint. +""" +struct Map <: AbstractDict{MOI.ConstraintIndex, AbstractBridge} + # Constraint Index of bridged constraint -> Bridge. + # It is set to `nothing` when the constraint is deleted. + bridges::Vector{Union{Nothing, AbstractBridge}} + # Constraint Index of bridged constraint -> Constraint type. + constraint_types::Vector{Tuple{DataType, DataType}} + # For `SingleVariable` constraints: (variable, set type) -> bridge + single_variable_constraints::Dict{Tuple{Int64, DataType}, AbstractBridge} + # For `VectorVariable` constraints: (variable, set type) -> bridge + vector_of_variables_constraints::Dict{Tuple{Int64, DataType}, AbstractBridge} +end +function Map() + return Map(Union{Nothing, AbstractBridge}[], + Tuple{DataType, DataType}[], + Dict{Tuple{Int64, DataType}, AbstractBridge}(), + Dict{Tuple{Int64, DataType}, AbstractBridge}()) +end + +# Implementation of `AbstractDict` interface. + +function Base.isempty(map::Map) + return all(bridge -> bridge === nothing, map.bridges) && + isempty(map.single_variable_constraints) && + isempty(map.vector_of_variables_constraints) +end +function Base.empty!(map::Map) + empty!(map.bridges) + empty!(map.constraint_types) + empty!(map.single_variable_constraints) + empty!(map.vector_of_variables_constraints) + return map +end +function Base.length(map::Map) + return count(bridge -> bridge !== nothing, map.bridges) + + length(map.single_variable_constraints) + + length(map.vector_of_variables_constraints) +end +function Base.haskey(map::Map, ci::MOI.ConstraintIndex{F, S}) where {F, S} + return 1 ≤ ci.value ≤ length(map.bridges) && + map.bridges[ci.value] !== nothing && + (F, S) == map.constraint_types[ci.value] +end +function Base.getindex(map::Map, + ci::MOI.ConstraintIndex) + return map.bridges[ci.value] +end +function Base.haskey(map::Map, ci::MOI.ConstraintIndex{MOI.SingleVariable, S}) where S + return haskey(map.single_variable_constraints, (ci.value, S)) +end +function Base.getindex(map::Map, + ci::MOI.ConstraintIndex{MOI.SingleVariable, S}) where S + return map.single_variable_constraints[(ci.value, S)] +end +function Base.haskey(map::Map, ci::MOI.ConstraintIndex{MOI.VectorOfVariables, S}) where S + return haskey(map.vector_of_variables_constraints, (ci.value, S)) +end +function Base.getindex(map::Map, + ci::MOI.ConstraintIndex{MOI.VectorOfVariables, S}) where S + return map.vector_of_variables_constraints[(ci.value, S)] +end +function Base.delete!(map::Map, ci::MOI.ConstraintIndex) + map.bridges[ci.value] = nothing + return map +end +function Base.delete!(map::Map, ci::MOI.ConstraintIndex{MOI.SingleVariable, S}) where S + delete!(map.single_variable_constraints, (ci.value, S)) + return map +end +function Base.delete!(map::Map, ci::MOI.ConstraintIndex{MOI.VectorOfVariables, S}) where S + delete!(map.vector_of_variables_constraints, (ci.value, S)) + return map +end +function Base.values(map::Map) + return Base.Iterators.flatten(( + # See comment in `values(::Variable.Map)`. + Base.Iterators.Filter(bridge -> bridge !== nothing, map.bridges), + values(map.single_variable_constraints), + values(map.vector_of_variables_constraints) + )) +end + +# Implementation of iterate: it should combine non-variablewise constraints, +# `SingleVariable` constraints and `VectorOfVariables` constraints. +function _iterate_vov(map::Map, elem_state=iterate(map.vector_of_variables_constraints)) + if elem_state === nothing + return nothing + else + i, S = elem_state[1].first + bridge = elem_state[1].second + ci = MOI.ConstraintIndex{MOI.VectorOfVariables, S}(i) + return ci => bridge, (3, elem_state[2]) + end +end +function _iterate_sv(map::Map, elem_state=iterate(map.single_variable_constraints)) + if elem_state === nothing + return _iterate_vov(map) + else + i, S = elem_state[1].first + bridge = elem_state[1].second + ci = MOI.ConstraintIndex{MOI.SingleVariable, S}(i) + return ci => bridge, (2, elem_state[2]) + end +end +function _iterate(map::Map, state=1) + while state ≤ length(map.bridges) && map.bridges[state] === nothing + state += 1 + end + if state > length(map.bridges) + return _iterate_sv(map) + else + F, S = map.constraint_types[state] + return MOI.ConstraintIndex{F, S}(state) => map.bridges[state], (1, state + 1) + end +end +Base.iterate(map::Map) = _iterate(map) +function Base.iterate(map::Map, state) + if state[1] == 1 + return _iterate(map, state[2]) + elseif state[1] == 2 + return _iterate_sv(map, iterate(map.single_variable_constraints, state[2])) + else + return _iterate_vov(map, iterate(map.vector_of_variables_constraints, state[2])) + end +end + +# Custom interface for information needed by `AbstractBridgeOptimizer`s that is +# not part of the `AbstractDict` interface. + +""" + number_of_type(map::Map, C::Type{<:MOI.ConstraintIndex}) + +Return the number of keys of type `C` in `map`. +""" +function number_of_type end + +""" + keys_of_type(map::Map, C::Type{<:MOI.ConstraintIndex}) + +Return a list of all the keys of type `C` in `map`. +""" +function keys_of_type end + +function number_of_type(map::Map, C::Type{MOI.ConstraintIndex{F, S}}) where {F, S} + return count(i -> haskey(map, C(i)), eachindex(map.bridges)) +end +function keys_of_type(map::Map, C::Type{MOI.ConstraintIndex{F, S}}) where {F, S} + return Base.Iterators.Filter( + ci -> haskey(map, ci), + MOI.Bridges.LazyMap{C}( + i -> C(i), eachindex(map.bridges))) +end +function number_of_type(map::Map, C::Type{MOI.ConstraintIndex{MOI.SingleVariable, S}}) where S + return count(key -> key[2] == S, keys(map.single_variable_constraints)) +end +function keys_of_type(map::Map, C::Type{MOI.ConstraintIndex{MOI.SingleVariable, S}}) where S + return MOI.Bridges.LazyMap{C}( + key -> C(key[1]), + Base.Iterators.Filter(key -> key[2] == S, keys(map.single_variable_constraints)) + ) +end +function number_of_type(map::Map, C::Type{MOI.ConstraintIndex{MOI.VectorOfVariables, S}}) where S + return count(key -> key[2] == S, keys(map.vector_of_variables_constraints)) +end +function keys_of_type(map::Map, C::Type{MOI.ConstraintIndex{MOI.VectorOfVariables, S}}) where S + return MOI.Bridges.LazyMap{C}( + key -> C(key[1]), + Base.Iterators.Filter(key -> key[2] == S, keys(map.vector_of_variables_constraints)) + ) +end + +""" + list_of_key_types(map::Map) + +Return a list of all the different concrete type of keys in `map`. +""" +function list_of_key_types(map::Map) + list = Set{Tuple{DataType, DataType}}() + for i in eachindex(map.bridges) + if map.bridges[i] !== nothing + push!(list, map.constraint_types[i]) + end + end + for key in keys(map.single_variable_constraints) + push!(list, (MOI.SingleVariable, key[2])) + end + for key in keys(map.vector_of_variables_constraints) + push!(list, (MOI.VectorOfVariables, key[2])) + end + return list +end + +""" + variable_constraints(map::Map, vi::MOI.VariableIndex) + +Return the list of all keys corresponding to [`MathOptInterface.SingleVariable`](@ref) +constraints on the variable `vi`. +""" +function variable_constraints(map::Map, vi::MOI.VariableIndex) + cis = MOI.ConstraintIndex{MOI.SingleVariable}[] + for key in keys(map.single_variable_constraints) + if key[1] == vi.value + push!(cis, MOI.ConstraintIndex{MOI.SingleVariable, key[2]}(vi.value)) + end + end + return cis +end + +""" + variable_constraints(map::Map, vis::Vector{MOI.VariableIndex}) + +Return the list of all keys that *may* corresponding to +[`MathOptInterface.VectorOfVariables`](@ref) constraints on the variables `vis`. +""" +function variable_constraints(map::Map, vis::Vector{MOI.VariableIndex}) + cis = MOI.ConstraintIndex{MOI.VectorOfVariables}[] + for key in keys(map.vector_of_variables_constraints) + if key[1] == first(vis).value + push!(cis, MOI.ConstraintIndex{MOI.VectorOfVariables, key[2]}(key[1])) + end + end + return cis +end + +""" + has_bridges(map::Map)::Bool + +Return a `Bool` indicating whether any bridge were added yet. Note that it +returns `false` even if all bridges were deleted while `isempty` would return +`true`. It is computed in `O(1)` while `isempty` needs `O(n)` hence it is used +by [`MathOptInterface.Bridges.AbstractBridgeOptimizer`](@ref) to shortcut +operations in case variable bridges are not used. +""" +function has_bridges(map::Map) + return !isempty(map.bridges) || + !isempty(map.single_variable_constraints) || + !isempty(map.vector_of_variables_constraints) +end + + + +""" + add_key_for_bridge(map::Map, bridge::AbstractBridge, + func::MOI.AbstractFunction, set::MOI.AbstractSet) + +Return a new constraint index `ci` and stores the mapping `ci => bridge`. +""" +function add_key_for_bridge end + +function add_key_for_bridge(map::Map, bridge::AbstractBridge, + func::MOI.AbstractFunction, set::MOI.AbstractSet) + push!(map.bridges, bridge) + push!(map.constraint_types, (typeof(func), typeof(set))) + return MOI.ConstraintIndex{typeof(func), typeof(set)}(length(map.bridges)) +end +function add_key_for_bridge(map::Map, bridge::AbstractBridge, + func::MOI.SingleVariable, set::MOI.AbstractScalarSet) + map.single_variable_constraints[(func.variable.value, typeof(set))] = bridge + return MOI.ConstraintIndex{MOI.SingleVariable, typeof(set)}(func.variable.value) +end +function add_key_for_bridge(map::Map, bridge::AbstractBridge, + func::MOI.VectorOfVariables, set::MOI.AbstractVectorSet) + index = first(func.variables).value + map.vector_of_variables_constraints[(index, typeof(set))] = bridge + return MOI.ConstraintIndex{MOI.VectorOfVariables, typeof(set)}(index) +end + +""" + EmptyMap <: AbstractDict{MOI.ConstraintIndex, AbstractBridge} + +Empty version of [`Map`](@ref). It is used by +[`MathOptInterface.Bridges.Variable.SingleBridgeOptimizer`](@ref) as it does +not bridge any constraint. +""" +struct EmptyMap <: AbstractDict{MOI.VariableIndex, AbstractBridge} end +Base.isempty(::EmptyMap) = true +function Base.empty!(::EmptyMap) end +Base.keys(::EmptyMap) = MOIB.EmptyVector{MOI.VariableIndex}() +Base.values(::EmptyMap) = MOIB.EmptyVector{AbstractBridge}() +has_bridges(::EmptyMap) = false +number_of_type(::EmptyMap, ::Type{<:MOI.ConstraintIndex}) = 0 diff --git a/test/Bridges/Constraint/Constraint.jl b/test/Bridges/Constraint/Constraint.jl index f7e257c15d..dbce4c1017 100644 --- a/test/Bridges/Constraint/Constraint.jl +++ b/test/Bridges/Constraint/Constraint.jl @@ -1,3 +1,7 @@ +using Test +@testset "Map" begin + include("map.jl") +end include("flip_sign.jl") include("vectorize.jl") include("scalarize.jl") diff --git a/test/Bridges/Constraint/map.jl b/test/Bridges/Constraint/map.jl new file mode 100644 index 0000000000..eddeec73dd --- /dev/null +++ b/test/Bridges/Constraint/map.jl @@ -0,0 +1,121 @@ +using Test +using MathOptInterface +const MOI = MathOptInterface +const MOIB = MOI.Bridges + +struct ConstraintDummyBridge <: MOIB.Constraint.AbstractBridge + id::Int +end + +map = MOIB.Constraint.Map() +@test isempty(map) +@test !MOIB.Constraint.has_bridges(map) +x = MOI.VariableIndex(1) +y = MOI.VariableIndex(2) +fx = MOI.SingleVariable(x) +b1 = ConstraintDummyBridge(1) +c1 = MOIB.Constraint.add_key_for_bridge(map, b1, fx, MOI.EqualTo(0.0)) +@test c1.value == x.value +@test haskey(map, c1) +@test map[c1] == b1 +@test MOIB.Constraint.number_of_type(map, typeof(c1)) == 1 +@test collect(MOIB.Constraint.keys_of_type(map, typeof(c1))) == [c1] + +@test length(map) == 1 +@test !isempty(map) +@test MOIB.Constraint.has_bridges(map) + +b2 = ConstraintDummyBridge(2) +vov = MOI.VectorOfVariables([x, y]) +c2 = MOIB.Constraint.add_key_for_bridge(map, b2, vov, MOI.SecondOrderCone(2)) +@test c2.value == x.value +@test haskey(map, c2) +@test map[c2] == b2 +@test MOIB.Constraint.number_of_type(map, typeof(c2)) == 1 +@test collect(MOIB.Constraint.keys_of_type(map, typeof(c2))) == [c2] + +@test length(map) == 2 +@test !isempty(map) +@test MOIB.Constraint.has_bridges(map) + +b3 = ConstraintDummyBridge(3) +c3 = MOIB.Constraint.add_key_for_bridge(map, b3, 1.0fx + 2.0, MOI.EqualTo(0.0)) +@test haskey(map, c3) +@test map[c3] == b3 +@test MOIB.Constraint.number_of_type(map, typeof(c3)) == 1 +@test collect(MOIB.Constraint.keys_of_type(map, typeof(c3))) == [c3] + +@test length(map) == 3 +@test !isempty(map) +@test MOIB.Constraint.has_bridges(map) + +bridges = collect(values(map)) +@test sort([b.id for b in bridges]) == 1:3 +elements = sort(collect(map), by = el -> el.second.id) +@test elements[1].first == c1 +@test elements[1].second == b1 +@test elements[2].first == c2 +@test elements[2].second == b2 +@test elements[3].first == c3 +@test elements[3].second == b3 + +@test MOIB.Constraint.list_of_key_types(map) == Set([ + (MOI.VectorOfVariables, MOI.SecondOrderCone), + (MOI.SingleVariable, MOI.EqualTo{Float64}), + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) +]) +@test MOIB.Constraint.variable_constraints(map, x) == [c1] +@test isempty(MOIB.Constraint.variable_constraints(map, y)) + +@testset "Delete" begin + delete!(map, c1) + @test length(map) == 2 + @test !isempty(map) + @test MOIB.Constraint.has_bridges(map) + + @test MOIB.Constraint.list_of_key_types(map) == Set([ + (MOI.VectorOfVariables, MOI.SecondOrderCone), + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) + ]) + @test isempty(MOIB.Constraint.variable_constraints(map, x)) + @test isempty(MOIB.Constraint.variable_constraints(map, y)) + @test MOIB.Constraint.number_of_type(map, typeof(c1)) == 0 + @test MOIB.Constraint.number_of_type(map, typeof(c2)) == 1 + @test MOIB.Constraint.number_of_type(map, typeof(c3)) == 1 + + delete!(map, c2) + @test length(map) == 1 + @test !isempty(map) + @test MOIB.Constraint.has_bridges(map) + + @test MOIB.Constraint.list_of_key_types(map) == Set([ + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) + ]) + @test MOIB.Constraint.number_of_type(map, typeof(c1)) == 0 + @test MOIB.Constraint.number_of_type(map, typeof(c2)) == 0 + @test MOIB.Constraint.number_of_type(map, typeof(c3)) == 1 + + delete!(map, c3) + @test length(map) == 0 + @test isempty(map) + @test MOIB.Constraint.has_bridges(map) + + @test isempty(MOIB.Constraint.list_of_key_types(map)) + @test MOIB.Constraint.number_of_type(map, typeof(c1)) == 0 + @test MOIB.Constraint.number_of_type(map, typeof(c2)) == 0 + @test MOIB.Constraint.number_of_type(map, typeof(c3)) == 0 + + @test isempty(collect(map)) +end + +@testset "EmptyMap" begin + map = MOIB.Constraint.EmptyMap() + empty!(map) + @test isempty(map) + @test isempty(keys(map)) + @test isempty(values(map)) + @test !MOIB.Constraint.has_bridges(map) + @test iszero(MOIB.Constraint.number_of_type(map, typeof(c1))) + @test iszero(MOIB.Constraint.number_of_type(map, typeof(c2))) + @test iszero(MOIB.Constraint.number_of_type(map, typeof(c3))) +end From 298944b5e881d881a7b96dc7384deda9665a8ac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 1 Aug 2019 21:41:31 +0200 Subject: [PATCH 2/3] Fix --- src/Bridges/Constraint/map.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Bridges/Constraint/map.jl b/src/Bridges/Constraint/map.jl index 989f04c62b..295ba018cd 100644 --- a/src/Bridges/Constraint/map.jl +++ b/src/Bridges/Constraint/map.jl @@ -151,14 +151,14 @@ end function keys_of_type(map::Map, C::Type{MOI.ConstraintIndex{F, S}}) where {F, S} return Base.Iterators.Filter( ci -> haskey(map, ci), - MOI.Bridges.LazyMap{C}( + MOIU.LazyMap{C}( i -> C(i), eachindex(map.bridges))) end function number_of_type(map::Map, C::Type{MOI.ConstraintIndex{MOI.SingleVariable, S}}) where S return count(key -> key[2] == S, keys(map.single_variable_constraints)) end function keys_of_type(map::Map, C::Type{MOI.ConstraintIndex{MOI.SingleVariable, S}}) where S - return MOI.Bridges.LazyMap{C}( + return MOIU.LazyMap{C}( key -> C(key[1]), Base.Iterators.Filter(key -> key[2] == S, keys(map.single_variable_constraints)) ) @@ -167,7 +167,7 @@ function number_of_type(map::Map, C::Type{MOI.ConstraintIndex{MOI.VectorOfVariab return count(key -> key[2] == S, keys(map.vector_of_variables_constraints)) end function keys_of_type(map::Map, C::Type{MOI.ConstraintIndex{MOI.VectorOfVariables, S}}) where S - return MOI.Bridges.LazyMap{C}( + return MOIU.LazyMap{C}( key -> C(key[1]), Base.Iterators.Filter(key -> key[2] == S, keys(map.vector_of_variables_constraints)) ) @@ -279,7 +279,7 @@ not bridge any constraint. struct EmptyMap <: AbstractDict{MOI.VariableIndex, AbstractBridge} end Base.isempty(::EmptyMap) = true function Base.empty!(::EmptyMap) end -Base.keys(::EmptyMap) = MOIB.EmptyVector{MOI.VariableIndex}() -Base.values(::EmptyMap) = MOIB.EmptyVector{AbstractBridge}() +Base.keys(::EmptyMap) = MOIU.EmptyVector{MOI.VariableIndex}() +Base.values(::EmptyMap) = MOIU.EmptyVector{AbstractBridge}() has_bridges(::EmptyMap) = false number_of_type(::EmptyMap, ::Type{<:MOI.ConstraintIndex}) = 0 From b90e7139b8267d2ce4455a97954e1fe55715e155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Fri, 2 Aug 2019 11:13:52 +0200 Subject: [PATCH 3/3] Address comments --- src/Bridges/Constraint/map.jl | 4 +- test/Bridges/Constraint/map.jl | 96 ++++++++++++++++++---------------- 2 files changed, 54 insertions(+), 46 deletions(-) diff --git a/src/Bridges/Constraint/map.jl b/src/Bridges/Constraint/map.jl index 295ba018cd..47eaf265ed 100644 --- a/src/Bridges/Constraint/map.jl +++ b/src/Bridges/Constraint/map.jl @@ -213,7 +213,7 @@ end """ variable_constraints(map::Map, vis::Vector{MOI.VariableIndex}) -Return the list of all keys that *may* corresponding to +Return the list of all keys that *may* correspond to [`MathOptInterface.VectorOfVariables`](@ref) constraints on the variables `vis`. """ function variable_constraints(map::Map, vis::Vector{MOI.VariableIndex}) @@ -229,7 +229,7 @@ end """ has_bridges(map::Map)::Bool -Return a `Bool` indicating whether any bridge were added yet. Note that it +Return a `Bool` indicating whether any bridge was added yet. Note that it returns `false` even if all bridges were deleted while `isempty` would return `true`. It is computed in `O(1)` while `isempty` needs `O(n)` hence it is used by [`MathOptInterface.Bridges.AbstractBridgeOptimizer`](@ref) to shortcut diff --git a/test/Bridges/Constraint/map.jl b/test/Bridges/Constraint/map.jl index eddeec73dd..f2ba84d55d 100644 --- a/test/Bridges/Constraint/map.jl +++ b/test/Bridges/Constraint/map.jl @@ -8,64 +8,72 @@ struct ConstraintDummyBridge <: MOIB.Constraint.AbstractBridge end map = MOIB.Constraint.Map() -@test isempty(map) -@test !MOIB.Constraint.has_bridges(map) +@testset "Empty" begin + @test isempty(map) + @test !MOIB.Constraint.has_bridges(map) +end x = MOI.VariableIndex(1) y = MOI.VariableIndex(2) fx = MOI.SingleVariable(x) b1 = ConstraintDummyBridge(1) c1 = MOIB.Constraint.add_key_for_bridge(map, b1, fx, MOI.EqualTo(0.0)) -@test c1.value == x.value -@test haskey(map, c1) -@test map[c1] == b1 -@test MOIB.Constraint.number_of_type(map, typeof(c1)) == 1 -@test collect(MOIB.Constraint.keys_of_type(map, typeof(c1))) == [c1] +@testset "SingleVariable" begin + @test c1.value == x.value + @test haskey(map, c1) + @test map[c1] == b1 + @test MOIB.Constraint.number_of_type(map, typeof(c1)) == 1 + @test collect(MOIB.Constraint.keys_of_type(map, typeof(c1))) == [c1] -@test length(map) == 1 -@test !isempty(map) -@test MOIB.Constraint.has_bridges(map) + @test length(map) == 1 + @test !isempty(map) + @test MOIB.Constraint.has_bridges(map) +end b2 = ConstraintDummyBridge(2) vov = MOI.VectorOfVariables([x, y]) c2 = MOIB.Constraint.add_key_for_bridge(map, b2, vov, MOI.SecondOrderCone(2)) -@test c2.value == x.value -@test haskey(map, c2) -@test map[c2] == b2 -@test MOIB.Constraint.number_of_type(map, typeof(c2)) == 1 -@test collect(MOIB.Constraint.keys_of_type(map, typeof(c2))) == [c2] +@testset "VectorOfVariables" begin + @test c2.value == x.value + @test haskey(map, c2) + @test map[c2] == b2 + @test MOIB.Constraint.number_of_type(map, typeof(c2)) == 1 + @test collect(MOIB.Constraint.keys_of_type(map, typeof(c2))) == [c2] -@test length(map) == 2 -@test !isempty(map) -@test MOIB.Constraint.has_bridges(map) + @test length(map) == 2 + @test !isempty(map) + @test MOIB.Constraint.has_bridges(map) +end b3 = ConstraintDummyBridge(3) c3 = MOIB.Constraint.add_key_for_bridge(map, b3, 1.0fx + 2.0, MOI.EqualTo(0.0)) -@test haskey(map, c3) -@test map[c3] == b3 -@test MOIB.Constraint.number_of_type(map, typeof(c3)) == 1 -@test collect(MOIB.Constraint.keys_of_type(map, typeof(c3))) == [c3] - -@test length(map) == 3 -@test !isempty(map) -@test MOIB.Constraint.has_bridges(map) - -bridges = collect(values(map)) -@test sort([b.id for b in bridges]) == 1:3 -elements = sort(collect(map), by = el -> el.second.id) -@test elements[1].first == c1 -@test elements[1].second == b1 -@test elements[2].first == c2 -@test elements[2].second == b2 -@test elements[3].first == c3 -@test elements[3].second == b3 - -@test MOIB.Constraint.list_of_key_types(map) == Set([ - (MOI.VectorOfVariables, MOI.SecondOrderCone), - (MOI.SingleVariable, MOI.EqualTo{Float64}), - (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) -]) -@test MOIB.Constraint.variable_constraints(map, x) == [c1] -@test isempty(MOIB.Constraint.variable_constraints(map, y)) +@testset "ScalarAffineFunction" begin + @test haskey(map, c3) + @test map[c3] == b3 + @test MOIB.Constraint.number_of_type(map, typeof(c3)) == 1 + @test collect(MOIB.Constraint.keys_of_type(map, typeof(c3))) == [c3] + + @test length(map) == 3 + @test !isempty(map) + @test MOIB.Constraint.has_bridges(map) + + bridges = collect(values(map)) + @test sort([b.id for b in bridges]) == 1:3 + elements = sort(collect(map), by = el -> el.second.id) + @test elements[1].first == c1 + @test elements[1].second == b1 + @test elements[2].first == c2 + @test elements[2].second == b2 + @test elements[3].first == c3 + @test elements[3].second == b3 + + @test MOIB.Constraint.list_of_key_types(map) == Set([ + (MOI.VectorOfVariables, MOI.SecondOrderCone), + (MOI.SingleVariable, MOI.EqualTo{Float64}), + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) + ]) + @test MOIB.Constraint.variable_constraints(map, x) == [c1] + @test isempty(MOIB.Constraint.variable_constraints(map, y)) +end @testset "Delete" begin delete!(map, c1)