diff --git a/src/Utilities/distance_to_set.jl b/src/Utilities/distance_to_set.jl index cd59fc8926..0fcd6259d4 100644 --- a/src/Utilities/distance_to_set.jl +++ b/src/Utilities/distance_to_set.jl @@ -193,3 +193,23 @@ function distance_to_set( _check_dimension(x, set) return zero(T) end + +function distance_to_set( + ::ProjectionUpperBoundDistance, + x::AbstractVector{T}, + set::MOI.SecondOrderCone, +) where {T<:Real} + _check_dimension(x, set) + # Projections come from: + # Parikh, N., & Boyd, S. (2014). Proximal algorithms. Foundations and + # trends in Optimization, page 184, section 6.3.2. + t, rhs = x[1], LinearAlgebra.norm(@views x[2:end]) + if t >= rhs + return zero(T) # The point is feasible! + end + if rhs <= -t # Projection to the point (0, [0]) + return LinearAlgebra.norm(x) + end + # Projection to the point (t, x) + 0.5 * (|x|_2 - t, (t/|x|_2 - 1) * x) + return sqrt(2) / 2 * abs(t - rhs) +end diff --git a/test/Utilities/distance_to_set.jl b/test/Utilities/distance_to_set.jl index 6dd3c7b807..9fe510a773 100644 --- a/test/Utilities/distance_to_set.jl +++ b/test/Utilities/distance_to_set.jl @@ -8,6 +8,7 @@ module TestFeasibilityChecker using Test +import LinearAlgebra import MathOptInterface const MOI = MathOptInterface @@ -127,6 +128,21 @@ function test_zeros() return end +function test_secondordercone() + @test_throws( + DimensionMismatch, + MOI.Utilities.distance_to_set([-1.0, 1.0], MOI.SecondOrderCone(3)) + ) + set = MOI.SecondOrderCone(3) + @test MOI.Utilities.distance_to_set([sqrt(2), 1, 1], set) ≈ 0.0 + @test MOI.Utilities.distance_to_set([-sqrt(2), 1, 1], set) ≈ 2 + @test MOI.Utilities.distance_to_set([-2, 1, 1], set) ≈ sqrt(6) + # According to Boyd, (t, x) = (1, [1, 1]), projects to: + d = ((1 / 2) * (1 + 1 / √2) * [√2, 1, 1]) .- [1, 1, 1] + @test MOI.Utilities.distance_to_set([1, 1, 1], set) ≈ LinearAlgebra.norm(d) + return +end + end TestFeasibilityChecker.runtests()