Skip to content

Commit 5e7d88e

Browse files
timholydevmotion
andauthored
Coercing constructors & convert (#212)
This adds a "constructor cascade" that allows one to coerce the inputs to the chosen type. This is frequently useful in generic programming where you want an object to be of the same type as something else, for example when appending to a list of objects. Co-authored-by: David Widmann <[email protected]>
1 parent fb363a3 commit 5e7d88e

File tree

5 files changed

+81
-21
lines changed

5 files changed

+81
-21
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "PDMats"
22
uuid = "90014a1f-27ba-587c-ab20-58faa44d9150"
3-
version = "0.11.31"
3+
version = "0.11.32"
44

55
[deps]
66
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

src/chol.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ function invquad(A::Cholesky, x::AbstractVector)
7373
@check_argdims size(A, 1) == size(x, 1)
7474
return sum(abs2, chol_lower(A) \ x)
7575
end
76-
function invquad(A::Cholesky, X::AbstractMatrix)
76+
function invquad(A::Cholesky, X::AbstractMatrix)
7777
@check_argdims size(A, 1) == size(X, 1)
7878
Z = chol_lower(A) \ X
7979
return vec(sum(abs2, Z; dims=1))

src/pdmat.jl

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,54 @@
11
"""
22
Full positive definite matrix together with a Cholesky factorization object.
33
"""
4-
struct PDMat{T<:Real,S<:AbstractMatrix} <: AbstractPDMat{T}
4+
struct PDMat{T<:Real,S<:AbstractMatrix{T}} <: AbstractPDMat{T}
55
mat::S
66
chol::Cholesky{T,S}
77

8-
PDMat{T,S}(m::AbstractMatrix{T},c::Cholesky{T,S}) where {T,S} = new{T,S}(m,c)
9-
end
10-
11-
function PDMat(mat::AbstractMatrix,chol::Cholesky{T,S}) where {T,S}
12-
d = LinearAlgebra.checksquare(mat)
13-
if size(chol, 1) != d
14-
throw(DimensionMismatch("Dimensions of mat and chol are inconsistent."))
8+
function PDMat{T,S}(m::AbstractMatrix, c::Cholesky) where {T<:Real,S<:AbstractMatrix{T}}
9+
d = LinearAlgebra.checksquare(m)
10+
if size(c, 1) != d
11+
throw(DimensionMismatch("Dimensions of mat and chol are inconsistent."))
12+
end
13+
# in principle we might want to check that `c` is a Cholesky factorization of `m`,
14+
# but that's slow
15+
return new{T,S}(m,c)
1516
end
16-
PDMat{T,S}(convert(S, mat), chol)
1717
end
18+
function PDMat{T}(m::AbstractMatrix, c::Cholesky) where T<:Real
19+
c = convert(Cholesky{T}, c)
20+
return PDMat{T,typeof(c.factors)}(m, c)
21+
end
22+
PDMat(mat::AbstractMatrix,chol::Cholesky{T,S}) where {T<:Real,S<:AbstractMatrix{T}} = PDMat{T,S}(mat, chol)
1823

24+
# Construction from another PDMat
25+
PDMat{T,S}(pdm::PDMat{T,S}) where {T<:Real,S<:AbstractMatrix{T}} = pdm # since PDMat doesn't support `setindex!` it's not mutable (xref https://docs.julialang.org/en/v1/manual/conversion-and-promotion/#Mutable-collections)
26+
PDMat{T,S}(pdm::PDMat) where {T<:Real,S<:AbstractMatrix{T}} = PDMat{T,S}(pdm.mat, pdm.chol)
27+
PDMat{T}(pdm::PDMat{T}) where T<:Real = pdm
28+
PDMat{T}(pdm::PDMat) where T<:Real = PDMat{T}(pdm.mat, pdm.chol)
29+
PDMat(pdm::PDMat) = pdm
30+
31+
# Construction from an AbstractMatrix
32+
function PDMat{T,S}(mat::AbstractMatrix) where {T<:Real,S<:AbstractMatrix{T}}
33+
mat = convert(S, mat)
34+
return PDMat{T,S}(mat, cholesky(mat))
35+
end
36+
function PDMat{T}(mat::AbstractMatrix) where T<:Real
37+
mat = convert(AbstractMatrix{T}, mat)
38+
return PDMat{T}(mat, cholesky(mat))
39+
end
1940
PDMat(mat::AbstractMatrix) = PDMat(mat, cholesky(mat))
20-
PDMat(fac::Cholesky) = PDMat(AbstractMatrix(fac), fac)
41+
42+
# Construction from a Cholesky factorization
43+
function PDMat{T,S}(c::Cholesky) where {T<:Real,S<:AbstractMatrix{T}}
44+
c = convert(Cholesky{T,S}, c)
45+
return PDMat{T,S}(AbstractMatrix(c), c)
46+
end
47+
function PDMat{T}(c::Cholesky) where T<:Real
48+
c = convert(Cholesky{T}, c)
49+
return PDMat{T}(AbstractMatrix(c), c)
50+
end
51+
PDMat(c::Cholesky) = PDMat(AbstractMatrix(c), c)
2152

2253
function Base.getproperty(a::PDMat, s::Symbol)
2354
if s === :dim
@@ -30,13 +61,11 @@ Base.propertynames(::PDMat) = (:mat, :chol, :dim)
3061
AbstractPDMat(A::Cholesky) = PDMat(A)
3162

3263
### Conversion
33-
Base.convert(::Type{PDMat{T}}, a::PDMat{T}) where {T<:Real} = a
34-
function Base.convert(::Type{PDMat{T}}, a::PDMat) where {T<:Real}
35-
chol = convert(Cholesky{T}, a.chol)
36-
S = typeof(chol.factors)
37-
mat = convert(S, a.mat)
38-
return PDMat{T,S}(mat, chol)
39-
end
64+
# This next method isn't needed because PDMat{T}(a) returns `a` directly
65+
# Base.convert(::Type{PDMat{T}}, a::PDMat{T}) where {T<:Real} = a
66+
Base.convert(::Type{PDMat{T}}, a::PDMat) where {T<:Real} = PDMat{T}(a)
67+
Base.convert(::Type{PDMat{T,S}}, a::PDMat) where {T<:Real,S<:AbstractMatrix{T}} = PDMat{T,S}(a)
68+
4069
Base.convert(::Type{AbstractPDMat{T}}, a::PDMat) where {T<:Real} = convert(PDMat{T}, a)
4170

4271
### Basics
@@ -55,7 +84,7 @@ Base.IndexStyle(::Type{PDMat{T,S}}) where {T,S} = Base.IndexStyle(S)
5584
# Linear Indexing
5685
Base.@propagate_inbounds Base.getindex(a::PDMat, i::Int) = getindex(a.mat, i)
5786
# Cartesian Indexing
58-
Base.@propagate_inbounds Base.getindex(a::PDMat, I::Vararg{Int, 2}) = getindex(a.mat, I...)
87+
Base.@propagate_inbounds Base.getindex(a::PDMat, i::Int, j::Int) = getindex(a.mat, i, j)
5988

6089
### Arithmetics
6190

test/pdmtypes.jl

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,36 @@ using Test
1818
M = convert(Array{T,2}, [4. -2. -1.; -2. 5. -1.; -1. -1. 6.])
1919
V = convert(Array{T,1}, [1.5, 2.5, 2.0])
2020
X = convert(T,2.0)
21+
f64M = Float64.(M)
2122

2223
@testset "PDMat from Matrix" begin
24+
pdf64M = PDMat(f64M)
2325
test_pdmat(PDMat(M), M, cmat_eq=true, verbose=1)
26+
test_pdmat(PDMat{Float64}(M), f64M, cmat_eq=true, verbose=1)
27+
test_pdmat(PDMat{Float64,Matrix{Float64}}(M), f64M, cmat_eq=true, verbose=1)
28+
@test_throws TypeError PDMat{Float32,Matrix{Float64}}(M)
29+
end
30+
@testset "PDMat from PDMat" begin
31+
pdM = PDMat(M)
32+
pdf64M = PDMat(f64M)
33+
@test pdM === PDMat(pdM)
34+
@test pdf64M === PDMat{Float64}(pdf64M) === PDMat{Float64,Matrix{Float64}}(pdf64M)
35+
test_pdmat(PDMat(pdM), M, cmat_eq=true, verbose=1)
36+
test_pdmat(PDMat{Float64}(pdf64M), f64M, cmat_eq=true, verbose=1)
37+
test_pdmat(PDMat{Float64,Matrix{Float64}}(pdf64M), f64M, cmat_eq=true, verbose=1)
38+
if Base.VERSION >= v"1.12.0-DEV.1654" # julia #56562
39+
@test isa(convert(typeof(pdf64M), pdM), typeof(pdf64M))
40+
end
41+
@test_throws TypeError PDMat{Float32,Matrix{Float64}}(pdM)
2442
end
2543
@testset "PDMat from Cholesky" begin
2644
cholL = Cholesky(Matrix(transpose(cholesky(M).factors)), 'L', 0)
45+
cholLf64 = Cholesky(Matrix(transpose(cholesky(f64M).factors)), 'L', 0)
2746
test_pdmat(PDMat(cholL), M, cmat_eq=true, verbose=1)
47+
test_pdmat(PDMat{Float64}(cholLf64), f64M, cmat_eq=true, verbose=1)
48+
if Base.VERSION >= v"1.12.0-DEV.1654" # julia #56562
49+
test_pdmat(PDMat{Float64,Matrix{Float64}}(cholLf64), f64M, cmat_eq=true, verbose=1)
50+
end
2851
end
2952
@testset "PDiagMat" begin
3053
test_pdmat(PDiagMat(V), Matrix(Diagonal(V)), cmat_eq=true, verbose=1)
@@ -142,7 +165,7 @@ using Test
142165
# right division not defined for CHOLMOD:
143166
# `rdiv!(::Matrix{Float64}, ::SuiteSparse.CHOLMOD.Factor{Float64})` not defined
144167
if !HAVE_CHOLMOD
145-
z = x / PDSparseMat(sparse(first(A), 1, 1))
168+
z = x / PDSparseMat(sparse(first(A), 1, 1))
146169
@test typeof(z) === typeof(y)
147170
@test size(z) == size(y)
148171
@test z y

test/specialarrays.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ using StaticArrays
1515
@test PDMat(S, C) === PDS
1616
@test @allocated(PDMat(S)) == @allocated(PDMat(C)) == @allocated(PDMat(S, C))
1717

18+
if Base.VERSION >= v"1.12.0-DEV.1654" # julia #56562
19+
A = PDMat(Matrix{Float64}(I, 2, 2))
20+
B = PDMat(SMatrix{2,2,Float64}(I))
21+
@test !isa(A.mat, typeof(B.mat))
22+
S = convert(typeof(B), A)
23+
@test isa(S.mat, typeof(B.mat))
24+
end
25+
1826
# Diagonal matrix
1927
D = PDiagMat(@SVector(rand(4)))
2028
@test D isa PDiagMat{Float64, <:SVector{4, Float64}}

0 commit comments

Comments
 (0)