diff --git a/docs/src/apireference.md b/docs/src/apireference.md index 253885adbe..25f60eeb24 100644 --- a/docs/src/apireference.md +++ b/docs/src/apireference.md @@ -225,18 +225,43 @@ constant(f::VectorOfVariables, T::DataType) ## Sets -List of recognized sets. - +All sets are subtypes of [`AbstractSet`](@ref) and they should either be scalar +or vector sets. ```@docs AbstractSet -Reals -Zeros -Nonnegatives -Nonpositives +AbstractScalarSet +AbstractVectorSet +``` + +Functions for getting properties of sets. +```@docs +dimension +constant(s::EqualTo) +``` + +### Scalar sets + +List of recognized scalar sets. +```@docs GreaterThan LessThan EqualTo Interval +Integer +ZeroOne +Semicontinuous +Semiinteger +``` + + +### Vector sets + +List of recognized vector sets. +```@docs +Reals +Zeros +Nonnegatives +Nonpositives SecondOrderCone RotatedSecondOrderCone GeometricMeanCone @@ -244,28 +269,42 @@ ExponentialCone DualExponentialCone PowerCone DualPowerCone -PositiveSemidefiniteConeTriangle -PositiveSemidefiniteConeSquare -LogDetConeTriangle -LogDetConeSquare -RootDetConeTriangle -RootDetConeSquare -Integer -ZeroOne -Semicontinuous -Semiinteger SOS1 SOS2 IndicatorSet ``` -Functions for getting and setting properties of sets. +### Matrix sets +Matrix sets are vectorized in order to be subtypes of +[`AbstractVectorSet`](@ref). For sets of symmetric matrices, storing both the +`(i, j)` and `(j, i)` elements is redundant so there exists the +[`AbstractSymmetricMatrixSetTriangle`](@ref) set to represent only the +vectorization of the upper triangular part of the matrix. When the matrix +of expressions constrained to be in the set is not symmetric and hence +the `(i, j)` and `(j, i)` elements should be constrained to be symmetric, +the [`AbstractSymmetricMatrixSetSquare`](@ref) set can be used. The +[`Bridges.SquareBridge`](@ref) can transform a set from the square form +to the [`triangular_form`](@ref) by adding appropriate constraints if +the `(i, j)` and `(j, i)` expressions are different. ```@docs -dimension -constant(s::EqualTo) +AbstractSymmetricMatrixSetTriangle +AbstractSymmetricMatrixSetSquare +side_dimension +triangular_form +``` +List of recognized matrix sets. +```@docs +PositiveSemidefiniteConeTriangle +PositiveSemidefiniteConeSquare +LogDetConeTriangle +LogDetConeSquare +RootDetConeTriangle +RootDetConeSquare ``` +## Sets + ## Modifications Functions for modifying objective and constraint functions. @@ -379,7 +418,7 @@ Bridges.SplitIntervalBridge Bridges.RSOCBridge Bridges.QuadtoSOCBridge Bridges.GeoMeanBridge -Bridges.SquarePSDBridge +Bridges.SquareBridge Bridges.RootDetBridge Bridges.LogDetBridge Bridges.SOCtoPSDBridge diff --git a/src/Bridges/Bridges.jl b/src/Bridges/Bridges.jl index 0f836763f1..3a702d3d0c 100644 --- a/src/Bridges/Bridges.jl +++ b/src/Bridges/Bridges.jl @@ -42,7 +42,7 @@ function full_bridge_optimizer(model::MOI.ModelLike, ::Type{T}) where T add_bridge(bridged_model, SplitIntervalBridge{T}) add_bridge(bridged_model, QuadtoSOCBridge{T}) add_bridge(bridged_model, GeoMeanBridge{T}) - add_bridge(bridged_model, SquarePSDBridge{T}) + add_bridge(bridged_model, SquareBridge{T}) add_bridge(bridged_model, LogDetBridge{T}) add_bridge(bridged_model, RootDetBridge{T}) add_bridge(bridged_model, RSOCBridge{T}) @@ -74,8 +74,8 @@ include("quadtosocbridge.jl") const QuadtoSOC{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{QuadtoSOCBridge{T}, OT} include("geomeanbridge.jl") const GeoMean{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{GeoMeanBridge{T}, OT} -include("squarepsdbridge.jl") -const SquarePSD{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{SquarePSDBridge{T}, OT} +include("square_bridge.jl") +const Square{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{SquareBridge{T}, OT} include("detbridge.jl") const LogDet{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{LogDetBridge{T}, OT} const RootDet{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{RootDetBridge{T}, OT} diff --git a/src/Bridges/squarepsdbridge.jl b/src/Bridges/square_bridge.jl similarity index 76% rename from src/Bridges/squarepsdbridge.jl rename to src/Bridges/square_bridge.jl index 5dff132b5a..8fa87790d9 100644 --- a/src/Bridges/squarepsdbridge.jl +++ b/src/Bridges/square_bridge.jl @@ -35,12 +35,14 @@ # In the last primal program, we have the variables Z = X + Xᵀ and a upper triangular matrix S such that X = Z + S - Sᵀ """ - SquarePSDBridge{T} + SquareBridge{T, F<:MOI.AbstractVectorFunction, + G<:MOI.AbstractScalarFunction, + TT<:MOI.AbstractSymmetricMatrixSetTriangle, + ST<:MOI.AbstractSymmetricMatrixSetSquare} <: AbstractBridge -The `SquarePSDBridge` reformulates the constraint of a square matrix to be PSD -and symmetric, i.e. belongs to the [`MOI.PositiveSemidefiniteConeSquare`](@ref), +The `SquareBridge` reformulates the constraint of a square matrix to be in `ST` to a list of equality constraints for pair or off-diagonal entries with -different expressions and a PSD constraint the upper triangular part of the +different expressions and a `TT` constraint the upper triangular part of the matrix. For instance, the constraint for the matrix @@ -64,18 +66,20 @@ and the equality constraint between the off-diagonal entries (2, 3) and (3, 2) the off-diagonal entries (1, 2) and (2, 1) or between (1, 3) and (3, 1) since the expressions are the same. """ -struct SquarePSDBridge{T, F<:MOI.AbstractVectorFunction, - G<:MOI.AbstractScalarFunction} <: AbstractBridge +struct SquareBridge{T, F<:MOI.AbstractVectorFunction, + G<:MOI.AbstractScalarFunction, + TT<:MOI.AbstractSymmetricMatrixSetTriangle, + ST<:MOI.AbstractSymmetricMatrixSetSquare} <: AbstractBridge side_dimension::Int - psd::CI{F, MOI.PositiveSemidefiniteConeTriangle} + triangle::CI{F, TT} sym::Vector{Pair{Tuple{Int, Int}, CI{G, MOI.EqualTo{T}}}} end -function bridge_constraint(::Type{SquarePSDBridge{T, F, G}}, +function bridge_constraint(::Type{SquareBridge{T, F, G, TT, ST}}, model::MOI.ModelLike, f::F, - s::MOI.PositiveSemidefiniteConeSquare) where {T, F, G} + s::ST) where {T, F, G, TT, ST} f_scalars = MOIU.eachscalar(f) sym = Pair{Tuple{Int, Int}, CI{G, MOI.EqualTo{T}}}[] - dim = s.side_dimension + dim = MOI.side_dimension(s) upper_triangle_indices = Int[] trilen = div(dim * (dim + 1), 2) sizehint!(upper_triangle_indices, trilen) @@ -109,48 +113,48 @@ function bridge_constraint(::Type{SquarePSDBridge{T, F, G}}, k += dim - j end @assert length(upper_triangle_indices) == trilen - psd = MOI.add_constraint(model, f_scalars[upper_triangle_indices], - MOI.PositiveSemidefiniteConeTriangle(dim)) - return SquarePSDBridge(dim, psd, sym) + triangle = MOI.add_constraint(model, f_scalars[upper_triangle_indices], MOI.triangular_form(s)) + return SquareBridge{T, F, G, TT, ST}(dim, triangle, sym) end -function MOI.supports_constraint(::Type{SquarePSDBridge{T}}, +function MOI.supports_constraint(::Type{SquareBridge{T}}, ::Type{<:MOI.AbstractVectorFunction}, - ::Type{MOI.PositiveSemidefiniteConeSquare}) where T + ::Type{<:MOI.AbstractSymmetricMatrixSetSquare}) where T return true end -function added_constraint_types(::Type{SquarePSDBridge{T, F, G}}) where {T, F, G} - return [(F, MOI.PositiveSemidefiniteConeTriangle), (G, MOI.EqualTo{T})] +function added_constraint_types(::Type{SquareBridge{T, F, G, TT, ST}}) where {T, F, G, TT, ST} + return [(F, TT), (G, MOI.EqualTo{T})] end -function concrete_bridge_type(::Type{<:SquarePSDBridge{T}}, +function concrete_bridge_type(::Type{<:SquareBridge{T}}, F::Type{<:MOI.AbstractVectorFunction}, - ::Type{MOI.PositiveSemidefiniteConeSquare}) where T + ST::Type{<:MOI.AbstractSymmetricMatrixSetSquare}) where T S = MOIU.scalar_type(F) G = MOIU.promote_operation(-, T, S, S) - SquarePSDBridge{T, F, G} + TT = MOI.triangular_form(ST) + SquareBridge{T, F, G, TT, ST} end # Attributes, Bridge acting as an model -function MOI.get(::SquarePSDBridge{T, F}, - ::MOI.NumberOfConstraints{F, MOI.PositiveSemidefiniteConeTriangle}) where {T, F} +function MOI.get(::SquareBridge{T, F, G, TT}, + ::MOI.NumberOfConstraints{F, TT}) where {T, F, G, TT} return 1 end -function MOI.get(bridge::SquarePSDBridge{T, F, G}, +function MOI.get(bridge::SquareBridge{T, F, G}, ::MOI.NumberOfConstraints{G, MOI.EqualTo{T}}) where {T, F, G} return length(bridge.sym) end -function MOI.get(bridge::SquarePSDBridge{T, F}, - ::MOI.ListOfConstraintIndices{F, MOI.PositiveSemidefiniteConeTriangle}) where {T, F} - return [bridge.psd] +function MOI.get(bridge::SquareBridge{T, F, G, TT}, + ::MOI.ListOfConstraintIndices{F, TT}) where {T, F, G, TT} + return [bridge.triangle] end -function MOI.get(bridge::SquarePSDBridge{T, F, G}, +function MOI.get(bridge::SquareBridge{T, F, G}, ::MOI.ListOfConstraintIndices{G, MOI.EqualTo{T}}) where {T, F, G} return map(pair -> pair.second, bridge.sym) end # Indices -function MOI.delete(model::MOI.ModelLike, bridge::SquarePSDBridge) - MOI.delete(model, bridge.psd) +function MOI.delete(model::MOI.ModelLike, bridge::SquareBridge) + MOI.delete(model, bridge.triangle) for pair in bridge.sym MOI.delete(model, pair.second) end @@ -158,8 +162,8 @@ end # Attributes, Bridge acting as a constraint function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintPrimal, - bridge::SquarePSDBridge{T}) where T - tri = MOI.get(model, attr, bridge.psd) + bridge::SquareBridge{T}) where T + tri = MOI.get(model, attr, bridge.triangle) dim = bridge.side_dimension sqr = Vector{eltype(tri)}(undef, dim^2) k = 0 @@ -172,15 +176,15 @@ function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintPrimal, return sqr end function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintDual, - bridge::SquarePSDBridge) - tri = MOI.get(model, attr, bridge.psd) + bridge::SquareBridge) + tri = MOI.get(model, attr, bridge.triangle) dim = bridge.side_dimension sqr = Vector{eltype(tri)}(undef, dim^2) k = 0 for j in 1:dim for i in 1:j k += 1 - # The psd constraint uses only the upper triangular part + # The triangle constraint uses only the upper triangular part if i == j sqr[i + (j - 1) * dim] = tri[k] else diff --git a/src/Utilities/results.jl b/src/Utilities/results.jl index b82f3c462e..f27c8c84e6 100644 --- a/src/Utilities/results.jl +++ b/src/Utilities/results.jl @@ -407,8 +407,8 @@ function triangle_dot(x::Vector{S}, y::Vector{T}, dim::Int, offset::Int) where { end function set_dot(x::Vector, y::Vector, - set::MOI.PositiveSemidefiniteConeTriangle) - return triangle_dot(x, y, set.side_dimension, 0) + set::MOI.AbstractSymmetricMatrixSetTriangle) + return triangle_dot(x, y, MOI.side_dimension(set), 0) end function set_dot(x::Vector, y::Vector, set::MOI.RootDetConeTriangle) @@ -441,9 +441,9 @@ function triangle_coefficients!(b::Vector{T}, dim::Int, offset::Int) where T end end -function dot_coefficients(a::Vector, set::MOI.PositiveSemidefiniteConeTriangle) +function dot_coefficients(a::Vector, set::MOI.AbstractSymmetricMatrixSetTriangle) b = copy(a) - triangle_coefficients!(b, set.side_dimension, 0) + triangle_coefficients!(b, MOI.side_dimension(set), 0) return b end diff --git a/src/sets.jl b/src/sets.jl index 2616bf2cff..e769cf3221 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -207,14 +207,14 @@ end dimension(s::Union{ExponentialCone, DualExponentialCone, PowerCone, DualPowerCone}) = 3 """ - PositiveSemidefiniteConeTriangle(side_dimension) - -The (vectorized) cone of symmetric positive semidefinite matrices, with -`side_dimension` rows and columns. The entries of the upper-right triangular -part of the matrix are given column by column (or equivalently, the entries of -the lower-left triangular part are given row by row). A vectorized cone of -[`dimension`](@ref) ``n`` corresponds to a square matrix with side dimension -``\\sqrt{1/4 + 2 n} - 1/2``. (Because a ``d \\times d`` matrix has + abstract type AbstractSymmetricMatrixSetTriangle <: AbstractVectorSet end + +Abstract supertype for subsets of the (vectorized) cone of symmetric matrices, +with [`side_dimension`](@ref) rows and columns. The entries of the upper-right +triangular part of the matrix are given column by column (or equivalently, the +entries of the lower-left triangular part are given row by row). A vectorized +cone of [`dimension`](@ref) ``n`` corresponds to a square matrix with side +dimension ``\\sqrt{1/4 + 2 n} - 1/2``. (Because a ``d \\times d`` matrix has ``d(d + 1) / 2`` elements in the upper or lower triangle.) ### Examples @@ -227,7 +227,7 @@ The matrix 4 & 5 & 6 \\end{bmatrix} ``` -corresponds to ``(1, 2, 3, 4, 5, 6)`` for `PositiveSemidefiniteConeTriangle(3)` +has [`side_dimension`](@ref) 3 and vectorization ``(1, 2, 3, 4, 5, 6)``. ### Note @@ -248,11 +248,15 @@ Indeed, `j = div(1 + isqrt(8k - 7), 2)` and `i = k - div((j - 1) * j, 2)`. ### Duality note + The scalar product for the symmetric matrix in its vectorized form is the sum of the pairwise product of the diagonal entries plus twice the sum of the pairwise product of the upper diagonal entries; see [p. 634, 1]. This has important consequence for duality. + Consider for example the following problem +([`PositiveSemidefiniteConeTriangle`](@ref) is a subtype of +[`AbstractSymmetricMatrixSetTriangle`](@ref)) ```math \\begin{align*} & \\max_{x \\in \\mathbb{R}} & x @@ -296,16 +300,92 @@ products we have [1] Boyd, S. and Vandenberghe, L.. *Convex optimization*. Cambridge university press, 2004. """ -struct PositiveSemidefiniteConeTriangle <: AbstractVectorSet - side_dimension::Int +abstract type AbstractSymmetricMatrixSetTriangle <: AbstractVectorSet end + +function dimension(set::AbstractSymmetricMatrixSetTriangle) + d = side_dimension(set) + return div(d * (d + 1), 2) end -dimension(s::PositiveSemidefiniteConeTriangle) = div(s.side_dimension * (s.side_dimension + 1), 2) +""" + abstract type AbstractSymmetricMatrixSetSquare <: AbstractVectorSet end + +Abstract supertype for subsets of the (vectorized) cone of symmetric matrices, +with [`side_dimension`](@ref) rows and columns. The entries of the matrix are +given column by column (or equivalently, row by row). The matrix is both +constrained to be symmetric and to have its [`triangular_form`](@ref) belong +to the corresponding set. That is, if the functions in entries ``(i, j)`` and +``(j, i)`` are different, then a constraint will be added to make sure that the +entries are equal. + +### Examples + +[`PositiveSemidefiniteConeSquare`](@ref) is a subtype of +[`AbstractSymmetricMatrixSetSquare`](@ref) and constraining the matrix +```math +\\begin{bmatrix} + 1 & -y\\\\ + -z & 0\\\\ +\\end{bmatrix} +``` +to be symmetric positive semidefinite can be achieved by constraining the vector +``(1, -z, -y, 0)`` (or ``(1, -y, -z, 0)``) to belong to the +`PositiveSemidefiniteConeSquare(2)`. It both constrains ``y = z`` and +``(1, -y, 0)`` (or ``(1, -z, 0)``) to be in +`PositiveSemidefiniteConeTriangle(2)`, since +`triangular_form(PositiveSemidefiniteConeSquare)` is +`PositiveSemidefiniteConeTriangle`. +""" +abstract type AbstractSymmetricMatrixSetSquare <: AbstractVectorSet end + +dimension(set::AbstractSymmetricMatrixSetSquare) = side_dimension(set)^2 """ - PositiveSemidefiniteConeSquare(side_dimension) + side_dimension(set::Union{AbstractSymmetricMatrixSetTriangle, + AbstractSymmetricMatrixSetSquare}) + +Side dimension of the matrices in `set`. By convention, it should be stored in +the `side_dimension` field but if it is not the case for a subtype of +[`AbstractSymmetricMatrixSetTriangle`](@ref), the method should be implemented +for this subtype. +""" +function side_dimension(set::Union{AbstractSymmetricMatrixSetTriangle, + AbstractSymmetricMatrixSetSquare}) + return set.side_dimension +end + +""" + triangular_form(S::Type{<:AbstractSymmetricMatrixSetSquare}) + triangular_form(set::AbstractSymmetricMatrixSetSquare) + +Return the [`AbstractSymmetricMatrixSetTriangle`](@ref) corresponding to the +vectorization of the upper triangular part of matrices in the +[`AbstractSymmetricMatrixSetSquare`](@ref) set. +""" +function triangular_form end +function triangular_form(set::AbstractSymmetricMatrixSetSquare) + return triangular_form(typeof(set))(side_dimension(set)) +end + +""" + PositiveSemidefiniteConeTriangle(side_dimension) <: AbstractSymmetricMatrixSetTriangle + +The (vectorized) cone of symmetric positive semidefinite matrices, with +`side_dimension` rows and columns. See +[`AbstractSymmetricMatrixSetTriangle`](@ref) for more details on the vectorized +form. +""" +struct PositiveSemidefiniteConeTriangle <: AbstractSymmetricMatrixSetTriangle + side_dimension::Int +end + +""" + PositiveSemidefiniteConeSquare(side_dimension) <: AbstractSymmetricMatrixSetSquare The cone of symmetric positive semidefinite matrices, with side length `side_dimension`. + See [`AbstractSymmetricMatrixSetSquare`](@ref) for more details on the vectorized +form. + The entries of the matrix are given column by column (or equivalently, row by row). The matrix is both constrained to be symmetric and to be positive semidefinite. That is, if the functions in entries ``(i, j)`` and ``(j, i)`` are different, then a constraint will be added to make sure that the entries are equal. @@ -323,11 +403,11 @@ to be symmetric positive semidefinite can be achieved by constraining the vector to belong to the `PositiveSemidefiniteConeSquare(2)`. It both constrains ``y = z`` and ``(1, -y, 0)`` (or ``(1, -z, 0)``) to be in `PositiveSemidefiniteConeTriangle(2)`. """ -struct PositiveSemidefiniteConeSquare <: AbstractVectorSet +struct PositiveSemidefiniteConeSquare <: AbstractSymmetricMatrixSetSquare side_dimension::Int end -dimension(s::PositiveSemidefiniteConeSquare) = s.side_dimension^2 +triangular_form(::Type{PositiveSemidefiniteConeSquare}) = PositiveSemidefiniteConeTriangle """ LogDetConeTriangle(side_dimension) diff --git a/test/Bridges/Bridges.jl b/test/Bridges/Bridges.jl index 55b71591ee..f0b45fc7d3 100644 --- a/test/Bridges/Bridges.jl +++ b/test/Bridges/Bridges.jl @@ -16,7 +16,7 @@ end include("rsocbridge.jl") include("quadtosocbridge.jl") include("geomeanbridge.jl") - include("squarepsdbridge.jl") + include("square_bridge.jl") include("detbridge.jl") include("soctopsdbridge.jl") end diff --git a/test/Bridges/squarepsdbridge.jl b/test/Bridges/square_bridge.jl similarity index 74% rename from test/Bridges/squarepsdbridge.jl rename to test/Bridges/square_bridge.jl index e3ff7e892c..b7985bd08b 100644 --- a/test/Bridges/squarepsdbridge.jl +++ b/test/Bridges/square_bridge.jl @@ -1,5 +1,20 @@ -@testset "SquarePSD" begin - bridged_mock = MOIB.SquarePSD{Float64}(mock) +using Test + +using MathOptInterface +const MOI = MathOptInterface +const MOIT = MathOptInterface.Test +const MOIU = MathOptInterface.Utilities +const MOIB = MathOptInterface.Bridges + +include("utilities.jl") + +include("simple_model.jl") + +mock = MOIU.MockOptimizer(SimpleModel{Float64}()) +config = MOIT.TestConfig() + +@testset "Square" begin + bridged_mock = MOIB.Square{Float64}(mock) mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, ones(4), (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [2, 2]) MOIT.psds0vtest(bridged_mock, config)