From 66fcd61ae996983ecc2fa73c4f3e1842758fa039 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/15] 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 0aa4690640..08b1d0010a 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -792,6 +792,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 534106e9de..c821e9e20a 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -1791,6 +1791,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 e37bd5315db54a1c572334b7d2ea1f1a70806b63 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/15] 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 08b1d0010a..0aa4690640 100644 --- a/src/Utilities/model.jl +++ b/src/Utilities/model.jl @@ -792,8 +792,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 d0f3718083..188b6824a3 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 c821e9e20a..8e5c81b1d4 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -1792,58 +1792,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 e8ae8fd99a5be34c689aa42008603bf747c45ffd 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/15] 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 188b6824a3..488ee0a4b9 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 8e5c81b1d4..090856761a 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -1810,9 +1810,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 @@ -1830,10 +1828,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 a19ca8d76c6116f44a303bd443e4704f41dda1eb 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/15] 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 e0856f1b4b4261d920fe36d02a04f0674dd28242 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/15] Add low rank matrix --- src/sets.jl | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/sets.jl b/src/sets.jl index 090856761a..45c608e49f 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -1835,6 +1835,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 84097c132543e31837bb8b64f1c6ba006397fd70 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/15] Add bridge --- src/Bridges/Variable/Variable.jl | 2 + 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, 101 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 5c905c9146..38a3b7635c 100644 --- a/src/Bridges/Variable/Variable.jl +++ b/src/Bridges/Variable/Variable.jl @@ -22,6 +22,7 @@ include("bridges/vectorize.jl") include("bridges/zeros.jl") include("bridges/hermitian.jl") include("bridges/parameter.jl") +include("bridges/set_dot.jl") """ add_all_bridges(model, ::Type{T}) where {T} @@ -40,6 +41,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 e3f0d5a172..822f013c3c 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 45c608e49f..ea3fe6404f 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -1843,15 +1843,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 7b782f53f55e177c1dd7b7beb5c04434e837fdf2 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/15] Add copy --- src/sets.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sets.jl b/src/sets.jl index ea3fe6404f..46d4d50cf1 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -1802,6 +1802,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 1b312df535b134183f909f1ae5cffa15d78cf4b8 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/15] 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 822f013c3c..a9fb462e48 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 46d4d50cf1..366994575f 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -1792,73 +1792,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 28b0c5000a..3e25051c12 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 a1e87a8446c50e26f2c84e0162444617e629f1ea 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/15] 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 fc9b6f1ebf8aee1f185df9d69fc7821eb2565c21 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/15] 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 366994575f..548b7dc3d4 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -1802,6 +1802,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 fc936c22040a4d3b821dcbf6b70e711952adbcf7 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/15] 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 2caee53fe8737e19da3ff11a43baa6d6973cfb11 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/15] 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 | 2 + 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, 213 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 6f6e7bec07..10727ff5be 100644 --- a/src/Bridges/Constraint/Constraint.jl +++ b/src/Bridges/Constraint/Constraint.jl @@ -66,6 +66,7 @@ include("bridges/zero_one.jl") include("bridges/sos1_to_milp.jl") include("bridges/sos2_to_milp.jl") include("bridges/indicator_to_milp.jl") +include("bridges/linear_combination.jl") """ add_all_bridges(bridged_model, ::Type{T}) where {T} @@ -153,6 +154,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 488ee0a4b9..d0f3718083 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 548b7dc3d4..b85e0bafc1 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -1792,31 +1792,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 @@ -1831,14 +1831,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 66a0bddca1d12c4ae88b144204d862b3221c2f08 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/15] 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 0fc97ba41e3dbe629ab54759d7f7585235dd19cc 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/15] 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 6a1053a79aff051ec6596ad0614a47811fc0dd58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 31 Jul 2024 23:21:32 +0200 Subject: [PATCH 15/15] Typo fix detected by Vale --- src/Bridges/Variable/bridges/set_dot.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridges/Variable/bridges/set_dot.jl b/src/Bridges/Variable/bridges/set_dot.jl index a2468f4b9c..51cfc3664f 100644 --- a/src/Bridges/Variable/bridges/set_dot.jl +++ b/src/Bridges/Variable/bridges/set_dot.jl @@ -67,7 +67,7 @@ function MOI.Bridges.map_function(bridge::DotProductsBridge{T}, func) where {T} end # This returns `true` by default for `SetMapBridge` -# but is is not supported for this bridge because `inverse_map_function` +# but it is not supported for this bridge because `inverse_map_function` # is not implemented function MOI.supports( ::MOI.ModelLike,