From 9bb6619469d4cac3a1bede9b960335873b4f22aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 8 Jun 2023 17:31:50 +0200 Subject: [PATCH 01/21] Add set for low-rank constrained SDP --- docs/src/background/duality.md | 4 ++- docs/src/manual/standard_form.md | 2 ++ docs/src/reference/standard_form.md | 2 ++ src/Utilities/model.jl | 2 ++ src/Utilities/results.jl | 26 ++++++++++++++ src/sets.jl | 55 +++++++++++++++++++++++++++++ 6 files changed, 90 insertions(+), 1 deletion(-) diff --git a/docs/src/background/duality.md b/docs/src/background/duality.md index 1042e2b5eb..602fdad25f 100644 --- a/docs/src/background/duality.md +++ b/docs/src/background/duality.md @@ -113,7 +113,9 @@ and similarly, the dual is: The scalar product is different from the canonical one for the sets [`PositiveSemidefiniteConeTriangle`](@ref), [`LogDetConeTriangle`](@ref), -[`RootDetConeTriangle`](@ref). +[`RootDetConeTriangle`](@ref), +[`FrobeniusProductPostiviveSemidefiniteConeTriangle`](@ref) and +[`LinearMatrixInequalityConeTriangle`](@ref). If the set ``C_i`` of the section [Duality](@ref) is one of these three cones, then the rows of the matrix ``A_i`` corresponding to off-diagonal entries are diff --git a/docs/src/manual/standard_form.md b/docs/src/manual/standard_form.md index 57b8a8e2d2..ef4fd9d57b 100644 --- a/docs/src/manual/standard_form.md +++ b/docs/src/manual/standard_form.md @@ -100,6 +100,8 @@ The matrix-valued set types implemented in MathOptInterface.jl are: | [`HermitianPositiveSemidefiniteConeTriangle(d)`](@ref MathOptInterface.HermitianPositiveSemidefiniteConeTriangle) | The cone of Hermitian positive semidefinite matrices, with `side_dimension` rows and columns. | | [`Scaled(S)`](@ref MathOptInterface.Scaled) | The set `S` scaled so that [`Utilities.set_dot`](@ref MathOptInterface.Utilities.set_dot) corresponds to `LinearAlgebra.dot` | +| [`FrobeniusProductPostiviveSemidefiniteConeTriangle(d, A)`](@ref MathOptInterface.FrobeniusProductPostiviveSemidefiniteConeTriangle) | The cone of positive semidefinite matrices, with `side_dimension` rows and columns and their Frobenius inner product with the matrices in `A`. | +| [`LinearMatrixInequalityConeTriangle(d, A)`](@ref MathOptInterface.LinearMatrixInequalityConeTriangle) | The cone of vector `y` and symmetric `C`, with `side_dimension` rows and columns such that ``\sum_i y_i A_i + C`` is positive semidefinite. | Some of these cones can take two forms: `XXXConeTriangle` and `XXXConeSquare`. diff --git a/docs/src/reference/standard_form.md b/docs/src/reference/standard_form.md index 98ff739a33..c7e6e96045 100644 --- a/docs/src/reference/standard_form.md +++ b/docs/src/reference/standard_form.md @@ -153,4 +153,6 @@ LogDetConeTriangle LogDetConeSquare RootDetConeTriangle RootDetConeSquare +FrobeniusProductPostiviveSemidefiniteConeTriangle +LinearMatrixInequalityConeTriangle ``` diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index 0cf349def1..892703c2ce 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -806,6 +806,8 @@ const LessThanIndicatorZero{T} = MOI.Scaled{MOI.PositiveSemidefiniteConeTriangle}, MOI.RootDetConeTriangle, MOI.RootDetConeSquare, + MOI.FrobeniusProductPostiviveSemidefiniteConeTriangle, + MOI.LinearMatrixInequalityConeTriangle, MOI.LogDetConeTriangle, MOI.LogDetConeSquare, MOI.AllDifferent, diff --git a/src/Utilities/results.jl b/src/Utilities/results.jl index 29acf4a8d1..c4e6d1afd3 100644 --- a/src/Utilities/results.jl +++ b/src/Utilities/results.jl @@ -498,3 +498,29 @@ function get_fallback( f = MOI.get(model, MOI.ConstraintFunction(), ci) return _variable_dual(T, model, attr, ci, f) end + +function set_dot( + x::AbstractVector, + y::AbstractVector, + set::Union{ + MOI.FrobeniusProductPostiviveSemidefiniteConeTriangle, + MOI.LinearMatrixInequalityConeTriangle, + }, +) + m = length(set.matrices) + return LinearAlgebra.dot(view(x, 1:m), view(y, 1:m)) + + triangle_dot(x, y, set.side_dimension, m) +end + + +function set_dot( + a::AbstractVector, + set::Union{ + MOI.FrobeniusProductPostiviveSemidefiniteConeTriangle, + MOI.LinearMatrixInequalityConeTriangle, + }, +) + b = copy(a) + triangle_coefficients!(b, set.side_dimension, length(set.matrices)) + return b +end diff --git a/src/sets.jl b/src/sets.jl index 3cc6f42572..ec5389e87d 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -1803,6 +1803,61 @@ function Base.getproperty( end end +""" + FrobeniusProductPostiviveSemidefiniteConeTriangle{M}(side_dimension::Int, matrices::Vector{M}) + +Given `m` symmetric matrices `A_1`, ..., `A_m` given in `matrices`, the frobenius inner +product of positive semidefinite matrices is the convex cone: +``\\{ ((\\langle A_1, X \\rangle, ..., \\langle A_m, X \\rangle, X) \\in \\mathbb{R}^{m + d(d+1)/2} : X \\text{ postive semidefinite} \\}``, +where the matrix `X` is represented in the same symmetric packed format as in +the [`PositiveSemidefiniteConeTriangle`](@ref). +""" +struct FrobeniusProductPostiviveSemidefiniteConeTriangle{M} <: AbstractVectorSet + side_dimension::Int + matrices::Vector{M} +end + +function dimension(s::FrobeniusProductPostiviveSemidefiniteConeTriangle) + return length(s.matrices) + s.side_dimension^2 +end + +function dual_set(s::FrobeniusProductPostiviveSemidefiniteConeTriangle) + return LinearMatrixInequalityConeTriangle(s.side_dimension, s.matrices) +end + +function dual_set_type( + ::Type{FrobeniusProductPostiviveSemidefiniteConeTriangle{M}}, +) where {M} + return LinearMatrixInequalityConeTriangle +end + +""" + LinearMatrixInequalityConeTriangle{M}(side_dimension::Int, matrices::Vector{M}) + +Given `m` symmetric matrices `A_1`, ..., `A_m` given in `matrices`, the linear +matrix inequality cone is the convex cone: +``\\{ ((y, C) \\in \\mathbb{R}^{m + d(d+1)/2} : \\sum_{i=1}^m y_i A_i + C \\text{ postive semidefinite} \\}``, +where the matrix `C` is represented in the same symmetric packed format as in +the [`PositiveSemidefiniteConeTriangle`](@ref). +""" +struct LinearMatrixInequalityConeTriangle{M} <: AbstractVectorSet + side_dimension::Int + matrices::Vector{M} +end + +dimension(s::LinearMatrixInequalityConeTriangle) = length(s.matrices) + s.side_dimension^2 + +function dual_set(s::LinearMatrixInequalityConeTriangle) + return FrobeniusProductPostiviveSemidefiniteConeTriangle( + s.side_dimension, + s.matrices, + ) +end + +function dual_set_type(::Type{LinearMatrixInequalityConeTriangle{M}}) where {M} + return FrobeniusProductPostiviveSemidefiniteConeTriangle +end + """ SOS1{T<:Real}(weights::Vector{T}) From 2c38cb0a9a0e2c23a818f5289295f0f91387b009 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 10 Jul 2024 18:00:08 +0200 Subject: [PATCH 02/21] SetWithDotProducts --- docs/src/background/duality.md | 4 +-- docs/src/manual/standard_form.md | 4 +-- docs/src/reference/standard_form.md | 4 +-- src/Utilities/model.jl | 2 -- src/Utilities/results.jl | 26 -------------- src/Utilities/set_dot.jl | 26 ++++++++++++++ src/sets.jl | 54 +++++++++++++---------------- 7 files changed, 56 insertions(+), 64 deletions(-) diff --git a/docs/src/background/duality.md b/docs/src/background/duality.md index 602fdad25f..df00e4216c 100644 --- a/docs/src/background/duality.md +++ b/docs/src/background/duality.md @@ -114,8 +114,8 @@ and similarly, the dual is: The scalar product is different from the canonical one for the sets [`PositiveSemidefiniteConeTriangle`](@ref), [`LogDetConeTriangle`](@ref), [`RootDetConeTriangle`](@ref), -[`FrobeniusProductPostiviveSemidefiniteConeTriangle`](@ref) and -[`LinearMatrixInequalityConeTriangle`](@ref). +[`SetWithDotProducts`](@ref) and +[`LinearCombinationInSet`](@ref). If the set ``C_i`` of the section [Duality](@ref) is one of these three cones, then the rows of the matrix ``A_i`` corresponding to off-diagonal entries are diff --git a/docs/src/manual/standard_form.md b/docs/src/manual/standard_form.md index ef4fd9d57b..38dde836c3 100644 --- a/docs/src/manual/standard_form.md +++ b/docs/src/manual/standard_form.md @@ -82,6 +82,8 @@ The vector-valued set types implemented in MathOptInterface.jl are: | [`RelativeEntropyCone(d)`](@ref MathOptInterface.RelativeEntropyCone) | ``\{ (u, v, w) \in \mathbb{R}^{d} : u \ge \sum_i w_i \log (\frac{w_i}{v_i}), v_i \ge 0, w_i \ge 0 \}`` | | [`HyperRectangle(l, u)`](@ref MathOptInterface.HyperRectangle) | ``\{x \in \bar{\mathbb{R}}^d: x_i \in [l_i, u_i] \forall i=1,\ldots,d\}`` | | [`NormCone(p, d)`](@ref MathOptInterface.NormCone) | ``\{ (t,x) \in \mathbb{R}^{d} : t \ge \left(\sum\limits_i \lvert x_i \rvert^p\right)^{\frac{1}{p}} \}`` | +| [`SetWithDotProcuts(s, v)`](@ref MathOptInterface.SetWithDotProducts) | The cone `s` with dot products with the fixed vectors `v`. | +| [`LinearCombinationInSet(s, v)`](@ref MathOptInterface.LinearCombinationInSet) | The cone of vector `(y, x)` such that ``\sum_i y_i v_i + x`` belongs to `s`. | ## Matrix cones @@ -100,8 +102,6 @@ The matrix-valued set types implemented in MathOptInterface.jl are: | [`HermitianPositiveSemidefiniteConeTriangle(d)`](@ref MathOptInterface.HermitianPositiveSemidefiniteConeTriangle) | The cone of Hermitian positive semidefinite matrices, with `side_dimension` rows and columns. | | [`Scaled(S)`](@ref MathOptInterface.Scaled) | The set `S` scaled so that [`Utilities.set_dot`](@ref MathOptInterface.Utilities.set_dot) corresponds to `LinearAlgebra.dot` | -| [`FrobeniusProductPostiviveSemidefiniteConeTriangle(d, A)`](@ref MathOptInterface.FrobeniusProductPostiviveSemidefiniteConeTriangle) | The cone of positive semidefinite matrices, with `side_dimension` rows and columns and their Frobenius inner product with the matrices in `A`. | -| [`LinearMatrixInequalityConeTriangle(d, A)`](@ref MathOptInterface.LinearMatrixInequalityConeTriangle) | The cone of vector `y` and symmetric `C`, with `side_dimension` rows and columns such that ``\sum_i y_i A_i + C`` is positive semidefinite. | Some of these cones can take two forms: `XXXConeTriangle` and `XXXConeSquare`. diff --git a/docs/src/reference/standard_form.md b/docs/src/reference/standard_form.md index c7e6e96045..963623def4 100644 --- a/docs/src/reference/standard_form.md +++ b/docs/src/reference/standard_form.md @@ -153,6 +153,6 @@ LogDetConeTriangle LogDetConeSquare RootDetConeTriangle RootDetConeSquare -FrobeniusProductPostiviveSemidefiniteConeTriangle -LinearMatrixInequalityConeTriangle +SetWithDotProducts, +LinearCombinationInSet, ``` diff --git a/src/Utilities/model.jl b/src/Utilities/model.jl index 892703c2ce..0cf349def1 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -806,8 +806,6 @@ const LessThanIndicatorZero{T} = MOI.Scaled{MOI.PositiveSemidefiniteConeTriangle}, MOI.RootDetConeTriangle, MOI.RootDetConeSquare, - MOI.FrobeniusProductPostiviveSemidefiniteConeTriangle, - MOI.LinearMatrixInequalityConeTriangle, MOI.LogDetConeTriangle, MOI.LogDetConeSquare, MOI.AllDifferent, diff --git a/src/Utilities/results.jl b/src/Utilities/results.jl index c4e6d1afd3..29acf4a8d1 100644 --- a/src/Utilities/results.jl +++ b/src/Utilities/results.jl @@ -498,29 +498,3 @@ function get_fallback( f = MOI.get(model, MOI.ConstraintFunction(), ci) return _variable_dual(T, model, attr, ci, f) end - -function set_dot( - x::AbstractVector, - y::AbstractVector, - set::Union{ - MOI.FrobeniusProductPostiviveSemidefiniteConeTriangle, - MOI.LinearMatrixInequalityConeTriangle, - }, -) - m = length(set.matrices) - return LinearAlgebra.dot(view(x, 1:m), view(y, 1:m)) + - triangle_dot(x, y, set.side_dimension, m) -end - - -function set_dot( - a::AbstractVector, - set::Union{ - MOI.FrobeniusProductPostiviveSemidefiniteConeTriangle, - MOI.LinearMatrixInequalityConeTriangle, - }, -) - b = copy(a) - triangle_coefficients!(b, set.side_dimension, length(set.matrices)) - return b -end diff --git a/src/Utilities/set_dot.jl b/src/Utilities/set_dot.jl index 31378da1fe..b6b2c8463f 100644 --- a/src/Utilities/set_dot.jl +++ b/src/Utilities/set_dot.jl @@ -86,6 +86,19 @@ function set_dot( return x[1] * y[1] + x[2] * y[2] + triangle_dot(x, y, set.side_dimension, 2) end +function set_dot( + x::AbstractVector, + y::AbstractVector, + set::Union{ + MOI.SetWithDotProducts, + MOI.LinearCombinationInSet, + }, +) + m = length(set.matrices) + return LinearAlgebra.dot(view(x, 1:m), view(y, 1:m)) + + set_dot(view(x, (m+1):length(x)), view(y, (m+1):length(y)), set.set) +end + """ dot_coefficients(a::AbstractVector, set::AbstractVectorSet) @@ -145,6 +158,19 @@ function dot_coefficients(a::AbstractVector, set::MOI.LogDetConeTriangle) return b end +function dot_coefficients( + a::AbstractVector, + set::Union{ + MOI.SetWithDotProducts, + MOI.LinearCombinationInSet, + }, +) + b = copy(a) + m = length(set.vectors) + b[(m+1):end] = dot_coefficients(b[(m+1):end], set.set) + return b +end + # For `SetDotScalingVector`, we would like to compute the dot product # of canonical vectors in O(1) instead of O(n) # See https://github.com/jump-dev/Dualization.jl/pull/135 diff --git a/src/sets.jl b/src/sets.jl index ec5389e87d..52f2cb3522 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -1804,58 +1804,52 @@ function Base.getproperty( end """ - FrobeniusProductPostiviveSemidefiniteConeTriangle{M}(side_dimension::Int, matrices::Vector{M}) + SetWithDotProducts(set, vectors::Vector{V}) -Given `m` symmetric matrices `A_1`, ..., `A_m` given in `matrices`, the frobenius inner -product of positive semidefinite matrices is the convex cone: -``\\{ ((\\langle A_1, X \\rangle, ..., \\langle A_m, X \\rangle, X) \\in \\mathbb{R}^{m + d(d+1)/2} : X \\text{ postive semidefinite} \\}``, -where the matrix `X` is represented in the same symmetric packed format as in -the [`PositiveSemidefiniteConeTriangle`](@ref). +Given a set `set` of dimension `d` and `m` vectors `v_1`, ..., `v_m` given in `vectors`, this is the set: +``\\{ ((\\langle v_1, x \\rangle, ..., \\langle v_m, x \\rangle, x) \\in \\mathbb{R}^{m + d} : x \\in \\text{set} \\}.`` """ -struct FrobeniusProductPostiviveSemidefiniteConeTriangle{M} <: AbstractVectorSet - side_dimension::Int - matrices::Vector{M} +struct SetWithDotProducts{S,V} <: AbstractVectorSet + set::S + vectors::Vector{V} end -function dimension(s::FrobeniusProductPostiviveSemidefiniteConeTriangle) - return length(s.matrices) + s.side_dimension^2 +function dimension(s::SetWithDotProducts) + return length(s.vectors) + dimension(s.set) end -function dual_set(s::FrobeniusProductPostiviveSemidefiniteConeTriangle) - return LinearMatrixInequalityConeTriangle(s.side_dimension, s.matrices) +function dual_set(s::SetWithDotProducts) + return LinearCombinationInSet(s.set, s.vectors) end function dual_set_type( - ::Type{FrobeniusProductPostiviveSemidefiniteConeTriangle{M}}, -) where {M} - return LinearMatrixInequalityConeTriangle + ::Type{SetWithDotProducts{S,V}}, +) where {S,V} + return LinearCombinationInSet{S,V} end """ - LinearMatrixInequalityConeTriangle{M}(side_dimension::Int, matrices::Vector{M}) + LinearCombinationInSet{S,V}(set::S, matrices::Vector{V}) -Given `m` symmetric matrices `A_1`, ..., `A_m` given in `matrices`, the linear -matrix inequality cone is the convex cone: -``\\{ ((y, C) \\in \\mathbb{R}^{m + d(d+1)/2} : \\sum_{i=1}^m y_i A_i + C \\text{ postive semidefinite} \\}``, -where the matrix `C` is represented in the same symmetric packed format as in -the [`PositiveSemidefiniteConeTriangle`](@ref). +Given a set `set` of dimension `d` and `m` vectors `v_1`, ..., `v_m` given in `vectors`, this is the set: +``\\{ ((y, x) \\in \\mathbb{R}^{m + d} : \\sum_{i=1}^m y_i v_i + x \\in \\text{set} \\}.`` """ -struct LinearMatrixInequalityConeTriangle{M} <: AbstractVectorSet - side_dimension::Int - matrices::Vector{M} +struct LinearCombinationInSet{S,V} <: AbstractVectorSet + set::S + vectors::Vector{V} end -dimension(s::LinearMatrixInequalityConeTriangle) = length(s.matrices) + s.side_dimension^2 +dimension(s::LinearCombinationInSet) = length(s.vectors) + simension(s.set) -function dual_set(s::LinearMatrixInequalityConeTriangle) - return FrobeniusProductPostiviveSemidefiniteConeTriangle( +function dual_set(s::LinearCombinationInSet) + return SetWithDotProducts( s.side_dimension, s.matrices, ) end -function dual_set_type(::Type{LinearMatrixInequalityConeTriangle{M}}) where {M} - return FrobeniusProductPostiviveSemidefiniteConeTriangle +function dual_set_type(::Type{LinearCombinationInSet{S,V}}) where {S,V} + return SetWithDotProducts{S,V} end """ From f7fa2884a4645289ac86d0d4497e2432a982b81b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 10 Jul 2024 18:00:18 +0200 Subject: [PATCH 03/21] Fix format --- src/Utilities/set_dot.jl | 10 ++-------- src/sets.jl | 9 ++------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/Utilities/set_dot.jl b/src/Utilities/set_dot.jl index b6b2c8463f..e571c167fb 100644 --- a/src/Utilities/set_dot.jl +++ b/src/Utilities/set_dot.jl @@ -89,10 +89,7 @@ end function set_dot( x::AbstractVector, y::AbstractVector, - set::Union{ - MOI.SetWithDotProducts, - MOI.LinearCombinationInSet, - }, + set::Union{MOI.SetWithDotProducts,MOI.LinearCombinationInSet}, ) m = length(set.matrices) return LinearAlgebra.dot(view(x, 1:m), view(y, 1:m)) + @@ -160,10 +157,7 @@ end function dot_coefficients( a::AbstractVector, - set::Union{ - MOI.SetWithDotProducts, - MOI.LinearCombinationInSet, - }, + set::Union{MOI.SetWithDotProducts,MOI.LinearCombinationInSet}, ) b = copy(a) m = length(set.vectors) diff --git a/src/sets.jl b/src/sets.jl index 52f2cb3522..cb2a464f3d 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -1822,9 +1822,7 @@ function dual_set(s::SetWithDotProducts) return LinearCombinationInSet(s.set, s.vectors) end -function dual_set_type( - ::Type{SetWithDotProducts{S,V}}, -) where {S,V} +function dual_set_type(::Type{SetWithDotProducts{S,V}}) where {S,V} return LinearCombinationInSet{S,V} end @@ -1842,10 +1840,7 @@ end dimension(s::LinearCombinationInSet) = length(s.vectors) + simension(s.set) function dual_set(s::LinearCombinationInSet) - return SetWithDotProducts( - s.side_dimension, - s.matrices, - ) + return SetWithDotProducts(s.side_dimension, s.matrices) end function dual_set_type(::Type{LinearCombinationInSet{S,V}}) where {S,V} From 7e11ac92b4d56649478303079537c9e1ecc35d65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 10 Jul 2024 18:15:23 +0200 Subject: [PATCH 04/21] Update docs/src/manual/standard_form.md --- docs/src/manual/standard_form.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/manual/standard_form.md b/docs/src/manual/standard_form.md index 38dde836c3..dd1d0217ec 100644 --- a/docs/src/manual/standard_form.md +++ b/docs/src/manual/standard_form.md @@ -82,7 +82,7 @@ The vector-valued set types implemented in MathOptInterface.jl are: | [`RelativeEntropyCone(d)`](@ref MathOptInterface.RelativeEntropyCone) | ``\{ (u, v, w) \in \mathbb{R}^{d} : u \ge \sum_i w_i \log (\frac{w_i}{v_i}), v_i \ge 0, w_i \ge 0 \}`` | | [`HyperRectangle(l, u)`](@ref MathOptInterface.HyperRectangle) | ``\{x \in \bar{\mathbb{R}}^d: x_i \in [l_i, u_i] \forall i=1,\ldots,d\}`` | | [`NormCone(p, d)`](@ref MathOptInterface.NormCone) | ``\{ (t,x) \in \mathbb{R}^{d} : t \ge \left(\sum\limits_i \lvert x_i \rvert^p\right)^{\frac{1}{p}} \}`` | -| [`SetWithDotProcuts(s, v)`](@ref MathOptInterface.SetWithDotProducts) | The cone `s` with dot products with the fixed vectors `v`. | +| [`SetWithDotProducts(s, v)`](@ref MathOptInterface.SetWithDotProducts) | The cone `s` with dot products with the fixed vectors `v`. | | [`LinearCombinationInSet(s, v)`](@ref MathOptInterface.LinearCombinationInSet) | The cone of vector `(y, x)` such that ``\sum_i y_i v_i + x`` belongs to `s`. | ## Matrix cones From 01fa32ca423c5943c95a944888b41740bc2bef38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 10 Jul 2024 19:48:56 +0200 Subject: [PATCH 05/21] Add low rank matrix --- src/sets.jl | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/sets.jl b/src/sets.jl index cb2a464f3d..6822eca392 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -1847,6 +1847,23 @@ function dual_set_type(::Type{LinearCombinationInSet{S,V}}) where {S,V} return SetWithDotProducts{S,V} end +""" + struct LowRankMatrix{T} + diagonal::Vector{T} + factor::Matrix{T} + end + +`factor * Diagonal(diagonal) * factor'`. +""" +struct LowRankMatrix{T} + diagonal::Vector{T} + factor::Matrix{T} +end + +struct TriangleVectorization{M} + matrix::M +end + """ SOS1{T<:Real}(weights::Vector{T}) From 114fa43fb852dc08071aac9fa722d4a14e7c7e88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 11 Jul 2024 23:25:32 +0200 Subject: [PATCH 06/21] Add bridge --- src/Bridges/Variable/Variable.jl | 1 + src/Bridges/Variable/bridges/set_dot.jl | 62 +++++++++++++++++++++++++ src/Bridges/Variable/set_map.jl | 11 +++-- src/Bridges/set_map.jl | 4 ++ src/Utilities/functions.jl | 9 +++- src/sets.jl | 22 ++++++++- 6 files changed, 100 insertions(+), 9 deletions(-) create mode 100644 src/Bridges/Variable/bridges/set_dot.jl diff --git a/src/Bridges/Variable/Variable.jl b/src/Bridges/Variable/Variable.jl index 1cffd8fd62..62778ae90e 100644 --- a/src/Bridges/Variable/Variable.jl +++ b/src/Bridges/Variable/Variable.jl @@ -34,6 +34,7 @@ function add_all_bridges(model, ::Type{T}) where {T} MOI.Bridges.add_bridge(model, RSOCtoPSDBridge{T}) MOI.Bridges.add_bridge(model, HermitianToSymmetricPSDBridge{T}) MOI.Bridges.add_bridge(model, ParameterToEqualToBridge{T}) + MOI.Bridges.add_bridge(model, DotProductsBridge{T}) return end diff --git a/src/Bridges/Variable/bridges/set_dot.jl b/src/Bridges/Variable/bridges/set_dot.jl new file mode 100644 index 0000000000..b2643321f3 --- /dev/null +++ b/src/Bridges/Variable/bridges/set_dot.jl @@ -0,0 +1,62 @@ +struct DotProductsBridge{T,S,V} <: SetMapBridge{T,S,MOI.SetWithDotProducts{S,V}} + variables::Vector{MOI.VariableIndex} + constraint::MOI.ConstraintIndex{MOI.VectorOfVariables, S} + set::MOI.SetWithDotProducts{S,V} +end + +function supports_constrained_variable( + ::Type{<:DotProductsBridge}, + ::Type{<:MOI.SetWithDotProducts}, +) + return true +end + +function concrete_bridge_type( + ::Type{<:DotProductsBridge{T}}, + ::Type{MOI.SetWithDotProducts{S,V}}, +) where {T,S,V} + return DotProductsBridge{T,S,V} +end + +function bridge_constrained_variable( + BT::Type{DotProductsBridge{T,S,V}}, + model::MOI.ModelLike, + set::MOI.SetWithDotProducts{S,V}, +) where {T,S,V} + variables, constraint = + _add_constrained_var(model, MOI.Bridges.inverse_map_set(BT, set)) + return BT(variables, constraint, set) +end + +function MOI.Bridges.map_set( + bridge::DotProductsBridge{T,S}, + set::S, +) where {T,S} + return MOI.SetWithDotProducts(set, bridge.vectors) +end + +function MOI.Bridges.inverse_map_set( + ::Type{<:DotProductsBridge}, + set::MOI.SetWithDotProducts, +) + return set.set +end + +function MOI.Bridges.map_function( + bridge::DotProductsBridge{T}, + func, + i::MOI.Bridges.IndexInVector, +) where {T} + scalars = MOI.Utilities.eachscalar(func) + if i.value in eachindex(bridge.set.vectors) + return MOI.Utilities.set_dot(bridge.set.vectors[i.value], scalars, bridge.set.set) + else + return convert(MOI.ScalarAffineFunction{T}, scalars[i.value - length(bridge.vectors)]) + end +end + +function MOI.Bridges.inverse_map_function(bridge::DotProductsBridge{T}, func) where {T} + m = length(bridge.set.vectors) + return MOI.Utilities.operate(vcat, T, MOI.Utilities.eachscalar(func)[(m+1):end]) +end + diff --git a/src/Bridges/Variable/set_map.jl b/src/Bridges/Variable/set_map.jl index 6ebaf57538..574ef805cf 100644 --- a/src/Bridges/Variable/set_map.jl +++ b/src/Bridges/Variable/set_map.jl @@ -171,7 +171,7 @@ function MOI.get( if any(isnothing, value) return nothing end - return MOI.Bridges.map_function(typeof(bridge), value, i) + return MOI.Bridges.map_function(bridge, value, i) end function MOI.supports( @@ -203,7 +203,7 @@ function MOI.Bridges.bridged_function( i::MOI.Bridges.IndexInVector, ) where {T} func = MOI.Bridges.map_function( - typeof(bridge), + bridge, MOI.VectorOfVariables(bridge.variables), i, ) @@ -212,7 +212,7 @@ end function unbridged_map(bridge::SetMapBridge{T}, vi::MOI.VariableIndex) where {T} F = MOI.ScalarAffineFunction{T} - mapped = MOI.Bridges.inverse_map_function(typeof(bridge), vi) + mapped = MOI.Bridges.inverse_map_function(bridge, vi) return Pair{MOI.VariableIndex,F}[bridge.variable=>mapped] end @@ -222,9 +222,10 @@ function unbridged_map( ) where {T} F = MOI.ScalarAffineFunction{T} func = MOI.VectorOfVariables(vis) - funcs = MOI.Bridges.inverse_map_function(typeof(bridge), func) + funcs = MOI.Bridges.inverse_map_function(bridge, func) scalars = MOI.Utilities.eachscalar(funcs) + # FIXME not correct for SetWithDotProducts, it won't recover the dot product variables return Pair{MOI.VariableIndex,F}[ - bridge.variables[i] => scalars[i] for i in eachindex(vis) + bridge.variables[i] => scalars[i] for i in eachindex(bridge.variables) ] end diff --git a/src/Bridges/set_map.jl b/src/Bridges/set_map.jl index c665c1dd00..11a51a9906 100644 --- a/src/Bridges/set_map.jl +++ b/src/Bridges/set_map.jl @@ -80,6 +80,10 @@ function map_function(::Type{BT}, func, i::IndexInVector) where {BT} return MOI.Utilities.eachscalar(map_function(BT, func))[i.value] end +function map_function(bridge::AbstractBridge, func, i::IndexInVector) + return map_function(typeof(bridge), func, i) +end + """ inverse_map_function(bridge::MOI.Bridges.AbstractBridge, func) inverse_map_function(::Type{BT}, func) where {BT} diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index 85cda9e0df..b2579159f0 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -638,18 +638,23 @@ end A type that allows iterating over the scalar-functions that comprise an `AbstractVectorFunction`. """ -struct ScalarFunctionIterator{F<:MOI.AbstractVectorFunction,C} +struct ScalarFunctionIterator{F<:MOI.AbstractVectorFunction,C,S} <: AbstractVector{S} f::F # Cache that can be used to store a precomputed datastructure that allows # an efficient implementation of `getindex`. cache::C + function ScalarFunctionIterator(f::MOI.AbstractVectorFunction, cache) + return new{typeof(f),typeof(cache),scalar_type(typeof(f))}(f, cache) + end end function ScalarFunctionIterator(func::MOI.AbstractVectorFunction) return ScalarFunctionIterator(func, scalar_iterator_cache(func)) end -scalar_iterator_cache(func::MOI.AbstractVectorFunction) = nothing +Base.size(s::ScalarFunctionIterator) = (MOI.output_dimension(s.f),) + +scalar_iterator_cache(::MOI.AbstractVectorFunction) = nothing function output_index_iterator(terms::AbstractVector, output_dimension) start = zeros(Int, output_dimension) diff --git a/src/sets.jl b/src/sets.jl index 6822eca392..30dde096a8 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -1855,15 +1855,33 @@ end `factor * Diagonal(diagonal) * factor'`. """ -struct LowRankMatrix{T} +struct LowRankMatrix{T} <: AbstractMatrix{T} diagonal::Vector{T} factor::Matrix{T} end -struct TriangleVectorization{M} +function Base.size(m::LowRankMatrix) + n = size(m.factor, 1) + return (n, n) +end + +function Base.getindex(m::LowRankMatrix, i::Int, j::Int) + return sum(m.factor[i, k] * m.diagonal[k] * m.factor[j, k]' for k in eachindex(m.diagonal)) +end + +struct TriangleVectorization{T,M<:AbstractMatrix{T}} <: AbstractVector{T} matrix::M end +function Base.size(v::TriangleVectorization) + n = size(v.matrix, 1) + return (Utilities.trimap(n, n),) +end + +function Base.getindex(v::TriangleVectorization, k::Int) + return getindex(v.matrix, Utilities.inverse_trimap(k)...) +end + """ SOS1{T<:Real}(weights::Vector{T}) From c126a5758374c0896e8e3ca8f59e0d57ff199e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Fri, 19 Jul 2024 11:49:13 -0400 Subject: [PATCH 07/21] Add copy --- src/sets.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sets.jl b/src/sets.jl index 30dde096a8..d7b8607ecf 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -1814,6 +1814,10 @@ struct SetWithDotProducts{S,V} <: AbstractVectorSet vectors::Vector{V} end +function Base.copy(s::SetWithDotProducts) + return SetWithDotProducts(copy(s.set), copy(s.vectors)) +end + function dimension(s::SetWithDotProducts) return length(s.vectors) + dimension(s.set) end From ce419fcc14db554148728a87c460910227b02a9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 30 Jul 2024 11:12:17 +0200 Subject: [PATCH 08/21] Updates --- src/Bridges/Variable/bridges/set_dot.jl | 30 ++++-- src/Utilities/functions.jl | 3 +- src/sets.jl | 120 +++++++++++++++++------- test/sets.jl | 36 +++++++ 4 files changed, 146 insertions(+), 43 deletions(-) diff --git a/src/Bridges/Variable/bridges/set_dot.jl b/src/Bridges/Variable/bridges/set_dot.jl index b2643321f3..ba593a717a 100644 --- a/src/Bridges/Variable/bridges/set_dot.jl +++ b/src/Bridges/Variable/bridges/set_dot.jl @@ -1,6 +1,6 @@ struct DotProductsBridge{T,S,V} <: SetMapBridge{T,S,MOI.SetWithDotProducts{S,V}} variables::Vector{MOI.VariableIndex} - constraint::MOI.ConstraintIndex{MOI.VectorOfVariables, S} + constraint::MOI.ConstraintIndex{MOI.VectorOfVariables,S} set::MOI.SetWithDotProducts{S,V} end @@ -28,10 +28,7 @@ function bridge_constrained_variable( return BT(variables, constraint, set) end -function MOI.Bridges.map_set( - bridge::DotProductsBridge{T,S}, - set::S, -) where {T,S} +function MOI.Bridges.map_set(bridge::DotProductsBridge{T,S}, set::S) where {T,S} return MOI.SetWithDotProducts(set, bridge.vectors) end @@ -49,14 +46,27 @@ function MOI.Bridges.map_function( ) where {T} scalars = MOI.Utilities.eachscalar(func) if i.value in eachindex(bridge.set.vectors) - return MOI.Utilities.set_dot(bridge.set.vectors[i.value], scalars, bridge.set.set) + return MOI.Utilities.set_dot( + bridge.set.vectors[i.value], + scalars, + bridge.set.set, + ) else - return convert(MOI.ScalarAffineFunction{T}, scalars[i.value - length(bridge.vectors)]) + return convert( + MOI.ScalarAffineFunction{T}, + scalars[i.value-length(bridge.vectors)], + ) end end -function MOI.Bridges.inverse_map_function(bridge::DotProductsBridge{T}, func) where {T} +function MOI.Bridges.inverse_map_function( + bridge::DotProductsBridge{T}, + func, +) where {T} m = length(bridge.set.vectors) - return MOI.Utilities.operate(vcat, T, MOI.Utilities.eachscalar(func)[(m+1):end]) + return MOI.Utilities.operate( + vcat, + T, + MOI.Utilities.eachscalar(func)[(m+1):end], + ) end - diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index b2579159f0..2d558f92e4 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -638,7 +638,8 @@ end A type that allows iterating over the scalar-functions that comprise an `AbstractVectorFunction`. """ -struct ScalarFunctionIterator{F<:MOI.AbstractVectorFunction,C,S} <: AbstractVector{S} +struct ScalarFunctionIterator{F<:MOI.AbstractVectorFunction,C,S} <: + AbstractVector{S} f::F # Cache that can be used to store a precomputed datastructure that allows # an efficient implementation of `getindex`. diff --git a/src/sets.jl b/src/sets.jl index d7b8607ecf..2af3b3e4cd 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -1804,73 +1804,129 @@ function Base.getproperty( end """ - SetWithDotProducts(set, vectors::Vector{V}) + SetWithDotProducts(set::MOI.AbstractSet, vectors::AbstractVector) -Given a set `set` of dimension `d` and `m` vectors `v_1`, ..., `v_m` given in `vectors`, this is the set: -``\\{ ((\\langle v_1, x \\rangle, ..., \\langle v_m, x \\rangle, x) \\in \\mathbb{R}^{m + d} : x \\in \\text{set} \\}.`` +Given a set `set` of dimension `d` and `m` vectors `a_1`, ..., `a_m` given in `vectors`, this is the set: +``\\{ ((\\langle a_1, x \\rangle, ..., \\langle a_m, x \\rangle) \\in \\mathbb{R}^{m} : x \\in \\text{set} \\}.`` """ -struct SetWithDotProducts{S,V} <: AbstractVectorSet +struct SetWithDotProducts{S,A,V<:AbstractVector{A}} <: AbstractVectorSet set::S - vectors::Vector{V} + vectors::V end function Base.copy(s::SetWithDotProducts) return SetWithDotProducts(copy(s.set), copy(s.vectors)) end -function dimension(s::SetWithDotProducts) - return length(s.vectors) + dimension(s.set) -end +dimension(s::SetWithDotProducts) = length(s.vectors) function dual_set(s::SetWithDotProducts) return LinearCombinationInSet(s.set, s.vectors) end -function dual_set_type(::Type{SetWithDotProducts{S,V}}) where {S,V} - return LinearCombinationInSet{S,V} +function dual_set_type(::Type{SetWithDotProducts{S,A,V}}) where {S,A,V} + return LinearCombinationInSet{S,A,V} end """ - LinearCombinationInSet{S,V}(set::S, matrices::Vector{V}) + LinearCombinationInSet(set::MOI.AbstractSet, matrices::AbstractVector) -Given a set `set` of dimension `d` and `m` vectors `v_1`, ..., `v_m` given in `vectors`, this is the set: -``\\{ ((y, x) \\in \\mathbb{R}^{m + d} : \\sum_{i=1}^m y_i v_i + x \\in \\text{set} \\}.`` +Given a set `set` of dimension `d` and `m` vectors `a_1`, ..., `a_m` given in `vectors`, this is the set: +``\\{ (y \\in \\mathbb{R}^{m} : \\sum_{i=1}^m y_i a_i \\in \\text{set} \\}.`` """ -struct LinearCombinationInSet{S,V} <: AbstractVectorSet +struct LinearCombinationInSet{S,A,V<:AbstractVector{A}} <: AbstractVectorSet set::S - vectors::Vector{V} + vectors::V end -dimension(s::LinearCombinationInSet) = length(s.vectors) + simension(s.set) +dimension(s::LinearCombinationInSet) = length(s.vectors) function dual_set(s::LinearCombinationInSet) - return SetWithDotProducts(s.side_dimension, s.matrices) + return SetWithDotProducts(s.side_dimension, s.vectors) +end + +function dual_set_type(::Type{LinearCombinationInSet{S,A,V}}) where {S,A,V} + return SetWithDotProducts{S,A,V} end -function dual_set_type(::Type{LinearCombinationInSet{S,V}}) where {S,V} - return SetWithDotProducts{S,V} +abstract type AbstractFactorization{T,F} <: AbstractMatrix{T} end + +function Base.size(m::AbstractFactorization) + n = size(m.factor, 1) + return (n, n) end """ - struct LowRankMatrix{T} - diagonal::Vector{T} - factor::Matrix{T} + struct Factorization{ + T, + F<:Union{AbstractVector{T},AbstractMatrix{T}}, + D<:Union{T,AbstractVector{T}}, + } <: AbstractMatrix{T} + factor::F + scaling::D end -`factor * Diagonal(diagonal) * factor'`. -""" -struct LowRankMatrix{T} <: AbstractMatrix{T} - diagonal::Vector{T} - factor::Matrix{T} +Matrix corresponding to `factor * Diagonal(diagonal) * factor'`. +If `factor` is a vector and `diagonal` is a scalar, this corresponds to +the matrix `diagonal * factor * factor'`. +If `factor` is a matrix and `diagonal` is a vector, this corresponds to +the matrix `factor * Diagonal(scaling) * factor'`. +""" +struct Factorization{ + T, + F<:Union{AbstractVector{T},AbstractMatrix{T}}, + D<:Union{T,AbstractVector{T}}, +} <: AbstractFactorization{T,F} + factor::F + scaling::D + function Factorization( + factor::AbstractMatrix{T}, + scaling::AbstractVector{T}, + ) where {T} + if length(scaling) != size(factor, 2) + error( + "Length `$(length(scaling))` of diagonal does not match number of columns `$(size(factor, 2))` of factor", + ) + end + return new{T,typeof(factor),typeof(scaling)}(factor, scaling) + end + function Factorization(factor::AbstractVector{T}, scaling::T) where {T} + return new{T,typeof(factor),typeof(scaling)}(factor, scaling) + end end -function Base.size(m::LowRankMatrix) - n = size(m.factor, 1) - return (n, n) +function Base.getindex(m::Factorization, i::Int, j::Int) + return sum( + m.factor[i, k] * m.scaling[k] * m.factor[j, k]' for + k in eachindex(m.scaling) + ) +end + +""" + struct Factorization{ + T, + F<:Union{AbstractVector{T},AbstractMatrix{T}}, + D<:Union{T,AbstractVector{T}}, + } <: AbstractMatrix{T} + factor::F + scaling::D + end + +Matrix corresponding to `factor * Diagonal(diagonal) * factor'`. +If `factor` is a vector and `diagonal` is a scalar, this corresponds to +the matrix `diagonal * factor * factor'`. +If `factor` is a matrix and `diagonal` is a vector, this corresponds to +the matrix `factor * Diagonal(scaling) * factor'`. +""" +struct PositiveSemidefiniteFactorization{ + T, + F<:Union{AbstractVector{T},AbstractMatrix{T}}, +} <: AbstractFactorization{T,F} + factor::F end -function Base.getindex(m::LowRankMatrix, i::Int, j::Int) - return sum(m.factor[i, k] * m.diagonal[k] * m.factor[j, k]' for k in eachindex(m.diagonal)) +function Base.getindex(m::PositiveSemidefiniteFactorization, i::Int, j::Int) + return sum(m.factor[i, k] * m.factor[j, k]' for k in axes(m.factor, 2)) end struct TriangleVectorization{T,M<:AbstractMatrix{T}} <: AbstractVector{T} diff --git a/test/sets.jl b/test/sets.jl index bf52326974..122a562f7b 100644 --- a/test/sets.jl +++ b/test/sets.jl @@ -8,6 +8,7 @@ module TestSets using Test import MathOptInterface as MOI +import LinearAlgebra include("dummy.jl") @@ -353,6 +354,41 @@ function test_sets_reified() return end +function _test_factorization(A, B) + @test size(A) == size(B) + @test A ≈ B + d = LinearAlgebra.checksquare(A) + n = div(d * (d + 1), 2) + vA = MOI.TriangleVectorization(A) + @test length(vA) == n + @test eachindex(vA) == Base.OneTo(n) + vB = MOI.TriangleVectorization(B) + @test length(vB) == n + @test eachindex(vA) == Base.OneTo(n) + k = 0 + for j in 1:d + for i in 1:j + k += 1 + @test vA[k] == vB[k] + @test vA[k] == A[i, j] + end + end + return +end + +function test_factorizations() + f = [1, 2] + _test_factorization(f * f', MOI.PositiveSemidefiniteFactorization(f)) + _test_factorization(2 * f * f', MOI.Factorization(f, 2)) + F = [1 2; 3 4; 5 6] + d = [7, 8] + _test_factorization(F * F', MOI.PositiveSemidefiniteFactorization(F)) + return _test_factorization( + F * LinearAlgebra.Diagonal(d) * F', + MOI.Factorization(F, d), + ) +end + function runtests() for name in names(@__MODULE__; all = true) if startswith("$name", "test_") From c80fd0121ff0f44ddc4c967de410da470cd2e751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 30 Jul 2024 11:30:21 +0200 Subject: [PATCH 09/21] Fix --- docs/src/reference/standard_form.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/reference/standard_form.md b/docs/src/reference/standard_form.md index 963623def4..f24c009084 100644 --- a/docs/src/reference/standard_form.md +++ b/docs/src/reference/standard_form.md @@ -153,6 +153,6 @@ LogDetConeTriangle LogDetConeSquare RootDetConeTriangle RootDetConeSquare -SetWithDotProducts, -LinearCombinationInSet, +SetWithDotProducts +LinearCombinationInSet ``` From 2ef6709ab41405bedf89eb83e9372461ce3ac450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 30 Jul 2024 12:00:25 +0200 Subject: [PATCH 10/21] Add test for bridge --- src/Bridges/Variable/bridges/set_dot.jl | 57 +++++++++----------- src/Bridges/Variable/set_map.jl | 2 +- src/sets.jl | 4 ++ test/Bridges/Variable/set_dot.jl | 72 +++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 32 deletions(-) create mode 100644 test/Bridges/Variable/set_dot.jl diff --git a/src/Bridges/Variable/bridges/set_dot.jl b/src/Bridges/Variable/bridges/set_dot.jl index ba593a717a..d8b5dd1df8 100644 --- a/src/Bridges/Variable/bridges/set_dot.jl +++ b/src/Bridges/Variable/bridges/set_dot.jl @@ -1,7 +1,7 @@ -struct DotProductsBridge{T,S,V} <: SetMapBridge{T,S,MOI.SetWithDotProducts{S,V}} +struct DotProductsBridge{T,S,A,V} <: SetMapBridge{T,S,MOI.SetWithDotProducts{S,A,V}} variables::Vector{MOI.VariableIndex} constraint::MOI.ConstraintIndex{MOI.VectorOfVariables,S} - set::MOI.SetWithDotProducts{S,V} + set::MOI.SetWithDotProducts{S,A,V} end function supports_constrained_variable( @@ -13,23 +13,23 @@ end function concrete_bridge_type( ::Type{<:DotProductsBridge{T}}, - ::Type{MOI.SetWithDotProducts{S,V}}, -) where {T,S,V} - return DotProductsBridge{T,S,V} + ::Type{MOI.SetWithDotProducts{S,A,V}}, +) where {T,S,A,V} + return DotProductsBridge{T,S,A,V} end function bridge_constrained_variable( - BT::Type{DotProductsBridge{T,S,V}}, + BT::Type{DotProductsBridge{T,S,A,V}}, model::MOI.ModelLike, - set::MOI.SetWithDotProducts{S,V}, -) where {T,S,V} + set::MOI.SetWithDotProducts{S,A,V}, +) where {T,S,A,V} variables, constraint = _add_constrained_var(model, MOI.Bridges.inverse_map_set(BT, set)) return BT(variables, constraint, set) end function MOI.Bridges.map_set(bridge::DotProductsBridge{T,S}, set::S) where {T,S} - return MOI.SetWithDotProducts(set, bridge.vectors) + return bridge.set end function MOI.Bridges.inverse_map_set( @@ -45,28 +45,23 @@ function MOI.Bridges.map_function( i::MOI.Bridges.IndexInVector, ) where {T} scalars = MOI.Utilities.eachscalar(func) - if i.value in eachindex(bridge.set.vectors) - return MOI.Utilities.set_dot( - bridge.set.vectors[i.value], - scalars, - bridge.set.set, - ) - else - return convert( - MOI.ScalarAffineFunction{T}, - scalars[i.value-length(bridge.vectors)], - ) - end + return MOI.Utilities.set_dot( + bridge.set.vectors[i.value], + scalars, + bridge.set.set, + ) end -function MOI.Bridges.inverse_map_function( - bridge::DotProductsBridge{T}, - func, -) where {T} - m = length(bridge.set.vectors) - return MOI.Utilities.operate( - vcat, - T, - MOI.Utilities.eachscalar(func)[(m+1):end], - ) +# This returns `true` by default for `SetMapBridge` +# but is is not supported for this bridge because `inverse_map_function` +# is not implemented +function MOI.supports(::MOI.ModelLike, ::MOI.VariablePrimalStart, ::Type{<:DotProductsBridge}) + return false +end + +function unbridged_map( + ::DotProductsBridge, + ::Vector{MOI.VariableIndex}, +) + return nothing end diff --git a/src/Bridges/Variable/set_map.jl b/src/Bridges/Variable/set_map.jl index 574ef805cf..6d0e39091e 100644 --- a/src/Bridges/Variable/set_map.jl +++ b/src/Bridges/Variable/set_map.jl @@ -129,7 +129,7 @@ function MOI.get( bridge::SetMapBridge, ) set = MOI.get(model, attr, bridge.constraint) - return MOI.Bridges.map_set(typeof(bridge), set) + return MOI.Bridges.map_set(bridge, set) end function MOI.set( diff --git a/src/sets.jl b/src/sets.jl index 2af3b3e4cd..ed49de0d6b 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -1814,6 +1814,10 @@ struct SetWithDotProducts{S,A,V<:AbstractVector{A}} <: AbstractVectorSet vectors::V end +function Base.:(==)(s1::SetWithDotProducts, s2::SetWithDotProducts) + return s1.set == s2.set && s1.vectors == s2.vectors +end + function Base.copy(s::SetWithDotProducts) return SetWithDotProducts(copy(s.set), copy(s.vectors)) end diff --git a/test/Bridges/Variable/set_dot.jl b/test/Bridges/Variable/set_dot.jl new file mode 100644 index 0000000000..e77d775b64 --- /dev/null +++ b/test/Bridges/Variable/set_dot.jl @@ -0,0 +1,72 @@ +# 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 TestVariableDotProducts + +using Test + +import MathOptInterface as MOI + +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_psd() + MOI.Bridges.runtests( + MOI.Bridges.Variable.DotProductsBridge, + model -> begin + x, _ = MOI.add_constrained_variables(model, + MOI.SetWithDotProducts( + MOI.PositiveSemidefiniteConeTriangle(2), + MOI.TriangleVectorization.([ + [1 2.0 + 2 3], + [4 5.0 + 5 6], + ]), + ) + ) + MOI.add_constraint( + model, + 1.0x[1], + MOI.EqualTo(0.0), + ) + MOI.add_constraint( + model, + 1.0x[2], + MOI.LessThan(0.0), + ) + end, + model -> begin + Q, _ = MOI.add_constrained_variables(model, MOI.PositiveSemidefiniteConeTriangle(2)) + MOI.add_constraint( + model, + 1.0 * Q[1] + 4.0 * Q[2] + 3.0 * Q[3], + MOI.EqualTo(0.0), + ) + MOI.add_constraint( + model, + 4.0 * Q[1] + 10.0 * Q[2] + 6.0 * Q[3], + MOI.LessThan(0.0), + ) + end; + cannot_unbridge = true, + ) + return +end + +end # module + +TestVariableDotProducts.runtests() From 046e0a3917a4589ecca0cf58819cbd1bd7db64aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 30 Jul 2024 12:05:51 +0200 Subject: [PATCH 11/21] Fix format --- src/Bridges/Variable/bridges/set_dot.jl | 14 +++++----- test/Bridges/Variable/set_dot.jl | 34 ++++++++++++------------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/Bridges/Variable/bridges/set_dot.jl b/src/Bridges/Variable/bridges/set_dot.jl index d8b5dd1df8..d378e7d290 100644 --- a/src/Bridges/Variable/bridges/set_dot.jl +++ b/src/Bridges/Variable/bridges/set_dot.jl @@ -1,4 +1,5 @@ -struct DotProductsBridge{T,S,A,V} <: SetMapBridge{T,S,MOI.SetWithDotProducts{S,A,V}} +struct DotProductsBridge{T,S,A,V} <: + SetMapBridge{T,S,MOI.SetWithDotProducts{S,A,V}} variables::Vector{MOI.VariableIndex} constraint::MOI.ConstraintIndex{MOI.VectorOfVariables,S} set::MOI.SetWithDotProducts{S,A,V} @@ -55,13 +56,14 @@ end # This returns `true` by default for `SetMapBridge` # but is is not supported for this bridge because `inverse_map_function` # is not implemented -function MOI.supports(::MOI.ModelLike, ::MOI.VariablePrimalStart, ::Type{<:DotProductsBridge}) +function MOI.supports( + ::MOI.ModelLike, + ::MOI.VariablePrimalStart, + ::Type{<:DotProductsBridge}, +) return false end -function unbridged_map( - ::DotProductsBridge, - ::Vector{MOI.VariableIndex}, -) +function unbridged_map(::DotProductsBridge, ::Vector{MOI.VariableIndex}) return nothing end diff --git a/test/Bridges/Variable/set_dot.jl b/test/Bridges/Variable/set_dot.jl index e77d775b64..65a058af4f 100644 --- a/test/Bridges/Variable/set_dot.jl +++ b/test/Bridges/Variable/set_dot.jl @@ -27,30 +27,30 @@ function test_psd() MOI.Bridges.runtests( MOI.Bridges.Variable.DotProductsBridge, model -> begin - x, _ = MOI.add_constrained_variables(model, + x, _ = MOI.add_constrained_variables( + model, MOI.SetWithDotProducts( MOI.PositiveSemidefiniteConeTriangle(2), MOI.TriangleVectorization.([ - [1 2.0 - 2 3], - [4 5.0 - 5 6], + [ + 1 2.0 + 2 3 + ], + [ + 4 5.0 + 5 6 + ], ]), - ) - ) - MOI.add_constraint( - model, - 1.0x[1], - MOI.EqualTo(0.0), - ) - MOI.add_constraint( - model, - 1.0x[2], - MOI.LessThan(0.0), + ), ) + MOI.add_constraint(model, 1.0x[1], MOI.EqualTo(0.0)) + MOI.add_constraint(model, 1.0x[2], MOI.LessThan(0.0)) end, model -> begin - Q, _ = MOI.add_constrained_variables(model, MOI.PositiveSemidefiniteConeTriangle(2)) + Q, _ = MOI.add_constrained_variables( + model, + MOI.PositiveSemidefiniteConeTriangle(2), + ) MOI.add_constraint( model, 1.0 * Q[1] + 4.0 * Q[2] + 3.0 * Q[3], From 9e3fe6b7b3cccd55a471b9c5db81ea2ee017370a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 31 Jul 2024 21:49:56 +0200 Subject: [PATCH 12/21] Add tests --- docs/src/background/duality.md | 2 +- docs/src/manual/standard_form.md | 2 +- docs/src/reference/standard_form.md | 2 +- src/Bridges/Constraint/Constraint.jl | 1 + src/Bridges/Variable/bridges/set_dot.jl | 35 ++++-- src/Bridges/Variable/set_map.jl | 4 +- src/Test/test_conic.jl | 159 ++++++++++++++++++++++++ src/Utilities/set_dot.jl | 20 --- src/sets.jl | 28 +++-- test/Bridges/Variable/set_dot.jl | 2 +- 10 files changed, 212 insertions(+), 43 deletions(-) diff --git a/docs/src/background/duality.md b/docs/src/background/duality.md index df00e4216c..f1c56d27f5 100644 --- a/docs/src/background/duality.md +++ b/docs/src/background/duality.md @@ -114,7 +114,7 @@ and similarly, the dual is: The scalar product is different from the canonical one for the sets [`PositiveSemidefiniteConeTriangle`](@ref), [`LogDetConeTriangle`](@ref), [`RootDetConeTriangle`](@ref), -[`SetWithDotProducts`](@ref) and +[`SetDotProducts`](@ref) and [`LinearCombinationInSet`](@ref). If the set ``C_i`` of the section [Duality](@ref) is one of these three cones, diff --git a/docs/src/manual/standard_form.md b/docs/src/manual/standard_form.md index dd1d0217ec..4596021400 100644 --- a/docs/src/manual/standard_form.md +++ b/docs/src/manual/standard_form.md @@ -82,7 +82,7 @@ The vector-valued set types implemented in MathOptInterface.jl are: | [`RelativeEntropyCone(d)`](@ref MathOptInterface.RelativeEntropyCone) | ``\{ (u, v, w) \in \mathbb{R}^{d} : u \ge \sum_i w_i \log (\frac{w_i}{v_i}), v_i \ge 0, w_i \ge 0 \}`` | | [`HyperRectangle(l, u)`](@ref MathOptInterface.HyperRectangle) | ``\{x \in \bar{\mathbb{R}}^d: x_i \in [l_i, u_i] \forall i=1,\ldots,d\}`` | | [`NormCone(p, d)`](@ref MathOptInterface.NormCone) | ``\{ (t,x) \in \mathbb{R}^{d} : t \ge \left(\sum\limits_i \lvert x_i \rvert^p\right)^{\frac{1}{p}} \}`` | -| [`SetWithDotProducts(s, v)`](@ref MathOptInterface.SetWithDotProducts) | The cone `s` with dot products with the fixed vectors `v`. | +| [`SetDotProducts(s, v)`](@ref MathOptInterface.SetDotProducts) | The cone `s` with dot products with the fixed vectors `v`. | | [`LinearCombinationInSet(s, v)`](@ref MathOptInterface.LinearCombinationInSet) | The cone of vector `(y, x)` such that ``\sum_i y_i v_i + x`` belongs to `s`. | ## Matrix cones diff --git a/docs/src/reference/standard_form.md b/docs/src/reference/standard_form.md index f24c009084..3c62a6b632 100644 --- a/docs/src/reference/standard_form.md +++ b/docs/src/reference/standard_form.md @@ -153,6 +153,6 @@ LogDetConeTriangle LogDetConeSquare RootDetConeTriangle RootDetConeSquare -SetWithDotProducts +SetDotProducts LinearCombinationInSet ``` diff --git a/src/Bridges/Constraint/Constraint.jl b/src/Bridges/Constraint/Constraint.jl index 99f25a1a67..35798a45a9 100644 --- a/src/Bridges/Constraint/Constraint.jl +++ b/src/Bridges/Constraint/Constraint.jl @@ -108,6 +108,7 @@ function add_all_bridges(bridged_model, ::Type{T}) where {T} MOI.Bridges.add_bridge(bridged_model, SOS1ToMILPBridge{T}) MOI.Bridges.add_bridge(bridged_model, SOS2ToMILPBridge{T}) MOI.Bridges.add_bridge(bridged_model, IndicatorToMILPBridge{T}) + MOI.Bridges.add_bridge(bridged_model, LinearCombinationBridge{T}) return end diff --git a/src/Bridges/Variable/bridges/set_dot.jl b/src/Bridges/Variable/bridges/set_dot.jl index d378e7d290..93fb3f8f45 100644 --- a/src/Bridges/Variable/bridges/set_dot.jl +++ b/src/Bridges/Variable/bridges/set_dot.jl @@ -1,20 +1,26 @@ +# 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. + struct DotProductsBridge{T,S,A,V} <: - SetMapBridge{T,S,MOI.SetWithDotProducts{S,A,V}} + SetMapBridge{T,S,MOI.SetDotProducts{S,A,V}} variables::Vector{MOI.VariableIndex} constraint::MOI.ConstraintIndex{MOI.VectorOfVariables,S} - set::MOI.SetWithDotProducts{S,A,V} + set::MOI.SetDotProducts{S,A,V} end function supports_constrained_variable( ::Type{<:DotProductsBridge}, - ::Type{<:MOI.SetWithDotProducts}, + ::Type{<:MOI.SetDotProducts}, ) return true end function concrete_bridge_type( ::Type{<:DotProductsBridge{T}}, - ::Type{MOI.SetWithDotProducts{S,A,V}}, + ::Type{MOI.SetDotProducts{S,A,V}}, ) where {T,S,A,V} return DotProductsBridge{T,S,A,V} end @@ -22,20 +28,20 @@ end function bridge_constrained_variable( BT::Type{DotProductsBridge{T,S,A,V}}, model::MOI.ModelLike, - set::MOI.SetWithDotProducts{S,A,V}, + set::MOI.SetDotProducts{S,A,V}, ) where {T,S,A,V} variables, constraint = _add_constrained_var(model, MOI.Bridges.inverse_map_set(BT, set)) return BT(variables, constraint, set) end -function MOI.Bridges.map_set(bridge::DotProductsBridge{T,S}, set::S) where {T,S} +function MOI.Bridges.map_set(bridge::DotProductsBridge{T,S}, ::S) where {T,S} return bridge.set end function MOI.Bridges.inverse_map_set( ::Type{<:DotProductsBridge}, - set::MOI.SetWithDotProducts, + set::MOI.SetDotProducts, ) return set.set end @@ -53,6 +59,21 @@ function MOI.Bridges.map_function( ) end +function MOI.Bridges.map_function( + bridge::DotProductsBridge{T}, + func, +) where {T} + scalars = MOI.Utilities.eachscalar(func) + return MOI.Utilities.vectorize([ + MOI.Utilities.set_dot( + vector, + scalars, + bridge.set.set, + ) + for vector in bridge.set.vectors + ]) +end + # This returns `true` by default for `SetMapBridge` # but is is not supported for this bridge because `inverse_map_function` # is not implemented diff --git a/src/Bridges/Variable/set_map.jl b/src/Bridges/Variable/set_map.jl index 6d0e39091e..6743b8fdb3 100644 --- a/src/Bridges/Variable/set_map.jl +++ b/src/Bridges/Variable/set_map.jl @@ -149,7 +149,7 @@ function MOI.get( bridge::SetMapBridge, ) value = MOI.get(model, attr, bridge.constraint) - return MOI.Bridges.map_function(typeof(bridge), value) + return MOI.Bridges.map_function(bridge, value) end function MOI.get( @@ -224,7 +224,7 @@ function unbridged_map( func = MOI.VectorOfVariables(vis) funcs = MOI.Bridges.inverse_map_function(bridge, func) scalars = MOI.Utilities.eachscalar(funcs) - # FIXME not correct for SetWithDotProducts, it won't recover the dot product variables + # FIXME not correct for SetDotProducts, it won't recover the dot product variables return Pair{MOI.VariableIndex,F}[ bridge.variables[i] => scalars[i] for i in eachindex(bridge.variables) ] diff --git a/src/Test/test_conic.jl b/src/Test/test_conic.jl index b2dca3f3da..b3add76ded 100644 --- a/src/Test/test_conic.jl +++ b/src/Test/test_conic.jl @@ -5770,6 +5770,165 @@ function setup_test( return end +""" +The goal is to find the maximum lower bound `γ` for the polynomial `x^2 - 2x`. +Using samples `-1` and `1`, the polynomial `x^2 - 2x - γ` evaluates at `-γ` +and `2 - γ` respectively. +The dot product with the gram matrix is the evaluation of `[1; x] * [1 x]` hence +`[1; -1] * [1 -1]` and `[1; 1] * [1 1]` respectively. + +The polynomial version is: +max γ +s.t. [-γ, 2 - γ] in SetDotProducts( + PSD(2), + [[1; -1] * [1 -1], [1; 1] * [1 1]], +) +Its dual (moment version) is: +min -y[1] - y[2] +s.t. [-γ, 2 - γ] in LinearCombinationInSet( + PSD(2), + [[1; -1] * [1 -1], [1; 1] * [1 1]], +) +""" +function test_conic_PositiveSemidefinite_RankOne_polynomial( + model::MOI.ModelLike, + config::Config{T}, +) where {T} + set = MOI.SetDotProducts( + MOI.PositiveSemidefiniteConeTriangle(2), + MOI.TriangleVectorization.([ + MOI.PositiveSemidefiniteFactorization(T[1, -1]), + MOI.PositiveSemidefiniteFactorization(T[1, 1]), + ]), + ) + @requires MOI.supports_constraint(model, MOI.VectorAffineFunction{T}, typeof(set)) + @requires MOI.supports_incremental_interface(model) + @requires MOI.supports(model, MOI.ObjectiveSense()) + @requires MOI.supports(model, MOI.ObjectiveFunction{MOI.VariableIndex}()) + γ = MOI.add_variable(model) + c = MOI.add_constraint( + model, + MOI.Utilities.operate(vcat, T, T(3) - T(1) * γ, T(-1) - T(1) * γ), + set, + ) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + MOI.set(model, MOI.ObjectiveFunction{MOI.VariableIndex}(), γ) + if _supports(config, MOI.optimize!) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status + @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT + if _supports(config, MOI.ConstraintDual) + @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT + end + @test ≈(MOI.get(model, MOI.ObjectiveValue()), T(-1), config) + if _supports(config, MOI.DualObjectiveValue) + @test ≈(MOI.get(model, MOI.DualObjectiveValue()), T(-1), config) + end + @test ≈(MOI.get(model, MOI.VariablePrimal(), γ), T(-1), config) + @test ≈(MOI.get(model, MOI.ConstraintPrimal(), c), T[4, 0], config) + if _supports(config, MOI.ConstraintDual) + @test ≈(MOI.get(model, MOI.ConstraintDual(), c), T[0, 1], config) + end + end + return +end + +function setup_test( + ::typeof(test_conic_PositiveSemidefinite_RankOne_polynomial), + model::MOIU.MockOptimizer, + ::Config{T}, +) where {T<:Real} + A = MOI.TriangleVectorization{T,MOI.PositiveSemidefiniteFactorization{T,Vector{T}}} + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + T[-1], + (MOI.VectorAffineFunction{T}, MOI.SetDotProducts{ + MOI.PositiveSemidefiniteConeTriangle, + A, + Vector{A}, + }) => [T[0, 1]], + ), + ) + return +end + +""" +The moment version of `test_conic_PositiveSemidefinite_RankOne_polynomial` + +We look for a measure `μ = y1 * δ_{-1} + y2 * δ_{1}` where `δ_{c}` is the Dirac +measure centered at `c`. The objective is +`⟨μ, x^2 - 2x⟩ = y1 * ⟨δ_{-1}, x^2 - 2x⟩ + y2 * ⟨δ_{1}, x^2 - 2x⟩ = 3y1 - y2`. +We want `μ` to be a probability measure so `1 = ⟨μ, 1⟩ = y1 + y2`. +""" +function test_conic_PositiveSemidefinite_RankOne_moment( + model::MOI.ModelLike, + config::Config{T}, +) where {T} + set = MOI.LinearCombinationInSet( + MOI.PositiveSemidefiniteConeTriangle(2), + MOI.TriangleVectorization.([ + MOI.PositiveSemidefiniteFactorization(T[1, -1]), + MOI.PositiveSemidefiniteFactorization(T[1, 1]), + ]), + ) + @requires MOI.supports_add_constrained_variables(model, typeof(set)) + @requires MOI.supports_incremental_interface(model) + @requires MOI.supports(model, MOI.ObjectiveSense()) + @requires MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}()) + y, cy = MOI.add_constrained_variables( + model, + set, + ) + c = MOI.add_constraint(model, T(1) * y[1] + T(1) * y[2], MOI.EqualTo(T(1))) + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(), T(3) * y[1] - T(1) * y[2]) + if _supports(config, MOI.optimize!) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status + @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT + if _supports(config, MOI.ConstraintDual) + @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT + end + @test ≈(MOI.get(model, MOI.ObjectiveValue()), T(-1), config) + if _supports(config, MOI.DualObjectiveValue) + @test ≈(MOI.get(model, MOI.DualObjectiveValue()), T(-1), config) + end + @test ≈(MOI.get(model, MOI.VariablePrimal(), y), T[0, 1], config) + @test ≈(MOI.get(model, MOI.ConstraintPrimal(), c), T(1), config) + if _supports(config, MOI.ConstraintDual) + @test ≈(MOI.get(model, MOI.ConstraintDual(), cy), T[4, 0], config) + @test ≈(MOI.get(model, MOI.ConstraintDual(), c), T(-1), config) + end + end + return +end + +function setup_test( + ::typeof(test_conic_PositiveSemidefinite_RankOne_moment), + model::MOIU.MockOptimizer, + ::Config{T}, +) where {T<:Real} + A = MOI.TriangleVectorization{T,MOI.PositiveSemidefiniteFactorization{T,Vector{T}}} + MOIU.set_mock_optimize!( + model, + (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( + mock, + T[0, 1], + (MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}) => [T(-1)], + (MOI.VectorOfVariables, MOI.LinearCombinationInSet{ + MOI.PositiveSemidefiniteConeTriangle, + A, + Vector{A}, + }) => [T[4, 0]], + ), + ) + return +end + """ _test_det_cone_helper_ellipsoid( model::MOI.ModelLike, diff --git a/src/Utilities/set_dot.jl b/src/Utilities/set_dot.jl index e571c167fb..31378da1fe 100644 --- a/src/Utilities/set_dot.jl +++ b/src/Utilities/set_dot.jl @@ -86,16 +86,6 @@ function set_dot( return x[1] * y[1] + x[2] * y[2] + triangle_dot(x, y, set.side_dimension, 2) end -function set_dot( - x::AbstractVector, - y::AbstractVector, - set::Union{MOI.SetWithDotProducts,MOI.LinearCombinationInSet}, -) - m = length(set.matrices) - return LinearAlgebra.dot(view(x, 1:m), view(y, 1:m)) + - set_dot(view(x, (m+1):length(x)), view(y, (m+1):length(y)), set.set) -end - """ dot_coefficients(a::AbstractVector, set::AbstractVectorSet) @@ -155,16 +145,6 @@ function dot_coefficients(a::AbstractVector, set::MOI.LogDetConeTriangle) return b end -function dot_coefficients( - a::AbstractVector, - set::Union{MOI.SetWithDotProducts,MOI.LinearCombinationInSet}, -) - b = copy(a) - m = length(set.vectors) - b[(m+1):end] = dot_coefficients(b[(m+1):end], set.set) - return b -end - # For `SetDotScalingVector`, we would like to compute the dot product # of canonical vectors in O(1) instead of O(n) # See https://github.com/jump-dev/Dualization.jl/pull/135 diff --git a/src/sets.jl b/src/sets.jl index ed49de0d6b..1ed64088d7 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -1804,31 +1804,31 @@ function Base.getproperty( end """ - SetWithDotProducts(set::MOI.AbstractSet, vectors::AbstractVector) + SetDotProducts(set::MOI.AbstractSet, vectors::AbstractVector) Given a set `set` of dimension `d` and `m` vectors `a_1`, ..., `a_m` given in `vectors`, this is the set: ``\\{ ((\\langle a_1, x \\rangle, ..., \\langle a_m, x \\rangle) \\in \\mathbb{R}^{m} : x \\in \\text{set} \\}.`` """ -struct SetWithDotProducts{S,A,V<:AbstractVector{A}} <: AbstractVectorSet +struct SetDotProducts{S,A,V<:AbstractVector{A}} <: AbstractVectorSet set::S vectors::V end -function Base.:(==)(s1::SetWithDotProducts, s2::SetWithDotProducts) +function Base.:(==)(s1::SetDotProducts, s2::SetDotProducts) return s1.set == s2.set && s1.vectors == s2.vectors end -function Base.copy(s::SetWithDotProducts) - return SetWithDotProducts(copy(s.set), copy(s.vectors)) +function Base.copy(s::SetDotProducts) + return SetDotProducts(copy(s.set), copy(s.vectors)) end -dimension(s::SetWithDotProducts) = length(s.vectors) +dimension(s::SetDotProducts) = length(s.vectors) -function dual_set(s::SetWithDotProducts) +function dual_set(s::SetDotProducts) return LinearCombinationInSet(s.set, s.vectors) end -function dual_set_type(::Type{SetWithDotProducts{S,A,V}}) where {S,A,V} +function dual_set_type(::Type{SetDotProducts{S,A,V}}) where {S,A,V} return LinearCombinationInSet{S,A,V} end @@ -1843,14 +1843,22 @@ struct LinearCombinationInSet{S,A,V<:AbstractVector{A}} <: AbstractVectorSet vectors::V end +function Base.:(==)(s1::LinearCombinationInSet, s2::LinearCombinationInSet) + return s1.set == s2.set && s1.vectors == s2.vectors +end + +function Base.copy(s::LinearCombinationInSet) + return LinearCombinationInSet(copy(s.set), copy(s.vectors)) +end + dimension(s::LinearCombinationInSet) = length(s.vectors) function dual_set(s::LinearCombinationInSet) - return SetWithDotProducts(s.side_dimension, s.vectors) + return SetDotProducts(s.side_dimension, s.vectors) end function dual_set_type(::Type{LinearCombinationInSet{S,A,V}}) where {S,A,V} - return SetWithDotProducts{S,A,V} + return SetDotProducts{S,A,V} end abstract type AbstractFactorization{T,F} <: AbstractMatrix{T} end diff --git a/test/Bridges/Variable/set_dot.jl b/test/Bridges/Variable/set_dot.jl index 65a058af4f..b9a52377a9 100644 --- a/test/Bridges/Variable/set_dot.jl +++ b/test/Bridges/Variable/set_dot.jl @@ -29,7 +29,7 @@ function test_psd() model -> begin x, _ = MOI.add_constrained_variables( model, - MOI.SetWithDotProducts( + MOI.SetDotProducts( MOI.PositiveSemidefiniteConeTriangle(2), MOI.TriangleVectorization.([ [ From e954eecfdbabd9bb87905f526c3802b58e486f59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 31 Jul 2024 21:50:07 +0200 Subject: [PATCH 13/21] Add bridge --- .../Constraint/bridges/linear_combination.jl | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/Bridges/Constraint/bridges/linear_combination.jl diff --git a/src/Bridges/Constraint/bridges/linear_combination.jl b/src/Bridges/Constraint/bridges/linear_combination.jl new file mode 100644 index 0000000000..ef21fe735f --- /dev/null +++ b/src/Bridges/Constraint/bridges/linear_combination.jl @@ -0,0 +1,73 @@ +# 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. + +struct LinearCombinationBridge{T,S,A,V,F,G} <: + SetMapBridge{T,S,MOI.LinearCombinationInSet{S,A,V},F,G} + constraint::MOI.ConstraintIndex{F,S} + set::MOI.LinearCombinationInSet{S,A,V} +end + +function MOI.supports_constraint( + ::Type{<:LinearCombinationBridge}, + ::Type{<:MOI.AbstractVectorFunction}, + ::Type{<:MOI.LinearCombinationInSet}, +) + return true +end + +function concrete_bridge_type( + ::Type{<:LinearCombinationBridge{T}}, + G::Type{<:MOI.AbstractVectorFunction}, + ::Type{MOI.LinearCombinationInSet{S,A,V}}, +) where {T,S,A,V} + U = MOI.Utilities.promote_operation(*, T, MOI.Utilities.scalar_type(G), T) + F = MOI.Utilities.promote_operation(vcat, T, U) + return LinearCombinationBridge{T,S,A,V,F,G} +end + +function _map_function(set::MOI.LinearCombinationInSet, func) + scalars = MOI.Utilities.eachscalar(func) + return MOI.Utilities.vectorize([ + sum(scalars[j] * set.vectors[j][i] for j in eachindex(scalars)) + for i in 1:MOI.dimension(set.set) + ]) +end + +function bridge_constraint( + ::Type{LinearCombinationBridge{T,S,A,V,F,G}}, + model::MOI.ModelLike, + func::G, + set::MOI.LinearCombinationInSet{S,A,V}, +) where {T,S,A,F,G,V} + mapped_func = _map_function(set, func) + constraint = MOI.add_constraint(model, mapped_func, set.set) + return LinearCombinationBridge{T,S,A,V,F,G}(constraint, set) +end + +function MOI.Bridges.map_set( + ::Type{<:LinearCombinationBridge}, + set::MOI.LinearCombinationInSet, +) + return set.set +end + +function MOI.Bridges.inverse_map_set( + bridge::LinearCombinationBridge, + ::MOI.AbstractSet, +) + return bridge.set +end + +function MOI.Bridges.adjoint_map_function( + bridge::LinearCombinationBridge, + func, +) + scalars = MOI.Utilities.eachscalar(func) + return MOI.Utilities.vectorize([ + MOI.Utilities.set_dot(vector, scalars, bridge.set.set) + for vector in bridge.set.vectors + ]) +end From 7919b2fed772b2a64938a40e262364df99cc251c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 31 Jul 2024 21:50:17 +0200 Subject: [PATCH 14/21] Fix format --- .../Constraint/bridges/linear_combination.jl | 13 ++--- src/Bridges/Variable/bridges/set_dot.jl | 16 ++---- src/Test/test_conic.jl | 54 +++++++++++++------ 3 files changed, 46 insertions(+), 37 deletions(-) diff --git a/src/Bridges/Constraint/bridges/linear_combination.jl b/src/Bridges/Constraint/bridges/linear_combination.jl index ef21fe735f..2656cea235 100644 --- a/src/Bridges/Constraint/bridges/linear_combination.jl +++ b/src/Bridges/Constraint/bridges/linear_combination.jl @@ -31,8 +31,8 @@ end function _map_function(set::MOI.LinearCombinationInSet, func) scalars = MOI.Utilities.eachscalar(func) return MOI.Utilities.vectorize([ - sum(scalars[j] * set.vectors[j][i] for j in eachindex(scalars)) - for i in 1:MOI.dimension(set.set) + sum(scalars[j] * set.vectors[j][i] for j in eachindex(scalars)) for + i in 1:MOI.dimension(set.set) ]) end @@ -61,13 +61,10 @@ function MOI.Bridges.inverse_map_set( return bridge.set end -function MOI.Bridges.adjoint_map_function( - bridge::LinearCombinationBridge, - func, -) +function MOI.Bridges.adjoint_map_function(bridge::LinearCombinationBridge, func) scalars = MOI.Utilities.eachscalar(func) return MOI.Utilities.vectorize([ - MOI.Utilities.set_dot(vector, scalars, bridge.set.set) - for vector in bridge.set.vectors + MOI.Utilities.set_dot(vector, scalars, bridge.set.set) for + vector in bridge.set.vectors ]) end diff --git a/src/Bridges/Variable/bridges/set_dot.jl b/src/Bridges/Variable/bridges/set_dot.jl index 93fb3f8f45..a2468f4b9c 100644 --- a/src/Bridges/Variable/bridges/set_dot.jl +++ b/src/Bridges/Variable/bridges/set_dot.jl @@ -4,8 +4,7 @@ # 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. -struct DotProductsBridge{T,S,A,V} <: - SetMapBridge{T,S,MOI.SetDotProducts{S,A,V}} +struct DotProductsBridge{T,S,A,V} <: SetMapBridge{T,S,MOI.SetDotProducts{S,A,V}} variables::Vector{MOI.VariableIndex} constraint::MOI.ConstraintIndex{MOI.VectorOfVariables,S} set::MOI.SetDotProducts{S,A,V} @@ -59,18 +58,11 @@ function MOI.Bridges.map_function( ) end -function MOI.Bridges.map_function( - bridge::DotProductsBridge{T}, - func, -) where {T} +function MOI.Bridges.map_function(bridge::DotProductsBridge{T}, func) where {T} scalars = MOI.Utilities.eachscalar(func) return MOI.Utilities.vectorize([ - MOI.Utilities.set_dot( - vector, - scalars, - bridge.set.set, - ) - for vector in bridge.set.vectors + MOI.Utilities.set_dot(vector, scalars, bridge.set.set) for + vector in bridge.set.vectors ]) end diff --git a/src/Test/test_conic.jl b/src/Test/test_conic.jl index b3add76ded..48f05000a6 100644 --- a/src/Test/test_conic.jl +++ b/src/Test/test_conic.jl @@ -5801,7 +5801,11 @@ function test_conic_PositiveSemidefinite_RankOne_polynomial( MOI.PositiveSemidefiniteFactorization(T[1, 1]), ]), ) - @requires MOI.supports_constraint(model, MOI.VectorAffineFunction{T}, typeof(set)) + @requires MOI.supports_constraint( + model, + MOI.VectorAffineFunction{T}, + typeof(set), + ) @requires MOI.supports_incremental_interface(model) @requires MOI.supports(model, MOI.ObjectiveSense()) @requires MOI.supports(model, MOI.ObjectiveFunction{MOI.VariableIndex}()) @@ -5839,17 +5843,23 @@ function setup_test( model::MOIU.MockOptimizer, ::Config{T}, ) where {T<:Real} - A = MOI.TriangleVectorization{T,MOI.PositiveSemidefiniteFactorization{T,Vector{T}}} + A = MOI.TriangleVectorization{ + T, + MOI.PositiveSemidefiniteFactorization{T,Vector{T}}, + } MOIU.set_mock_optimize!( model, (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( mock, T[-1], - (MOI.VectorAffineFunction{T}, MOI.SetDotProducts{ - MOI.PositiveSemidefiniteConeTriangle, - A, - Vector{A}, - }) => [T[0, 1]], + ( + MOI.VectorAffineFunction{T}, + MOI.SetDotProducts{ + MOI.PositiveSemidefiniteConeTriangle, + A, + Vector{A}, + }, + ) => [T[0, 1]], ), ) return @@ -5877,14 +5887,18 @@ function test_conic_PositiveSemidefinite_RankOne_moment( @requires MOI.supports_add_constrained_variables(model, typeof(set)) @requires MOI.supports_incremental_interface(model) @requires MOI.supports(model, MOI.ObjectiveSense()) - @requires MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}()) - y, cy = MOI.add_constrained_variables( + @requires MOI.supports( model, - set, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(), ) + y, cy = MOI.add_constrained_variables(model, set) c = MOI.add_constraint(model, T(1) * y[1] + T(1) * y[2], MOI.EqualTo(T(1))) MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) - MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(), T(3) * y[1] - T(1) * y[2]) + MOI.set( + model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(), + T(3) * y[1] - T(1) * y[2], + ) if _supports(config, MOI.optimize!) @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED MOI.optimize!(model) @@ -5912,18 +5926,24 @@ function setup_test( model::MOIU.MockOptimizer, ::Config{T}, ) where {T<:Real} - A = MOI.TriangleVectorization{T,MOI.PositiveSemidefiniteFactorization{T,Vector{T}}} + A = MOI.TriangleVectorization{ + T, + MOI.PositiveSemidefiniteFactorization{T,Vector{T}}, + } MOIU.set_mock_optimize!( model, (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( mock, T[0, 1], (MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}) => [T(-1)], - (MOI.VectorOfVariables, MOI.LinearCombinationInSet{ - MOI.PositiveSemidefiniteConeTriangle, - A, - Vector{A}, - }) => [T[4, 0]], + ( + MOI.VectorOfVariables, + MOI.LinearCombinationInSet{ + MOI.PositiveSemidefiniteConeTriangle, + A, + Vector{A}, + }, + ) => [T[4, 0]], ), ) return From 14b00315ace3f006a235125f42fd2c44f2e5115c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 5 Dec 2024 22:56:43 +0100 Subject: [PATCH 15/21] Rename --- .../bridges/{linear_combination.jl => LinearCombinationBridge.jl} | 0 src/Bridges/Variable/bridges/{set_dot.jl => DotProductsBridge.jl} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/Bridges/Constraint/bridges/{linear_combination.jl => LinearCombinationBridge.jl} (100%) rename src/Bridges/Variable/bridges/{set_dot.jl => DotProductsBridge.jl} (100%) diff --git a/src/Bridges/Constraint/bridges/linear_combination.jl b/src/Bridges/Constraint/bridges/LinearCombinationBridge.jl similarity index 100% rename from src/Bridges/Constraint/bridges/linear_combination.jl rename to src/Bridges/Constraint/bridges/LinearCombinationBridge.jl diff --git a/src/Bridges/Variable/bridges/set_dot.jl b/src/Bridges/Variable/bridges/DotProductsBridge.jl similarity index 100% rename from src/Bridges/Variable/bridges/set_dot.jl rename to src/Bridges/Variable/bridges/DotProductsBridge.jl From dfa44d9e0dc76a02555164fdbcce5f85a0176df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 11 Dec 2024 09:11:10 +0100 Subject: [PATCH 16/21] Add conversions --- .../bridges/LinearCombinationBridge.jl | 70 ---------------- .../Variable/bridges/DotProductsBridge.jl | 82 ------------------- src/sets.jl | 77 +++++++++++++++-- 3 files changed, 69 insertions(+), 160 deletions(-) delete mode 100644 src/Bridges/Constraint/bridges/LinearCombinationBridge.jl delete mode 100644 src/Bridges/Variable/bridges/DotProductsBridge.jl diff --git a/src/Bridges/Constraint/bridges/LinearCombinationBridge.jl b/src/Bridges/Constraint/bridges/LinearCombinationBridge.jl deleted file mode 100644 index 2656cea235..0000000000 --- a/src/Bridges/Constraint/bridges/LinearCombinationBridge.jl +++ /dev/null @@ -1,70 +0,0 @@ -# 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. - -struct LinearCombinationBridge{T,S,A,V,F,G} <: - SetMapBridge{T,S,MOI.LinearCombinationInSet{S,A,V},F,G} - constraint::MOI.ConstraintIndex{F,S} - set::MOI.LinearCombinationInSet{S,A,V} -end - -function MOI.supports_constraint( - ::Type{<:LinearCombinationBridge}, - ::Type{<:MOI.AbstractVectorFunction}, - ::Type{<:MOI.LinearCombinationInSet}, -) - return true -end - -function concrete_bridge_type( - ::Type{<:LinearCombinationBridge{T}}, - G::Type{<:MOI.AbstractVectorFunction}, - ::Type{MOI.LinearCombinationInSet{S,A,V}}, -) where {T,S,A,V} - U = MOI.Utilities.promote_operation(*, T, MOI.Utilities.scalar_type(G), T) - F = MOI.Utilities.promote_operation(vcat, T, U) - return LinearCombinationBridge{T,S,A,V,F,G} -end - -function _map_function(set::MOI.LinearCombinationInSet, func) - scalars = MOI.Utilities.eachscalar(func) - return MOI.Utilities.vectorize([ - sum(scalars[j] * set.vectors[j][i] for j in eachindex(scalars)) for - i in 1:MOI.dimension(set.set) - ]) -end - -function bridge_constraint( - ::Type{LinearCombinationBridge{T,S,A,V,F,G}}, - model::MOI.ModelLike, - func::G, - set::MOI.LinearCombinationInSet{S,A,V}, -) where {T,S,A,F,G,V} - mapped_func = _map_function(set, func) - constraint = MOI.add_constraint(model, mapped_func, set.set) - return LinearCombinationBridge{T,S,A,V,F,G}(constraint, set) -end - -function MOI.Bridges.map_set( - ::Type{<:LinearCombinationBridge}, - set::MOI.LinearCombinationInSet, -) - return set.set -end - -function MOI.Bridges.inverse_map_set( - bridge::LinearCombinationBridge, - ::MOI.AbstractSet, -) - return bridge.set -end - -function MOI.Bridges.adjoint_map_function(bridge::LinearCombinationBridge, func) - scalars = MOI.Utilities.eachscalar(func) - return MOI.Utilities.vectorize([ - MOI.Utilities.set_dot(vector, scalars, bridge.set.set) for - vector in bridge.set.vectors - ]) -end diff --git a/src/Bridges/Variable/bridges/DotProductsBridge.jl b/src/Bridges/Variable/bridges/DotProductsBridge.jl deleted file mode 100644 index a2468f4b9c..0000000000 --- a/src/Bridges/Variable/bridges/DotProductsBridge.jl +++ /dev/null @@ -1,82 +0,0 @@ -# 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. - -struct DotProductsBridge{T,S,A,V} <: SetMapBridge{T,S,MOI.SetDotProducts{S,A,V}} - variables::Vector{MOI.VariableIndex} - constraint::MOI.ConstraintIndex{MOI.VectorOfVariables,S} - set::MOI.SetDotProducts{S,A,V} -end - -function supports_constrained_variable( - ::Type{<:DotProductsBridge}, - ::Type{<:MOI.SetDotProducts}, -) - return true -end - -function concrete_bridge_type( - ::Type{<:DotProductsBridge{T}}, - ::Type{MOI.SetDotProducts{S,A,V}}, -) where {T,S,A,V} - return DotProductsBridge{T,S,A,V} -end - -function bridge_constrained_variable( - BT::Type{DotProductsBridge{T,S,A,V}}, - model::MOI.ModelLike, - set::MOI.SetDotProducts{S,A,V}, -) where {T,S,A,V} - variables, constraint = - _add_constrained_var(model, MOI.Bridges.inverse_map_set(BT, set)) - return BT(variables, constraint, set) -end - -function MOI.Bridges.map_set(bridge::DotProductsBridge{T,S}, ::S) where {T,S} - return bridge.set -end - -function MOI.Bridges.inverse_map_set( - ::Type{<:DotProductsBridge}, - set::MOI.SetDotProducts, -) - return set.set -end - -function MOI.Bridges.map_function( - bridge::DotProductsBridge{T}, - func, - i::MOI.Bridges.IndexInVector, -) where {T} - scalars = MOI.Utilities.eachscalar(func) - return MOI.Utilities.set_dot( - bridge.set.vectors[i.value], - scalars, - bridge.set.set, - ) -end - -function MOI.Bridges.map_function(bridge::DotProductsBridge{T}, func) where {T} - scalars = MOI.Utilities.eachscalar(func) - return MOI.Utilities.vectorize([ - MOI.Utilities.set_dot(vector, scalars, bridge.set.set) for - vector in bridge.set.vectors - ]) -end - -# This returns `true` by default for `SetMapBridge` -# but is is not supported for this bridge because `inverse_map_function` -# is not implemented -function MOI.supports( - ::MOI.ModelLike, - ::MOI.VariablePrimalStart, - ::Type{<:DotProductsBridge}, -) - return false -end - -function unbridged_map(::DotProductsBridge, ::Vector{MOI.VariableIndex}) - return nothing -end diff --git a/src/sets.jl b/src/sets.jl index 1ed64088d7..3e802db1aa 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -1824,12 +1824,18 @@ end dimension(s::SetDotProducts) = length(s.vectors) -function dual_set(s::SetDotProducts) - return LinearCombinationInSet(s.set, s.vectors) +function MOI.Bridges.Constraint.conversion_cost( + ::Type{SetDotProducts{S,A1,Vector{A1}}}, + ::Type{SetDotProducts{S,A2,Vector{A2}}}, +) where {S,A1,A2} + return MOI.Bridges.Constraint.conversion_cost(A1, A2) end -function dual_set_type(::Type{SetDotProducts{S,A,V}}) where {S,A,V} - return LinearCombinationInSet{S,A,V} +function convert( + ::Type{SetDotProducts{S,A,Vector{A}}}, + set::SetDotProducts, +) + return SetDotProducts(set.set, convert(A, set.vectors)) end """ @@ -1853,6 +1859,28 @@ end dimension(s::LinearCombinationInSet) = length(s.vectors) +function MOI.Bridges.Constraint.conversion_cost( + ::Type{LinearCombinationInSet{S,A1,Vector{A1}}}, + ::Type{LinearCombinationInSet{S,A2,Vector{A2}}}, +) where {S,A1,A2} + return MOI.Bridges.Constraint.conversion_cost(A1, A2) +end + +function convert( + ::Type{LinearCombinationInSet{S,A,Vector{A}}}, + set::LinearCombinationInSet, +) + return LinearCombinationInSet(set.set, convert(A, set.vectors)) +end + +function dual_set(s::SetDotProducts) + return LinearCombinationInSet(s.set, s.vectors) +end + +function dual_set_type(::Type{SetDotProducts{S,A,V}}) where {S,A,V} + return LinearCombinationInSet{S,A,V} +end + function dual_set(s::LinearCombinationInSet) return SetDotProducts(s.side_dimension, s.vectors) end @@ -1915,13 +1943,11 @@ function Base.getindex(m::Factorization, i::Int, j::Int) end """ - struct Factorization{ + struct PositiveSemidefiniteFactorization{ T, F<:Union{AbstractVector{T},AbstractMatrix{T}}, - D<:Union{T,AbstractVector{T}}, - } <: AbstractMatrix{T} + } <: AbstractFactorization{T,F} factor::F - scaling::D end Matrix corresponding to `factor * Diagonal(diagonal) * factor'`. @@ -1941,10 +1967,45 @@ function Base.getindex(m::PositiveSemidefiniteFactorization, i::Int, j::Int) return sum(m.factor[i, k] * m.factor[j, k]' for k in axes(m.factor, 2)) end +function MOI.Bridges.Constraint.conversion_cost( + ::Type{<:AbstractMatrix}, + ::Type{<:AbstractMatrix}, +) + return Inf +end + +function MOI.Bridges.Constraint.conversion_cost( + ::Type{<:Factorization{T,F}}, + ::Type{PositiveSemidefiniteFactorization{T,F}}, +) where {T,F} + return 1.0 +end + +function Base.convert( + ::Type{Factorization{T,F,D}}, + f::PositiveSemidefiniteFactorization{T,F}, +) where {F<:AbstractVector} + return Factorization{T,F,D}(f.factor, one(T)) +end + +function Base.convert( + ::Type{Factorization{T,F,D}}, + f::PositiveSemidefiniteFactorization{T,F}, +) where {F<:AbstractVector} + return Factorization{T,F,D}(f.factor, one(T)) +end + struct TriangleVectorization{T,M<:AbstractMatrix{T}} <: AbstractVector{T} matrix::M end +function MOI.Bridges.Constraint.conversion_cost( + ::Type{TriangleVectorization{T,M1}}, + ::Type{TriangleVectorization{T,M2}}, +) where {T,M1,M2} + return MOI.Bridges.Constraint.conversion_cost(M1, M2) +end + function Base.size(v::TriangleVectorization) n = size(v.matrix, 1) return (Utilities.trimap(n, n),) From 9564dc14629a23f0cd007ff5f3cb34cc99e8bbc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 11 Dec 2024 09:24:52 +0100 Subject: [PATCH 17/21] Remove what was moved to LowRankOpt --- src/Bridges/Constraint/Constraint.jl | 1 - src/Bridges/Variable/Variable.jl | 1 - src/Test/test_conic.jl | 179 ---------------------- src/sets.jl | 212 --------------------------- test/sets.jl | 35 ----- 5 files changed, 428 deletions(-) diff --git a/src/Bridges/Constraint/Constraint.jl b/src/Bridges/Constraint/Constraint.jl index 35798a45a9..99f25a1a67 100644 --- a/src/Bridges/Constraint/Constraint.jl +++ b/src/Bridges/Constraint/Constraint.jl @@ -108,7 +108,6 @@ function add_all_bridges(bridged_model, ::Type{T}) where {T} MOI.Bridges.add_bridge(bridged_model, SOS1ToMILPBridge{T}) MOI.Bridges.add_bridge(bridged_model, SOS2ToMILPBridge{T}) MOI.Bridges.add_bridge(bridged_model, IndicatorToMILPBridge{T}) - MOI.Bridges.add_bridge(bridged_model, LinearCombinationBridge{T}) return end diff --git a/src/Bridges/Variable/Variable.jl b/src/Bridges/Variable/Variable.jl index 62778ae90e..1cffd8fd62 100644 --- a/src/Bridges/Variable/Variable.jl +++ b/src/Bridges/Variable/Variable.jl @@ -34,7 +34,6 @@ function add_all_bridges(model, ::Type{T}) where {T} MOI.Bridges.add_bridge(model, RSOCtoPSDBridge{T}) MOI.Bridges.add_bridge(model, HermitianToSymmetricPSDBridge{T}) MOI.Bridges.add_bridge(model, ParameterToEqualToBridge{T}) - MOI.Bridges.add_bridge(model, DotProductsBridge{T}) return end diff --git a/src/Test/test_conic.jl b/src/Test/test_conic.jl index 48f05000a6..b2dca3f3da 100644 --- a/src/Test/test_conic.jl +++ b/src/Test/test_conic.jl @@ -5770,185 +5770,6 @@ function setup_test( return end -""" -The goal is to find the maximum lower bound `γ` for the polynomial `x^2 - 2x`. -Using samples `-1` and `1`, the polynomial `x^2 - 2x - γ` evaluates at `-γ` -and `2 - γ` respectively. -The dot product with the gram matrix is the evaluation of `[1; x] * [1 x]` hence -`[1; -1] * [1 -1]` and `[1; 1] * [1 1]` respectively. - -The polynomial version is: -max γ -s.t. [-γ, 2 - γ] in SetDotProducts( - PSD(2), - [[1; -1] * [1 -1], [1; 1] * [1 1]], -) -Its dual (moment version) is: -min -y[1] - y[2] -s.t. [-γ, 2 - γ] in LinearCombinationInSet( - PSD(2), - [[1; -1] * [1 -1], [1; 1] * [1 1]], -) -""" -function test_conic_PositiveSemidefinite_RankOne_polynomial( - model::MOI.ModelLike, - config::Config{T}, -) where {T} - set = MOI.SetDotProducts( - MOI.PositiveSemidefiniteConeTriangle(2), - MOI.TriangleVectorization.([ - MOI.PositiveSemidefiniteFactorization(T[1, -1]), - MOI.PositiveSemidefiniteFactorization(T[1, 1]), - ]), - ) - @requires MOI.supports_constraint( - model, - MOI.VectorAffineFunction{T}, - typeof(set), - ) - @requires MOI.supports_incremental_interface(model) - @requires MOI.supports(model, MOI.ObjectiveSense()) - @requires MOI.supports(model, MOI.ObjectiveFunction{MOI.VariableIndex}()) - γ = MOI.add_variable(model) - c = MOI.add_constraint( - model, - MOI.Utilities.operate(vcat, T, T(3) - T(1) * γ, T(-1) - T(1) * γ), - set, - ) - MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) - MOI.set(model, MOI.ObjectiveFunction{MOI.VariableIndex}(), γ) - if _supports(config, MOI.optimize!) - @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status - @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT - if _supports(config, MOI.ConstraintDual) - @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT - end - @test ≈(MOI.get(model, MOI.ObjectiveValue()), T(-1), config) - if _supports(config, MOI.DualObjectiveValue) - @test ≈(MOI.get(model, MOI.DualObjectiveValue()), T(-1), config) - end - @test ≈(MOI.get(model, MOI.VariablePrimal(), γ), T(-1), config) - @test ≈(MOI.get(model, MOI.ConstraintPrimal(), c), T[4, 0], config) - if _supports(config, MOI.ConstraintDual) - @test ≈(MOI.get(model, MOI.ConstraintDual(), c), T[0, 1], config) - end - end - return -end - -function setup_test( - ::typeof(test_conic_PositiveSemidefinite_RankOne_polynomial), - model::MOIU.MockOptimizer, - ::Config{T}, -) where {T<:Real} - A = MOI.TriangleVectorization{ - T, - MOI.PositiveSemidefiniteFactorization{T,Vector{T}}, - } - MOIU.set_mock_optimize!( - model, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - T[-1], - ( - MOI.VectorAffineFunction{T}, - MOI.SetDotProducts{ - MOI.PositiveSemidefiniteConeTriangle, - A, - Vector{A}, - }, - ) => [T[0, 1]], - ), - ) - return -end - -""" -The moment version of `test_conic_PositiveSemidefinite_RankOne_polynomial` - -We look for a measure `μ = y1 * δ_{-1} + y2 * δ_{1}` where `δ_{c}` is the Dirac -measure centered at `c`. The objective is -`⟨μ, x^2 - 2x⟩ = y1 * ⟨δ_{-1}, x^2 - 2x⟩ + y2 * ⟨δ_{1}, x^2 - 2x⟩ = 3y1 - y2`. -We want `μ` to be a probability measure so `1 = ⟨μ, 1⟩ = y1 + y2`. -""" -function test_conic_PositiveSemidefinite_RankOne_moment( - model::MOI.ModelLike, - config::Config{T}, -) where {T} - set = MOI.LinearCombinationInSet( - MOI.PositiveSemidefiniteConeTriangle(2), - MOI.TriangleVectorization.([ - MOI.PositiveSemidefiniteFactorization(T[1, -1]), - MOI.PositiveSemidefiniteFactorization(T[1, 1]), - ]), - ) - @requires MOI.supports_add_constrained_variables(model, typeof(set)) - @requires MOI.supports_incremental_interface(model) - @requires MOI.supports(model, MOI.ObjectiveSense()) - @requires MOI.supports( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(), - ) - y, cy = MOI.add_constrained_variables(model, set) - c = MOI.add_constraint(model, T(1) * y[1] + T(1) * y[2], MOI.EqualTo(T(1))) - MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) - MOI.set( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{T}}(), - T(3) * y[1] - T(1) * y[2], - ) - if _supports(config, MOI.optimize!) - @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status - @test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT - if _supports(config, MOI.ConstraintDual) - @test MOI.get(model, MOI.DualStatus()) == MOI.FEASIBLE_POINT - end - @test ≈(MOI.get(model, MOI.ObjectiveValue()), T(-1), config) - if _supports(config, MOI.DualObjectiveValue) - @test ≈(MOI.get(model, MOI.DualObjectiveValue()), T(-1), config) - end - @test ≈(MOI.get(model, MOI.VariablePrimal(), y), T[0, 1], config) - @test ≈(MOI.get(model, MOI.ConstraintPrimal(), c), T(1), config) - if _supports(config, MOI.ConstraintDual) - @test ≈(MOI.get(model, MOI.ConstraintDual(), cy), T[4, 0], config) - @test ≈(MOI.get(model, MOI.ConstraintDual(), c), T(-1), config) - end - end - return -end - -function setup_test( - ::typeof(test_conic_PositiveSemidefinite_RankOne_moment), - model::MOIU.MockOptimizer, - ::Config{T}, -) where {T<:Real} - A = MOI.TriangleVectorization{ - T, - MOI.PositiveSemidefiniteFactorization{T,Vector{T}}, - } - MOIU.set_mock_optimize!( - model, - (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!( - mock, - T[0, 1], - (MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}) => [T(-1)], - ( - MOI.VectorOfVariables, - MOI.LinearCombinationInSet{ - MOI.PositiveSemidefiniteConeTriangle, - A, - Vector{A}, - }, - ) => [T[4, 0]], - ), - ) - return -end - """ _test_det_cone_helper_ellipsoid( model::MOI.ModelLike, diff --git a/src/sets.jl b/src/sets.jl index 3e802db1aa..3cc6f42572 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -1803,218 +1803,6 @@ function Base.getproperty( end end -""" - SetDotProducts(set::MOI.AbstractSet, vectors::AbstractVector) - -Given a set `set` of dimension `d` and `m` vectors `a_1`, ..., `a_m` given in `vectors`, this is the set: -``\\{ ((\\langle a_1, x \\rangle, ..., \\langle a_m, x \\rangle) \\in \\mathbb{R}^{m} : x \\in \\text{set} \\}.`` -""" -struct SetDotProducts{S,A,V<:AbstractVector{A}} <: AbstractVectorSet - set::S - vectors::V -end - -function Base.:(==)(s1::SetDotProducts, s2::SetDotProducts) - return s1.set == s2.set && s1.vectors == s2.vectors -end - -function Base.copy(s::SetDotProducts) - return SetDotProducts(copy(s.set), copy(s.vectors)) -end - -dimension(s::SetDotProducts) = length(s.vectors) - -function MOI.Bridges.Constraint.conversion_cost( - ::Type{SetDotProducts{S,A1,Vector{A1}}}, - ::Type{SetDotProducts{S,A2,Vector{A2}}}, -) where {S,A1,A2} - return MOI.Bridges.Constraint.conversion_cost(A1, A2) -end - -function convert( - ::Type{SetDotProducts{S,A,Vector{A}}}, - set::SetDotProducts, -) - return SetDotProducts(set.set, convert(A, set.vectors)) -end - -""" - LinearCombinationInSet(set::MOI.AbstractSet, matrices::AbstractVector) - -Given a set `set` of dimension `d` and `m` vectors `a_1`, ..., `a_m` given in `vectors`, this is the set: -``\\{ (y \\in \\mathbb{R}^{m} : \\sum_{i=1}^m y_i a_i \\in \\text{set} \\}.`` -""" -struct LinearCombinationInSet{S,A,V<:AbstractVector{A}} <: AbstractVectorSet - set::S - vectors::V -end - -function Base.:(==)(s1::LinearCombinationInSet, s2::LinearCombinationInSet) - return s1.set == s2.set && s1.vectors == s2.vectors -end - -function Base.copy(s::LinearCombinationInSet) - return LinearCombinationInSet(copy(s.set), copy(s.vectors)) -end - -dimension(s::LinearCombinationInSet) = length(s.vectors) - -function MOI.Bridges.Constraint.conversion_cost( - ::Type{LinearCombinationInSet{S,A1,Vector{A1}}}, - ::Type{LinearCombinationInSet{S,A2,Vector{A2}}}, -) where {S,A1,A2} - return MOI.Bridges.Constraint.conversion_cost(A1, A2) -end - -function convert( - ::Type{LinearCombinationInSet{S,A,Vector{A}}}, - set::LinearCombinationInSet, -) - return LinearCombinationInSet(set.set, convert(A, set.vectors)) -end - -function dual_set(s::SetDotProducts) - return LinearCombinationInSet(s.set, s.vectors) -end - -function dual_set_type(::Type{SetDotProducts{S,A,V}}) where {S,A,V} - return LinearCombinationInSet{S,A,V} -end - -function dual_set(s::LinearCombinationInSet) - return SetDotProducts(s.side_dimension, s.vectors) -end - -function dual_set_type(::Type{LinearCombinationInSet{S,A,V}}) where {S,A,V} - return SetDotProducts{S,A,V} -end - -abstract type AbstractFactorization{T,F} <: AbstractMatrix{T} end - -function Base.size(m::AbstractFactorization) - n = size(m.factor, 1) - return (n, n) -end - -""" - struct Factorization{ - T, - F<:Union{AbstractVector{T},AbstractMatrix{T}}, - D<:Union{T,AbstractVector{T}}, - } <: AbstractMatrix{T} - factor::F - scaling::D - end - -Matrix corresponding to `factor * Diagonal(diagonal) * factor'`. -If `factor` is a vector and `diagonal` is a scalar, this corresponds to -the matrix `diagonal * factor * factor'`. -If `factor` is a matrix and `diagonal` is a vector, this corresponds to -the matrix `factor * Diagonal(scaling) * factor'`. -""" -struct Factorization{ - T, - F<:Union{AbstractVector{T},AbstractMatrix{T}}, - D<:Union{T,AbstractVector{T}}, -} <: AbstractFactorization{T,F} - factor::F - scaling::D - function Factorization( - factor::AbstractMatrix{T}, - scaling::AbstractVector{T}, - ) where {T} - if length(scaling) != size(factor, 2) - error( - "Length `$(length(scaling))` of diagonal does not match number of columns `$(size(factor, 2))` of factor", - ) - end - return new{T,typeof(factor),typeof(scaling)}(factor, scaling) - end - function Factorization(factor::AbstractVector{T}, scaling::T) where {T} - return new{T,typeof(factor),typeof(scaling)}(factor, scaling) - end -end - -function Base.getindex(m::Factorization, i::Int, j::Int) - return sum( - m.factor[i, k] * m.scaling[k] * m.factor[j, k]' for - k in eachindex(m.scaling) - ) -end - -""" - struct PositiveSemidefiniteFactorization{ - T, - F<:Union{AbstractVector{T},AbstractMatrix{T}}, - } <: AbstractFactorization{T,F} - factor::F - end - -Matrix corresponding to `factor * Diagonal(diagonal) * factor'`. -If `factor` is a vector and `diagonal` is a scalar, this corresponds to -the matrix `diagonal * factor * factor'`. -If `factor` is a matrix and `diagonal` is a vector, this corresponds to -the matrix `factor * Diagonal(scaling) * factor'`. -""" -struct PositiveSemidefiniteFactorization{ - T, - F<:Union{AbstractVector{T},AbstractMatrix{T}}, -} <: AbstractFactorization{T,F} - factor::F -end - -function Base.getindex(m::PositiveSemidefiniteFactorization, i::Int, j::Int) - return sum(m.factor[i, k] * m.factor[j, k]' for k in axes(m.factor, 2)) -end - -function MOI.Bridges.Constraint.conversion_cost( - ::Type{<:AbstractMatrix}, - ::Type{<:AbstractMatrix}, -) - return Inf -end - -function MOI.Bridges.Constraint.conversion_cost( - ::Type{<:Factorization{T,F}}, - ::Type{PositiveSemidefiniteFactorization{T,F}}, -) where {T,F} - return 1.0 -end - -function Base.convert( - ::Type{Factorization{T,F,D}}, - f::PositiveSemidefiniteFactorization{T,F}, -) where {F<:AbstractVector} - return Factorization{T,F,D}(f.factor, one(T)) -end - -function Base.convert( - ::Type{Factorization{T,F,D}}, - f::PositiveSemidefiniteFactorization{T,F}, -) where {F<:AbstractVector} - return Factorization{T,F,D}(f.factor, one(T)) -end - -struct TriangleVectorization{T,M<:AbstractMatrix{T}} <: AbstractVector{T} - matrix::M -end - -function MOI.Bridges.Constraint.conversion_cost( - ::Type{TriangleVectorization{T,M1}}, - ::Type{TriangleVectorization{T,M2}}, -) where {T,M1,M2} - return MOI.Bridges.Constraint.conversion_cost(M1, M2) -end - -function Base.size(v::TriangleVectorization) - n = size(v.matrix, 1) - return (Utilities.trimap(n, n),) -end - -function Base.getindex(v::TriangleVectorization, k::Int) - return getindex(v.matrix, Utilities.inverse_trimap(k)...) -end - """ SOS1{T<:Real}(weights::Vector{T}) diff --git a/test/sets.jl b/test/sets.jl index 122a562f7b..952cf7ce49 100644 --- a/test/sets.jl +++ b/test/sets.jl @@ -354,41 +354,6 @@ function test_sets_reified() return end -function _test_factorization(A, B) - @test size(A) == size(B) - @test A ≈ B - d = LinearAlgebra.checksquare(A) - n = div(d * (d + 1), 2) - vA = MOI.TriangleVectorization(A) - @test length(vA) == n - @test eachindex(vA) == Base.OneTo(n) - vB = MOI.TriangleVectorization(B) - @test length(vB) == n - @test eachindex(vA) == Base.OneTo(n) - k = 0 - for j in 1:d - for i in 1:j - k += 1 - @test vA[k] == vB[k] - @test vA[k] == A[i, j] - end - end - return -end - -function test_factorizations() - f = [1, 2] - _test_factorization(f * f', MOI.PositiveSemidefiniteFactorization(f)) - _test_factorization(2 * f * f', MOI.Factorization(f, 2)) - F = [1 2; 3 4; 5 6] - d = [7, 8] - _test_factorization(F * F', MOI.PositiveSemidefiniteFactorization(F)) - return _test_factorization( - F * LinearAlgebra.Diagonal(d) * F', - MOI.Factorization(F, d), - ) -end - function runtests() for name in names(@__MODULE__; all = true) if startswith("$name", "test_") From 3c17ebf76dd6cbf0b24c0c30e8b84766dde64127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 11 Dec 2024 09:26:51 +0100 Subject: [PATCH 18/21] Remove what was moved to LowRankOpt --- docs/src/background/duality.md | 4 +- docs/src/manual/standard_form.md | 2 - docs/src/reference/standard_form.md | 2 - test/Bridges/Variable/set_dot.jl | 72 ----------------------------- test/sets.jl | 1 - 5 files changed, 1 insertion(+), 80 deletions(-) delete mode 100644 test/Bridges/Variable/set_dot.jl diff --git a/docs/src/background/duality.md b/docs/src/background/duality.md index f1c56d27f5..1042e2b5eb 100644 --- a/docs/src/background/duality.md +++ b/docs/src/background/duality.md @@ -113,9 +113,7 @@ and similarly, the dual is: The scalar product is different from the canonical one for the sets [`PositiveSemidefiniteConeTriangle`](@ref), [`LogDetConeTriangle`](@ref), -[`RootDetConeTriangle`](@ref), -[`SetDotProducts`](@ref) and -[`LinearCombinationInSet`](@ref). +[`RootDetConeTriangle`](@ref). If the set ``C_i`` of the section [Duality](@ref) is one of these three cones, then the rows of the matrix ``A_i`` corresponding to off-diagonal entries are diff --git a/docs/src/manual/standard_form.md b/docs/src/manual/standard_form.md index 4596021400..57b8a8e2d2 100644 --- a/docs/src/manual/standard_form.md +++ b/docs/src/manual/standard_form.md @@ -82,8 +82,6 @@ The vector-valued set types implemented in MathOptInterface.jl are: | [`RelativeEntropyCone(d)`](@ref MathOptInterface.RelativeEntropyCone) | ``\{ (u, v, w) \in \mathbb{R}^{d} : u \ge \sum_i w_i \log (\frac{w_i}{v_i}), v_i \ge 0, w_i \ge 0 \}`` | | [`HyperRectangle(l, u)`](@ref MathOptInterface.HyperRectangle) | ``\{x \in \bar{\mathbb{R}}^d: x_i \in [l_i, u_i] \forall i=1,\ldots,d\}`` | | [`NormCone(p, d)`](@ref MathOptInterface.NormCone) | ``\{ (t,x) \in \mathbb{R}^{d} : t \ge \left(\sum\limits_i \lvert x_i \rvert^p\right)^{\frac{1}{p}} \}`` | -| [`SetDotProducts(s, v)`](@ref MathOptInterface.SetDotProducts) | The cone `s` with dot products with the fixed vectors `v`. | -| [`LinearCombinationInSet(s, v)`](@ref MathOptInterface.LinearCombinationInSet) | The cone of vector `(y, x)` such that ``\sum_i y_i v_i + x`` belongs to `s`. | ## Matrix cones diff --git a/docs/src/reference/standard_form.md b/docs/src/reference/standard_form.md index 3c62a6b632..98ff739a33 100644 --- a/docs/src/reference/standard_form.md +++ b/docs/src/reference/standard_form.md @@ -153,6 +153,4 @@ LogDetConeTriangle LogDetConeSquare RootDetConeTriangle RootDetConeSquare -SetDotProducts -LinearCombinationInSet ``` diff --git a/test/Bridges/Variable/set_dot.jl b/test/Bridges/Variable/set_dot.jl deleted file mode 100644 index b9a52377a9..0000000000 --- a/test/Bridges/Variable/set_dot.jl +++ /dev/null @@ -1,72 +0,0 @@ -# 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 TestVariableDotProducts - -using Test - -import MathOptInterface as MOI - -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_psd() - MOI.Bridges.runtests( - MOI.Bridges.Variable.DotProductsBridge, - model -> begin - x, _ = MOI.add_constrained_variables( - model, - MOI.SetDotProducts( - MOI.PositiveSemidefiniteConeTriangle(2), - MOI.TriangleVectorization.([ - [ - 1 2.0 - 2 3 - ], - [ - 4 5.0 - 5 6 - ], - ]), - ), - ) - MOI.add_constraint(model, 1.0x[1], MOI.EqualTo(0.0)) - MOI.add_constraint(model, 1.0x[2], MOI.LessThan(0.0)) - end, - model -> begin - Q, _ = MOI.add_constrained_variables( - model, - MOI.PositiveSemidefiniteConeTriangle(2), - ) - MOI.add_constraint( - model, - 1.0 * Q[1] + 4.0 * Q[2] + 3.0 * Q[3], - MOI.EqualTo(0.0), - ) - MOI.add_constraint( - model, - 4.0 * Q[1] + 10.0 * Q[2] + 6.0 * Q[3], - MOI.LessThan(0.0), - ) - end; - cannot_unbridge = true, - ) - return -end - -end # module - -TestVariableDotProducts.runtests() diff --git a/test/sets.jl b/test/sets.jl index 952cf7ce49..bf52326974 100644 --- a/test/sets.jl +++ b/test/sets.jl @@ -8,7 +8,6 @@ module TestSets using Test import MathOptInterface as MOI -import LinearAlgebra include("dummy.jl") From a21c804cb6fd45b3979189cd11838bed6a0e0311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 12 Dec 2024 17:24:56 +0100 Subject: [PATCH 19/21] Add test --- src/Bridges/Variable/set_map.jl | 2 +- test/Bridges/set_map.jl | 92 ++++++++++++++++++++++++++------- 2 files changed, 73 insertions(+), 21 deletions(-) diff --git a/src/Bridges/Variable/set_map.jl b/src/Bridges/Variable/set_map.jl index 6743b8fdb3..d34685e661 100644 --- a/src/Bridges/Variable/set_map.jl +++ b/src/Bridges/Variable/set_map.jl @@ -192,7 +192,7 @@ function MOI.set( if value === nothing MOI.set(model, attr, bridge.variables[i.value], nothing) else - bridged_value = MOI.Bridges.inverse_map_function(typeof(bridge), value) + bridged_value = MOI.Bridges.inverse_map_function(bridge, value) MOI.set(model, attr, bridge.variables[i.value], bridged_value) end return diff --git a/test/Bridges/set_map.jl b/test/Bridges/set_map.jl index f4f600c60d..c6d54da00f 100644 --- a/test/Bridges/set_map.jl +++ b/test/Bridges/set_map.jl @@ -22,7 +22,45 @@ end MOI.dimension(::SwapSet) = 2 -struct SwapBridge{T} <: MOI.Bridges.Constraint.SetMapBridge{ +struct VariableSwapBridge{T} <: MOI.Bridges.Variable.SetMapBridge{ + T, + MOI.Nonnegatives, + SwapSet, +} + variables::MOI.Vector{MOI.VariableIndex} + constraint::MOI.ConstraintIndex{MOI.VectorOfVariables,MOI.Nonnegatives} + set::SwapSet +end + +function MOI.Bridges.Variable.bridge_constrained_variable( + ::Type{VariableSwapBridge{T}}, + model::MOI.ModelLike, + set::SwapSet, +) where {T} + variables, constraint = MOI.add_constrained_variables( + model, + MOI.Nonnegatives(2), + ) + return VariableSwapBridge{T}(variables, constraint, set) +end + +MOI.Bridges.map_set(bridge::VariableSwapBridge, ::MOI.Nonnegatives) = bridge.set + +function MOI.Bridges.inverse_map_set(bridge::VariableSwapBridge, set::SwapSet) + if set.swap != bridge.set.swap + error("Cannot change swap set") + end + return MOI.Nonnegatives(2) +end + +function MOI.Bridges.map_function(bridge::VariableSwapBridge, func, i::MOI.Bridges.IndexInVector) + return MOI.Bridges.map_function(bridge, func)[i.value] +end + +# Workaround until https://github.com/jump-dev/MathOptInterface.jl/issues/2117 is fixed +MOI.Bridges.inverse_map_function(::VariableSwapBridge, a::Float64) = a + +struct ConstraintSwapBridge{T} <: MOI.Bridges.Constraint.SetMapBridge{ T, MOI.Nonnegatives, SwapSet, @@ -34,7 +72,7 @@ struct SwapBridge{T} <: MOI.Bridges.Constraint.SetMapBridge{ end function MOI.Bridges.Constraint.bridge_constraint( - ::Type{SwapBridge{T}}, + ::Type{ConstraintSwapBridge{T}}, model::MOI.ModelLike, func::MOI.VectorOfVariables, set::SwapSet, @@ -44,17 +82,19 @@ function MOI.Bridges.Constraint.bridge_constraint( MOI.VectorOfVariables(swap(func.variables, set.swap)), MOI.Nonnegatives(2), ) - return SwapBridge{T}(ci, set) + return ConstraintSwapBridge{T}(ci, set) end -function MOI.Bridges.map_set(bridge::SwapBridge, set::SwapSet) +function MOI.Bridges.map_set(bridge::ConstraintSwapBridge, set::SwapSet) if set.swap != bridge.set.swap error("Cannot change swap set") end return MOI.Nonnegatives(2) end -MOI.Bridges.inverse_map_set(bridge::SwapBridge, ::MOI.Nonnegatives) = bridge.set +MOI.Bridges.inverse_map_set(bridge::ConstraintSwapBridge, ::MOI.Nonnegatives) = bridge.set + +const SwapBridge{T} = Union{VariableSwapBridge{T},ConstraintSwapBridge{T}} function MOI.Bridges.map_function(bridge::SwapBridge, func) return swap(func, bridge.set.swap) @@ -90,19 +130,8 @@ function swap(f::MOI.VectorOfVariables, do_swap::Bool) return MOI.VectorOfVariables(swap(f.variables, do_swap)) end -function runtests() - for name in names(@__MODULE__; all = true) - if startswith("$(name)", "test_") - @testset "$(name)" begin - getfield(@__MODULE__, name)() - end - end - end - return -end - function test_other_error() - model = MOI.Bridges.Constraint.SingleBridgeOptimizer{SwapBridge{Float64}}( + model = MOI.Bridges.Constraint.SingleBridgeOptimizer{ConstraintSwapBridge{Float64}}( MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), ) x = MOI.add_variables(model, 2) @@ -123,8 +152,9 @@ function test_other_error() ) return end -function test_not_invertible() - model = MOI.Bridges.Constraint.SingleBridgeOptimizer{SwapBridge{Float64}}( + +function test_constraint_not_invertible() + model = MOI.Bridges.Constraint.SingleBridgeOptimizer{ConstraintSwapBridge{Float64}}( MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), ) x = MOI.add_variables(model, 2) @@ -162,7 +192,7 @@ end function test_runtests() for do_swap in [false, true] MOI.Bridges.runtests( - SwapBridge, + ConstraintSwapBridge, model -> begin x = MOI.add_variables(model, 2) func = MOI.VectorOfVariables(x) @@ -176,6 +206,28 @@ function test_runtests() MOI.add_constraint(model, func, set) end, ) + MOI.Bridges.runtests( + VariableSwapBridge, + model -> begin + set = SwapSet(do_swap, NONE) + x = MOI.add_constrained_variables(model, set) + end, + model -> begin + set = MOI.Nonnegatives(2) + x = MOI.add_constrained_variables(model, set) + end, + ) + end + return +end + +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$(name)", "test_") + @testset "$(name)" begin + getfield(@__MODULE__, name)() + end + end end return end From 34d107b60ab264bff5b9a26ce6d0992a40c9d614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 12 Dec 2024 17:25:23 +0100 Subject: [PATCH 20/21] Fix format --- test/Bridges/set_map.jl | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/test/Bridges/set_map.jl b/test/Bridges/set_map.jl index c6d54da00f..cc64cb18ed 100644 --- a/test/Bridges/set_map.jl +++ b/test/Bridges/set_map.jl @@ -22,11 +22,8 @@ end MOI.dimension(::SwapSet) = 2 -struct VariableSwapBridge{T} <: MOI.Bridges.Variable.SetMapBridge{ - T, - MOI.Nonnegatives, - SwapSet, -} +struct VariableSwapBridge{T} <: + MOI.Bridges.Variable.SetMapBridge{T,MOI.Nonnegatives,SwapSet} variables::MOI.Vector{MOI.VariableIndex} constraint::MOI.ConstraintIndex{MOI.VectorOfVariables,MOI.Nonnegatives} set::SwapSet @@ -37,10 +34,8 @@ function MOI.Bridges.Variable.bridge_constrained_variable( model::MOI.ModelLike, set::SwapSet, ) where {T} - variables, constraint = MOI.add_constrained_variables( - model, - MOI.Nonnegatives(2), - ) + variables, constraint = + MOI.add_constrained_variables(model, MOI.Nonnegatives(2)) return VariableSwapBridge{T}(variables, constraint, set) end @@ -53,7 +48,11 @@ function MOI.Bridges.inverse_map_set(bridge::VariableSwapBridge, set::SwapSet) return MOI.Nonnegatives(2) end -function MOI.Bridges.map_function(bridge::VariableSwapBridge, func, i::MOI.Bridges.IndexInVector) +function MOI.Bridges.map_function( + bridge::VariableSwapBridge, + func, + i::MOI.Bridges.IndexInVector, +) return MOI.Bridges.map_function(bridge, func)[i.value] end @@ -92,7 +91,12 @@ function MOI.Bridges.map_set(bridge::ConstraintSwapBridge, set::SwapSet) return MOI.Nonnegatives(2) end -MOI.Bridges.inverse_map_set(bridge::ConstraintSwapBridge, ::MOI.Nonnegatives) = bridge.set +function MOI.Bridges.inverse_map_set( + bridge::ConstraintSwapBridge, + ::MOI.Nonnegatives, +) + return bridge.set +end const SwapBridge{T} = Union{VariableSwapBridge{T},ConstraintSwapBridge{T}} @@ -131,7 +135,9 @@ function swap(f::MOI.VectorOfVariables, do_swap::Bool) end function test_other_error() - model = MOI.Bridges.Constraint.SingleBridgeOptimizer{ConstraintSwapBridge{Float64}}( + model = MOI.Bridges.Constraint.SingleBridgeOptimizer{ + ConstraintSwapBridge{Float64}, + }( MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), ) x = MOI.add_variables(model, 2) @@ -154,7 +160,9 @@ function test_other_error() end function test_constraint_not_invertible() - model = MOI.Bridges.Constraint.SingleBridgeOptimizer{ConstraintSwapBridge{Float64}}( + model = MOI.Bridges.Constraint.SingleBridgeOptimizer{ + ConstraintSwapBridge{Float64}, + }( MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), ) x = MOI.add_variables(model, 2) From 27631087c72adb5745e215f48b9b9336e61af2e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Thu, 12 Dec 2024 17:28:32 +0100 Subject: [PATCH 21/21] Add tests --- test/Utilities/functions.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/Utilities/functions.jl b/test/Utilities/functions.jl index 78f63c90bb..6438436103 100644 --- a/test/Utilities/functions.jl +++ b/test/Utilities/functions.jl @@ -434,6 +434,8 @@ end function test_iteration_and_indexing_on_VectorOfVariables() f = MOI.VectorOfVariables([z, w, x, y]) it = MOI.Utilities.eachscalar(f) + @test it isa AbstractVector{MOI.VariableIndex} + @test size(it) == (4,) @test length(it) == 4 @test eltype(it) == MOI.VariableIndex @test collect(it) == [z, w, x, y] @@ -454,6 +456,8 @@ function test_indexing_on_VectorAffineFunction() [2, 7, 5], ) it = MOI.Utilities.eachscalar(f) + @test it isa AbstractVector{MOI.ScalarAffineFunction{Int}} + @test size(it) == (3,) @test length(it) == 3 @test eltype(it) == MOI.ScalarAffineFunction{Int} g = it[2]