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_")