From 22684b935cecbf0d10c424b6d08000e34b2f0947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sat, 23 Jul 2022 09:22:36 +0200 Subject: [PATCH 01/20] Add bridge from hermitian PSD to PSD --- src/Bridges/Variable/Variable.jl | 2 + src/Bridges/Variable/bridges/hermitian.jl | 350 ++++++++++++++++++++++ src/Test/Test.jl | 2 + src/Test/test_conic.jl | 162 ++++++++++ src/Utilities/results.jl | 25 ++ src/sets.jl | 13 + test/Bridges/Variable/hermitian.jl | 58 ++++ 7 files changed, 612 insertions(+) create mode 100644 src/Bridges/Variable/bridges/hermitian.jl create mode 100644 test/Bridges/Variable/hermitian.jl diff --git a/src/Bridges/Variable/Variable.jl b/src/Bridges/Variable/Variable.jl index d91145788b..9db20bc32d 100644 --- a/src/Bridges/Variable/Variable.jl +++ b/src/Bridges/Variable/Variable.jl @@ -22,6 +22,7 @@ include("bridges/rsoc_soc.jl") include("bridges/soc_rsoc.jl") include("bridges/vectorize.jl") include("bridges/zeros.jl") +include("bridges/hermitian.jl") """ add_all_bridges(model, ::Type{T}) where {T} @@ -38,6 +39,7 @@ function add_all_bridges(model, ::Type{T}) where {T} MOI.Bridges.add_bridge(model, SOCtoRSOCBridge{T}) MOI.Bridges.add_bridge(model, RSOCtoSOCBridge{T}) MOI.Bridges.add_bridge(model, RSOCtoPSDBridge{T}) + MOI.Bridges.add_bridge(model, HermitianToSymmetricPSDBridge{T}) return end diff --git a/src/Bridges/Variable/bridges/hermitian.jl b/src/Bridges/Variable/bridges/hermitian.jl new file mode 100644 index 0000000000..07f16c5fa7 --- /dev/null +++ b/src/Bridges/Variable/bridges/hermitian.jl @@ -0,0 +1,350 @@ +# Copyright (c) 2017: Miles Lubin and contributors +# Copyright (c) 2017: Google Inc. +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + +const EQ{T} = MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}} + +""" + HermitianToSymmetricPSDBridge{T} <: Bridges.Variable.AbstractBridge + +`HermitianToSymmetricPSDBridge` implements the following reformulation: + +* Hermitian positive semidefinite `n x n` complex matrix to a symmetric + positive semidefinite `2n x 2n` real matrix satisfying equality constraints + described below. + +## Source node + +`HermitianToSymmetricPSDBridge` supports: + + * [`MOI.VectorOfVariables`](@ref) in [`MOI.HermitianPositiveSemidefiniteConeTriangle`](@ref) + +## Target node + +`HermitianToSymmetricPSDBridge` creates: + + * [`MOI.VectorOfVariables`](@ref) in [`MOI.PositiveSemidefiniteConeTriangle`](@ref) + * [`MOI.ScalarAffineFunction{T}`](@ref) in [`MOI.EqualTo{T}`](@ref) + +## Duality notes + +Suppose for simplicity that the elements of a `2n x 2n` matrix are ordered as: +``` +\\ 1 |\\ 2 + \\ | 3 + \\ |4_\\ + \\ 5 + \\ + \\ +``` +Let `H = HermitianToSymmetricPSDBridge(n)`, +`S = PositiveSemidefiniteConeTriangle(2n)` and `con_11_22` (resp. `con_12_21`, +`con12diag`) be the set of `2n x 2n` symmetric matrices such that the block `1` and `5` are equal (resp. `2` and `4` are opposite, `3` is zero). +We consider the cone `P = S ∩ con_11_22 ∩ con_12_21 ∩ con12diag`. +We have `P = A * H` where +``` + [I 0] + [0 I] +A = [0 0] + [0 -I] + [I 0] +``` +Therefore, `H* = A* * P*` where +``` + [I 0 0 0 I] +A* = [0 I 0 -I 0] +``` +Moreover, as `(S ∩ T)* = S* + T*` for cones `S` and `T`, we have +``` +P* = S* + con_11_22* + con_12_21* + con12diag* +``` +the dual vector of `P*` is the dual vector of `S*` for which we add in the corresponding +entries the dual of the three constraints, multiplied by the coefficients for the `EqualTo` constraints. +Note that these contributions cancel out when we multiply them by `A*`: +A* * (S* + con_11_22* + con_12_21* + con12diag*) = A* * S* +so we can just ignore them. +""" +struct HermitianToSymmetricPSDBridge{T} <: AbstractBridge + variables::Vector{MOI.VariableIndex} + psd_constraint::MOI.ConstraintIndex{ + MOI.VectorOfVariables, + MOI.PositiveSemidefiniteConeTriangle, + } + con_11_22::Vector{EQ{T}} + con12diag::Vector{EQ{T}} + con_12_21::Vector{EQ{T}} +end + +const HermitianToSymmetricPSD{T,OT<:MOI.ModelLike} = + SingleBridgeOptimizer{HermitianToSymmetricPSDBridge{T},OT} + +function bridge_constrained_variable( + ::Type{HermitianToSymmetricPSDBridge{T}}, + model::MOI.ModelLike, + set::MOI.HermitianPositiveSemidefiniteConeTriangle, +) where {T} + n = set.side_dimension + variables, psd_constraint = MOI.add_constrained_variables( + model, + MOI.PositiveSemidefiniteConeTriangle(2n), + ) + + k11 = 0 + k12 = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(n)) + k21 = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(2n)) + 1 + k22 = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(n)) + X11() = variables[k11] + X12() = variables[k12] + function X21(i, j) + I = j + J = n + i + k21 = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(J - 1)) + I + return variables[k21] + end + X22() = variables[k22] + con_11_22 = EQ{T}[] + con12diag = EQ{T}[] + con_12_21 = EQ{T}[] + for j in 1:n + k22 += n + for i in 1:j + k11 += 1 + k12 += 1 + k22 += 1 + push!( + con_11_22, + MOI.add_constraint( + model, + MOI.Utilities.operate(-, T, X11(), X22()), + MOI.EqualTo(zero(T)), + ), + ) + if i == j + push!( + con12diag, + MOI.add_constraint( + model, + convert(MOI.ScalarAffineFunction{T}, X12()), + MOI.EqualTo(zero(T)), + ), + ) + else + push!( + con_12_21, + MOI.add_constraint( + model, + MOI.Utilities.operate(+, T, X21(i, j), X12()), + MOI.EqualTo(zero(T)), + ), + ) + end + end + k12 += n + end + + return HermitianToSymmetricPSDBridge( + variables, + psd_constraint, + con_11_22, + con12diag, + con_12_21, + ) +end + +const HermitianToSymmetricPSD{T,OT<:MOI.ModelLike} = + SingleBridgeOptimizer{HermitianToSymmetricPSDBridge{T},OT} + +function supports_constrained_variable( + ::Type{<:HermitianToSymmetricPSDBridge}, + ::Type{MOI.HermitianPositiveSemidefiniteConeTriangle}, +) + return true +end +function MOI.Bridges.added_constrained_variable_types( + ::Type{<:HermitianToSymmetricPSDBridge}, +) + return [(MOI.PositiveSemidefiniteConeTriangle,)] +end +function MOI.Bridges.added_constraint_types( + ::Type{HermitianToSymmetricPSDBridge{T}}, +) where {T} + return [(MOI.ScalarAffineFunction{T}, MOI.EqualTo{T})] +end + +# Attributes, Bridge acting as a model +function MOI.get(bridge::HermitianToSymmetricPSDBridge, ::MOI.NumberOfVariables) + return length(bridge.variables) +end +function MOI.get( + bridge::HermitianToSymmetricPSDBridge, + ::MOI.ListOfVariableIndices, +) + return bridge.variables +end +function MOI.get( + bridge::HermitianToSymmetricPSDBridge, + ::MOI.NumberOfConstraints{ + MOI.VectorOfVariables, + MOI.PositiveSemidefiniteConeTriangle, + }, +) + return 1 +end +function MOI.get( + bridge::HermitianToSymmetricPSDBridge, + ::MOI.ListOfConstraintIndices{ + MOI.VectorOfVariables, + MOI.PositiveSemidefiniteConeTriangle, + }, +) + return [bridge.psd_constraint] +end +function MOI.get( + bridge::HermitianToSymmetricPSDBridge{T}, + ::MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}, +) where {T} + return length(bridge.con_11_22) + + length(bridge.con12diag) + + length(bridge.con_12_21) +end +function MOI.get( + bridge::HermitianToSymmetricPSDBridge{T}, + ::MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}, +) where {T} + return [bridge.con_11_22; bridge.con12diag; bridge.con_12_21] +end + +# References +function MOI.delete(model::MOI.ModelLike, bridge::HermitianToSymmetricPSDBridge) + for ci in bridge.con_11_22 + MOI.delete(model, ci) + end + for ci in bridge.con12diag + MOI.delete(model, ci) + end + for ci in bridge.con_12_21 + MOI.delete(model, ci) + end + return MOI.delete(model, bridge.variables) +end + +# Attributes, Bridge acting as a constraint + +function MOI.get( + model::MOI.ModelLike, + ::MOI.ConstraintSet, + bridge::HermitianToSymmetricPSDBridge, +) + return MOI.HermitianPositiveSemidefiniteConeTriangle( + length(bridge.con12diag), + ) +end + +function _matrix_indices(k) + # If `k` is a diagonal index, `s(k)` is odd and 1 + 8k is a perfect square. + n = 1 + 8k + s = isqrt(n) + if s^2 == n + j = div(s, 2) + else + # Otherwise, if it is after the diagonal index `k` but before the diagonal + # index `k'` with `s(k') = s(k) + 2`, we have `s(k) <= s < s(k) + 2`. + # By shifting by `+1` before `div`, we make sure to have the right column. + j = div(s + 1, 2) + end + i = k - MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(j - 1)) + return i, j +end + +function _variable_map(idx::MOI.Bridges.IndexInVector, n) + N = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(n)) + if idx.value <= N + return idx.value + else + i, j = _matrix_indices(idx.value - N) + return N + + j * n + + MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(j)) + + i + end +end +function _variable(bridge::HermitianToSymmetricPSDBridge, i::MOI.Bridges.IndexInVector) + return bridge.variables[_variable_map(i, length(bridge.con12diag))] +end + +function MOI.get( + model::MOI.ModelLike, + attr::MOI.ConstraintPrimal, + bridge::HermitianToSymmetricPSDBridge{T}, +) where {T} + values = MOI.get(model, attr, bridge.psd_constraint) + M = MOI.dimension(MOI.get(model, MOI.ConstraintSet(), bridge)) + n = length(bridge.con12diag) + return [values[_variable_map(MOI.Bridges.IndexInVector(i), n)] for i in 1:M] +end + +# See docstring of bridge for why we ignore the dual of the constraints +# `con_11_22`, `con_12_21` and `con12diag`. +function MOI.get( + model::MOI.ModelLike, + attr::MOI.ConstraintDual, + bridge::HermitianToSymmetricPSDBridge{T}, +) where {T} + dual = MOI.get(model, attr, bridge.psd_constraint) + M = MOI.dimension(MOI.get(model, MOI.ConstraintSet(), bridge)) + mapped = zeros(T, M) + n = length(bridge.con12diag) + N = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(n)) + k11 = 0 + k12 = N + k21 = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(2n)) + 1 + k22 = N + k = 0 + for j in 1:n + k21 -= n + 1 - j + k22 += n + for i in 1:j + k11 += 1 + k12 += 1 + k21 -= 1 + k22 += 1 + mapped[k11] += dual[k11] + mapped[k11] += dual[k22] + if i != j + k += 1 + mapped[N+k] += dual[k12] + mapped[N+k] -= dual[k21] + end + end + k12 += n + k21 -= n - j + end + return mapped +end + +function MOI.get( + model::MOI.ModelLike, + attr::MOI.VariablePrimal, + bridge::HermitianToSymmetricPSDBridge{T}, + i::MOI.Bridges.IndexInVector, +) where {T} + return value = MOI.get(model, attr, _variable(bridge, i)) +end + +function MOI.Bridges.bridged_function( + bridge::HermitianToSymmetricPSDBridge{T}, + i::MOI.Bridges.IndexInVector, +) where {T} + func = _variable(bridge, i) + return convert(MOI.ScalarAffineFunction{T}, func) +end +function unbridged_map( + bridge::HermitianToSymmetricPSDBridge{T}, + vi::MOI.VariableIndex, + i::MOI.Bridges.IndexInVector, +) where {T} + func = convert(MOI.ScalarAffineFunction{T}, vi) + return (_variable(bridge, i) => func,) +end diff --git a/src/Test/Test.jl b/src/Test/Test.jl index a632366b42..638820c887 100644 --- a/src/Test/Test.jl +++ b/src/Test/Test.jl @@ -6,6 +6,8 @@ module Test +import LinearAlgebra + using MathOptInterface const MOI = MathOptInterface const MOIU = MOI.Utilities diff --git a/src/Test/test_conic.jl b/src/Test/test_conic.jl index bee4e59eae..94c0d73b34 100644 --- a/src/Test/test_conic.jl +++ b/src/Test/test_conic.jl @@ -6831,3 +6831,165 @@ function setup_test( ) return end + +""" + test_conic_HermitianPositiveSemidefiniteConeTriangle_1(model::MOI.ModelLike, config::Config) + +Test the computation of the projection of a hermitian matrix to the cone +of hermitian positive semidefinite matrices. +The matrix is: +``` +[ 1 -1+im] [ 1-im ?] [√3 ] [ 1+im -1+√3] +[-1-im -1 ] = [-1+√3 ?] * [ -√3] * [ ? ? ] / (6 - 2√3) +``` +so it's projection to the Hermitian PSD cone is: +``` + [1-im] [ 1+im 1-√3] +√3 / (6 - 2√3) * [1-√3] +``` +which is: +``` + [1-im] [ 1+im 1-√3] +(1 + √3) / 4 * [1-√3] +``` +which is +``` +[(1+√3)/2 -1/2+im/2] +[-1/2+im/2 (-1+√3)/2] +``` +""" +function test_conic_HermitianPositiveSemidefiniteConeTriangle_1(optimizer::MOI.ModelLike, config::Config{T}) where {T} + atol = config.atol + rtol = config.rtol + + MOI.empty!(optimizer) + set = MOI.HermitianPositiveSemidefiniteConeTriangle(2) + x, cx = MOI.add_constrained_variables(optimizer, set) + x11 = x[1:3] + x12 = x[4] + t = MOI.add_variable(optimizer) + soc_set = MOI.SecondOrderCone(5) + con_soc = MOI.add_constraint( + optimizer, + MOI.Utilities.operate( + vcat, + T, + t, + x[1] - one(T), + √T(2) * (x[2] + one(T)), + x[3] + one(T), + √T(2) * (x[4] - one(T)), + ), + soc_set, + ) + MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MIN_SENSE) + MOI.set(optimizer, MOI.ObjectiveFunction{typeof(t)}(), t) + if _supports(config, MOI.optimize!) + MOI.optimize!(optimizer) + primal = [(T(1) + √T(3)) / T(2), -T(1) / T(2), (-T(1) + √T(3)) / T(2), T(1) / T(2)] + soc_primal = [ + primal[1] - one(T), + √T(2) * (primal[2] + one(T)), + primal[3] + one(T), + √T(2) * (primal[4] - one(T)), + ] + t_value = LinearAlgebra.norm(soc_primal) + soc_primal = [t_value; soc_primal] + dual = [(T(3) - √T(3)) / T(6), √T(3)/T(6), (T(3) + √T(3)) / T(6), -√T(3)/T(6)] + @test MOI.Utilities.set_dot(primal, dual, set) ≈ 0 atol = atol rtol = rtol + soc_dual = [one(T), -dual[1], -dual[2]*√T(2), -dual[3], -dual[4]*√T(2)] + @test MOI.Utilities.set_dot(soc_primal, soc_dual, soc_set) ≈ 0 atol = atol rtol = rtol + @test MOI.get(optimizer, MOI.VariablePrimal(), x) ≈ primal atol = atol rtol = + rtol + @test MOI.get(optimizer, MOI.VariablePrimal(), t) ≈ t_value atol = atol rtol = rtol + @test MOI.get(optimizer, MOI.ConstraintPrimal(), cx) ≈ primal atol = atol rtol = + rtol + @test MOI.get(optimizer, MOI.ConstraintPrimal(), con_soc) ≈ soc_primal atol = atol rtol = + rtol + if _supports(config, MOI.ConstraintDual) + @test MOI.get(optimizer, MOI.ConstraintDual(), cx) ≈ dual atol = atol rtol = + rtol + @test MOI.get(optimizer, MOI.ConstraintDual(), con_soc) ≈ soc_dual atol = atol rtol = + rtol + end + end +end + +function setup_test( + ::typeof(test_conic_HermitianPositiveSemidefiniteConeTriangle_1), + model::MOIU.MockOptimizer, + ::Config{T}, +) where {T} + primal = [(T(1) + √T(3)) / T(2), -T(1) / T(2), (-T(1) + √T(3)) / T(2), T(1) / T(2)] + soc_primal = [ + primal[1] - one(T), + √T(2) * (primal[2] + one(T)), + primal[3] + one(T), + √T(2) * (primal[4] - one(T)), + ] + t_value = LinearAlgebra.norm(soc_primal) + push!(primal, t_value) + soc_primal = [t_value; soc_primal] + dual = [(T(3) - √T(3)) / T(6), √T(3)/T(6), (T(3) + √T(3)) / T(6), -√T(3)/T(6)] + soc_dual = [one(T), -dual[1], -dual[2]*√T(2), -dual[3], -dual[4]*√T(2)] + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + primal, + (MOI.VectorAffineFunction{T}, MOI.SecondOrderCone) => [soc_dual], + ), + ) + return +end + +""" + test_conic_HermitianPositiveSemidefiniteConeTriangle_2(model::MOI.ModelLike, config::Config) + +Test adding [`MathOptInterface.VariableIndex`](@ref)-in-[`MathOptInterface.EqualTo](@ref)` +on [`MathOptInterface.HermitianPositiveSemidefiniteConeTriangle`](@ref) +variables. +``` +""" +function test_conic_HermitianPositiveSemidefiniteConeTriangle_2(optimizer::MOI.ModelLike, config::Config{T}) where {T} + atol = config.atol + rtol = config.rtol + + MOI.empty!(optimizer) + set = MOI.HermitianPositiveSemidefiniteConeTriangle(3) + x, cx = MOI.add_constrained_variables(optimizer, set) + primal = T[1, 0, 1, 0, 0, 0, -1, 0, 0] + MOI.add_constraints( + optimizer, + x, + MOI.EqualTo.(primal), + ) + if _supports(config, MOI.optimize!) + MOI.optimize!(optimizer) + @test MOI.get(optimizer, MOI.VariablePrimal(), x) ≈ primal atol = atol rtol = + rtol + @test MOI.get(optimizer, MOI.ConstraintPrimal(), cx) ≈ primal atol = atol rtol = + rtol + if _supports(config, MOI.ConstraintDual) + @test MOI.get(optimizer, MOI.ConstraintDual(), cx) ≈ zeros(9) atol = atol rtol = + rtol + end + end +end + +function setup_test( + ::typeof(test_conic_HermitianPositiveSemidefiniteConeTriangle_2), + model::MOIU.MockOptimizer, + ::Config{T}, +) where {T} + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + T[1, 0, 1, 0, 0, 0, -1, 0, 0], + (MOI.VectorOfVariables, MOI.HermitianPositiveSemidefiniteConeTriangle) => [zeros(T, 9)], + ), + ) + model.eval_variable_constraint_dual = false + return () -> model.eval_variable_constraint_dual = true +end diff --git a/src/Utilities/results.jl b/src/Utilities/results.jl index 10eb39b271..1646199381 100644 --- a/src/Utilities/results.jl +++ b/src/Utilities/results.jl @@ -546,6 +546,19 @@ function set_dot( return triangle_dot(x, y, MOI.side_dimension(set), 0) end +function MOI.Utilities.set_dot( + x::AbstractVector, + y::AbstractVector, + set::MOI.HermitianPositiveSemidefiniteConeTriangle, +) + sym = MOI.PositiveSemidefiniteConeTriangle(set.side_dimension) + result = set_dot(x, y, sym) + for k in (MOI.dimension(sym)+1):MOI.dimension(set) + result = MA.add_mul!!(result, 2, x[k], y[k]) + end + return result +end + function set_dot( x::AbstractVector, y::AbstractVector, @@ -597,6 +610,18 @@ function dot_coefficients( return b end +function dot_coefficients( + a::AbstractVector, + set::MOI.HermitianPositiveSemidefiniteConeTriangle, +) + sym = MOI.PositiveSemidefiniteConeTriangle(set.side_dimension) + b = dot_coefficients(a, sym) + for k in (MOI.dimension(sym)+1):MOI.dimension(set) + b[k] /= 2 + end + return b +end + function dot_coefficients(a::AbstractVector, set::MOI.RootDetConeTriangle) b = copy(a) triangle_coefficients!(b, set.side_dimension, 1) diff --git a/src/sets.jl b/src/sets.jl index 14a213006c..b7ff0b0a3f 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -795,6 +795,18 @@ function triangular_form(::Type{PositiveSemidefiniteConeSquare}) return PositiveSemidefiniteConeTriangle end +struct HermitianPositiveSemidefiniteConeTriangle <: AbstractVectorSet + side_dimension::Int +end + +function dimension(set::HermitianPositiveSemidefiniteConeTriangle) + return dimension( + PositiveSemidefiniteConeTriangle(set.side_dimension), + ) + dimension( + PositiveSemidefiniteConeTriangle(set.side_dimension - 1), + ) +end + """ LogDetConeTriangle(side_dimension) @@ -1559,6 +1571,7 @@ function Base.copy( NormNuclearCone, PositiveSemidefiniteConeTriangle, PositiveSemidefiniteConeSquare, + HermitianPositiveSemidefiniteConeTriangle, LogDetConeTriangle, LogDetConeSquare, RootDetConeTriangle, diff --git a/test/Bridges/Variable/hermitian.jl b/test/Bridges/Variable/hermitian.jl new file mode 100644 index 0000000000..1d70e7ac51 --- /dev/null +++ b/test/Bridges/Variable/hermitian.jl @@ -0,0 +1,58 @@ +# Copyright (c) 2017: Miles Lubin and contributors +# Copyright (c) 2017: Google Inc. +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + +module TestVariableHermitianToSymmetricPSD + +using LinearAlgebra, Test + +using MathOptInterface +const MOI = MathOptInterface + +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$(name)", "test_") + @testset "$(name)" begin + getfield(@__MODULE__, name)() + end + end + end + return +end + +include("../utilities.jl") + +function test_conic_HermitianPositiveSemidefiniteConeTriangle_1() + T = Float64 + mock = MOI.Utilities.MockOptimizer(MOI.Utilities.Model{T}()) + bridged_mock = MOI.Bridges.Variable.HermitianToSymmetricPSD{T}(mock) + primal = [(T(1) + √T(3)) / T(2), -T(1) / T(2), (-T(1) + √T(3)) / T(2), T(1) / T(2)] + soc_primal = [ + primal[1] - one(T), + √T(2) * (primal[2] + one(T)), + primal[3] + one(T), + √T(2) * (primal[4] - one(T)), + ] + t_value = norm(soc_primal) + real_primal = [primal[1:3]; zero(T); -primal[4]; primal[1]; primal[4]; zero(T); primal[2:3]; t_value] + dual = [(T(3) - √T(3)) / T(6), √T(3)/T(6), (T(3) + √T(3)) / T(6), -√T(3)/T(6)] + soc_dual = [one(T), -dual[1], -dual[2]*√T(2), -dual[3], -dual[4]*√T(2)] + mock.optimize! = + (mock::MOI.Utilities.MockOptimizer) -> MOI.Utilities.mock_optimize!( + mock, + real_primal, + (MOI.VectorAffineFunction{T}, MOI.SecondOrderCone) => [soc_dual], + (MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}) => [dual[1]/T(2); zero(T); dual[2]; -dual[2]; dual[3]/T(2); zero(T)], + ) + MOI.Test.test_conic_HermitianPositiveSemidefiniteConeTriangle_1( + bridged_mock, + MOI.Test.Config(), + ) + return +end + +end # module + +TestVariableHermitianToSymmetricPSD.runtests() From cc03b95246339127a6c3f252d3551b1b0aacca28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 24 Jul 2022 00:07:43 +0200 Subject: [PATCH 02/20] Update src/Bridges/Variable/bridges/hermitian.jl Co-authored-by: Oscar Dowson --- src/Bridges/Variable/bridges/hermitian.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridges/Variable/bridges/hermitian.jl b/src/Bridges/Variable/bridges/hermitian.jl index 07f16c5fa7..6d2c5c39f7 100644 --- a/src/Bridges/Variable/bridges/hermitian.jl +++ b/src/Bridges/Variable/bridges/hermitian.jl @@ -189,7 +189,7 @@ function MOI.get( MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeTriangle, }, -) +)::Int64 return 1 end function MOI.get( From f8ace542a4fc527c566b0edd6be7d9df8b6541ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 24 Jul 2022 00:07:49 +0200 Subject: [PATCH 03/20] Update src/Bridges/Variable/bridges/hermitian.jl Co-authored-by: Oscar Dowson --- src/Bridges/Variable/bridges/hermitian.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridges/Variable/bridges/hermitian.jl b/src/Bridges/Variable/bridges/hermitian.jl index 6d2c5c39f7..e189b8e22b 100644 --- a/src/Bridges/Variable/bridges/hermitian.jl +++ b/src/Bridges/Variable/bridges/hermitian.jl @@ -330,7 +330,7 @@ function MOI.get( bridge::HermitianToSymmetricPSDBridge{T}, i::MOI.Bridges.IndexInVector, ) where {T} - return value = MOI.get(model, attr, _variable(bridge, i)) + return MOI.get(model, attr, _variable(bridge, i)) end function MOI.Bridges.bridged_function( From 416300f497a77eceddf7f54e0a42141bf6672999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 24 Jul 2022 00:08:03 +0200 Subject: [PATCH 04/20] Update src/Test/test_conic.jl Co-authored-by: Oscar Dowson --- src/Test/test_conic.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Test/test_conic.jl b/src/Test/test_conic.jl index 94c0d73b34..0f9da0153b 100644 --- a/src/Test/test_conic.jl +++ b/src/Test/test_conic.jl @@ -6899,7 +6899,7 @@ function test_conic_HermitianPositiveSemidefiniteConeTriangle_1(optimizer::MOI.M @test MOI.Utilities.set_dot(primal, dual, set) ≈ 0 atol = atol rtol = rtol soc_dual = [one(T), -dual[1], -dual[2]*√T(2), -dual[3], -dual[4]*√T(2)] @test MOI.Utilities.set_dot(soc_primal, soc_dual, soc_set) ≈ 0 atol = atol rtol = rtol - @test MOI.get(optimizer, MOI.VariablePrimal(), x) ≈ primal atol = atol rtol = + @test ≈(MOI.get(optimizer, MOI.VariablePrimal(), x), primal, config) rtol @test MOI.get(optimizer, MOI.VariablePrimal(), t) ≈ t_value atol = atol rtol = rtol @test MOI.get(optimizer, MOI.ConstraintPrimal(), cx) ≈ primal atol = atol rtol = From bb47efc2027bd4824d26b6ea0297ef50f0229b5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 24 Jul 2022 00:08:13 +0200 Subject: [PATCH 05/20] Update src/Bridges/Variable/bridges/hermitian.jl Co-authored-by: Oscar Dowson --- src/Bridges/Variable/bridges/hermitian.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Bridges/Variable/bridges/hermitian.jl b/src/Bridges/Variable/bridges/hermitian.jl index e189b8e22b..2b2ff3e154 100644 --- a/src/Bridges/Variable/bridges/hermitian.jl +++ b/src/Bridges/Variable/bridges/hermitian.jl @@ -162,6 +162,7 @@ function supports_constrained_variable( ) return true end + function MOI.Bridges.added_constrained_variable_types( ::Type{<:HermitianToSymmetricPSDBridge}, ) From 0f1c1ae2cd31baa5a4a4434ce46ad4df3cadc163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 24 Jul 2022 00:08:23 +0200 Subject: [PATCH 06/20] Update src/Bridges/Variable/bridges/hermitian.jl Co-authored-by: Oscar Dowson --- src/Bridges/Variable/bridges/hermitian.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Bridges/Variable/bridges/hermitian.jl b/src/Bridges/Variable/bridges/hermitian.jl index 2b2ff3e154..8e6308aa18 100644 --- a/src/Bridges/Variable/bridges/hermitian.jl +++ b/src/Bridges/Variable/bridges/hermitian.jl @@ -228,7 +228,8 @@ function MOI.delete(model::MOI.ModelLike, bridge::HermitianToSymmetricPSDBridge) for ci in bridge.con_12_21 MOI.delete(model, ci) end - return MOI.delete(model, bridge.variables) + MOI.delete(model, bridge.variables) + return end # Attributes, Bridge acting as a constraint From 99bd26daca0c299ff90ad278189f16b85b48f5e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 24 Jul 2022 00:08:34 +0200 Subject: [PATCH 07/20] Update src/Test/test_conic.jl Co-authored-by: Oscar Dowson --- src/Test/test_conic.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Test/test_conic.jl b/src/Test/test_conic.jl index 0f9da0153b..b95f8d8fa1 100644 --- a/src/Test/test_conic.jl +++ b/src/Test/test_conic.jl @@ -6913,6 +6913,7 @@ function test_conic_HermitianPositiveSemidefiniteConeTriangle_1(optimizer::MOI.M rtol end end + return end function setup_test( From 8eb190befe713b2eff5092091eda821c8cc7c70b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 24 Jul 2022 00:08:45 +0200 Subject: [PATCH 08/20] Update src/Test/test_conic.jl Co-authored-by: Oscar Dowson --- src/Test/test_conic.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Test/test_conic.jl b/src/Test/test_conic.jl index b95f8d8fa1..3e9fc4f387 100644 --- a/src/Test/test_conic.jl +++ b/src/Test/test_conic.jl @@ -6862,7 +6862,6 @@ function test_conic_HermitianPositiveSemidefiniteConeTriangle_1(optimizer::MOI.M atol = config.atol rtol = config.rtol - MOI.empty!(optimizer) set = MOI.HermitianPositiveSemidefiniteConeTriangle(2) x, cx = MOI.add_constrained_variables(optimizer, set) x11 = x[1:3] From 38a06ab2ace5edaa3400defa7457a408384bf24c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 24 Jul 2022 00:19:12 +0200 Subject: [PATCH 09/20] Fix format --- src/Bridges/Variable/bridges/hermitian.jl | 5 +- src/Test/test_conic.jl | 90 +++++++++++++++-------- src/sets.jl | 7 +- test/Bridges/Variable/hermitian.jl | 39 ++++++++-- 4 files changed, 99 insertions(+), 42 deletions(-) diff --git a/src/Bridges/Variable/bridges/hermitian.jl b/src/Bridges/Variable/bridges/hermitian.jl index 8e6308aa18..97498ee94a 100644 --- a/src/Bridges/Variable/bridges/hermitian.jl +++ b/src/Bridges/Variable/bridges/hermitian.jl @@ -272,7 +272,10 @@ function _variable_map(idx::MOI.Bridges.IndexInVector, n) i end end -function _variable(bridge::HermitianToSymmetricPSDBridge, i::MOI.Bridges.IndexInVector) +function _variable( + bridge::HermitianToSymmetricPSDBridge, + i::MOI.Bridges.IndexInVector, +) return bridge.variables[_variable_map(i, length(bridge.con12diag))] end diff --git a/src/Test/test_conic.jl b/src/Test/test_conic.jl index 3e9fc4f387..ddfaded08b 100644 --- a/src/Test/test_conic.jl +++ b/src/Test/test_conic.jl @@ -6858,7 +6858,10 @@ which is [-1/2+im/2 (-1+√3)/2] ``` """ -function test_conic_HermitianPositiveSemidefiniteConeTriangle_1(optimizer::MOI.ModelLike, config::Config{T}) where {T} +function test_conic_HermitianPositiveSemidefiniteConeTriangle_1( + optimizer::MOI.ModelLike, + config::Config{T}, +) where {T} atol = config.atol rtol = config.rtol @@ -6885,7 +6888,12 @@ function test_conic_HermitianPositiveSemidefiniteConeTriangle_1(optimizer::MOI.M MOI.set(optimizer, MOI.ObjectiveFunction{typeof(t)}(), t) if _supports(config, MOI.optimize!) MOI.optimize!(optimizer) - primal = [(T(1) + √T(3)) / T(2), -T(1) / T(2), (-T(1) + √T(3)) / T(2), T(1) / T(2)] + primal = [ + (T(1) + √T(3)) / T(2), + -T(1) / T(2), + (-T(1) + √T(3)) / T(2), + T(1) / T(2), + ] soc_primal = [ primal[1] - one(T), √T(2) * (primal[2] + one(T)), @@ -6894,22 +6902,31 @@ function test_conic_HermitianPositiveSemidefiniteConeTriangle_1(optimizer::MOI.M ] t_value = LinearAlgebra.norm(soc_primal) soc_primal = [t_value; soc_primal] - dual = [(T(3) - √T(3)) / T(6), √T(3)/T(6), (T(3) + √T(3)) / T(6), -√T(3)/T(6)] - @test MOI.Utilities.set_dot(primal, dual, set) ≈ 0 atol = atol rtol = rtol - soc_dual = [one(T), -dual[1], -dual[2]*√T(2), -dual[3], -dual[4]*√T(2)] - @test MOI.Utilities.set_dot(soc_primal, soc_dual, soc_set) ≈ 0 atol = atol rtol = rtol - @test ≈(MOI.get(optimizer, MOI.VariablePrimal(), x), primal, config) - rtol - @test MOI.get(optimizer, MOI.VariablePrimal(), t) ≈ t_value atol = atol rtol = rtol - @test MOI.get(optimizer, MOI.ConstraintPrimal(), cx) ≈ primal atol = atol rtol = + dual = [ + (T(3) - √T(3)) / T(6), + √T(3) / T(6), + (T(3) + √T(3)) / T(6), + -√T(3) / T(6), + ] + @test MOI.Utilities.set_dot(primal, dual, set) ≈ 0 atol = atol rtol = rtol - @test MOI.get(optimizer, MOI.ConstraintPrimal(), con_soc) ≈ soc_primal atol = atol rtol = + soc_dual = + [one(T), -dual[1], -dual[2] * √T(2), -dual[3], -dual[4] * √T(2)] + @test MOI.Utilities.set_dot(soc_primal, soc_dual, soc_set) ≈ 0 atol = + atol rtol = rtol + @test ≈(MOI.get(optimizer, MOI.VariablePrimal(), x), primal, config) + rtol + @test MOI.get(optimizer, MOI.VariablePrimal(), t) ≈ t_value atol = atol rtol = rtol + @test MOI.get(optimizer, MOI.ConstraintPrimal(), cx) ≈ primal atol = + atol rtol = rtol + @test MOI.get(optimizer, MOI.ConstraintPrimal(), con_soc) ≈ soc_primal atol = + atol rtol = rtol if _supports(config, MOI.ConstraintDual) - @test MOI.get(optimizer, MOI.ConstraintDual(), cx) ≈ dual atol = atol rtol = - rtol - @test MOI.get(optimizer, MOI.ConstraintDual(), con_soc) ≈ soc_dual atol = atol rtol = - rtol + @test MOI.get(optimizer, MOI.ConstraintDual(), cx) ≈ dual atol = + atol rtol = rtol + @test MOI.get(optimizer, MOI.ConstraintDual(), con_soc) ≈ soc_dual atol = + atol rtol = rtol end end return @@ -6920,7 +6937,12 @@ function setup_test( model::MOIU.MockOptimizer, ::Config{T}, ) where {T} - primal = [(T(1) + √T(3)) / T(2), -T(1) / T(2), (-T(1) + √T(3)) / T(2), T(1) / T(2)] + primal = [ + (T(1) + √T(3)) / T(2), + -T(1) / T(2), + (-T(1) + √T(3)) / T(2), + T(1) / T(2), + ] soc_primal = [ primal[1] - one(T), √T(2) * (primal[2] + one(T)), @@ -6930,14 +6952,20 @@ function setup_test( t_value = LinearAlgebra.norm(soc_primal) push!(primal, t_value) soc_primal = [t_value; soc_primal] - dual = [(T(3) - √T(3)) / T(6), √T(3)/T(6), (T(3) + √T(3)) / T(6), -√T(3)/T(6)] - soc_dual = [one(T), -dual[1], -dual[2]*√T(2), -dual[3], -dual[4]*√T(2)] + dual = [ + (T(3) - √T(3)) / T(6), + √T(3) / T(6), + (T(3) + √T(3)) / T(6), + -√T(3) / T(6), + ] + soc_dual = [one(T), -dual[1], -dual[2] * √T(2), -dual[3], -dual[4] * √T(2)] MOIU.set_mock_optimize!( model, (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( mock, primal, - (MOI.VectorAffineFunction{T}, MOI.SecondOrderCone) => [soc_dual], + (MOI.VectorAffineFunction{T}, MOI.SecondOrderCone) => + [soc_dual], ), ) return @@ -6951,7 +6979,10 @@ on [`MathOptInterface.HermitianPositiveSemidefiniteConeTriangle`](@ref) variables. ``` """ -function test_conic_HermitianPositiveSemidefiniteConeTriangle_2(optimizer::MOI.ModelLike, config::Config{T}) where {T} +function test_conic_HermitianPositiveSemidefiniteConeTriangle_2( + optimizer::MOI.ModelLike, + config::Config{T}, +) where {T} atol = config.atol rtol = config.rtol @@ -6959,20 +6990,16 @@ function test_conic_HermitianPositiveSemidefiniteConeTriangle_2(optimizer::MOI.M set = MOI.HermitianPositiveSemidefiniteConeTriangle(3) x, cx = MOI.add_constrained_variables(optimizer, set) primal = T[1, 0, 1, 0, 0, 0, -1, 0, 0] - MOI.add_constraints( - optimizer, - x, - MOI.EqualTo.(primal), - ) + MOI.add_constraints(optimizer, x, MOI.EqualTo.(primal)) if _supports(config, MOI.optimize!) MOI.optimize!(optimizer) @test MOI.get(optimizer, MOI.VariablePrimal(), x) ≈ primal atol = atol rtol = rtol - @test MOI.get(optimizer, MOI.ConstraintPrimal(), cx) ≈ primal atol = atol rtol = - rtol + @test MOI.get(optimizer, MOI.ConstraintPrimal(), cx) ≈ primal atol = + atol rtol = rtol if _supports(config, MOI.ConstraintDual) - @test MOI.get(optimizer, MOI.ConstraintDual(), cx) ≈ zeros(9) atol = atol rtol = - rtol + @test MOI.get(optimizer, MOI.ConstraintDual(), cx) ≈ zeros(9) atol = + atol rtol = rtol end end end @@ -6987,7 +7014,10 @@ function setup_test( (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( mock, T[1, 0, 1, 0, 0, 0, -1, 0, 0], - (MOI.VectorOfVariables, MOI.HermitianPositiveSemidefiniteConeTriangle) => [zeros(T, 9)], + ( + MOI.VectorOfVariables, + MOI.HermitianPositiveSemidefiniteConeTriangle, + ) => [zeros(T, 9)], ), ) model.eval_variable_constraint_dual = false diff --git a/src/sets.jl b/src/sets.jl index b7ff0b0a3f..33a5a2f1d0 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -800,11 +800,8 @@ struct HermitianPositiveSemidefiniteConeTriangle <: AbstractVectorSet end function dimension(set::HermitianPositiveSemidefiniteConeTriangle) - return dimension( - PositiveSemidefiniteConeTriangle(set.side_dimension), - ) + dimension( - PositiveSemidefiniteConeTriangle(set.side_dimension - 1), - ) + return dimension(PositiveSemidefiniteConeTriangle(set.side_dimension)) + + dimension(PositiveSemidefiniteConeTriangle(set.side_dimension - 1)) end """ diff --git a/test/Bridges/Variable/hermitian.jl b/test/Bridges/Variable/hermitian.jl index 1d70e7ac51..6aa63f761c 100644 --- a/test/Bridges/Variable/hermitian.jl +++ b/test/Bridges/Variable/hermitian.jl @@ -28,7 +28,12 @@ function test_conic_HermitianPositiveSemidefiniteConeTriangle_1() T = Float64 mock = MOI.Utilities.MockOptimizer(MOI.Utilities.Model{T}()) bridged_mock = MOI.Bridges.Variable.HermitianToSymmetricPSD{T}(mock) - primal = [(T(1) + √T(3)) / T(2), -T(1) / T(2), (-T(1) + √T(3)) / T(2), T(1) / T(2)] + primal = [ + (T(1) + √T(3)) / T(2), + -T(1) / T(2), + (-T(1) + √T(3)) / T(2), + T(1) / T(2), + ] soc_primal = [ primal[1] - one(T), √T(2) * (primal[2] + one(T)), @@ -36,15 +41,37 @@ function test_conic_HermitianPositiveSemidefiniteConeTriangle_1() √T(2) * (primal[4] - one(T)), ] t_value = norm(soc_primal) - real_primal = [primal[1:3]; zero(T); -primal[4]; primal[1]; primal[4]; zero(T); primal[2:3]; t_value] - dual = [(T(3) - √T(3)) / T(6), √T(3)/T(6), (T(3) + √T(3)) / T(6), -√T(3)/T(6)] - soc_dual = [one(T), -dual[1], -dual[2]*√T(2), -dual[3], -dual[4]*√T(2)] + real_primal = [ + primal[1:3] + zero(T) + -primal[4] + primal[1] + primal[4] + zero(T) + primal[2:3] + t_value + ] + dual = [ + (T(3) - √T(3)) / T(6), + √T(3) / T(6), + (T(3) + √T(3)) / T(6), + -√T(3) / T(6), + ] + soc_dual = [one(T), -dual[1], -dual[2] * √T(2), -dual[3], -dual[4] * √T(2)] mock.optimize! = (mock::MOI.Utilities.MockOptimizer) -> MOI.Utilities.mock_optimize!( mock, real_primal, - (MOI.VectorAffineFunction{T}, MOI.SecondOrderCone) => [soc_dual], - (MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}) => [dual[1]/T(2); zero(T); dual[2]; -dual[2]; dual[3]/T(2); zero(T)], + (MOI.VectorAffineFunction{T}, MOI.SecondOrderCone) => + [soc_dual], + (MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}) => [ + dual[1] / T(2) + zero(T) + dual[2] + -dual[2] + dual[3] / T(2) + zero(T) + ], ) MOI.Test.test_conic_HermitianPositiveSemidefiniteConeTriangle_1( bridged_mock, From 08102a21cbe3ad1af9340466a9ebf5491f57cf9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 24 Jul 2022 00:22:37 +0200 Subject: [PATCH 10/20] Address review comments --- .../src/submodules/Bridges/list_of_bridges.md | 1 + src/Bridges/Variable/bridges/hermitian.jl | 26 +++++++++++-------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/docs/src/submodules/Bridges/list_of_bridges.md b/docs/src/submodules/Bridges/list_of_bridges.md index 26b24453eb..168951e672 100644 --- a/docs/src/submodules/Bridges/list_of_bridges.md +++ b/docs/src/submodules/Bridges/list_of_bridges.md @@ -84,4 +84,5 @@ Bridges.Variable.RSOCtoSOCBridge Bridges.Variable.SOCtoRSOCBridge Bridges.Variable.VectorizeBridge Bridges.Variable.ZerosBridge +Bridges.Variable.HermitianToSymmetricPSDBridge ``` diff --git a/src/Bridges/Variable/bridges/hermitian.jl b/src/Bridges/Variable/bridges/hermitian.jl index 97498ee94a..1ace6953a0 100644 --- a/src/Bridges/Variable/bridges/hermitian.jl +++ b/src/Bridges/Variable/bridges/hermitian.jl @@ -4,8 +4,6 @@ # Use of this source code is governed by an MIT-style license that can be found # in the LICENSE.md file or at https://opensource.org/licenses/MIT. -const EQ{T} = MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}} - """ HermitianToSymmetricPSDBridge{T} <: Bridges.Variable.AbstractBridge @@ -72,9 +70,15 @@ struct HermitianToSymmetricPSDBridge{T} <: AbstractBridge MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeTriangle, } - con_11_22::Vector{EQ{T}} - con12diag::Vector{EQ{T}} - con_12_21::Vector{EQ{T}} + con_11_22::Vector{ + MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}, + } + con12diag::Vector{ + MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}, + } + con_12_21::Vector{ + MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}, + } end const HermitianToSymmetricPSD{T,OT<:MOI.ModelLike} = @@ -104,9 +108,12 @@ function bridge_constrained_variable( return variables[k21] end X22() = variables[k22] - con_11_22 = EQ{T}[] - con12diag = EQ{T}[] - con_12_21 = EQ{T}[] + con_11_22 = + MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}[] + con12diag = + MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}[] + con_12_21 = + MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}[] for j in 1:n k22 += n for i in 1:j @@ -153,9 +160,6 @@ function bridge_constrained_variable( ) end -const HermitianToSymmetricPSD{T,OT<:MOI.ModelLike} = - SingleBridgeOptimizer{HermitianToSymmetricPSDBridge{T},OT} - function supports_constrained_variable( ::Type{<:HermitianToSymmetricPSDBridge}, ::Type{MOI.HermitianPositiveSemidefiniteConeTriangle}, From 207a1a6e4eddb729ed4d0c5be83ea2c5148bb21d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 24 Jul 2022 00:30:05 +0200 Subject: [PATCH 11/20] Add docstring --- docs/src/reference/standard_form.md | 1 + src/sets.jl | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/docs/src/reference/standard_form.md b/docs/src/reference/standard_form.md index 041138dec7..3a26183f33 100644 --- a/docs/src/reference/standard_form.md +++ b/docs/src/reference/standard_form.md @@ -141,6 +141,7 @@ List of recognized matrix sets. ```@docs PositiveSemidefiniteConeTriangle PositiveSemidefiniteConeSquare +HermitianPositiveSemidefiniteConeTriangle LogDetConeTriangle LogDetConeSquare RootDetConeTriangle diff --git a/src/sets.jl b/src/sets.jl index 33a5a2f1d0..91385532f6 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -795,6 +795,33 @@ function triangular_form(::Type{PositiveSemidefiniteConeSquare}) return PositiveSemidefiniteConeTriangle end +""" + HermitianPositiveSemidefiniteConeTriangle(side_dimension) <: AbstractVectorSet + +The (vectorized) cone of hermitian positive semidefinite matrices, with +`side_dimension` rows and columns. + +As the matrix is hermitian, the diagonal is real and the lower triangular +entries are obtained as the conjugate of corresponding upper triangular entries. +The vectorized form starts with real part of the entries of the upper-right +triangular part of the matrix given column by column as explained in +[`AbstractSymmetricMatrixSetSquare`](@ref). +It is then followed by the imaginary part of the off-diagonal entries of the +upper-right triangular part given column by column. + + +### Examples + +The matrix +```math +\\begin{bmatrix} + 1 & 2 + 7im & 4 + 8im\\\\ + 2 - 7im & 3 & 5 + 9im\\\\ + 4 - 8im & 5 - 9im & 6 +\\end{bmatrix} +``` +has [`side_dimension`](@ref) 3 and vectorization ``(1, 2, 3, 4, 5, 6, 7, 8, 9)``. +""" struct HermitianPositiveSemidefiniteConeTriangle <: AbstractVectorSet side_dimension::Int end From f2247ab61d5b8b792b51aa2b659679291e9b7317 Mon Sep 17 00:00:00 2001 From: odow Date: Sun, 24 Jul 2022 17:14:13 +1200 Subject: [PATCH 12/20] Add bridge tests --- src/Bridges/Variable/bridges/hermitian.jl | 77 ++++++++++++----------- src/sets.jl | 3 +- test/Bridges/Variable/hermitian.jl | 27 ++++++++ 3 files changed, 70 insertions(+), 37 deletions(-) diff --git a/src/Bridges/Variable/bridges/hermitian.jl b/src/Bridges/Variable/bridges/hermitian.jl index 1ace6953a0..fcc25dda08 100644 --- a/src/Bridges/Variable/bridges/hermitian.jl +++ b/src/Bridges/Variable/bridges/hermitian.jl @@ -9,22 +9,23 @@ `HermitianToSymmetricPSDBridge` implements the following reformulation: -* Hermitian positive semidefinite `n x n` complex matrix to a symmetric - positive semidefinite `2n x 2n` real matrix satisfying equality constraints - described below. + * Hermitian positive semidefinite `n x n` complex matrix to a symmetric + positive semidefinite `2n x 2n` real matrix satisfying equality constraints + described below. ## Source node `HermitianToSymmetricPSDBridge` supports: - * [`MOI.VectorOfVariables`](@ref) in [`MOI.HermitianPositiveSemidefiniteConeTriangle`](@ref) + * [`MOI.VectorOfVariables`](@ref) in + [`MOI.HermitianPositiveSemidefiniteConeTriangle`](@ref) ## Target node `HermitianToSymmetricPSDBridge` creates: - * [`MOI.VectorOfVariables`](@ref) in [`MOI.PositiveSemidefiniteConeTriangle`](@ref) - * [`MOI.ScalarAffineFunction{T}`](@ref) in [`MOI.EqualTo{T}`](@ref) + * [`MOI.VectorOfVariables`](@ref) in [`MOI.PositiveSemidefiniteConeTriangle`](@ref) + * [`MOI.ScalarAffineFunction{T}`](@ref) in [`MOI.EqualTo{T}`](@ref) ## Duality notes @@ -39,9 +40,11 @@ Suppose for simplicity that the elements of a `2n x 2n` matrix are ordered as: ``` Let `H = HermitianToSymmetricPSDBridge(n)`, `S = PositiveSemidefiniteConeTriangle(2n)` and `con_11_22` (resp. `con_12_21`, -`con12diag`) be the set of `2n x 2n` symmetric matrices such that the block `1` and `5` are equal (resp. `2` and `4` are opposite, `3` is zero). -We consider the cone `P = S ∩ con_11_22 ∩ con_12_21 ∩ con12diag`. -We have `P = A * H` where +`con12diag`) be the set of `2n x 2n` symmetric matrices such that the block `1` +and `5` are equal (resp. `2` and `4` are opposite, `3` is zero). + +We consider the cone `P = S ∩ con_11_22 ∩ con_12_21 ∩ con12diag`. We have +`P = A * H` where ``` [I 0] [0 I] @@ -58,11 +61,13 @@ Moreover, as `(S ∩ T)* = S* + T*` for cones `S` and `T`, we have ``` P* = S* + con_11_22* + con_12_21* + con12diag* ``` -the dual vector of `P*` is the dual vector of `S*` for which we add in the corresponding -entries the dual of the three constraints, multiplied by the coefficients for the `EqualTo` constraints. +the dual vector of `P*` is the dual vector of `S*` for which we add in the +corresponding entries the dual of the three constraints, multiplied by the +coefficients for the `EqualTo` constraints. + Note that these contributions cancel out when we multiply them by `A*`: -A* * (S* + con_11_22* + con_12_21* + con12diag*) = A* * S* -so we can just ignore them. +A* * (S* + con_11_22* + con_12_21* + con12diag*) = A* * S* so we can just ignore +them. """ struct HermitianToSymmetricPSDBridge{T} <: AbstractBridge variables::Vector{MOI.VariableIndex} @@ -94,7 +99,6 @@ function bridge_constrained_variable( model, MOI.PositiveSemidefiniteConeTriangle(2n), ) - k11 = 0 k12 = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(n)) k21 = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(2n)) + 1 @@ -150,7 +154,6 @@ function bridge_constrained_variable( end k12 += n end - return HermitianToSymmetricPSDBridge( variables, psd_constraint, @@ -170,26 +173,28 @@ end function MOI.Bridges.added_constrained_variable_types( ::Type{<:HermitianToSymmetricPSDBridge}, ) - return [(MOI.PositiveSemidefiniteConeTriangle,)] + return Tuple{Type}[(MOI.PositiveSemidefiniteConeTriangle,)] end + function MOI.Bridges.added_constraint_types( ::Type{HermitianToSymmetricPSDBridge{T}}, ) where {T} - return [(MOI.ScalarAffineFunction{T}, MOI.EqualTo{T})] + return Tuple{Type,Type}[(MOI.ScalarAffineFunction{T}, MOI.EqualTo{T})] end -# Attributes, Bridge acting as a model function MOI.get(bridge::HermitianToSymmetricPSDBridge, ::MOI.NumberOfVariables) return length(bridge.variables) end + function MOI.get( bridge::HermitianToSymmetricPSDBridge, ::MOI.ListOfVariableIndices, ) - return bridge.variables + return copy(bridge.variables) end + function MOI.get( - bridge::HermitianToSymmetricPSDBridge, + ::HermitianToSymmetricPSDBridge, ::MOI.NumberOfConstraints{ MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeTriangle, @@ -197,6 +202,7 @@ function MOI.get( )::Int64 return 1 end + function MOI.get( bridge::HermitianToSymmetricPSDBridge, ::MOI.ListOfConstraintIndices{ @@ -206,6 +212,7 @@ function MOI.get( ) return [bridge.psd_constraint] end + function MOI.get( bridge::HermitianToSymmetricPSDBridge{T}, ::MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}, @@ -214,6 +221,7 @@ function MOI.get( length(bridge.con12diag) + length(bridge.con_12_21) end + function MOI.get( bridge::HermitianToSymmetricPSDBridge{T}, ::MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}, @@ -221,7 +229,6 @@ function MOI.get( return [bridge.con_11_22; bridge.con12diag; bridge.con_12_21] end -# References function MOI.delete(model::MOI.ModelLike, bridge::HermitianToSymmetricPSDBridge) for ci in bridge.con_11_22 MOI.delete(model, ci) @@ -236,16 +243,13 @@ function MOI.delete(model::MOI.ModelLike, bridge::HermitianToSymmetricPSDBridge) return end -# Attributes, Bridge acting as a constraint - function MOI.get( - model::MOI.ModelLike, + ::MOI.ModelLike, ::MOI.ConstraintSet, bridge::HermitianToSymmetricPSDBridge, ) - return MOI.HermitianPositiveSemidefiniteConeTriangle( - length(bridge.con12diag), - ) + dimension = length(bridge.con12diag) + return MOI.HermitianPositiveSemidefiniteConeTriangle(dimension) end function _matrix_indices(k) @@ -255,9 +259,11 @@ function _matrix_indices(k) if s^2 == n j = div(s, 2) else - # Otherwise, if it is after the diagonal index `k` but before the diagonal - # index `k'` with `s(k') = s(k) + 2`, we have `s(k) <= s < s(k) + 2`. - # By shifting by `+1` before `div`, we make sure to have the right column. + # Otherwise, if it is after the diagonal index `k` but before the + # diagonal index `k'` with `s(k') = s(k) + 2`, we have + # `s(k) <= s < s(k) + 2`. + # By shifting by `+1` before `div`, we make sure to have the right + # column. j = div(s + 1, 2) end i = k - MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(j - 1)) @@ -268,14 +274,12 @@ function _variable_map(idx::MOI.Bridges.IndexInVector, n) N = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(n)) if idx.value <= N return idx.value - else - i, j = _matrix_indices(idx.value - N) - return N + - j * n + - MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(j)) + - i end + i, j = _matrix_indices(idx.value - N) + d = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(j)) + return N + j * n + d + i end + function _variable( bridge::HermitianToSymmetricPSDBridge, i::MOI.Bridges.IndexInVector, @@ -349,6 +353,7 @@ function MOI.Bridges.bridged_function( func = _variable(bridge, i) return convert(MOI.ScalarAffineFunction{T}, func) end + function unbridged_map( bridge::HermitianToSymmetricPSDBridge{T}, vi::MOI.VariableIndex, diff --git a/src/sets.jl b/src/sets.jl index 91385532f6..a1990dcc7f 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -803,13 +803,14 @@ The (vectorized) cone of hermitian positive semidefinite matrices, with As the matrix is hermitian, the diagonal is real and the lower triangular entries are obtained as the conjugate of corresponding upper triangular entries. + The vectorized form starts with real part of the entries of the upper-right triangular part of the matrix given column by column as explained in [`AbstractSymmetricMatrixSetSquare`](@ref). + It is then followed by the imaginary part of the off-diagonal entries of the upper-right triangular part given column by column. - ### Examples The matrix diff --git a/test/Bridges/Variable/hermitian.jl b/test/Bridges/Variable/hermitian.jl index 6aa63f761c..2d35a27dad 100644 --- a/test/Bridges/Variable/hermitian.jl +++ b/test/Bridges/Variable/hermitian.jl @@ -80,6 +80,33 @@ function test_conic_HermitianPositiveSemidefiniteConeTriangle_1() return end +function test_runtests() + MOI.Bridges.runtests( + MOI.Bridges.Variable.HermitianToSymmetricPSDBridge, + """ + constrainedvariable: [r11, r12, r22, c12] in HermitianPositiveSemidefiniteConeTriangle(2) + 1.0 * r11 >= 1.0 + 1.0 * r12 >= 2.0 + 1.0 * r22 >= 3.0 + 1.0 * c12 >= 4.0 + """, + """ + constrainedvariable: [v11, v12, v22, v13, v23, v33, v14, v24, v34, v44] in PositiveSemidefiniteConeTriangle(4) + 1.0 * v11 >= 1.0 + 1.0 * v12 >= 2.0 + 1.0 * v22 >= 3.0 + 1.0 * v14 >= 4.0 + 1.0 * v11 + -1.0 * v33 == 0.0 + 1.0 * v13 == 0.0 + 1.0 * v12 + -1.0 * v34 == 0.0 + 1.0 * v23 + 1.0 * v14 == 0.0 + 1.0 * v22 + -1.0 * v44 == 0.0 + 1.0 * v24 == 0.0 + """, + ) + return +end + end # module TestVariableHermitianToSymmetricPSD.runtests() From 9e17e1ec5da27c7579a269e5628602029bee07f8 Mon Sep 17 00:00:00 2001 From: odow Date: Sun, 24 Jul 2022 17:23:21 +1200 Subject: [PATCH 13/20] Tidy new tests --- src/Test/test_conic.jl | 64 +++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/Test/test_conic.jl b/src/Test/test_conic.jl index ddfaded08b..96b191024b 100644 --- a/src/Test/test_conic.jl +++ b/src/Test/test_conic.jl @@ -6833,7 +6833,10 @@ function setup_test( end """ - test_conic_HermitianPositiveSemidefiniteConeTriangle_1(model::MOI.ModelLike, config::Config) + test_conic_HermitianPositiveSemidefiniteConeTriangle_1( + model::MOI.ModelLike, + config::Config, + ) Test the computation of the projection of a hermitian matrix to the cone of hermitian positive semidefinite matrices. @@ -6862,13 +6865,8 @@ function test_conic_HermitianPositiveSemidefiniteConeTriangle_1( optimizer::MOI.ModelLike, config::Config{T}, ) where {T} - atol = config.atol - rtol = config.rtol - set = MOI.HermitianPositiveSemidefiniteConeTriangle(2) x, cx = MOI.add_constrained_variables(optimizer, set) - x11 = x[1:3] - x12 = x[4] t = MOI.add_variable(optimizer) soc_set = MOI.SecondOrderCone(5) con_soc = MOI.add_constraint( @@ -6908,25 +6906,29 @@ function test_conic_HermitianPositiveSemidefiniteConeTriangle_1( (T(3) + √T(3)) / T(6), -√T(3) / T(6), ] - @test MOI.Utilities.set_dot(primal, dual, set) ≈ 0 atol = atol rtol = - rtol + @test ≈(MOI.Utilities.set_dot(primal, dual, set), T(0), config) soc_dual = [one(T), -dual[1], -dual[2] * √T(2), -dual[3], -dual[4] * √T(2)] - @test MOI.Utilities.set_dot(soc_primal, soc_dual, soc_set) ≈ 0 atol = - atol rtol = rtol + @test ≈( + MOI.Utilities.set_dot(soc_primal, soc_dual, soc_set), + T(0), + config, + ) @test ≈(MOI.get(optimizer, MOI.VariablePrimal(), x), primal, config) - rtol - @test MOI.get(optimizer, MOI.VariablePrimal(), t) ≈ t_value atol = atol rtol = - rtol - @test MOI.get(optimizer, MOI.ConstraintPrimal(), cx) ≈ primal atol = - atol rtol = rtol - @test MOI.get(optimizer, MOI.ConstraintPrimal(), con_soc) ≈ soc_primal atol = - atol rtol = rtol + @test ≈(MOI.get(optimizer, MOI.VariablePrimal(), t), t_value, config) + @test ≈(MOI.get(optimizer, MOI.ConstraintPrimal(), cx), primal, config) + @test ≈( + MOI.get(optimizer, MOI.ConstraintPrimal(), con_soc), + soc_primal, + config, + ) if _supports(config, MOI.ConstraintDual) - @test MOI.get(optimizer, MOI.ConstraintDual(), cx) ≈ dual atol = - atol rtol = rtol - @test MOI.get(optimizer, MOI.ConstraintDual(), con_soc) ≈ soc_dual atol = - atol rtol = rtol + @test ≈(MOI.get(optimizer, MOI.ConstraintDual(), cx), dual, config) + @test ≈( + MOI.get(optimizer, MOI.ConstraintDual(), con_soc), + soc_dual, + config, + ) end end return @@ -6972,7 +6974,10 @@ function setup_test( end """ - test_conic_HermitianPositiveSemidefiniteConeTriangle_2(model::MOI.ModelLike, config::Config) + test_conic_HermitianPositiveSemidefiniteConeTriangle_2( + model::MOI.ModelLike, + config::Config, + ) Test adding [`MathOptInterface.VariableIndex`](@ref)-in-[`MathOptInterface.EqualTo](@ref)` on [`MathOptInterface.HermitianPositiveSemidefiniteConeTriangle`](@ref) @@ -6983,25 +6988,20 @@ function test_conic_HermitianPositiveSemidefiniteConeTriangle_2( optimizer::MOI.ModelLike, config::Config{T}, ) where {T} - atol = config.atol - rtol = config.rtol - - MOI.empty!(optimizer) set = MOI.HermitianPositiveSemidefiniteConeTriangle(3) x, cx = MOI.add_constrained_variables(optimizer, set) primal = T[1, 0, 1, 0, 0, 0, -1, 0, 0] MOI.add_constraints(optimizer, x, MOI.EqualTo.(primal)) if _supports(config, MOI.optimize!) MOI.optimize!(optimizer) - @test MOI.get(optimizer, MOI.VariablePrimal(), x) ≈ primal atol = atol rtol = - rtol - @test MOI.get(optimizer, MOI.ConstraintPrimal(), cx) ≈ primal atol = - atol rtol = rtol + @test ≈(MOI.get(optimizer, MOI.VariablePrimal(), x), primal, config) + @test ≈(MOI.get(optimizer, MOI.ConstraintPrimal(), cx), primal, config) if _supports(config, MOI.ConstraintDual) - @test MOI.get(optimizer, MOI.ConstraintDual(), cx) ≈ zeros(9) atol = - atol rtol = rtol + dual = zeros(T, 9) + @test ≈(MOI.get(optimizer, MOI.ConstraintDual(), cx), dual, config) end end + return end function setup_test( From 5b4d10748d6e17b9b73a2298e8b4958044562dff Mon Sep 17 00:00:00 2001 From: odow Date: Sun, 24 Jul 2022 17:25:48 +1200 Subject: [PATCH 14/20] Use import LinearAlgebra in tests --- test/Bridges/Variable/hermitian.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/Bridges/Variable/hermitian.jl b/test/Bridges/Variable/hermitian.jl index 2d35a27dad..8820f96cf0 100644 --- a/test/Bridges/Variable/hermitian.jl +++ b/test/Bridges/Variable/hermitian.jl @@ -6,11 +6,13 @@ module TestVariableHermitianToSymmetricPSD -using LinearAlgebra, Test +using Test using MathOptInterface const MOI = MathOptInterface +import LinearAlgebra + function runtests() for name in names(@__MODULE__; all = true) if startswith("$(name)", "test_") @@ -40,7 +42,7 @@ function test_conic_HermitianPositiveSemidefiniteConeTriangle_1() primal[3] + one(T), √T(2) * (primal[4] - one(T)), ] - t_value = norm(soc_primal) + t_value = LinearAlgebra.norm(soc_primal) real_primal = [ primal[1:3] zero(T) From da645ae6ee223c2cd8ad14f3cc0ab8d38887d8d9 Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 25 Jul 2022 11:29:19 +1200 Subject: [PATCH 15/20] Simplifications --- docs/src/manual/standard_form.md | 4 +- src/Bridges/Variable/bridges/hermitian.jl | 200 ++++++++-------------- src/Test/test_conic.jl | 4 +- src/sets.jl | 27 +-- 4 files changed, 87 insertions(+), 148 deletions(-) diff --git a/docs/src/manual/standard_form.md b/docs/src/manual/standard_form.md index f72a40c659..0111ec8da9 100644 --- a/docs/src/manual/standard_form.md +++ b/docs/src/manual/standard_form.md @@ -93,7 +93,9 @@ The matrix-valued set types implemented in MathOptInterface.jl are: | [`LogDetConeTriangle(d)`](@ref MathOptInterface.LogDetConeTriangle) | ``\{ (t,u,X) \in \mathbb{R}^{2+d(1+d)/2} : t \le u\log(\det(X/u)), X \mbox{ is the upper triangle of a PSD matrix}, u > 0 \}`` | | [`LogDetConeSquare(d)`](@ref MathOptInterface.LogDetConeSquare) | ``\{ (t,u,X) \in \mathbb{R}^{2+d^2} : t \le u \log(\det(X/u)), X \mbox{ is a PSD matrix}, u > 0 \}`` | | [`NormSpectralCone(r, c)`](@ref MathOptInterface.NormSpectralCone) | ``\{ (t, X) \in \mathbb{R}^{1 + r \times c} : t \ge \sigma_1(X), X \mbox{ is a } r\times c\mbox{ matrix} \}`` -| [`NormNuclearCone(r, c)`](@ref MathOptInterface.NormNuclearCone) | ``\{ (t, X) \in \mathbb{R}^{1 + r \times c} : t \ge \sum_i \sigma_i(X), X \mbox{ is a } r\times c\mbox{ matrix} \}`` +| [`NormNuclearCone(r, c)`](@ref MathOptInterface.NormNuclearCone) | ``\{ (t, X) \in \mathbb{R}^{1 + r \times c} : t \ge \sum_i \sigma_i(X), X \mbox{ is a } r\times c\mbox{ matrix} \}`` | +| [`HermitianPositiveSemidefiniteConeTriangle(d)`](@ref MathOptInterface.HermitianPositiveSemidefiniteConeTriangle) | The cone of Hermitian positive semidefinite matrices, with +`side_dimension` rows and columns. | Some of these cones can take two forms: `XXXConeTriangle` and `XXXConeSquare`. diff --git a/src/Bridges/Variable/bridges/hermitian.jl b/src/Bridges/Variable/bridges/hermitian.jl index fcc25dda08..66a55662f9 100644 --- a/src/Bridges/Variable/bridges/hermitian.jl +++ b/src/Bridges/Variable/bridges/hermitian.jl @@ -27,63 +27,48 @@ * [`MOI.VectorOfVariables`](@ref) in [`MOI.PositiveSemidefiniteConeTriangle`](@ref) * [`MOI.ScalarAffineFunction{T}`](@ref) in [`MOI.EqualTo{T}`](@ref) -## Duality notes +## Reformulation -Suppose for simplicity that the elements of a `2n x 2n` matrix are ordered as: -``` -\\ 1 |\\ 2 - \\ | 3 - \\ |4_\\ - \\ 5 - \\ - \\ -``` -Let `H = HermitianToSymmetricPSDBridge(n)`, -`S = PositiveSemidefiniteConeTriangle(2n)` and `con_11_22` (resp. `con_12_21`, -`con12diag`) be the set of `2n x 2n` symmetric matrices such that the block `1` -and `5` are equal (resp. `2` and `4` are opposite, `3` is zero). +The reformulation is best described by example. -We consider the cone `P = S ∩ con_11_22 ∩ con_12_21 ∩ con12diag`. We have -`P = A * H` where -``` - [I 0] - [0 I] -A = [0 0] - [0 -I] - [I 0] -``` -Therefore, `H* = A* * P*` where +The Hermitian matrix: +```math +\\begin{bmatrix} + x_{11} & x_{12} + y_{12}im & x_{13} + y_{13}im\\\\ + x_{12} - y_{12}im & x_{22} & x_{23} + y_{23}im\\\\ + x_{13} - y_{13}im & x_{23} - y_{23}im & x_{33} +\\end{bmatrix} ``` - [I 0 0 0 I] -A* = [0 I 0 -I 0] +is positive semidefinite if and only if the symmetric matrix: +```math +\\begin{bmatrix} + x_{11} & x_{12} & x_{13} & 0 & y_{12} & y_{13} \\\\ + & x_{22} & x_{23} & -y_{12} & 0 & y_{23} \\\\ + & & x_{33} & -y_{13} & -y_{23} & 0 \\\\ + & & & x_{11} & x_{12} & x_{13} \\\\ + & & & & x_{22} & x_{23} \\\\ + & & & & & x_{33} +\\end{bmatrix} ``` -Moreover, as `(S ∩ T)* = S* + T*` for cones `S` and `T`, we have -``` -P* = S* + con_11_22* + con_12_21* + con12diag* -``` -the dual vector of `P*` is the dual vector of `S*` for which we add in the -corresponding entries the dual of the three constraints, multiplied by the -coefficients for the `EqualTo` constraints. +is positive semidefinite. -Note that these contributions cancel out when we multiply them by `A*`: -A* * (S* + con_11_22* + con_12_21* + con12diag*) = A* * S* so we can just ignore -them. +The bridge achieves this reformulation by adding a new set of variables in +`MOI.PositiveSemidefiniteConeTriangle(6)`, and then adding three groups of +equality constraints to: + + * constrain the two `x` blocks to be equal + * force the diagonal of the `y` blocks to be `0` + * force the lower triangular of the `y` block to be the negative of the upper + triangle. """ struct HermitianToSymmetricPSDBridge{T} <: AbstractBridge variables::Vector{MOI.VariableIndex} - psd_constraint::MOI.ConstraintIndex{ + psd::MOI.ConstraintIndex{ MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeTriangle, } - con_11_22::Vector{ - MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}, - } - con12diag::Vector{ - MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}, - } - con_12_21::Vector{ - MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}, - } + n::Int + ceq::Vector{MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}} end const HermitianToSymmetricPSD{T,OT<:MOI.ModelLike} = @@ -95,72 +80,37 @@ function bridge_constrained_variable( set::MOI.HermitianPositiveSemidefiniteConeTriangle, ) where {T} n = set.side_dimension - variables, psd_constraint = MOI.add_constrained_variables( + variables, psd_ci = MOI.add_constrained_variables( model, MOI.PositiveSemidefiniteConeTriangle(2n), ) + ceq = MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}[] k11 = 0 - k12 = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(n)) - k21 = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(2n)) + 1 - k22 = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(n)) - X11() = variables[k11] - X12() = variables[k12] + k12 = k22 = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(n)) function X21(i, j) - I = j - J = n + i + I, J = j, n + i k21 = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(J - 1)) + I return variables[k21] end - X22() = variables[k22] - con_11_22 = - MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}[] - con12diag = - MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}[] - con_12_21 = - MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}[] for j in 1:n k22 += n for i in 1:j k11 += 1 k12 += 1 k22 += 1 - push!( - con_11_22, - MOI.add_constraint( - model, - MOI.Utilities.operate(-, T, X11(), X22()), - MOI.EqualTo(zero(T)), - ), - ) - if i == j - push!( - con12diag, - MOI.add_constraint( - model, - convert(MOI.ScalarAffineFunction{T}, X12()), - MOI.EqualTo(zero(T)), - ), - ) - else - push!( - con_12_21, - MOI.add_constraint( - model, - MOI.Utilities.operate(+, T, X21(i, j), X12()), - MOI.EqualTo(zero(T)), - ), - ) + f_x = MOI.Utilities.operate(-, T, variables[k11], variables[k22]) + push!(ceq, MOI.add_constraint(model, f_x, MOI.EqualTo(zero(T)))) + if i == j # y_{ii} = 0 + f_0 = convert(MOI.ScalarAffineFunction{T}, variables[k12]) + push!(ceq, MOI.add_constraint(model, f_0, MOI.EqualTo(zero(T)))) + else # y_{ij} = -y_{ji} + f_y = MOI.Utilities.operate(+, T, X21(i, j), variables[k12]) + push!(ceq, MOI.add_constraint(model, f_y, MOI.EqualTo(zero(T)))) end end k12 += n end - return HermitianToSymmetricPSDBridge( - variables, - psd_constraint, - con_11_22, - con12diag, - con_12_21, - ) + return HermitianToSymmetricPSDBridge(variables, psd_ci, n, ceq) end function supports_constrained_variable( @@ -210,35 +160,25 @@ function MOI.get( MOI.PositiveSemidefiniteConeTriangle, }, ) - return [bridge.psd_constraint] + return [bridge.psd] end function MOI.get( bridge::HermitianToSymmetricPSDBridge{T}, ::MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}, ) where {T} - return length(bridge.con_11_22) + - length(bridge.con12diag) + - length(bridge.con_12_21) + return length(bridge.ceq) end function MOI.get( bridge::HermitianToSymmetricPSDBridge{T}, ::MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}, ) where {T} - return [bridge.con_11_22; bridge.con12diag; bridge.con_12_21] + return copy(bridge.ceq) end function MOI.delete(model::MOI.ModelLike, bridge::HermitianToSymmetricPSDBridge) - for ci in bridge.con_11_22 - MOI.delete(model, ci) - end - for ci in bridge.con12diag - MOI.delete(model, ci) - end - for ci in bridge.con_12_21 - MOI.delete(model, ci) - end + MOI.delete(model, bridge.ceq) MOI.delete(model, bridge.variables) return end @@ -248,23 +188,22 @@ function MOI.get( ::MOI.ConstraintSet, bridge::HermitianToSymmetricPSDBridge, ) - dimension = length(bridge.con12diag) - return MOI.HermitianPositiveSemidefiniteConeTriangle(dimension) + return MOI.HermitianPositiveSemidefiniteConeTriangle(bridge.n) end function _matrix_indices(k) # If `k` is a diagonal index, `s(k)` is odd and 1 + 8k is a perfect square. n = 1 + 8k s = isqrt(n) - if s^2 == n - j = div(s, 2) + j = if s^2 == n + div(s, 2) else # Otherwise, if it is after the diagonal index `k` but before the # diagonal index `k'` with `s(k') = s(k) + 2`, we have # `s(k) <= s < s(k) + 2`. # By shifting by `+1` before `div`, we make sure to have the right # column. - j = div(s + 1, 2) + div(s + 1, 2) end i = k - MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(j - 1)) return i, j @@ -284,7 +223,7 @@ function _variable( bridge::HermitianToSymmetricPSDBridge, i::MOI.Bridges.IndexInVector, ) - return bridge.variables[_variable_map(i, length(bridge.con12diag))] + return bridge.variables[_variable_map(i, bridge.n)] end function MOI.get( @@ -292,28 +231,27 @@ function MOI.get( attr::MOI.ConstraintPrimal, bridge::HermitianToSymmetricPSDBridge{T}, ) where {T} - values = MOI.get(model, attr, bridge.psd_constraint) + values = MOI.get(model, attr, bridge.psd) M = MOI.dimension(MOI.get(model, MOI.ConstraintSet(), bridge)) - n = length(bridge.con12diag) + n = bridge.n return [values[_variable_map(MOI.Bridges.IndexInVector(i), n)] for i in 1:M] end -# See docstring of bridge for why we ignore the dual of the constraints -# `con_11_22`, `con_12_21` and `con12diag`. +# We don't need to take into account the equality constraints. We just need to +# sum up (with appropriate +/-) each dual variable associated with the original +# x or y element. function MOI.get( model::MOI.ModelLike, attr::MOI.ConstraintDual, bridge::HermitianToSymmetricPSDBridge{T}, ) where {T} - dual = MOI.get(model, attr, bridge.psd_constraint) + dual = MOI.get(model, attr, bridge.psd) M = MOI.dimension(MOI.get(model, MOI.ConstraintSet(), bridge)) - mapped = zeros(T, M) - n = length(bridge.con12diag) + result = zeros(T, M) + n = bridge.n N = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(n)) - k11 = 0 - k12 = N + k11, k12, k22 = 0, N, N k21 = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(2n)) + 1 - k22 = N k = 0 for j in 1:n k21 -= n + 1 - j @@ -323,18 +261,16 @@ function MOI.get( k12 += 1 k21 -= 1 k22 += 1 - mapped[k11] += dual[k11] - mapped[k11] += dual[k22] + result[k11] += dual[k11] + dual[k22] if i != j k += 1 - mapped[N+k] += dual[k12] - mapped[N+k] -= dual[k21] + result[N+k] += dual[k12] - dual[k21] end end k12 += n k21 -= n - j end - return mapped + return result end function MOI.get( @@ -350,8 +286,7 @@ function MOI.Bridges.bridged_function( bridge::HermitianToSymmetricPSDBridge{T}, i::MOI.Bridges.IndexInVector, ) where {T} - func = _variable(bridge, i) - return convert(MOI.ScalarAffineFunction{T}, func) + return convert(MOI.ScalarAffineFunction{T}, _variable(bridge, i)) end function unbridged_map( @@ -359,6 +294,5 @@ function unbridged_map( vi::MOI.VariableIndex, i::MOI.Bridges.IndexInVector, ) where {T} - func = convert(MOI.ScalarAffineFunction{T}, vi) - return (_variable(bridge, i) => func,) + return (_variable(bridge, i) => convert(MOI.ScalarAffineFunction{T}, vi),) end diff --git a/src/Test/test_conic.jl b/src/Test/test_conic.jl index 96b191024b..e2a2c0f2d6 100644 --- a/src/Test/test_conic.jl +++ b/src/Test/test_conic.jl @@ -6838,8 +6838,8 @@ end config::Config, ) -Test the computation of the projection of a hermitian matrix to the cone -of hermitian positive semidefinite matrices. +Test the computation of the projection of a Hermitian matrix to the cone +of Hermitian positive semidefinite matrices. The matrix is: ``` [ 1 -1+im] [ 1-im ?] [√3 ] [ 1+im -1+√3] diff --git a/src/sets.jl b/src/sets.jl index a1990dcc7f..680d34646d 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -798,22 +798,23 @@ end """ HermitianPositiveSemidefiniteConeTriangle(side_dimension) <: AbstractVectorSet -The (vectorized) cone of hermitian positive semidefinite matrices, with +The (vectorized) cone of Hermitian positive semidefinite matrices, with `side_dimension` rows and columns. -As the matrix is hermitian, the diagonal is real and the lower triangular -entries are obtained as the conjugate of corresponding upper triangular entries. +Becaue the matrix is Hermitian, the diagonal elements are real, and the +complex-valued lower triangular entries are obtained as the conjugate of +corresponding upper triangular entries. -The vectorized form starts with real part of the entries of the upper-right -triangular part of the matrix given column by column as explained in +### Vectorization format + +The vectorized form starts with real part of the entries of the upper triangular +part of the matrix, given column by column as explained in [`AbstractSymmetricMatrixSetSquare`](@ref). It is then followed by the imaginary part of the off-diagonal entries of the -upper-right triangular part given column by column. - -### Examples +upper triangular part, also given column by column. -The matrix +For example, the matrix ```math \\begin{bmatrix} 1 & 2 + 7im & 4 + 8im\\\\ @@ -821,15 +822,17 @@ The matrix 4 - 8im & 5 - 9im & 6 \\end{bmatrix} ``` -has [`side_dimension`](@ref) 3 and vectorization ``(1, 2, 3, 4, 5, 6, 7, 8, 9)``. +has [`side_dimension`](@ref) 3 and is represented as the vector +``[1, 2, 3, 4, 5, 6, 7, 8, 9]``. """ struct HermitianPositiveSemidefiniteConeTriangle <: AbstractVectorSet side_dimension::Int end function dimension(set::HermitianPositiveSemidefiniteConeTriangle) - return dimension(PositiveSemidefiniteConeTriangle(set.side_dimension)) + - dimension(PositiveSemidefiniteConeTriangle(set.side_dimension - 1)) + real_nnz = div(set.side_dimension * (set.side_dimension + 1), 2) + imag_nnz = div((set.side_dimension - 1) * set.side_dimension, 2) + return real_nnz + imag_nnz end """ From 8356373291204e38fe55e44a309cde570e76bec0 Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 25 Jul 2022 14:07:38 +1200 Subject: [PATCH 16/20] Update coverage --- test/Bridges/Variable/hermitian.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/Bridges/Variable/hermitian.jl b/test/Bridges/Variable/hermitian.jl index 8820f96cf0..5d658dc48a 100644 --- a/test/Bridges/Variable/hermitian.jl +++ b/test/Bridges/Variable/hermitian.jl @@ -86,13 +86,16 @@ function test_runtests() MOI.Bridges.runtests( MOI.Bridges.Variable.HermitianToSymmetricPSDBridge, """ + variables: a, b, c constrainedvariable: [r11, r12, r22, c12] in HermitianPositiveSemidefiniteConeTriangle(2) 1.0 * r11 >= 1.0 1.0 * r12 >= 2.0 1.0 * r22 >= 3.0 1.0 * c12 >= 4.0 + [a, b, c] in PositiveSemidefiniteConeTriangle(2) """, """ + variables: a, b, c constrainedvariable: [v11, v12, v22, v13, v23, v33, v14, v24, v34, v44] in PositiveSemidefiniteConeTriangle(4) 1.0 * v11 >= 1.0 1.0 * v12 >= 2.0 @@ -104,6 +107,7 @@ function test_runtests() 1.0 * v23 + 1.0 * v14 == 0.0 1.0 * v22 + -1.0 * v44 == 0.0 1.0 * v24 == 0.0 + [a, b, c] in PositiveSemidefiniteConeTriangle(2) """, ) return From fdcc6e36e18660a57a1b2682f61bac300fdc4985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 25 Jul 2022 17:37:17 -0400 Subject: [PATCH 17/20] Add explanation back --- src/Bridges/Variable/bridges/hermitian.jl | 36 +++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/Bridges/Variable/bridges/hermitian.jl b/src/Bridges/Variable/bridges/hermitian.jl index 66a55662f9..f5566b4380 100644 --- a/src/Bridges/Variable/bridges/hermitian.jl +++ b/src/Bridges/Variable/bridges/hermitian.jl @@ -240,6 +240,42 @@ end # We don't need to take into account the equality constraints. We just need to # sum up (with appropriate +/-) each dual variable associated with the original # x or y element. +# The reason for this is as follows: +# Suppose for simplicity that the elements of a `2n x 2n` matrix are ordered as: +# ``` +# \\ 1 |\\ 2 +# \\ | 3 +# \\ |4_\\ +# \\ 5 +# \\ +# \\ +# ``` +# Let `H = HermitianToSymmetricPSDBridge(n)`, +# `S = PositiveSemidefiniteConeTriangle(2n)` and `ceq` be the linear space of +# `2n x 2n` symmetric matrices such that the block `1` and `5` are equal, `2` and `4` are opposite and `3` is zero. +# We consider the cone `P = S ∩ ceq`. +# We have `P = A * H` where +# ``` +# [I 0] +# [0 I] +# A = [0 0] +# [0 -I] +# [I 0] +# ``` +# Therefore, `H* = A* * P*` where +# ``` +# [I 0 0 0 I] +# A* = [0 I 0 -I 0] +# ``` +# Moreover, as `(S ∩ T)* = S* + T*` for cones `S` and `T`, we have +# ``` +# P* = S* + ceq* +# ``` +# the dual vector of `P*` is the dual vector of `S*` for which we add in the corresponding +# entries the dual of the three constraints, multiplied by the coefficients for the `EqualTo` constraints. +# Note that these contributions cancel out when we multiply them by `A*`: +# A* * (S* + ceq*) = A* * S* +# so we can just ignore them. function MOI.get( model::MOI.ModelLike, attr::MOI.ConstraintDual, From 8984a7de923c0b830bd257e9993cdb7bb565b802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 25 Jul 2022 17:42:18 -0400 Subject: [PATCH 18/20] Clean up test --- src/Test/test_conic.jl | 121 ++++++++++++++++++++++++----------------- 1 file changed, 70 insertions(+), 51 deletions(-) diff --git a/src/Test/test_conic.jl b/src/Test/test_conic.jl index e2a2c0f2d6..af0c18e440 100644 --- a/src/Test/test_conic.jl +++ b/src/Test/test_conic.jl @@ -6865,6 +6865,16 @@ function test_conic_HermitianPositiveSemidefiniteConeTriangle_1( optimizer::MOI.ModelLike, config::Config{T}, ) where {T} + @requires _supports(config, MOI.optimize!) + @requires MOI.supports_add_constrained_variables( + model, + MOI.HermitianPositiveSemidefiniteConeTriangle, + ) + @requires MOI.supports_constraint( + model, + MOI.VectorAffineFunction{T}, + MOI.SecondOrderCone, + ) set = MOI.HermitianPositiveSemidefiniteConeTriangle(2) x, cx = MOI.add_constrained_variables(optimizer, set) t = MOI.add_variable(optimizer) @@ -6884,52 +6894,45 @@ function test_conic_HermitianPositiveSemidefiniteConeTriangle_1( ) MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MIN_SENSE) MOI.set(optimizer, MOI.ObjectiveFunction{typeof(t)}(), t) - if _supports(config, MOI.optimize!) - MOI.optimize!(optimizer) - primal = [ - (T(1) + √T(3)) / T(2), - -T(1) / T(2), - (-T(1) + √T(3)) / T(2), - T(1) / T(2), - ] - soc_primal = [ - primal[1] - one(T), - √T(2) * (primal[2] + one(T)), - primal[3] + one(T), - √T(2) * (primal[4] - one(T)), - ] - t_value = LinearAlgebra.norm(soc_primal) - soc_primal = [t_value; soc_primal] - dual = [ - (T(3) - √T(3)) / T(6), - √T(3) / T(6), - (T(3) + √T(3)) / T(6), - -√T(3) / T(6), - ] - @test ≈(MOI.Utilities.set_dot(primal, dual, set), T(0), config) - soc_dual = - [one(T), -dual[1], -dual[2] * √T(2), -dual[3], -dual[4] * √T(2)] - @test ≈( - MOI.Utilities.set_dot(soc_primal, soc_dual, soc_set), - T(0), - config, - ) - @test ≈(MOI.get(optimizer, MOI.VariablePrimal(), x), primal, config) - @test ≈(MOI.get(optimizer, MOI.VariablePrimal(), t), t_value, config) - @test ≈(MOI.get(optimizer, MOI.ConstraintPrimal(), cx), primal, config) + MOI.optimize!(optimizer) + primal = [ + (T(1) + √T(3)) / T(2), + -T(1) / T(2), + (-T(1) + √T(3)) / T(2), + T(1) / T(2), + ] + soc_primal = [ + primal[1] - one(T), + √T(2) * (primal[2] + one(T)), + primal[3] + one(T), + √T(2) * (primal[4] - one(T)), + ] + t_value = LinearAlgebra.norm(soc_primal) + soc_primal = [t_value; soc_primal] + dual = [ + (T(3) - √T(3)) / T(6), + √T(3) / T(6), + (T(3) + √T(3)) / T(6), + -√T(3) / T(6), + ] + @test ≈(MOI.Utilities.set_dot(primal, dual, set), T(0), config) + soc_dual = [one(T), -dual[1], -dual[2] * √T(2), -dual[3], -dual[4] * √T(2)] + @test ≈(MOI.Utilities.set_dot(soc_primal, soc_dual, soc_set), T(0), config) + @test ≈(MOI.get(optimizer, MOI.VariablePrimal(), x), primal, config) + @test ≈(MOI.get(optimizer, MOI.VariablePrimal(), t), t_value, config) + @test ≈(MOI.get(optimizer, MOI.ConstraintPrimal(), cx), primal, config) + @test ≈( + MOI.get(optimizer, MOI.ConstraintPrimal(), con_soc), + soc_primal, + config, + ) + if _supports(config, MOI.ConstraintDual) + @test ≈(MOI.get(optimizer, MOI.ConstraintDual(), cx), dual, config) @test ≈( - MOI.get(optimizer, MOI.ConstraintPrimal(), con_soc), - soc_primal, + MOI.get(optimizer, MOI.ConstraintDual(), con_soc), + soc_dual, config, ) - if _supports(config, MOI.ConstraintDual) - @test ≈(MOI.get(optimizer, MOI.ConstraintDual(), cx), dual, config) - @test ≈( - MOI.get(optimizer, MOI.ConstraintDual(), con_soc), - soc_dual, - config, - ) - end end return end @@ -6973,6 +6976,12 @@ function setup_test( return end +function version_added( + ::typeof(test_conic_HermitianPositiveSemidefiniteConeTriangle_1), +) + v"1.7.0" +end + """ test_conic_HermitianPositiveSemidefiniteConeTriangle_2( model::MOI.ModelLike, @@ -6988,18 +6997,22 @@ function test_conic_HermitianPositiveSemidefiniteConeTriangle_2( optimizer::MOI.ModelLike, config::Config{T}, ) where {T} + @requires _supports(config, MOI.optimize!) + @requires MOI.supports_add_constrained_variables( + model, + MOI.HermitianPositiveSemidefiniteConeTriangle, + ) + @requires MOI.supports_constraint(model, MOI.VariableIndex, MOI.EqualTo{T}) set = MOI.HermitianPositiveSemidefiniteConeTriangle(3) x, cx = MOI.add_constrained_variables(optimizer, set) primal = T[1, 0, 1, 0, 0, 0, -1, 0, 0] MOI.add_constraints(optimizer, x, MOI.EqualTo.(primal)) - if _supports(config, MOI.optimize!) - MOI.optimize!(optimizer) - @test ≈(MOI.get(optimizer, MOI.VariablePrimal(), x), primal, config) - @test ≈(MOI.get(optimizer, MOI.ConstraintPrimal(), cx), primal, config) - if _supports(config, MOI.ConstraintDual) - dual = zeros(T, 9) - @test ≈(MOI.get(optimizer, MOI.ConstraintDual(), cx), dual, config) - end + MOI.optimize!(optimizer) + @test ≈(MOI.get(optimizer, MOI.VariablePrimal(), x), primal, config) + @test ≈(MOI.get(optimizer, MOI.ConstraintPrimal(), cx), primal, config) + if _supports(config, MOI.ConstraintDual) + dual = zeros(T, 9) + @test ≈(MOI.get(optimizer, MOI.ConstraintDual(), cx), dual, config) end return end @@ -7023,3 +7036,9 @@ function setup_test( model.eval_variable_constraint_dual = false return () -> model.eval_variable_constraint_dual = true end + +function version_added( + ::typeof(test_conic_HermitianPositiveSemidefiniteConeTriangle_2), +) + v"1.7.0" +end From 6e3bb19c28d8429f41d77970bee395a0309dad3b Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 26 Jul 2022 11:01:19 +1200 Subject: [PATCH 19/20] Fix --- src/Test/test_conic.jl | 52 +++++++++++++----------------- test/Bridges/Variable/hermitian.jl | 1 + 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/src/Test/test_conic.jl b/src/Test/test_conic.jl index af0c18e440..8440714a97 100644 --- a/src/Test/test_conic.jl +++ b/src/Test/test_conic.jl @@ -6862,7 +6862,7 @@ which is ``` """ function test_conic_HermitianPositiveSemidefiniteConeTriangle_1( - optimizer::MOI.ModelLike, + model::MOI.ModelLike, config::Config{T}, ) where {T} @requires _supports(config, MOI.optimize!) @@ -6876,11 +6876,11 @@ function test_conic_HermitianPositiveSemidefiniteConeTriangle_1( MOI.SecondOrderCone, ) set = MOI.HermitianPositiveSemidefiniteConeTriangle(2) - x, cx = MOI.add_constrained_variables(optimizer, set) - t = MOI.add_variable(optimizer) + x, cx = MOI.add_constrained_variables(model, set) + t = MOI.add_variable(model) soc_set = MOI.SecondOrderCone(5) con_soc = MOI.add_constraint( - optimizer, + model, MOI.Utilities.operate( vcat, T, @@ -6892,9 +6892,9 @@ function test_conic_HermitianPositiveSemidefiniteConeTriangle_1( ), soc_set, ) - MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MIN_SENSE) - MOI.set(optimizer, MOI.ObjectiveFunction{typeof(t)}(), t) - MOI.optimize!(optimizer) + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + MOI.set(model, MOI.ObjectiveFunction{typeof(t)}(), t) + MOI.optimize!(model) primal = [ (T(1) + √T(3)) / T(2), -T(1) / T(2), @@ -6918,21 +6918,13 @@ function test_conic_HermitianPositiveSemidefiniteConeTriangle_1( @test ≈(MOI.Utilities.set_dot(primal, dual, set), T(0), config) soc_dual = [one(T), -dual[1], -dual[2] * √T(2), -dual[3], -dual[4] * √T(2)] @test ≈(MOI.Utilities.set_dot(soc_primal, soc_dual, soc_set), T(0), config) - @test ≈(MOI.get(optimizer, MOI.VariablePrimal(), x), primal, config) - @test ≈(MOI.get(optimizer, MOI.VariablePrimal(), t), t_value, config) - @test ≈(MOI.get(optimizer, MOI.ConstraintPrimal(), cx), primal, config) - @test ≈( - MOI.get(optimizer, MOI.ConstraintPrimal(), con_soc), - soc_primal, - config, - ) + @test ≈(MOI.get(model, MOI.VariablePrimal(), x), primal, config) + @test ≈(MOI.get(model, MOI.VariablePrimal(), t), t_value, config) + @test ≈(MOI.get(model, MOI.ConstraintPrimal(), cx), primal, config) + @test ≈(MOI.get(model, MOI.ConstraintPrimal(), con_soc), soc_primal, config) if _supports(config, MOI.ConstraintDual) - @test ≈(MOI.get(optimizer, MOI.ConstraintDual(), cx), dual, config) - @test ≈( - MOI.get(optimizer, MOI.ConstraintDual(), con_soc), - soc_dual, - config, - ) + @test ≈(MOI.get(model, MOI.ConstraintDual(), cx), dual, config) + @test ≈(MOI.get(model, MOI.ConstraintDual(), con_soc), soc_dual, config) end return end @@ -6979,7 +6971,7 @@ end function version_added( ::typeof(test_conic_HermitianPositiveSemidefiniteConeTriangle_1), ) - v"1.7.0" + return v"1.7.0" end """ @@ -6994,7 +6986,7 @@ variables. ``` """ function test_conic_HermitianPositiveSemidefiniteConeTriangle_2( - optimizer::MOI.ModelLike, + model::MOI.ModelLike, config::Config{T}, ) where {T} @requires _supports(config, MOI.optimize!) @@ -7004,15 +6996,15 @@ function test_conic_HermitianPositiveSemidefiniteConeTriangle_2( ) @requires MOI.supports_constraint(model, MOI.VariableIndex, MOI.EqualTo{T}) set = MOI.HermitianPositiveSemidefiniteConeTriangle(3) - x, cx = MOI.add_constrained_variables(optimizer, set) + x, cx = MOI.add_constrained_variables(model, set) primal = T[1, 0, 1, 0, 0, 0, -1, 0, 0] - MOI.add_constraints(optimizer, x, MOI.EqualTo.(primal)) - MOI.optimize!(optimizer) - @test ≈(MOI.get(optimizer, MOI.VariablePrimal(), x), primal, config) - @test ≈(MOI.get(optimizer, MOI.ConstraintPrimal(), cx), primal, config) + MOI.add_constraints(model, x, MOI.EqualTo.(primal)) + MOI.optimize!(model) + @test ≈(MOI.get(model, MOI.VariablePrimal(), x), primal, config) + @test ≈(MOI.get(model, MOI.ConstraintPrimal(), cx), primal, config) if _supports(config, MOI.ConstraintDual) dual = zeros(T, 9) - @test ≈(MOI.get(optimizer, MOI.ConstraintDual(), cx), dual, config) + @test ≈(MOI.get(model, MOI.ConstraintDual(), cx), dual, config) end return end @@ -7040,5 +7032,5 @@ end function version_added( ::typeof(test_conic_HermitianPositiveSemidefiniteConeTriangle_2), ) - v"1.7.0" + return sv"1.7.0" end diff --git a/test/Bridges/Variable/hermitian.jl b/test/Bridges/Variable/hermitian.jl index 5d658dc48a..b171b429e9 100644 --- a/test/Bridges/Variable/hermitian.jl +++ b/test/Bridges/Variable/hermitian.jl @@ -75,6 +75,7 @@ function test_conic_HermitianPositiveSemidefiniteConeTriangle_1() zero(T) ], ) + MOI.empty!(bridged_mock) MOI.Test.test_conic_HermitianPositiveSemidefiniteConeTriangle_1( bridged_mock, MOI.Test.Config(), From e7ae9d4b6dd93c9feb18440c51638ffdbd18c21a Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Tue, 26 Jul 2022 11:15:08 +1200 Subject: [PATCH 20/20] Update src/Test/test_conic.jl --- src/Test/test_conic.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Test/test_conic.jl b/src/Test/test_conic.jl index 8440714a97..be9186ce45 100644 --- a/src/Test/test_conic.jl +++ b/src/Test/test_conic.jl @@ -7032,5 +7032,5 @@ end function version_added( ::typeof(test_conic_HermitianPositiveSemidefiniteConeTriangle_2), ) - return sv"1.7.0" + return v"1.7.0" end