diff --git a/docs/src/getting_started.md b/docs/src/getting_started.md index f9aad6a6..9f53e54a 100644 --- a/docs/src/getting_started.md +++ b/docs/src/getting_started.md @@ -31,8 +31,9 @@ ZeroSet | The set ``\{ 0 \}^{dim}`` that contains the origin Nonnegatives | The nonnegative orthant ``\{ x \in \mathbb{R}^{dim} : x_i \ge 0, \forall i=1,\dots,\mathrm{dim} \}`` Box(l, u) | The hyperbox ``\{ x \in \mathbb{R}^{dim} : l \leq x \leq u\}`` with vectors ``l \in \mathbb{R}^{dim} \cup \{-\infty\}`` and ``u \in \mathbb{R}^{dim} \cup \{+\infty\}`` SecondOrderCone | The second-order (Lorenz) cone ``\{ (t,x) \in \mathbb{R}^{dim} : \|x\|_2 \leq t \}`` -PsdCone | The vectorized positive semidefinite cone ``\mathcal{S}_+^{dim}``. ``x`` is the vector obtained by stacking the columns of the positive semidefinite matrix ``X``, i.e. ``X \in \mathbb{S}^{\sqrt{dim}}_+ \rarr \text{vec}(X) = x \in \mathcal{S}_+^{dim}`` -PsdConeTriangle | The vectorized positive semidefinite cone ``\mathcal{S}_+^{dim}``. ``x`` is the vector obtained by stacking the columns of the upper triangular part of the positive semidefinite matrix ``X``, i.e. ``X \in \mathbb{S}^{d}_+ \rarr \text{svec}(X) = x \in \mathcal{S}_+^{dim}`` where ``d=\sqrt{1/4 + 2 \text{dim}} - 1/2`` +PsdCone | The vectorized positive real semidefinite cone ``\mathcal{S}_+^{dim}``. ``x`` is the vector obtained by stacking the columns of the positive semidefinite matrix ``X``, i.e. ``X \in \mathbb{S}^{\sqrt{dim}}_+ \rarr \text{vec}(X) = x \in \mathcal{S}_+^{dim}`` +PsdConeTriangle (real) | The vectorized real positive semidefinite cone ``\mathcal{S}_+^{dim}``. ``x`` is the vector obtained by stacking the columns of the upper triangular part of the positive semidefinite matrix ``X`` and scaling the off-diagonals by ``\sqrt{2}``, i.e. ``X \in \mathbb{S}^{d}_+ \rarr \text{svec}(X) = x \in \mathcal{S}_+^{dim}`` where ``d=\sqrt{1/4 + 2 \text{dim}} - 1/2`` +PsdConeTriangle (complex) | The vectorized complex positive semidefinite cone ``\mathcal{S}_+^{dim}``. ``x`` is the vector obtained by stacking the real and imaginary parts of the columns of the upper triangular part of the positive semidefinite matrix ``X`` and scaling the off-diagonals by ``\sqrt{2}``. The ordering follows [the one from MathOptInterface](https://jump.dev/MathOptInterface.jl/stable/reference/standard_form/#MathOptInterface.HermitianPositiveSemidefiniteConeTriangle). I.e. ``X \in \mathbb{H}^{\sqrt{dim}}_+ \rarr \text{svec}(X) = x \in \mathcal{S}_+^{dim}``. ExponentialCone | The exponential cone ``\mathcal{K}_{exp} = \{(x, y, z) \mid y \geq 0, ye^{x/y} ≤ z\} \cup \{ (x,y,z) \mid x \leq 0, y = 0, z \geq 0 \}`` DualExponentialCone | The dual exponential cone ``\mathcal{K}^*_{exp} = \{(x, y, z) \mid x < 0, -xe^{y/x} \leq e^1 z \} \cup \{ (0,y,z) \mid y \geq 0, z \geq 0 \}`` PowerCone(alpha) | The 3d power cone ``\mathcal{K}_{pow} = \{(x, y, z) \mid x^\alpha y^{(1-\alpha)} \geq \|z\|, x \geq 0, y \geq 0 \}`` with ``0 < \alpha < 1`` diff --git a/src/COSMO.jl b/src/COSMO.jl index 6c930996..c94c8005 100644 --- a/src/COSMO.jl +++ b/src/COSMO.jl @@ -10,6 +10,7 @@ using Reexport export assemble!, warm_start!, empty_model!, update! +const RealOrComplex{T <: Real} = Union{T, Complex{T}} const DefaultFloat = Float64 const DefaultInt = LinearAlgebra.BlasInt diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 849b3388..a4ba8462 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -20,7 +20,8 @@ const IntervalConvertible = Union{GreaterThan, Interval} const Zeros = MOI.Zeros const SOC = MOI.SecondOrderCone const PSDT = MOI.Scaled{MOI.PositiveSemidefiniteConeTriangle} -const SupportedVectorSets = Union{Zeros, MOI.Nonnegatives, SOC, PSDT, MOI.ExponentialCone, MOI.DualExponentialCone, MOI.PowerCone, MOI.DualPowerCone} +const ComplexPSDT = MOI.Scaled{MOI.HermitianPositiveSemidefiniteConeTriangle} +const SupportedVectorSets = Union{Zeros, MOI.Nonnegatives, SOC, PSDT, ComplexPSDT, MOI.ExponentialCone, MOI.DualExponentialCone, MOI.PowerCone, MOI.DualPowerCone} const AggregatedSets = Union{Zeros, MOI.Nonnegatives, MOI.GreaterThan} aggregate_equivalent(::Type{<: MOI.Zeros}) = COSMO.ZeroSet aggregate_equivalent(::Type{<: Union{MOI.GreaterThan, MOI.Nonnegatives}}) = COSMO.Nonnegatives @@ -449,7 +450,12 @@ end function processSet!(b::Vector{T}, rows::UnitRange{Int}, cs, s::PSDT) where {T <: AbstractFloat} - push!(cs, COSMO.PsdConeTriangle{T}(length(rows))) + push!(cs, COSMO.PsdConeTriangle{T, T}(length(rows))) + nothing +end + +function processSet!(b::Vector{T}, rows::UnitRange{Int}, cs, s::ComplexPSDT) where {T <: AbstractFloat} + push!(cs, COSMO.PsdConeTriangle{T, Complex{T}}(length(rows))) nothing end diff --git a/src/algebra.jl b/src/algebra.jl index a0a0d2da..e4daefd5 100644 --- a/src/algebra.jl +++ b/src/algebra.jl @@ -222,23 +222,23 @@ function symmetrize_full!(A::AbstractMatrix{T}) where {T <: AbstractFloat} nothing end -# this function assumes real symmetric X and only considers the upper triangular part -function is_pos_def!(X::AbstractMatrix{T}, tol::T=zero(T)) where T +# this function assumes real or complex Hermitian X and only considers the upper triangular part +function is_pos_def!(X::AbstractMatrix{<:RealOrComplex{T}}, tol::T=zero(T)) where {T} # See https://math.stackexchange.com/a/13311 @inbounds for i = 1:size(X, 1) X[i, i] += tol end - F = cholesky!(Symmetric(X), check = false) + F = cholesky!(Hermitian(X), check = false) return issuccess(F) end -function is_neg_def!(X::AbstractMatrix{T}, tol::T=zero(T)) where T +function is_neg_def!(X::AbstractMatrix{<:RealOrComplex{T}}, tol::T=zero(T)) where {T} @. X *= -one(T) return is_pos_def!(X, tol) end -is_pos_def(X::AbstractMatrix{T}, tol::T=zero(T)) where T = is_pos_def!(copy(X), tol) -is_neg_def(X::AbstractMatrix{T}, tol::T=zero(T)) where T = is_pos_def!(-X, tol) +is_pos_def(X::AbstractMatrix{<:RealOrComplex{T}}, tol::T=zero(T)) where {T} = is_pos_def!(copy(X), tol) +is_neg_def(X::AbstractMatrix{<:RealOrComplex{T}}, tol::T=zero(T)) where {T} = is_pos_def!(-X, tol) "Round x to the closest multiple of N." diff --git a/src/chordal_decomposition/chordal_decomposition.jl b/src/chordal_decomposition/chordal_decomposition.jl index f6a83684..17319beb 100644 --- a/src/chordal_decomposition/chordal_decomposition.jl +++ b/src/chordal_decomposition/chordal_decomposition.jl @@ -75,7 +75,8 @@ function _analyse_sparsity_pattern(ci::ChordalInfo{T}, csp::Array{Int, 1}, sets: end DenseEquivalent(C::COSMO.PsdCone{T}, dim::Int) where {T <: AbstractFloat} = COSMO.DensePsdCone{T}(dim) -DenseEquivalent(C::COSMO.PsdConeTriangle{T}, dim::Int) where {T <: AbstractFloat} = COSMO.DensePsdConeTriangle{T}(dim) +DenseEquivalent(C::COSMO.PsdConeTriangle{T, T}, dim::Int) where {T <: AbstractFloat} = COSMO.DensePsdConeTriangle{T, T}(dim) +DenseEquivalent(C::COSMO.PsdConeTriangle{T, Complex{T}}, dim::Int) where {T <: AbstractFloat} = COSMO.DensePsdConeTriangle{T, Complex{T}}(dim) function nz_rows(a::SparseMatrixCSC{T}, ind::UnitRange{Int}, DROP_ZEROS_FLAG::Bool) where {T <: AbstractFloat} DROP_ZEROS_FLAG && dropzeros!(a) diff --git a/src/constraint.jl b/src/constraint.jl index 582fc423..8a8daf86 100644 --- a/src/constraint.jl +++ b/src/constraint.jl @@ -93,7 +93,17 @@ function Constraint{T}( # this constructor doesnt work with cones that need special arguments like the power cone set_type <: ArgumentCones && error("You can't create a constraint by passing the convex set as a type, if your convex set is a $(set_type). Please pass an object.") # call the appropriate set constructor - convex_set = set_type{T}(size(A, 1)) + n = size(A, 1) + # we can deduce whether the PsdCone must be real or complex from the dimension + if set_type <: PsdConeTriangles + if n == 1 || isqrt(n)^2 != n + convex_set = set_type{T, T}(n) + else + convex_set = set_type{T, Complex{T}}(n) + end + else + convex_set = set_type{T}(n) + end Constraint{T}(A, b, convex_set, args...) end diff --git a/src/convexset.jl b/src/convexset.jl index 5bda3524..f047f0d6 100644 --- a/src/convexset.jl +++ b/src/convexset.jl @@ -2,6 +2,8 @@ using UnsafeArrays import Base: showarg, eltype const DSYEVR_ = (BLAS.@blasfunc(dsyevr_),Base.liblapack_name) const SSYEVR_ = (BLAS.@blasfunc(ssyevr_),Base.liblapack_name) +const ZHEEVR_ = (BLAS.@blasfunc(zheevr_),Base.liblapack_name) +const CHEEVR_ = (BLAS.@blasfunc(cheevr_),Base.liblapack_name) # ---------------------------------------------------- # Zero cone @@ -124,33 +126,37 @@ end # ---------------------------------------------------- #a type to maintain internal workspace data for the BLAS syevr function -mutable struct PsdBlasWorkspace{T} +mutable struct PsdBlasWorkspace{T, R} m::Base.RefValue{BLAS.BlasInt} w::Vector{T} - Z::Matrix{T} + Z::Matrix{R} isuppz::Vector{BLAS.BlasInt} - work::Vector{T} + work::Vector{R} lwork::BLAS.BlasInt + rwork::Vector{T} + lrwork::BLAS.BlasInt iwork::Vector{BLAS.BlasInt} liwork::BLAS.BlasInt info::Base.RefValue{BLAS.BlasInt} - function PsdBlasWorkspace{T}(n::Integer) where{T} + function PsdBlasWorkspace{T, R}(n::Integer) where {T <: Real, R <: RealOrComplex{T}} BlasInt = BLAS.BlasInt #workspace data for BLAS m = Ref{BlasInt}() w = Vector{T}(undef,n) - Z = Matrix{T}(undef,n,n) + Z = Matrix{R}(undef,n,n) isuppz = Vector{BlasInt}(undef, 2*n) - work = Vector{T}(undef, 1) + work = Vector{R}(undef, 1) lwork = BlasInt(-1) + rwork = Vector{T}(undef, 1) + lrwork = BlasInt(-1) iwork = Vector{BlasInt}(undef, 1) liwork = BlasInt(-1) info = Ref{BlasInt}() - new(m,w,Z,isuppz,work,lwork,iwork,liwork,info) + new(m,w,Z,isuppz,work,lwork,rwork,lrwork,iwork,liwork,info) end end @@ -158,9 +164,8 @@ for (syevr, elty) in ((DSYEVR_,:Float64), (SSYEVR_,:Float32)) @eval begin - function _syevr!(A::AbstractMatrix{$elty}, ws::PsdBlasWorkspace{$elty}) + function _syevr!(A::AbstractMatrix{$elty}, ws::PsdBlasWorkspace{$elty,$elty}) - #Float64 only support for now since we call dsyevr_ directly n = size(A,1) ldz = n lda = stride(A,2) @@ -183,7 +188,35 @@ for (syevr, elty) in end #@eval end #for -function _project!(X::AbstractMatrix, ws::PsdBlasWorkspace{T}) where{T} +for (syevr, elty, relty) in + ((ZHEEVR_,:ComplexF64,:Float64), + (CHEEVR_,:ComplexF32,:Float32)) + @eval begin + function _syevr!(A::AbstractMatrix{$elty}, ws::PsdBlasWorkspace{$relty,$elty}) + + n = size(A,1) + ldz = n + lda = stride(A,2) + + ccall($syevr, Cvoid, + (Ref{UInt8}, Ref{UInt8}, Ref{UInt8}, Ref{BLAS.BlasInt}, + Ptr{$elty}, Ref{BLAS.BlasInt}, Ref{$elty}, Ref{$elty}, + Ref{BLAS.BlasInt}, Ref{BLAS.BlasInt}, Ref{$elty}, Ptr{BLAS.BlasInt}, + Ptr{$relty}, Ptr{$elty}, Ref{BLAS.BlasInt}, Ptr{BLAS.BlasInt}, + Ptr{$elty}, Ref{BLAS.BlasInt}, Ptr{$relty}, Ref{BLAS.BlasInt}, + Ptr{BLAS.BlasInt}, Ref{BLAS.BlasInt}, Ptr{BLAS.BlasInt}), + 'V', 'A', 'U', n, + A, max(1,lda), 0.0, 0.0, + 0, 0, -1.0, ws.m, + ws.w, ws.Z, ldz, ws.isuppz, + ws.work, ws.lwork, ws.rwork, ws.lrwork, + ws.iwork, ws.liwork, ws.info) + LAPACK.chklapackerror(ws.info[]) + end + end #@eval +end #for + +function _project!(X::AbstractMatrix{R}, ws::PsdBlasWorkspace{T,R}) where {T <: Real, R <: RealOrComplex{T}} #computes the upper triangular part of the projection of X onto the PSD cone @@ -195,17 +228,22 @@ function _project!(X::AbstractMatrix, ws::PsdBlasWorkspace{T}) where{T} resize!(ws.work, ws.lwork) ws.liwork = ws.iwork[1] resize!(ws.iwork, ws.liwork) + if R <: Complex + ws.lrwork = BLAS.BlasInt(ws.rwork[1]) + resize!(ws.rwork, ws.lrwork) + end end - # below LAPACK function does the following: w,Z = eigen!(Symmetric(X)) + # below LAPACK function does the following: w,Z = eigen!(Hermitian(X)) _syevr!(X, ws) # compute upper triangle of: X .= Z*Diagonal(max.(w, 0.0))*Z' rank_k_update!(X, ws) end -function rank_k_update!(X::AbstractMatrix, ws::COSMO.PsdBlasWorkspace{T}) where {T} +function rank_k_update!(X::AbstractMatrix{R}, ws::PsdBlasWorkspace{T,R}) where {T <: Real, R <: RealOrComplex{T}} + _syrk! = R <: Real ? BLAS.syrk! : BLAS.herk! n = size(X, 1) - @. X = zero(T) + @. X = zero(R) nnz_λ = 0 for j = 1:length(ws.w) λ = ws.w[j] @@ -219,7 +257,7 @@ function rank_k_update!(X::AbstractMatrix, ws::COSMO.PsdBlasWorkspace{T}) where if nnz_λ > 0 V = uview(ws.Z, :, (n - nnz_λ + 1):n) - BLAS.syrk!('U', 'N', one(T), V, one(T), X) + _syrk!('U', 'N', true, V, true, X) end return nothing end @@ -233,14 +271,14 @@ Accordingly ``X \\in \\mathbb{S}_+ \\Rightarrow x \\in \\mathcal{S}_+^{dim}``, struct PsdCone{T} <: AbstractConvexCone{T} dim::Int sqrt_dim::Int - work::PsdBlasWorkspace{T} + work::PsdBlasWorkspace{T, T} tree_ind::Int # tree number that this cone belongs to clique_ind::Int function PsdCone{T}(dim::Int, tree_ind::Int, clique_ind::Int) where{T} dim >= 0 || throw(DomainError(dim, "dimension must be nonnegative")) iroot = isqrt(dim) iroot^2 == dim || throw(DomainError(dim, "dimension must be a square")) - new(dim, iroot, PsdBlasWorkspace{T}(iroot), tree_ind, clique_ind) + new(dim, iroot, PsdBlasWorkspace{T, T}(iroot), tree_ind, clique_ind) end end PsdCone(dim) = PsdCone{DefaultFloat}(dim) @@ -250,12 +288,12 @@ PsdCone{T}(dim::Int) where{T} = PsdCone{T}(dim, 0, 0) struct DensePsdCone{T} <: AbstractConvexCone{T} dim::Int sqrt_dim::Int - work::PsdBlasWorkspace{T} + work::PsdBlasWorkspace{T, T} function DensePsdCone{T}(dim::Int) where{T} dim >= 0 || throw(DomainError(dim, "dimension must be nonnegative")) iroot = isqrt(dim) iroot^2 == dim || throw(DomainError(dim, "dimension must be a square")) - new(dim, iroot, PsdBlasWorkspace{T}(iroot)) + new(dim, iroot, PsdBlasWorkspace{T, T}(iroot)) end end DensePsdCone(dim) = DensePsdCone{DefaultFloat}(dim) @@ -298,124 +336,157 @@ end in_pol_recc(x::AbstractVector{T}, cone::Union{PsdCone{T}, DensePsdCone{T}}, tol::T) where{T} = in_pol_recc!(copy(x), cone, tol) - - # ---------------------------------------------------- # Positive Semidefinite Cone (Triangle) # ---------------------------------------------------- # Psd cone given by upper-triangular entries of matrix """ - PsdConeTriangle(dim) + PsdConeTriangle{T, R}(dim) where {T <: Real, R <: Union{T, Complex{T}}} -Creates the cone of symmetric positive semidefinite matrices. The entries of the upper-triangular part of matrix `X` are stored in the vector `x` of dimension `dim`. -A ``r \\times r`` matrix has ``r(r+1)/2`` upper triangular elements and results in a vector of ``\\mathrm{dim} = r(r+1)/2``. +Creates the cone of real (when `R == T`) or complex (when `R == Complex{T}`) Hermitian positive semidefinite matrices. The entries of the upper-triangular part of matrix `X` are stored in the vector `x` of dimension `dim`. A ``r \\times r`` real matrix has ``r(r+1)/2`` upper triangular elements and results in a vector of ``\\mathrm{dim} = r(r+1)/2``. A ``r \\times r`` complex matrix has ``r^2`` upper triangular elements and results in a vector of ``\\mathrm{dim} = r^2``. ### Examples -The matrix +The real matrix ```math \\begin{bmatrix} x_1 & x_2 & x_4\\\\ x_2 & x_3 & x_5\\\\ x_4 & x_5 & x_6 \\end{bmatrix} ``` -is transformed to the vector ``[x_1, x_2, x_3, x_4, x_5, x_6]^\\top `` with corresponding constraint `PsdConeTriangle(6)`. +is transformed to the vector ``[x_1, \\sqrt{2}x_2, x_3, \\sqrt{2}x_4, \\sqrt{2}x_5, x_6]^\\top `` with corresponding constraint `PsdConeTriangle{T, T}(6)`. +The complex matrix +```math +\\begin{bmatrix} x_1 & x_2 & x_4\\\\ x_2 & x_3 & x_5\\\\ x_4 & x_5 & x_6 \\end{bmatrix} +``` +is transformed to the vector ``[x_1, \\sqrt{2}\\operatorname{re}(x_2), x_3, \\sqrt{2}\\operatorname{re}(x_4), \\sqrt{2}\\operatorname{re}(x_5), x_6, \\sqrt{2}\\operatorname{im}(x_2), \\sqrt{2}\\operatorname{im}(x_4), \\sqrt{2}\\operatorname{im}(x_5)]^\\top `` with corresponding constraint `PsdConeTriangle{T, Complex{T}}(9)`. """ -mutable struct PsdConeTriangle{T} <: AbstractConvexCone{T} +mutable struct PsdConeTriangle{T <: AbstractFloat, R <: RealOrComplex{T}} <: AbstractConvexCone{T} dim::Int #dimension of vector sqrt_dim::Int # side length of matrix - X::Array{T,2} - work::PsdBlasWorkspace{T} + X::Array{R,2} + work::PsdBlasWorkspace{T, R} tree_ind::Int # tree number that this cone belongs to clique_ind::Int - function PsdConeTriangle{T}(dim::Int, tree_ind::Int, clique_ind::Int) where{T} + function PsdConeTriangle{T, R}(dim::Int, tree_ind::Int, clique_ind::Int) where {T <: AbstractFloat, R <: RealOrComplex{T}} dim >= 0 || throw(DomainError(dim, "dimension must be nonnegative")) - side_dimension = Int(sqrt(0.25 + 2 * dim) - 0.5); - new(dim, side_dimension, zeros(side_dimension, side_dimension), PsdBlasWorkspace{T}(side_dimension), tree_ind, clique_ind) - + side_dimension = R <: Complex ? isqrt(dim) : div(isqrt(1 + 8dim) - 1, 2) + new(dim, side_dimension, zeros(R, side_dimension, side_dimension), PsdBlasWorkspace{T, R}(side_dimension), tree_ind, clique_ind) end end + PsdConeTriangle(dim) = PsdConeTriangle{DefaultFloat}(dim) -PsdConeTriangle{T}(dim::Int) where{T} = PsdConeTriangle{T}(dim, 0, 0) +PsdConeTriangle{T}(dim) where {T} = PsdConeTriangle{T, T}(dim) +PsdConeTriangle{T, R}(dim::Int) where {T <: AbstractFloat, R <: RealOrComplex{T}} = PsdConeTriangle{T, R}(dim, 0, 0) -DecomposableCones{T} = Union{PsdCone{T}, PsdConeTriangle{T}} +DecomposableCones{T} = Union{PsdCone{T}, PsdConeTriangle{T, T}} -mutable struct DensePsdConeTriangle{T} <: AbstractConvexCone{T} +mutable struct DensePsdConeTriangle{T <: AbstractFloat, R <: RealOrComplex{T}} <: AbstractConvexCone{T} dim::Int #dimension of vector sqrt_dim::Int # side length of matrix - X::Array{T,2} - work::PsdBlasWorkspace{T} + X::Array{R,2} + work::PsdBlasWorkspace{T, R} - function DensePsdConeTriangle{T}(dim::Int) where{T} + function DensePsdConeTriangle{T, R}(dim::Int) where {T <: AbstractFloat, R <: RealOrComplex{T}} dim >= 0 || throw(DomainError(dim, "dimension must be nonnegative")) - side_dimension = Int(sqrt(0.25 + 2 * dim) - 0.5); - new(dim, side_dimension, zeros(side_dimension, side_dimension), PsdBlasWorkspace{T}(side_dimension)) + side_dimension = R <: Complex ? isqrt(dim) : div(isqrt(1 + 8dim) - 1, 2) + new(dim, side_dimension, zeros(side_dimension, side_dimension), PsdBlasWorkspace{T, R}(side_dimension)) end end -DensePsdConeTriangle(dim) = DensePsdConeTriangle{DefaultFloat}(dim) +#DensePsdConeTriangle(dim) = DensePsdConeTriangle{DefaultFloat}(dim) +#DensePsdConeTriangle{T}(dim) where {T} = DensePsdConeTriangle{T, T}(dim) +# Union for all triangle PSD cones that might be complex +const PsdConeTriangles = Union{PsdConeTriangle, DensePsdConeTriangle} -function project!(x::AbstractArray, cone::Union{PsdConeTriangle{T}, DensePsdConeTriangle{T}}) where{T} +function project!(x::AbstractVector{T}, cone::Union{PsdConeTriangle{T, R}, DensePsdConeTriangle{T, R}}) where {T <: AbstractFloat, R <: RealOrComplex{T}} # handle 1D case if length(x) == 1 x .= max(x[1],zero(T)) else - populate_upper_triangle!(cone.X, x, 1 / sqrt(2)) - _project!(cone.X,cone.work) - extract_upper_triangle!(cone.X, x, sqrt(2) ) + populate_upper_triangle!(cone.X, x, 1 / sqrt(T(2))) + _project!(cone.X, cone.work) + extract_upper_triangle!(cone.X, x, sqrt(T(2)) ) end return nothing end # Notice that we are using a (faster) in-place version that modifies the input -function in_dual!(x::AbstractVector{T}, cone::Union{PsdConeTriangle{T}, DensePsdConeTriangle{T}}, tol::T) where{T} - n = cone.sqrt_dim - populate_upper_triangle!(cone.X, x, 1 / sqrt(2)) +function in_dual!(x::AbstractVector{T}, cone::Union{PsdConeTriangle{T, R}, DensePsdConeTriangle{T, R}}, tol::T) where {T <: AbstractFloat, R <: RealOrComplex{T}} + populate_upper_triangle!(cone.X, x, 1 / sqrt(T(2))) return COSMO.is_pos_def!(cone.X, tol) end -in_dual(x::AbstractVector{T}, cone::Union{PsdConeTriangle{T}, DensePsdConeTriangle{T}}, tol::T) where {T} = in_dual!(x, cone, tol) +in_dual(x::AbstractVector{T}, cone::Union{PsdConeTriangle{T, R}, DensePsdConeTriangle{T, R}}, tol::T) where {T <: AbstractFloat, R <: RealOrComplex{T}} = in_dual!(x, cone, tol) -function in_pol_recc!(x::AbstractVector{T}, cone::Union{PsdConeTriangle{T}, DensePsdConeTriangle{T}}, tol::T) where{T} - n = cone.sqrt_dim - populate_upper_triangle!(cone.X, x, 1 / sqrt(2)) - Xs = Symmetric(cone.X) +function in_pol_recc!(x::AbstractVector{T}, cone::Union{PsdConeTriangle{T, R}, DensePsdConeTriangle{T, R}}, tol::T) where {T <: AbstractFloat, R <: RealOrComplex{T}} + populate_upper_triangle!(cone.X, x, 1 / sqrt(T(2))) return COSMO.is_neg_def!(cone.X, tol) end -in_pol_recc(x::AbstractVector{T}, cone::Union{PsdConeTriangle{T}, DensePsdConeTriangle{T}}, tol::T) where {T} = in_pol_recc!(x, cone, tol) +in_pol_recc(x::AbstractVector{T}, cone::Union{PsdConeTriangle{T, R}, DensePsdConeTriangle{T, R}}, tol::T) where {T <: AbstractFloat, R <: RealOrComplex{T}} = in_pol_recc!(x, cone, tol) -function allocate_memory!(cone::Union{PsdConeTriangle{T}, DensePsdConeTriangle{T}}) where {T} - cone.X = zeros(cone.sqrt_dim, cone.sqrt_dim) +function allocate_memory!(cone::Union{PsdConeTriangle{T, R}, DensePsdConeTriangle{T, R}}) where {T <: AbstractFloat, R <: RealOrComplex{T}} + cone.X = zeros(R, cone.sqrt_dim, cone.sqrt_dim) end -function populate_upper_triangle!(A::AbstractMatrix, x::AbstractVector, scaling_factor::Float64) +function populate_upper_triangle!(A::AbstractMatrix{T}, x::AbstractVector{T}, scaling_factor::T) where {T <: AbstractFloat} k = 0 for j in 1:size(A, 2) - for i in 1:j - k += 1 - if i != j - A[i, j] = scaling_factor * x[k] - else - A[i, j] = x[k] - end - end - end - nothing -end - -function extract_upper_triangle!(A::AbstractMatrix, x::AbstractVector, scaling_factor::Float64) - k = 0 - for j in 1:size(A, 2) - for i in 1:j - k += 1 - if i != j - x[k] = scaling_factor * A[i, j] - else - x[k] = A[i, j] - end - end - end - nothing + for i in 1:j-1 + k += 1 + A[i, j] = scaling_factor * x[k] + end + k += 1 + A[j, j] = x[k] + end +end + +function populate_upper_triangle!(A::AbstractMatrix{Complex{T}}, x::AbstractVector{T}, scaling_factor::T) where {T <: AbstractFloat} + k = 0 + for j in 1:size(A, 2) + for i in 1:j-1 + k += 1 + A[i, j] = scaling_factor * x[k] + end + k += 1 + A[j, j] = x[k] + end + for j in 1:size(A, 2) + for i in 1:j-1 + k += 1 + A[i, j] += im * scaling_factor * x[k] + end + end +end + +function extract_upper_triangle!(A::AbstractMatrix{T}, x::AbstractVector{T}, scaling_factor::T) where {T <: AbstractFloat} + k = 0 + for j in 1:size(A, 2) + for i in 1:j-1 + k += 1 + x[k] = scaling_factor * A[i, j] + end + k += 1 + x[k] = A[j, j] + end +end + +function extract_upper_triangle!(A::AbstractMatrix{Complex{T}}, x::AbstractVector{T}, scaling_factor::T) where {T <: AbstractFloat} + k = 0 + for j in 1:size(A, 2) + for i in 1:j-1 + k += 1 + x[k] = scaling_factor * real(A[i, j]) + end + k += 1 + x[k] = A[j, j] + end + for j in 1:size(A, 2) + for i in 1:j-1 + k += 1 + x[k] = scaling_factor * imag(A[i, j]) + end + end end """ diff --git a/test/UnitTests/AccelerationTests/run_acceleration_tests.jl b/test/UnitTests/AccelerationTests/run_acceleration_tests.jl index 4d020281..010203be 100644 --- a/test/UnitTests/AccelerationTests/run_acceleration_tests.jl +++ b/test/UnitTests/AccelerationTests/run_acceleration_tests.jl @@ -4,7 +4,7 @@ rng = Random.MersenneTwister(12345) -include("../COSMOTestUtils.jl") +#include("../COSMOTestUtils.jl") @testset "Acceleration test set" begin diff --git a/test/UnitTests/COSMOTestUtils.jl b/test/UnitTests/COSMOTestUtils.jl index 20393094..d6dce036 100644 --- a/test/UnitTests/COSMOTestUtils.jl +++ b/test/UnitTests/COSMOTestUtils.jl @@ -8,7 +8,6 @@ end # generate a random pos def matrix with eigenvalues between 0.1 and 2 -generate_pos_def_matrix(rng::MersenneTwister, n::Integer, aMin::Float64 = 0.1, aMax::Float64 = 2.) = generate_pos_def_matrix(rng, n, aMin, aMax, MT = Float64) function generate_pos_def_matrix(rng::MersenneTwister, n::Integer, aMin::Real = 0.1, aMax::Real = 2.; MT::Type{<:AbstractFloat} = Float64 ) X = rand(rng, MT, n, n) # any real square matrix can be QP decomposed into a orthogonal matrix and an uppertriangular matrix R diff --git a/test/UnitTests/DecompositionTests/psd_completion_and_merging.jl b/test/UnitTests/DecompositionTests/psd_completion_and_merging.jl index 8043dd80..5f184f1c 100644 --- a/test/UnitTests/DecompositionTests/psd_completion_and_merging.jl +++ b/test/UnitTests/DecompositionTests/psd_completion_and_merging.jl @@ -5,7 +5,7 @@ # a graph based merge strategy is used using COSMO, Random, Test, LinearAlgebra, SparseArrays -include("./../COSMOTestUtils.jl") +#include("./../COSMOTestUtils.jl") rng = Random.MersenneTwister(375) diff --git a/test/UnitTests/kktsolver.jl b/test/UnitTests/kktsolver.jl index c81b2bd5..3a85ac88 100644 --- a/test/UnitTests/kktsolver.jl +++ b/test/UnitTests/kktsolver.jl @@ -1,5 +1,5 @@ using COSMO, Test, LinearAlgebra, SparseArrays, Random, QDLDL, Pkg -include("COSMOTestUtils.jl") +#include("COSMOTestUtils.jl") # check if optional dependencies are available test_iterative_solvers = pkg_installed("IterativeSolvers", "42fd0dbc-a981-5370-80f2-aaf504508153") && pkg_installed("LinearMaps", "7a12625a-238d-50fd-b39a-03d52299707e") diff --git a/test/UnitTests/least_eigenvalue.jl b/test/UnitTests/least_eigenvalue.jl new file mode 100644 index 00000000..3e73d52f --- /dev/null +++ b/test/UnitTests/least_eigenvalue.jl @@ -0,0 +1,38 @@ +using COSMO, Test, LinearAlgebra, SparseArrays +# this problem computes the least eigenvalue of a Hermitian matrix c + +# min dot(c, X) +# s.t. tr(X) = 1, X ⪰ 0 + +tri(d) = div(d*(d+1), 2) + +function mineig_raw(c::AbstractMatrix{R}) where {R} + d = size(c, 1) + T = real(R) + vec_dim = R <: Complex ? d^2 : tri(d) + + model = COSMO.Model{T}() + vec_c = zeros(T, vec_dim) + COSMO.extract_upper_triangle!(c, vec_c, sqrt(T(2))) + + P = zeros(T, vec_dim, vec_dim) + + id_vec = zeros(T, vec_dim) + diagonal_indices = tri.(1:d) + id_vec[diagonal_indices] .= 1 + + cs1 = COSMO.Constraint(id_vec', -T(1), COSMO.ZeroSet) + cs2 = COSMO.Constraint(Matrix(T(1)*I(vec_dim)), zeros(T,vec_dim), COSMO.PsdConeTriangle{T, R}(vec_dim)) + constraints = [cs1; cs2] + + assemble!(model, P, vec_c, constraints, settings = COSMO.Settings{T}(verbose = false, eps_abs = 1e-5, eps_rel = 1e-5)) + result = COSMO.optimize!(model) + return result.obj_val +end + +@testset "Complex PSD Cone" begin + X = Hermitian(ComplexF64.([ 1 im 0; + -im 1 im; + 0 -im 1])) + @test mineig_raw(X) ≈ 1-sqrt(2) atol = 1e-4 rtol = 1e-4 +end diff --git a/test/run_cosmo_tests.jl b/test/run_cosmo_tests.jl index 657e03b3..15ededa9 100644 --- a/test/run_cosmo_tests.jl +++ b/test/run_cosmo_tests.jl @@ -4,7 +4,7 @@ rng = Random.MersenneTwister(12345) -include("./UnitTests/COSMOTestUtils.jl") +#include("./UnitTests/COSMOTestUtils.jl") # Define the types to run the unit tests with UnitTestFloats = [Float32; Float64; BigFloat] @@ -12,6 +12,7 @@ UnitTestFloats = [Float32; Float64; BigFloat] include("./UnitTests/sets.jl") include("./UnitTests/nuclear_norm_minimization.jl") + include("./UnitTests/least_eigenvalue.jl") include("./UnitTests/DecompositionTests/psd_completion.jl") include("./UnitTests/DecompositionTests/psd_completion_and_merging.jl") include("./UnitTests/DecompositionTests/clique_merging_example.jl")