Skip to content

Commit 087d59f

Browse files
authored
Fix right division and add checks of dimensions (#153)
1 parent 15fecad commit 087d59f

File tree

7 files changed

+125
-40
lines changed

7 files changed

+125
-40
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.5"
3+
version = "0.11.6"
44

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

src/pdiagmat.jl

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,24 @@ function pdadd!(r::Matrix, a::Matrix, b::PDiagMat, c)
3939
return r
4040
end
4141

42-
*(a::PDiagMat, c::T) where {T<:Real} = PDiagMat(a.diag * c)
43-
*(a::PDiagMat, x::AbstractVector) = a.diag .* x
44-
*(a::PDiagMat, x::AbstractMatrix) = a.diag .* x
45-
\(a::PDiagMat, x::AbstractVecOrMat) = x ./ a.diag
46-
/(x::AbstractVecOrMat, a::PDiagMat) = x ./ a.diag
42+
*(a::PDiagMat, c::Real) = PDiagMat(a.diag * c)
43+
function *(a::PDiagMat, x::AbstractVector)
44+
@check_argdims dim(a) == length(x)
45+
return a.diag .* x
46+
end
47+
function *(a::PDiagMat, x::AbstractMatrix)
48+
@check_argdims dim(a) == size(x, 1)
49+
return a.diag .* x
50+
end
51+
function \(a::PDiagMat, x::AbstractVecOrMat)
52+
@check_argdims dim(a) == size(x, 1)
53+
return x ./ a.diag
54+
end
55+
function /(x::AbstractVecOrMat, a::PDiagMat)
56+
@check_argdims dim(a) == size(x, 2)
57+
# return matrix for 1-element vectors `x`, consistent with LinearAlgebra
58+
return reshape(x, Val(2)) ./ permutedims(a.diag) # = (a' \ x')'
59+
end
4760
Base.kron(A::PDiagMat, B::PDiagMat) = PDiagMat( vcat([A.diag[i] * B.diag for i in 1:dim(A)]...) )
4861

4962
### Algebra
@@ -127,21 +140,25 @@ end
127140
### tri products
128141

129142
function X_A_Xt(a::PDiagMat, x::StridedMatrix)
130-
z = x .* sqrt.(reshape(a.diag, 1, dim(a)))
143+
@check_argdims dim(a) == size(x, 2)
144+
z = x .* sqrt.(permutedims(a.diag))
131145
z * transpose(z)
132146
end
133147

134148
function Xt_A_X(a::PDiagMat, x::StridedMatrix)
149+
@check_argdims dim(a) == size(x, 1)
135150
z = x .* sqrt.(a.diag)
136151
transpose(z) * z
137152
end
138153

139154
function X_invA_Xt(a::PDiagMat, x::StridedMatrix)
140-
z = x ./ sqrt.(reshape(a.diag, 1, dim(a)))
155+
@check_argdims dim(a) == size(x, 2)
156+
z = x ./ sqrt.(permutedims(a.diag))
141157
z * transpose(z)
142158
end
143159

144160
function Xt_invA_X(a::PDiagMat, x::StridedMatrix)
161+
@check_argdims dim(a) == size(x, 1)
145162
z = x ./ sqrt.(a.diag)
146163
transpose(z) * z
147164
end

src/pdmat.jl

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,12 @@ function pdadd!(r::Matrix, a::Matrix, b::PDMat, c)
4444
_addscal!(r, a, b.mat, c)
4545
end
4646

47-
*(a::PDMat{S}, c::T) where {S<:Real, T<:Real} = PDMat(a.mat * c)
48-
*(a::PDMat, x::AbstractVector{T}) where {T} = a.mat * x
49-
*(a::PDMat, x::AbstractMatrix{T}) where {T} = a.mat * x
47+
*(a::PDMat, c::Real) = PDMat(a.mat * c)
48+
*(a::PDMat, x::AbstractVector) = a.mat * x
49+
*(a::PDMat, x::AbstractMatrix) = a.mat * x
5050
\(a::PDMat, x::AbstractVecOrMat) = a.chol \ x
51-
/(x::AbstractVecOrMat, a::PDMat) = x / a.chol
51+
# return matrix for 1-element vectors `x`, consistent with LinearAlgebra
52+
/(x::AbstractVecOrMat, a::PDMat) = reshape(x, Val(2)) / a.chol
5253

5354
### Algebra
5455

@@ -94,21 +95,25 @@ invquad!(r::AbstractArray, a::PDMat, x::StridedMatrix) = colwise_dot!(r, x, a.ma
9495
### tri products
9596

9697
function X_A_Xt(a::PDMat, x::StridedMatrix)
97-
z = rmul!(copy(x), chol_lower(a.chol))
98+
@check_argdims dim(a) == size(x, 2)
99+
z = x * chol_lower(a.chol)
98100
return z * transpose(z)
99101
end
100102

101103
function Xt_A_X(a::PDMat, x::StridedMatrix)
102-
z = lmul!(chol_upper(a.chol), copy(x))
104+
@check_argdims dim(a) == size(x, 1)
105+
z = chol_upper(a.chol) * x
103106
return transpose(z) * z
104107
end
105108

106109
function X_invA_Xt(a::PDMat, x::StridedMatrix)
107-
z = rdiv!(copy(x), chol_upper(a.chol))
110+
@check_argdims dim(a) == size(x, 2)
111+
z = x / chol_upper(a.chol)
108112
return z * transpose(z)
109113
end
110114

111115
function Xt_invA_X(a::PDMat, x::StridedMatrix)
112-
z = ldiv!(chol_lower(a.chol), copy(x))
116+
@check_argdims dim(a) == size(x, 1)
117+
z = chol_lower(a.chol) \ x
113118
return transpose(z) * z
114119
end

src/pdsparsemat.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ function pdadd!(r::Matrix, a::Matrix, b::PDSparseMat, c)
4343
_addscal!(r, a, b.mat, c)
4444
end
4545

46-
*(a::PDSparseMat, c::T) where {T<:Real} = PDSparseMat(a.mat * c)
46+
*(a::PDSparseMat, c::Real) = PDSparseMat(a.mat * c)
4747
*(a::PDSparseMat, x::StridedVecOrMat) = a.mat * x
4848
\(a::PDSparseMat{T}, x::StridedVecOrMat{T}) where {T<:Real} = convert(Array{T},a.chol \ convert(Array{Float64},x)) #to avoid limitations in sparse factorization library CHOLMOD, see e.g., julia issue #14076
4949
/(x::StridedVecOrMat{T}, a::PDSparseMat{T}) where {T<:Real} = convert(Array{T},convert(Array{Float64},x) / a.chol )

src/scalmat.jl

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,25 @@ function pdadd!(r::Matrix, a::Matrix, b::ScalMat, c)
3737
return r
3838
end
3939

40-
*(a::ScalMat, c::T) where {T<:Real} = ScalMat(a.dim, a.value * c)
41-
/(a::ScalMat{T}, c::T) where {T<:Real} = ScalMat(a.dim, a.value / c)
42-
*(a::ScalMat, x::AbstractVector) = a.value * x
43-
*(a::ScalMat, x::AbstractMatrix) = a.value * x
44-
\(a::ScalMat, x::AbstractVecOrMat) = x / a.value
45-
/(x::AbstractVecOrMat, a::ScalMat) = x / a.value
40+
*(a::ScalMat, c::Real) = ScalMat(a.dim, a.value * c)
41+
/(a::ScalMat, c::Real) = ScalMat(a.dim, a.value / c)
42+
function *(a::ScalMat, x::AbstractVector)
43+
@check_argdims dim(a) == length(x)
44+
return a.value * x
45+
end
46+
function *(a::ScalMat, x::AbstractMatrix)
47+
@check_argdims dim(a) == size(x, 1)
48+
return a.value * x
49+
end
50+
function \(a::ScalMat, x::AbstractVecOrMat)
51+
@check_argdims dim(a) == size(x, 1)
52+
return x / a.value
53+
end
54+
function /(x::AbstractVecOrMat, a::ScalMat)
55+
@check_argdims dim(a) == size(x, 2)
56+
# return matrix for 1-element vectors `x`, consistent with LinearAlgebra
57+
return reshape(x, Val(2)) / a.value
58+
end
4659
Base.kron(A::ScalMat, B::ScalMat) = ScalMat( dim(A) * dim(B), A.value * B.value )
4760

4861
### Algebra

test/pdmtypes.jl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,30 @@ using Test
8383
@test d isa PDiagMat{eltype(v),typeof(v)}
8484
@test d.diag === v
8585
end
86+
87+
@testset "division of vectors (dim = 1)" begin
88+
A = rand(1, 1)
89+
x = randn(1)
90+
y = x / A
91+
@assert x / A isa Matrix{Float64}
92+
@assert size(y) == (1, 1)
93+
94+
for M in (PDiagMat(vec(A)), ScalMat(1, first(A)))
95+
@test x / M isa Matrix{Float64}
96+
@test x / M y
97+
end
98+
99+
# requires https://github.com/JuliaLang/julia/pull/32594
100+
if VERSION >= v"1.3.0-DEV.562"
101+
@test x / PDMat(A) isa Matrix{Float64}
102+
@test x / PDMat(A) y
103+
end
104+
105+
# right division not defined for CHOLMOD:
106+
# `rdiv!(::Matrix{Float64}, ::SuiteSparse.CHOLMOD.Factor{Float64})` not defined
107+
if !HAVE_CHOLMOD
108+
@test x / PDSparseMat(sparse(first(A), 1, 1)) isa Matrix{Float64}
109+
@test x / PDSparseMat(sparse(first(A), 1, 1)) y
110+
end
111+
end
86112
end

test/testutils.jl

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function test_pdmat(C::AbstractPDMat, Cmat::Matrix;
2020
t_logdet::Bool=true, # whether to test logdet method
2121
t_eig::Bool=true, # whether to test eigmax and eigmin
2222
t_mul::Bool=true, # whether to test multiplication
23-
t_rdiv::Bool=true, # whether to test right division (solve)
23+
t_div::Bool=true, # whether to test division
2424
t_quad::Bool=true, # whether to test quad & invquad
2525
t_triprod::Bool=true, # whether to test X_A_Xt, Xt_A_X, X_invA_Xt, and Xt_invA_X
2626
t_whiten::Bool=true # whether to test whiten and unwhiten
@@ -45,7 +45,7 @@ function test_pdmat(C::AbstractPDMat, Cmat::Matrix;
4545
X = rand(eltype(C),d,n) .- convert(eltype(C),0.5)
4646

4747
t_mul && pdtest_mul(C, Cmat, X, verbose)
48-
t_rdiv && pdtest_rdiv(C, Imat, X, verbose)
48+
t_div && pdtest_div(C, Imat, X, verbose)
4949
t_quad && pdtest_quad(C, Cmat, Imat, X, verbose)
5050
t_triprod && pdtest_triprod(C, Cmat, Imat, X, verbose)
5151

@@ -174,42 +174,60 @@ end
174174

175175
function pdtest_mul(C::AbstractPDMat, Cmat::Matrix, verbose::Int)
176176
n = 5
177-
X = rand(eltype(C),dim(C), n)
178-
179-
_pdt(verbose, "multiply")
180-
@test C * X Cmat * X
181-
182-
for i = 1:n
183-
xi = vec(copy(X[:,i]))
184-
@test C * xi Cmat * xi
185-
end
177+
X = rand(eltype(C), dim(C), n)
178+
pdtest_mul(C, Cmat, X, verbose)
186179
end
187180

188181

189182
function pdtest_mul(C::AbstractPDMat, Cmat::Matrix, X::Matrix, verbose::Int)
190183
_pdt(verbose, "multiply")
184+
d, n = size(X)
185+
@assert d == dim(C)
186+
@assert size(Cmat) == size(C)
191187
@test C * X Cmat * X
192188

193-
y = similar(C * X, size(C, 1))
194-
ymat = similar(Cmat * X, size(Cmat, 1))
195-
for i = 1:size(X,2)
189+
y = similar(C * X, d)
190+
ymat = similar(Cmat * X, d)
191+
for i = 1:n
196192
xi = vec(copy(X[:,i]))
197193
@test C * xi Cmat * xi
198194

199195
mul!(y, C, xi)
200196
mul!(ymat, Cmat, xi)
201197
@test y ymat
202198
end
199+
200+
# Dimension mismatches
201+
@test_throws DimensionMismatch C * rand(d + 1)
202+
@test_throws DimensionMismatch C * rand(d + 1, n)
203203
end
204204

205205

206-
function pdtest_rdiv(C::AbstractPDMat, Imat::Matrix, X::Matrix, verbose::Int)
207-
_pdt(verbose, "rdivide")
206+
function pdtest_div(C::AbstractPDMat, Imat::Matrix, X::Matrix, verbose::Int)
207+
_pdt(verbose, "divide")
208+
d, n = size(X)
209+
@assert d == dim(C)
210+
@assert size(Imat) == size(C)
208211
@test C \ X Imat * X
212+
# Right division with Choleskyrequires https://github.com/JuliaLang/julia/pull/32594
213+
# CHOLMOD throws error since no method is found for
214+
# `rdiv!(::Matrix{Float64}, ::SuiteSparse.CHOLMOD.Factor{Float64})`
215+
check_rdiv = !(C isa PDMat && VERSION < v"1.3.0-DEV.562") && !(C isa PDSparseMat && HAVE_CHOLMOD)
216+
check_rdiv && @test Matrix(X') / C (C \ X)'
209217

210-
for i = 1:size(X,2)
218+
for i = 1:n
211219
xi = vec(copy(X[:,i]))
212220
@test C \ xi Imat * xi
221+
check_rdiv && @test Matrix(xi') / C (C \ xi)'
222+
end
223+
224+
225+
# Dimension mismatches
226+
@test_throws DimensionMismatch C \ rand(d + 1)
227+
@test_throws DimensionMismatch C \ rand(d + 1, n)
228+
if check_rdiv
229+
@test_throws DimensionMismatch rand(1, d + 1) / C
230+
@test_throws DimensionMismatch rand(n, d + 1) / C
213231
end
214232
end
215233

@@ -240,23 +258,29 @@ end
240258

241259

242260
function pdtest_triprod(C::AbstractPDMat, Cmat::Matrix, Imat::Matrix, X::Matrix, verbose::Int)
261+
d, n = size(X)
262+
@assert d == dim(C)
243263
Xt = copy(transpose(X))
244264

245265
_pdt(verbose, "X_A_Xt")
246266
# default tolerance in isapprox is different on 0.4. rtol argument can be deleted
247267
# ≈ form used when 0.4 is no longer supported
248268
lhs, rhs = X_A_Xt(C, Xt), Xt * Cmat * X
249269
@test isapprox(lhs, rhs, rtol=sqrt(max(eps(real(float(eltype(lhs)))), eps(real(float(eltype(rhs)))))))
270+
@test_throws DimensionMismatch X_A_Xt(C, rand(n, d + 1))
250271

251272
_pdt(verbose, "Xt_A_X")
252273
lhs, rhs = Xt_A_X(C, X), Xt * Cmat * X
253274
@test isapprox(lhs, rhs, rtol=sqrt(max(eps(real(float(eltype(lhs)))), eps(real(float(eltype(rhs)))))))
275+
@test_throws DimensionMismatch Xt_A_X(C, rand(d + 1, n))
254276

255277
_pdt(verbose, "X_invA_Xt")
256278
@test X_invA_Xt(C, Xt) Xt * Imat * X
279+
@test_throws DimensionMismatch X_invA_Xt(C, rand(n, d + 1))
257280

258281
_pdt(verbose, "Xt_invA_X")
259282
@test Xt_invA_X(C, X) Xt * Imat * X
283+
@test_throws DimensionMismatch Xt_invA_X(C, rand(d + 1, n))
260284
end
261285

262286

0 commit comments

Comments
 (0)