Skip to content

Commit a6a2149

Browse files
authored
Add support for SumOfSquares cone (#844)
* Add support for SumOfSquares cone * Fix format * Update to MOI interface * Use LowRankOpt * Fixes * Fix format * Drop Julia v1.6 * Add bound for LowRankOpt * Add tests * Bump doc * Fixes * Fix format
1 parent 9fd414f commit a6a2149

File tree

5 files changed

+90
-15
lines changed

5 files changed

+90
-15
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ GenericLinearAlgebra = "14197337-ba66-59df-a3e3-ca00e7dcff7a"
1010
IterativeSolvers = "42fd0dbc-a981-5370-80f2-aaf504508153"
1111
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1212
LinearMaps = "7a12625a-238d-50fd-b39a-03d52299707e"
13+
LowRankOpt = "607ca3ad-272e-43c8-bcbe-fc71b56c935c"
1314
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
1415
PolynomialRoots = "3a141323-8675-5d76-9d11-e1df1406c778"
1516
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
@@ -25,6 +26,7 @@ DocStringExtensions = "0.9"
2526
GenericLinearAlgebra = "0.3"
2627
IterativeSolvers = "0.9"
2728
LinearMaps = "3"
29+
LowRankOpt = "0.2"
2830
MathOptInterface = "1"
2931
PolynomialRoots = "1"
3032
Requires = "1"

src/Hypatia.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const VI = MOI.VariableIndex
4444
const SAF = MOI.ScalarAffineFunction
4545
const VV = MOI.VectorOfVariables
4646
const VAF = MOI.VectorAffineFunction
47+
import LowRankOpt as LRO
4748
include("MathOptInterface/cones.jl")
4849
include("MathOptInterface/transform.jl")
4950
include("MathOptInterface/wrapper.jl")

src/MathOptInterface/cones.jl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,32 @@ function cone_from_moi(
635635
return Cones.WSOSInterpNonnegative{T, R}(cone.U, cone.Ps, use_dual = cone.use_dual)
636636
end
637637

638+
const _PrimalRankOnePSD{T <: Real, F <: AbstractVector{T}} = LRO.SetDotProducts{
639+
LRO.WITHOUT_SET,
640+
MOI.PositiveSemidefiniteConeTriangle,
641+
LRO.TriangleVectorization{T, LRO.Factorization{T, F, LRO.One{T}}},
642+
}
643+
644+
const _DualRankOnePSD{T <: Real, F <: AbstractVector{T}} = LRO.LinearCombinationInSet{
645+
LRO.WITHOUT_SET,
646+
MOI.PositiveSemidefiniteConeTriangle,
647+
LRO.TriangleVectorization{T, LRO.Factorization{T, F, LRO.One{T}}},
648+
}
649+
650+
function cone_from_moi(
651+
::Type{T},
652+
cone::Union{_PrimalRankOnePSD, _DualRankOnePSD},
653+
) where {T <: Real}
654+
return cone_from_moi(
655+
T,
656+
WSOSInterpNonnegativeCone{T, T}(
657+
length(cone.vectors),
658+
[reduce(vcat, [v.matrix.factor' for v in cone.vectors])],
659+
cone isa _DualRankOnePSD,
660+
),
661+
)
662+
end
663+
638664
"""
639665
$(TYPEDEF)
640666
@@ -785,6 +811,8 @@ const SupportedCone{T <: Real} = Union{
785811
MOI.DualExponentialCone,
786812
MOI.LogDetConeTriangle,
787813
MOI.RelativeEntropyCone,
814+
_PrimalRankOnePSD{T},
815+
_DualRankOnePSD{T},
788816
}
789817

790818
Base.copy(cone::HypatiaCones) = cone # maybe should deep copy the cone struct, but this is expensive

test/moicones.jl

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ using Test
1313
using LinearAlgebra
1414
import SparseArrays
1515
import Random
16-
import MathOptInterface
17-
const MOI = MathOptInterface
16+
import MathOptInterface as MOI
17+
import LowRankOpt as LRO
1818
import Hypatia
1919
import Hypatia.Cones
2020

@@ -383,6 +383,37 @@ function test_moi_cones(T::Type{<:Real})
383383
@test MOI.dimension(moi_cone) == Cones.dimension(hyp_cone) == 3
384384
@test hyp_cone.Ps == Ps
385385

386+
@testset "LowRankOpt" begin
387+
P = Ps[1]
388+
moi_cone = LRO.SetDotProducts{LRO.WITHOUT_SET}(
389+
MOI.PositiveSemidefiniteConeTriangle(2),
390+
LRO.TriangleVectorization.([
391+
LRO.positive_semidefinite_factorization(P[1, :]),
392+
LRO.positive_semidefinite_factorization(P[2, :]),
393+
LRO.positive_semidefinite_factorization(P[3, :]),
394+
]),
395+
)
396+
hyp_cone = Hypatia.cone_from_moi(T, moi_cone)
397+
@test hyp_cone isa Cones.WSOSInterpNonnegative{T, T}
398+
@test MOI.dimension(moi_cone) == Cones.dimension(hyp_cone) == 3
399+
@test only(hyp_cone.Ps) == P
400+
@test hyp_cone.use_dual_barrier
401+
402+
moi_cone = LRO.LinearCombinationInSet{LRO.WITHOUT_SET}(
403+
MOI.PositiveSemidefiniteConeTriangle(2),
404+
LRO.TriangleVectorization.([
405+
LRO.positive_semidefinite_factorization(P[1, :]),
406+
LRO.positive_semidefinite_factorization(P[2, :]),
407+
LRO.positive_semidefinite_factorization(P[3, :]),
408+
]),
409+
)
410+
hyp_cone = Hypatia.cone_from_moi(T, moi_cone)
411+
@test hyp_cone isa Cones.WSOSInterpNonnegative{T, T}
412+
@test MOI.dimension(moi_cone) == Cones.dimension(hyp_cone) == 3
413+
@test only(hyp_cone.Ps) == P
414+
@test !hyp_cone.use_dual_barrier
415+
end
416+
386417
Ps = [rand(Complex{T}, 4, 3), rand(Complex{T}, 4, 2)]
387418
moi_cone = Hypatia.WSOSInterpNonnegativeCone{T, Complex{T}}(4, Ps)
388419
hyp_cone = Hypatia.cone_from_moi(T, moi_cone)

test/runmoitests.jl

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ run MOI tests
1010
=#
1111

1212
using Test
13-
import MathOptInterface
14-
const MOI = MathOptInterface
13+
import MathOptInterface as MOI
14+
import LowRankOpt as LRO
1515
import Hypatia
1616
include(joinpath(@__DIR__, "moicones.jl"))
1717

@@ -27,6 +27,17 @@ include(joinpath(@__DIR__, "moicones.jl"))
2727
end
2828
end
2929

30+
@testset "Supports $T" for T in [Float64, BigFloat]
31+
model = Hypatia.Optimizer{T}()
32+
for S in [
33+
Hypatia._PrimalRankOnePSD{T, Vector{T}}
34+
Hypatia._DualRankOnePSD{T, Vector{T}}
35+
]
36+
@test MOI.supports_add_constrained_variables(model, S)
37+
@test MOI.supports_constraint(model, MOI.VectorAffineFunction{T}, S)
38+
end
39+
end
40+
3041
# real types, tolerances, and tests to include for MOI.Test tests
3142
test_T = [
3243
(Float64, 2 * sqrt(sqrt(eps())), 4, String[], String[]),
@@ -49,19 +60,20 @@ include(joinpath(@__DIR__, "moicones.jl"))
4960
T,
5061
)
5162
MOI.set(model, MOI.Silent(), true)
63+
config = MOI.Test.Config(
64+
T,
65+
atol = tol_test,
66+
rtol = tol_test,
67+
exclude = Any[
68+
MOI.ConstraintBasisStatus,
69+
MOI.VariableBasisStatus,
70+
MOI.ObjectiveBound,
71+
MOI.SolverVersion,
72+
],
73+
)
5274
MOI.Test.runtests(
5375
model,
54-
MOI.Test.Config(
55-
T,
56-
atol = tol_test,
57-
rtol = tol_test,
58-
exclude = Any[
59-
MOI.ConstraintBasisStatus,
60-
MOI.VariableBasisStatus,
61-
MOI.ObjectiveBound,
62-
MOI.SolverVersion,
63-
],
64-
),
76+
config,
6577
include = includes,
6678
exclude = vcat(
6779
excludes,
@@ -72,5 +84,6 @@ include(joinpath(@__DIR__, "moicones.jl"))
7284
],
7385
),
7486
)
87+
MOI.Test.runtests(model, config, test_module = LRO.Test)
7588
end
7689
end;

0 commit comments

Comments
 (0)