diff --git a/src/Utilities/distance_to_set.jl b/src/Utilities/distance_to_set.jl index be2cb9f191..83e76ef51a 100644 --- a/src/Utilities/distance_to_set.jl +++ b/src/Utilities/distance_to_set.jl @@ -175,6 +175,39 @@ function _check_dimension(v::AbstractVector, s) return end +function _reshape( + x::AbstractVector, + set::Union{ + MOI.PositiveSemidefiniteConeSquare, + MOI.LogDetConeSquare, + MOI.RootDetConeSquare, + }, +) + n = isqrt(length(x)) + return reshape(x, (n, n)) +end + +function _reshape( + x::AbstractVector{T}, + set::Union{ + MOI.PositiveSemidefiniteConeTriangle, + MOI.LogDetConeTriangle, + MOI.RootDetConeTriangle, + }, +) where {T} + n = isqrt(2 * length(x)) + # The type annotation is needed for JET. + X = zeros(T, n, n)::Matrix{T} + k = 1 + for i in 1:n + for j in 1:i + X[j, i] = X[i, j] = x[k] + k += 1 + end + end + return LinearAlgebra.Symmetric(X) +end + # This is the minimal L2-norm. function distance_to_set( ::ProjectionUpperBoundDistance, @@ -499,28 +532,6 @@ function distance_to_set( return LinearAlgebra.norm(elements, 2) end -function _reshape(x::AbstractVector, set::MOI.PositiveSemidefiniteConeSquare) - n = MOI.side_dimension(set) - return reshape(x, (n, n)) -end - -function _reshape( - x::AbstractVector{T}, - set::MOI.PositiveSemidefiniteConeTriangle, -) where {T} - n = MOI.side_dimension(set) - # The type annotation is needed for JET. - X = zeros(T, n, n)::Matrix{T} - k = 1 - for i in 1:n - for j in 1:i - X[j, i] = X[i, j] = x[k] - k += 1 - end - end - return LinearAlgebra.Symmetric(X) -end - """ distance_to_set( ::ProjectionUpperBoundDistance, @@ -608,3 +619,86 @@ function distance_to_set( sqrt(x[1]^2 + distance_to_set(distance, x[2], set.set)^2), ) end + +""" + distance_to_set(::ProjectionUpperBoundDistance, x, set::MOI.NormNuclearCone) + +Let `(t, y...) = x`. Return the epigraph distance `d` such that `(t + d, y...)` +belongs to the set. +""" +function distance_to_set( + ::ProjectionUpperBoundDistance, + x::AbstractVector{T}, + set::MOI.NormNuclearCone, +) where {T} + _check_dimension(x, set) + X = reshape(x[2:end], set.row_dim, set.column_dim) + return max(sum(LinearAlgebra.svdvals(X)) - x[1], zero(T)) +end + +""" + distance_to_set(::ProjectionUpperBoundDistance, x, set::MOI.NormSpectralCone) + +Let `(t, y...) = x`. Return the epigraph distance `d` such that `(t + d, y...)` +belongs to the set. +""" +function distance_to_set( + ::ProjectionUpperBoundDistance, + x::AbstractVector{T}, + set::MOI.NormSpectralCone, +) where {T} + _check_dimension(x, set) + X = reshape(x[2:end], set.row_dim, set.column_dim) + return max(maximum(LinearAlgebra.svdvals(X)) - x[1], zero(T)) +end + +""" + distance_to_set( + ::ProjectionUpperBoundDistance, + x::AbstractVector, + set::Union{MOI.RootDetConeSquare,MOI.RootDetConeTriangle}, + ) + +Let ``Y`` be `y` in `x = (t, y)`, reshaped into the appropriate matrix. The +returned distance is ``||Y - Z||_2^2`` where ``Z`` is the eigen decomposition of +``Y`` with negative eigen values removed, plus the epigraph distance in `t` +needed to satisfy the root-determinant constraint. +""" +function distance_to_set( + ::ProjectionUpperBoundDistance, + x::AbstractVector{T}, + set::Union{MOI.RootDetConeSquare,MOI.RootDetConeTriangle}, +) where {T<:Real} + _check_dimension(x, set) + eigvals = LinearAlgebra.eigvals(_reshape(x[2:end], set)) + eigvals_neg = min.(zero(T), eigvals) + eigvals_pos = max.(zero(T), eigvals) + rootdet = prod(eigvals_pos)^(1 / set.side_dimension) + push!(eigvals_neg, max(x[1] - rootdet, zero(T))) + return LinearAlgebra.norm(eigvals_neg, 2) +end + +""" + distance_to_set( + ::ProjectionUpperBoundDistance, + x::AbstractVector, + set::Union{MOI.LogDetConeSquare,MOI.LogDetConeTriangle}, + ) + +Let ``Y`` be `y` in `x = (t, y)`, reshaped into the appropriate matrix. The +returned distance is ``||Y/u - Z||_2^2`` where ``Z`` is the eigen decomposition +of ``Y`` with negative eigen values removed, plus the epigraph distance in `t` +needed to satisfy the log-determinant constraint. +""" +function distance_to_set( + ::ProjectionUpperBoundDistance, + x::AbstractVector{T}, + set::Union{MOI.LogDetConeSquare,MOI.LogDetConeTriangle}, +) where {T<:Real} + _check_dimension(x, set) + eigvals = LinearAlgebra.eigvals(_reshape(x[3:end] ./ x[2], set)) + eigvals_neg = min.(eps(T), eigvals) + eigvals_pos = max.(eps(T), eigvals) + push!(eigvals_neg, max(x[1] - x[2] * sum(log.(eigvals_pos)), zero(T))) + return LinearAlgebra.norm(eigvals_neg, 2) +end diff --git a/test/Utilities/distance_to_set.jl b/test/Utilities/distance_to_set.jl index 2db868afa3..6016d09421 100644 --- a/test/Utilities/distance_to_set.jl +++ b/test/Utilities/distance_to_set.jl @@ -394,7 +394,83 @@ function test_indicator() [0.99, 1.0] => 0.01, [0.99, 1.1] => sqrt(0.01^2 + 0.1^2), [0.5, 1.1] => 0.5, - [0.8, 1.1] => sqrt(0.2^2 + 0.1^2), + [0.8, 1.1] => sqrt(0.2^2 + 0.1^2); + mismatch = [1.0], + ) + return +end + +function test_NormNuclearCone() + _test_set( + MOI.NormNuclearCone(2, 3), + [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0] => 10.039818672223756, + [11.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0] => 0.0; + mismatch = [1.0], + ) + return +end + +function test_NormSpectralCone() + _test_set( + MOI.NormSpectralCone(2, 3), + [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0] => 9.525518091565111, + [9.6, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0] => 0.0; + mismatch = [1.0], + ) + return +end + +function test_RootDetConeTriangle() + _test_set( + MOI.RootDetConeTriangle(2), + [2.0, 1.0, 0.0, 1.0] => 1.0, + [0.9, 1.0, 0.0, 1.0] => 0.0, + [2.0, 1.0, 0.0, 2.0] => 2 - sqrt(2), + # Projection onto PSD + [0.0, 1.0, 2.0, 3.0] => 0.2360679774997897, + # Projection onto PSD+t + [1.0, 1.0, 2.0, 3.0] => sqrt(1 + 0.2360679774997897^2); + mismatch = [1.0], + ) + return +end + +function test_RootDetConeSquare() + _test_set( + MOI.RootDetConeSquare(2), + [2.0, 1.0, 0.0, 0.0, 1.0] => 1.0, + [0.9, 1.0, 0.0, 0.0, 1.0] => 0.0, + [2.0, 1.0, 0.0, 0.0, 2.0] => 2 - sqrt(2), + # Projection onto PSD + [0.0, 1.0, 2.0, 2.0, 3.0] => 0.2360679774997897, + # Projection onto PSD+t + [1.0, 1.0, 2.0, 2.0, 3.0] => sqrt(1 + 0.2360679774997897^2); + mismatch = [1.0], + ) + return +end + +function test_LogDetConeTriangle() + _test_set( + MOI.LogDetConeTriangle(2), + [2.0, 1.0, 2.0, 0.0, 1.0] => 2 - 0.6931471805599453, + [0.69, 1.0, 2.0, 0.0, 1.0] => 0.0, + [0.0, 2.0, 2.0, 0.0, 1.0] => 1.3862943611198906, + mismatch = [1.0], + ) + return +end + +function test_LogDetConeSquare() + _test_set( + MOI.LogDetConeSquare(2), + [2.0, 1.0, 2.0, 0.0, 0.0, 1.0] => 2 - 0.6931471805599453, + [0.69, 1.0, 2.0, 0.0, 0.0, 1.0] => 0.0, + [0.0, 2.0, 2.0, 0.0, 0.0, 1.0] => 1.3862943611198906, + # Projection onto PSD + [0.0, 1.0, 1.0, 2.0, 2.0, 3.0] => 34.60082322336934, + # Projection onto PSD+t + [1.0, 1.0, 1.0, 2.0, 2.0, 3.0] => 35.60080060283381; mismatch = [1.0], ) return