From be4de21df7926e7dce7d0e017679b49e6e6398ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Fri, 16 Aug 2019 18:01:38 +0200 Subject: [PATCH 1/2] Fix SOC to PSD bridge --- src/Bridges/Constraint/soc_to_psd.jl | 174 ++++++++++++++++---------- src/Utilities/functions.jl | 26 ++++ test/Bridges/Constraint/soc_to_psd.jl | 12 +- 3 files changed, 141 insertions(+), 71 deletions(-) diff --git a/src/Bridges/Constraint/soc_to_psd.jl b/src/Bridges/Constraint/soc_to_psd.jl index 85257f692a..4a4f14a93f 100644 --- a/src/Bridges/Constraint/soc_to_psd.jl +++ b/src/Bridges/Constraint/soc_to_psd.jl @@ -5,27 +5,18 @@ Builds a VectorAffineFunction representing the upper (or lower) triangular part [ f[1] f[2:end]' ] [ f[2:end] g * I ] """ -function _SOCtoPSDaff(f::MOI.VectorAffineFunction{T}, g::MOI.ScalarAffineFunction{T}) where T +function _SOCtoPSDaff(T::Type, F::Type{<:MOI.AbstractVectorFunction}, + f::MOI.AbstractVectorFunction, g::MOI.AbstractScalarFunction) dim = MOI.output_dimension(f) n = div(dim * (dim+1), 2) - # Needs to add t*I - N0 = length(f.terms) - Ni = length(g.terms) - N = N0 + (dim-1) * Ni - terms = Vector{MOI.VectorAffineTerm{T}}(undef, N) - terms[1:N0] = MOI.VectorAffineTerm.(map(t -> trimap.(t.output_index, 1), f.terms), - MOI.ScalarAffineTerm.(map(t -> t.scalar_term.coefficient, f.terms), - map(t -> t.scalar_term.variable_index, f.terms))) - constant = [f.constants; zeros(T, n - dim)] - cur = N0 + h = MOIU.zero_with_output_dimension(F, n) + f_scalars = MOIU.eachscalar(f) + MOIU.operate_output_index!(+, T, trimap(1, 1), h, f_scalars[1]) for i in 2:dim - k = trimap(i, i) - terms[cur.+(1:Ni)] = MOI.VectorAffineTerm.(k, MOI.ScalarAffineTerm.(map(t -> t.coefficient, g.terms), - map(t -> t.variable_index, g.terms))) - constant[k] = g.constant - cur += Ni + MOIU.operate_output_index!(+, T, trimap(1, i), h, f_scalars[i]) + MOIU.operate_output_index!(+, T, trimap(i, i), h, g) end - MOI.VectorAffineFunction(terms, constant) + return h end """ @@ -51,39 +42,71 @@ which is equivalent to \\end{align*} ``` """ -struct SOCtoPSDBridge{T} <: AbstractBridge +struct SOCtoPSDBridge{T, F, G} <: AbstractBridge dim::Int - cr::CI{MOI.VectorAffineFunction{T}, MOI.PositiveSemidefiniteConeTriangle} + cr::MOI.ConstraintIndex{F, MOI.PositiveSemidefiniteConeTriangle} end -function bridge_constraint(::Type{SOCtoPSDBridge{T}}, model, f, - s::MOI.SecondOrderCone) where T +function bridge_constraint(::Type{SOCtoPSDBridge{T, F, G}}, model::MOI.ModelLike, g::G, + s::MOI.SecondOrderCone) where {T, F, G} d = MOI.dimension(s) - cr = MOI.add_constraint(model, _SOCtoPSDaff(f, T), MOI.PositiveSemidefiniteConeTriangle(d)) - SOCtoPSDBridge(d, cr) + f = _SOCtoPSDaff(T, F, g, MOIU.eachscalar(g)[1]) + cr = MOI.add_constraint(model, f, MOI.PositiveSemidefiniteConeTriangle(d)) + return SOCtoPSDBridge{T, F, G}(d, cr) end -_SOCtoPSDaff(f::MOI.VectorOfVariables, ::Type{T}) where T = _SOCtoPSDaff(MOI.VectorAffineFunction{T}(f), T) -_SOCtoPSDaff(f::MOI.VectorAffineFunction, ::Type) = _SOCtoPSDaff(f, MOIU.eachscalar(f)[1]) - -MOI.supports_constraint(::Type{SOCtoPSDBridge{T}}, ::Type{<:Union{MOI.VectorOfVariables, MOI.VectorAffineFunction{T}}}, ::Type{MOI.SecondOrderCone}) where T = true +function MOI.supports_constraint( + ::Type{<:SOCtoPSDBridge}, ::Type{<:MOI.AbstractVectorFunction}, + ::Type{MOI.SecondOrderCone}) + return true +end MOIB.added_constrained_variable_types(::Type{<:SOCtoPSDBridge}) = Tuple{DataType}[] -MOIB.added_constraint_types(::Type{SOCtoPSDBridge{T}}, ::Type{<:Union{MOI.VectorOfVariables, MOI.VectorAffineFunction{T}}}, ::Type{MOI.SecondOrderCone}) where T = [(MOI.VectorAffineFunction{T}, MOI.PositiveSemidefiniteConeTriangle)] +function MOIB.added_constraint_types(::Type{<:SOCtoPSDBridge{T, F}}) where {T, F} + return [(F, MOI.PositiveSemidefiniteConeTriangle)] +end +function concrete_bridge_type(::Type{<:SOCtoPSDBridge{T}}, + G::Type{<:MOI.AbstractVectorFunction}, + ::Type{MOI.SecondOrderCone}) where T + F = MOIU.promote_operation(vcat, T, MOIU.scalar_type(G), T) + return SOCtoPSDBridge{T, F, G} +end + + +# Attributes, Bridge acting as a model +function MOI.get( + ::SOCtoPSDBridge{T, F}, + ::MOI.NumberOfConstraints{F, MOI.PositiveSemidefiniteConeTriangle}) where {T, F} + return 1 +end +function MOI.get( + bridge::SOCtoPSDBridge{T}, + ::MOI.ListOfConstraintIndices{F, MOI.PositiveSemidefiniteConeTriangle}) where {T, F} + return [bridge.cr] +end + +# References +function MOI.delete(model::MOI.ModelLike, c::SOCtoPSDBridge) + MOI.delete(model, c.cr) +end +# Attributes, Bridge acting as a constraint +function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction, + bridge::SOCtoPSDBridge{T, F, G}) where {T, F, G} + f = MOI.get(model, attr, bridge.cr) + g = MOIU.eachscalar(f)[trimap.(1, 1:bridge.dim)] + return MOIU.convert_approx(G, g) +end +function MOI.get(::MOI.ModelLike, ::MOI.ConstraintSet, bridge::SOCtoPSDBridge) + return MOI.SecondOrderCone(bridge.dim) +end function MOI.get(model::MOI.ModelLike, a::MOI.ConstraintPrimal, c::SOCtoPSDBridge) - MOI.get(model, a, c.cr)[trimap.(1:c.dim, 1)] + return MOI.get(model, a, c.cr)[trimap.(1, 1:c.dim)] end function MOI.get(model::MOI.ModelLike, a::MOI.ConstraintDual, c::SOCtoPSDBridge) dual = MOI.get(model, a, c.cr) tdual = sum(i -> dual[trimap(i, i)], 1:c.dim) - [tdual; dual[trimap.(2:c.dim, 1)]*2] + return [tdual; dual[trimap.(2:c.dim, 1)]*2] end -MOI.get(::SOCtoPSDBridge{T}, ::MOI.NumberOfConstraints{MOI.VectorAffineFunction{T}, MOI.PositiveSemidefiniteConeTriangle}) where T = 1 -MOI.get(b::SOCtoPSDBridge{T}, ::MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{T}, MOI.PositiveSemidefiniteConeTriangle}) where T = [b.cr] - -function MOI.delete(model::MOI.ModelLike, c::SOCtoPSDBridge) - MOI.delete(model, c.cr) -end """ The `RSOCtoPSDBridge` transforms the second order cone constraint ``\\lVert x \\rVert \\le 2tu`` with ``u \\ge 0`` into the semidefinite cone constraints @@ -108,54 +131,59 @@ which is equivalent to \\end{align*} ``` """ -struct RSOCtoPSDBridge{T, G} <: AbstractBridge +struct RSOCtoPSDBridge{T, F, G} <: AbstractBridge dim::Int - cr::CI{MOI.VectorAffineFunction{T}, MOI.PositiveSemidefiniteConeTriangle} + cr::MOI.ConstraintIndex{F, MOI.PositiveSemidefiniteConeTriangle} +end +function bridge_constraint(::Type{RSOCtoPSDBridge{T, F, G}}, model::MOI.ModelLike, g::G, + set::MOI.RotatedSecondOrderCone) where {T, F, G} + dim = MOI.dimension(set) - 1 + g_scalars = MOIU.eachscalar(g) + h = MOIU.operate!(*, T, g_scalars[2], convert(T, 2)) + f = _SOCtoPSDaff(T, F, g_scalars[[1; 3:MOI.output_dimension(g)]], h) + cr = MOI.add_constraint(model, f, MOI.PositiveSemidefiniteConeTriangle(dim)) + return RSOCtoPSDBridge{T, F, G}(dim, cr) end -MOI.supports_constraint(::Type{RSOCtoPSDBridge{T}}, ::Type{<:Union{MOI.VectorOfVariables, MOI.VectorAffineFunction{T}}}, ::Type{MOI.RotatedSecondOrderCone}) where T = true + +function MOI.supports_constraint( + ::Type{<:RSOCtoPSDBridge}, ::Type{<:MOI.AbstractVectorFunction}, + ::Type{MOI.RotatedSecondOrderCone}) + return true +end MOIB.added_constrained_variable_types(::Type{<:RSOCtoPSDBridge}) = Tuple{DataType}[] -MOIB.added_constraint_types(::Type{<:RSOCtoPSDBridge{T}}, ::Type{<:Union{MOI.VectorOfVariables, MOI.VectorAffineFunction{T}}}, ::Type{MOI.RotatedSecondOrderCone}) where T = [(MOI.VectorAffineFunction{T}, MOI.PositiveSemidefiniteConeTriangle)] +function MOIB.added_constraint_types(::Type{<:RSOCtoPSDBridge{T, F}}) where {T, F} + return [(F, MOI.PositiveSemidefiniteConeTriangle)] +end function concrete_bridge_type(::Type{<:RSOCtoPSDBridge{T}}, G::Type{<:MOI.AbstractVectorFunction}, ::Type{MOI.RotatedSecondOrderCone}) where T - return RSOCtoPSDBridge{T, G} + S = MOIU.scalar_type(G) + H = MOIU.promote_operation(*, T, T, S) + F = MOIU.promote_operation(vcat, T, S, H, T) + return RSOCtoPSDBridge{T, F, G} end -function bridge_constraint(::Type{RSOCtoPSDBridge{T, G}}, model, g::G, - set::MOI.RotatedSecondOrderCone) where {T, G} - dim = MOI.dimension(set)-1 - cr = MOI.add_constraint(model, _RSOCtoPSDaff(g, T), MOI.PositiveSemidefiniteConeTriangle(dim)) - RSOCtoPSDBridge{T, G}(dim, cr) +# Attributes, Bridge acting as a model +function MOI.get( + ::RSOCtoPSDBridge{T, F}, + ::MOI.NumberOfConstraints{F, MOI.PositiveSemidefiniteConeTriangle}) where {T, F} + return 1 end - -_RSOCtoPSDaff(f::MOI.VectorOfVariables, ::Type{T}) where T = _RSOCtoPSDaff(MOI.VectorAffineFunction{T}(f), T) -function _RSOCtoPSDaff(f::MOI.VectorAffineFunction, ::Type{T}) where T - n = MOI.output_dimension(f) - f_scalars = MOIU.eachscalar(f) - g = MOIU.operate!(*, T, f_scalars[2], convert(T, 2)) - return _SOCtoPSDaff(f_scalars[[1; 3:n]], g) -end - -function MOI.get(model::MOI.ModelLike, a::MOI.ConstraintPrimal, bridge::RSOCtoPSDBridge) - x = MOI.get(model, MOI.ConstraintPrimal(), bridge.cr)[[trimap(1, 1); trimap(2, 2); trimap.(2:bridge.dim, 1)]] - x[2] /= 2 # It is (2u*I)[1,1] so it needs to be divided by 2 to get u - return x -end -function MOI.get(model::MOI.ModelLike, a::MOI.ConstraintDual, bridge::RSOCtoPSDBridge) - dual = MOI.get(model, MOI.ConstraintDual(), bridge.cr) - udual = sum(i -> dual[trimap(i, i)], 2:bridge.dim) - return [dual[1]; 2udual; dual[trimap.(2:bridge.dim, 1)]*2] +function MOI.get( + bridge::RSOCtoPSDBridge{T}, + ::MOI.ListOfConstraintIndices{F, MOI.PositiveSemidefiniteConeTriangle}) where {T, F} + return [bridge.cr] end -MOI.get(::RSOCtoPSDBridge{T}, ::MOI.NumberOfConstraints{MOI.VectorAffineFunction{T}, MOI.PositiveSemidefiniteConeTriangle}) where T = 1 -MOI.get(b::RSOCtoPSDBridge{T}, ::MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{T}, MOI.PositiveSemidefiniteConeTriangle}) where T = [b.cr] - +# References function MOI.delete(model::MOI.ModelLike, bridge::RSOCtoPSDBridge) MOI.delete(model, bridge.cr) end -function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction, bridge::RSOCtoPSDBridge{T, G}) where {T, G} +# Attributes, Bridge acting as a constraint +function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction, + bridge::RSOCtoPSDBridge{T, F, G}) where {T, F, G} f_scalars = MOIU.eachscalar(MOI.get(model, attr, bridge.cr)) t = f_scalars[1] u = MOIU.operate!(/, T, f_scalars[3], convert(T, 2)) @@ -168,3 +196,13 @@ end function MOI.get(::MOI.ModelLike, ::MOI.ConstraintSet, bridge::RSOCtoPSDBridge) return MOI.RotatedSecondOrderCone(bridge.dim + 1) end +function MOI.get(model::MOI.ModelLike, a::MOI.ConstraintPrimal, bridge::RSOCtoPSDBridge) + x = MOI.get(model, MOI.ConstraintPrimal(), bridge.cr)[[trimap(1, 1); trimap(2, 2); trimap.(2:bridge.dim, 1)]] + x[2] /= 2 # It is (2u*I)[1,1] so it needs to be divided by 2 to get u + return x +end +function MOI.get(model::MOI.ModelLike, a::MOI.ConstraintDual, bridge::RSOCtoPSDBridge) + dual = MOI.get(model, MOI.ConstraintDual(), bridge.cr) + udual = sum(i -> dual[trimap(i, i)], 2:bridge.dim) + return [dual[1]; 2udual; dual[trimap.(2:bridge.dim, 1)]*2] +end diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index 13360f618e..c69bd04ce3 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -297,6 +297,19 @@ function Base.getindex(it::ScalarFunctionIterator{VQF{T}}, I::AbstractVector) wh return VQF(affine_terms, quadratic_terms, constant) end +function zero_with_output_dimension(::Type{<:MOI.VectorAffineFunction{T}}, n::Integer) where T + return MOI.VectorAffineFunction{T}( + MOI.VectorAffineTerm{T}[], + zeros(T, n)) +end +function zero_with_output_dimension(::Type{<:MOI.VectorQuadraticFunction{T}}, n::Integer) where T + return MOI.VectorQuadraticFunction{T}( + MOI.VectorAffineTerm{T}[], + MOI.VectorQuadraticTerm{T}[], + zeros(T, n)) +end + + """ unsafe_add(t1::MOI.ScalarAffineTerm, t2::MOI.ScalarAffineTerm) @@ -1127,6 +1140,15 @@ function operate!(op::Union{typeof(+), typeof(-)}, ::Type{T}, f.constants .= op.(f.constants, g) return f end +function operate_output_index!( + op::Union{typeof(+), typeof(-)}, ::Type{T}, + output_index::Integer, + f::MOI.VectorAffineFunction{T}, + g::MOI.SingleVariable) where T + push!(f.terms, MOI.VectorAffineTerm( + output_index, MOI.ScalarAffineTerm(op(one(T)), g.variable))) + return f +end function operate!(op::Union{typeof(+), typeof(-)}, ::Type{T}, f::MOI.VectorAffineFunction{T}, g::MOI.VectorOfVariables) where T @@ -1344,6 +1366,10 @@ function promote_operation(::typeof(*), ::Type{T}, ::Type{T}, MOI.ScalarAffineFunction{T}}}) where T return MOI.ScalarAffineFunction{T} end +function promote_operation(::typeof(*), ::Type{T}, ::Type{T}, + ::Type{MOI.ScalarQuadraticFunction{T}}) where T + return MOI.ScalarQuadraticFunction{T} +end function promote_operation(::typeof(*), ::Type{T}, ::Type{<:Union{MOI.SingleVariable, MOI.ScalarAffineFunction{T}}}, diff --git a/test/Bridges/Constraint/soc_to_psd.jl b/test/Bridges/Constraint/soc_to_psd.jl index b77351fc54..55e8bbc40e 100644 --- a/test/Bridges/Constraint/soc_to_psd.jl +++ b/test/Bridges/Constraint/soc_to_psd.jl @@ -13,6 +13,12 @@ config = MOIT.TestConfig() @testset "SOCtoPSD" begin bridged_mock = MOIB.Constraint.SOCtoPSD{Float64}(mock) + + MOIT.basic_constraint_tests(bridged_mock, config, + include = [(F, MOI.SecondOrderCone) for F in [ + MOI.VectorOfVariables, MOI.VectorAffineFunction{Float64}, MOI.VectorQuadraticFunction{Float64} + ]]) + mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1/√2, 1/√2], (MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle) => [[√2/2, -1/2, √2/4, -1/2, √2/4, √2/4]], (MOI.VectorAffineFunction{Float64}, MOI.Zeros) => [[-√2]]) @@ -27,9 +33,9 @@ end MOIT.basic_constraint_tests( bridged_mock, config, - include = [(F, S) - for F in [MOI.VectorOfVariables, MOI.VectorAffineFunction{Float64}] - for S in [MOI.RotatedSecondOrderCone]]) + include = [(F, MOI.RotatedSecondOrderCone) + for F in [MOI.VectorOfVariables, MOI.VectorAffineFunction{Float64}, MOI.VectorQuadraticFunction{Float64} + ]]) mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [0.5, 1.0, 1/√2, 1/√2], (MOI.SingleVariable, MOI.EqualTo{Float64}) => [-√2, -1/√2], From 1336f76badd1ac69e97a09732e937970612da191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Fri, 16 Aug 2019 18:18:00 +0200 Subject: [PATCH 2/2] Fix tests --- test/Bridges/lazy_bridge_optimizer.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/Bridges/lazy_bridge_optimizer.jl b/test/Bridges/lazy_bridge_optimizer.jl index 652ddabfec..8c42816bd1 100644 --- a/test/Bridges/lazy_bridge_optimizer.jl +++ b/test/Bridges/lazy_bridge_optimizer.jl @@ -319,8 +319,10 @@ end end c = MOI.add_constraint(bridged_mock, MOI.VectorOfVariables(x), MOI.RotatedSecondOrderCone(3)) - @test MOIB.bridge_type(bridged_mock, MOI.VectorOfVariables, - MOI.RotatedSecondOrderCone) == MOIB.Constraint.RSOCtoPSDBridge{Float64, MOI.VectorOfVariables} + @test MOIB.bridge_type( + bridged_mock, MOI.VectorOfVariables, + MOI.RotatedSecondOrderCone) == MOIB.Constraint.RSOCtoPSDBridge{ + Float64, MOI.VectorAffineFunction{Float64}, MOI.VectorOfVariables} @test MOIB.bridge(bridged_mock, c) isa MOIB.Constraint.RSOCtoPSDBridge @test bridged_mock.constraint_dist[(MOI.VectorOfVariables, MOI.RotatedSecondOrderCone)] == 1