Skip to content

Commit 37d70a5

Browse files
fredrikekreandreasnoack
authored andcommitted
check kwarg for factorizations (#27336)
* check = (true|false) for LinearAlgebra.cholesky[!] * check = (true|false) for LinearAlgebra.lu[!] * check = (true|false) for LinearAlgebra.bunchkaufman[!] * check = (true|false) for CHOLMOD.cholesky[!] and CHOLMOD.ldlt[!] * check = (true|false) for UMFPACK.lu
1 parent 3ed10ed commit 37d70a5

File tree

14 files changed

+281
-211
lines changed

14 files changed

+281
-211
lines changed

stdlib/LinearAlgebra/src/bunchkaufman.jl

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,31 +24,35 @@ Base.iterate(S::BunchKaufman, ::Val{:done}) = nothing
2424

2525

2626
"""
27-
bunchkaufman!(A, rook::Bool=false) -> BunchKaufman
27+
bunchkaufman!(A, rook::Bool=false; check = true) -> BunchKaufman
2828
2929
`bunchkaufman!` is the same as [`bunchkaufman`](@ref), but saves space by overwriting the
3030
input `A`, instead of creating a copy.
3131
"""
32-
function bunchkaufman!(A::RealHermSymComplexSym{T,S} where {T<:BlasReal,S<:StridedMatrix}, rook::Bool = false)
32+
function bunchkaufman!(A::RealHermSymComplexSym{T,S} where {T<:BlasReal,S<:StridedMatrix},
33+
rook::Bool = false; check::Bool = true)
3334
LD, ipiv, info = rook ? LAPACK.sytrf_rook!(A.uplo, A.data) : LAPACK.sytrf!(A.uplo, A.data)
35+
check && checknonsingular(info)
3436
BunchKaufman(LD, ipiv, A.uplo, true, rook, info)
3537
end
36-
function bunchkaufman!(A::Hermitian{T,S} where {T<:BlasComplex,S<:StridedMatrix{T}}, rook::Bool = false)
38+
function bunchkaufman!(A::Hermitian{T,S} where {T<:BlasComplex,S<:StridedMatrix{T}},
39+
rook::Bool = false; check::Bool = true)
3740
LD, ipiv, info = rook ? LAPACK.hetrf_rook!(A.uplo, A.data) : LAPACK.hetrf!(A.uplo, A.data)
41+
check && checknonsingular(info)
3842
BunchKaufman(LD, ipiv, A.uplo, false, rook, info)
3943
end
40-
function bunchkaufman!(A::StridedMatrix{<:BlasFloat}, rook::Bool = false)
44+
function bunchkaufman!(A::StridedMatrix{<:BlasFloat}, rook::Bool = false; check::Bool = true)
4145
if ishermitian(A)
42-
return bunchkaufman!(Hermitian(A), rook)
46+
return bunchkaufman!(Hermitian(A), rook; check = check)
4347
elseif issymmetric(A)
44-
return bunchkaufman!(Symmetric(A), rook)
48+
return bunchkaufman!(Symmetric(A), rook; check = check)
4549
else
4650
throw(ArgumentError("Bunch-Kaufman decomposition is only valid for symmetric or Hermitian matrices"))
4751
end
4852
end
4953

5054
"""
51-
bunchkaufman(A, rook::Bool=false) -> S::BunchKaufman
55+
bunchkaufman(A, rook::Bool=false; check = true) -> S::BunchKaufman
5256
5357
Compute the Bunch-Kaufman [^Bunch1977] factorization of a `Symmetric` or
5458
`Hermitian` matrix `A` as ``P'*U*D*U'*P`` or ``P'*L*D*L'*P``, depending on
@@ -62,6 +66,10 @@ as appropriate given `S.uplo`, and `S.p`.
6266
If `rook` is `true`, rook pivoting is used. If `rook` is false,
6367
rook pivoting is not used.
6468
69+
When `check = true`, an error is thrown if the decomposition fails.
70+
When `check = false`, responsibility for checking the decomposition's
71+
validity (via [`issuccess`](@ref)) lies with the user.
72+
6573
The following functions are available for `BunchKaufman` objects:
6674
[`size`](@ref), `\\`, [`inv`](@ref), [`issymmetric`](@ref),
6775
[`ishermitian`](@ref), [`getindex`](@ref).
@@ -98,8 +106,8 @@ julia> d == S.D && u == S.U && p == S.p
98106
true
99107
```
100108
"""
101-
bunchkaufman(A::AbstractMatrix{T}, rook::Bool=false) where {T} =
102-
bunchkaufman!(copy_oftype(A, typeof(sqrt(one(T)))), rook)
109+
bunchkaufman(A::AbstractMatrix{T}, rook::Bool=false; check::Bool = true) where {T} =
110+
bunchkaufman!(copy_oftype(A, typeof(sqrt(one(T)))), rook; check = check)
103111

104112
convert(::Type{BunchKaufman{T}}, B::BunchKaufman{T}) where {T} = B
105113
convert(::Type{BunchKaufman{T}}, B::BunchKaufman) where {T} =
@@ -249,10 +257,6 @@ function Base.show(io::IO, mime::MIME{Symbol("text/plain")}, B::BunchKaufman)
249257
end
250258

251259
function inv(B::BunchKaufman{<:BlasReal})
252-
if !issuccess(B)
253-
throw(SingularException(B.info))
254-
end
255-
256260
if B.rook
257261
copytri!(LAPACK.sytri_rook!(B.uplo, copy(B.LD), B.ipiv), B.uplo, true)
258262
else
@@ -261,10 +265,6 @@ function inv(B::BunchKaufman{<:BlasReal})
261265
end
262266

263267
function inv(B::BunchKaufman{<:BlasComplex})
264-
if !issuccess(B)
265-
throw(SingularException(B.info))
266-
end
267-
268268
if issymmetric(B)
269269
if B.rook
270270
copytri!(LAPACK.sytri_rook!(B.uplo, copy(B.LD), B.ipiv), B.uplo)
@@ -281,21 +281,13 @@ function inv(B::BunchKaufman{<:BlasComplex})
281281
end
282282

283283
function ldiv!(B::BunchKaufman{T}, R::StridedVecOrMat{T}) where T<:BlasReal
284-
if !issuccess(B)
285-
throw(SingularException(B.info))
286-
end
287-
288284
if B.rook
289285
LAPACK.sytrs_rook!(B.uplo, B.LD, B.ipiv, R)
290286
else
291287
LAPACK.sytrs!(B.uplo, B.LD, B.ipiv, R)
292288
end
293289
end
294290
function ldiv!(B::BunchKaufman{T}, R::StridedVecOrMat{T}) where T<:BlasComplex
295-
if !issuccess(B)
296-
throw(SingularException(B.info))
297-
end
298-
299291
if B.rook
300292
if issymmetric(B)
301293
LAPACK.sytrs_rook!(B.uplo, B.LD, B.ipiv, R)

stdlib/LinearAlgebra/src/cholesky.jl

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -136,19 +136,15 @@ end
136136
# cholesky!. Destructive methods for computing Cholesky factorization of real symmetric
137137
# or Hermitian matrix
138138
## No pivoting (default)
139-
function cholesky!(A::RealHermSymComplexHerm, ::Val{false}=Val(false))
140-
if A.uplo == 'U'
141-
CU, info = _chol!(A.data, UpperTriangular)
142-
Cholesky(CU.data, 'U', info)
143-
else
144-
CL, info = _chol!(A.data, LowerTriangular)
145-
Cholesky(CL.data, 'L', info)
146-
end
139+
function cholesky!(A::RealHermSymComplexHerm, ::Val{false}=Val(false); check::Bool = true)
140+
C, info = _chol!(A.data, A.uplo == 'U' ? UpperTriangular : LowerTriangular)
141+
check && checkpositivedefinite(info)
142+
return Cholesky(C.data, A.uplo, info)
147143
end
148144

149145
### for StridedMatrices, check that matrix is symmetric/Hermitian
150146
"""
151-
cholesky!(A, Val(false)) -> Cholesky
147+
cholesky!(A, Val(false); check = true) -> Cholesky
152148
153149
The same as [`cholesky`](@ref), but saves space by overwriting the input `A`,
154150
instead of creating a copy. An [`InexactError`](@ref) exception is thrown if
@@ -168,53 +164,58 @@ Stacktrace:
168164
[...]
169165
```
170166
"""
171-
function cholesky!(A::StridedMatrix, ::Val{false}=Val(false))
167+
function cholesky!(A::StridedMatrix, ::Val{false}=Val(false); check::Bool = true)
172168
checksquare(A)
173169
if !ishermitian(A) # return with info = -1 if not Hermitian
170+
check && checkpositivedefinite(-1)
174171
return Cholesky(A, 'U', convert(BlasInt, -1))
175172
else
176-
return cholesky!(Hermitian(A), Val(false))
173+
return cholesky!(Hermitian(A), Val(false); check = check)
177174
end
178175
end
179176

180177

181178
## With pivoting
182179
### BLAS/LAPACK element types
183180
function cholesky!(A::RealHermSymComplexHerm{<:BlasReal,<:StridedMatrix},
184-
::Val{true}; tol = 0.0)
181+
::Val{true}; tol = 0.0, check::Bool = true)
185182
AA, piv, rank, info = LAPACK.pstrf!(A.uplo, A.data, tol)
186-
return CholeskyPivoted{eltype(AA),typeof(AA)}(AA, A.uplo, piv, rank, tol, info)
183+
C = CholeskyPivoted{eltype(AA),typeof(AA)}(AA, A.uplo, piv, rank, tol, info)
184+
check && chkfullrank(C)
185+
return C
187186
end
188187

189188
### Non BLAS/LAPACK element types (generic). Since generic fallback for pivoted Cholesky
190189
### is not implemented yet we throw an error
191-
cholesky!(A::RealHermSymComplexHerm{<:Real}, ::Val{true}; tol = 0.0) =
190+
cholesky!(A::RealHermSymComplexHerm{<:Real}, ::Val{true}; tol = 0.0, check::Bool = true) =
192191
throw(ArgumentError("generic pivoted Cholesky factorization is not implemented yet"))
193192

194193
### for StridedMatrices, check that matrix is symmetric/Hermitian
195194
"""
196-
cholesky!(A, Val(true); tol = 0.0) -> CholeskyPivoted
195+
cholesky!(A, Val(true); tol = 0.0, check = true) -> CholeskyPivoted
197196
198197
The same as [`cholesky`](@ref), but saves space by overwriting the input `A`,
199198
instead of creating a copy. An [`InexactError`](@ref) exception is thrown if the
200199
factorization produces a number not representable by the element type of `A`,
201200
e.g. for integer types.
202201
"""
203-
function cholesky!(A::StridedMatrix, ::Val{true}; tol = 0.0)
202+
function cholesky!(A::StridedMatrix, ::Val{true}; tol = 0.0, check::Bool = true)
204203
checksquare(A)
205-
if !ishermitian(A) # return with info = -1 if not Hermitian
206-
return CholeskyPivoted(A, 'U', Vector{BlasInt}(),convert(BlasInt, 1),
207-
tol, convert(BlasInt, -1))
204+
if !ishermitian(A)
205+
C = CholeskyPivoted(A, 'U', Vector{BlasInt}(),convert(BlasInt, 1),
206+
tol, convert(BlasInt, -1))
207+
check && chkfullrank(C)
208+
return C
208209
else
209-
return cholesky!(Hermitian(A), Val(true); tol = tol)
210+
return cholesky!(Hermitian(A), Val(true); tol = tol, check = check)
210211
end
211212
end
212213

213214
# cholesky. Non-destructive methods for computing Cholesky factorization of real symmetric
214215
# or Hermitian matrix
215216
## No pivoting (default)
216217
"""
217-
cholesky(A, Val(false)) -> Cholesky
218+
cholesky(A, Val(false); check = true) -> Cholesky
218219
219220
Compute the Cholesky factorization of a dense symmetric positive definite matrix `A`
220221
and return a `Cholesky` factorization. The matrix `A` can either be a [`Symmetric`](@ref) or [`Hermitian`](@ref)
@@ -223,6 +224,10 @@ The triangular Cholesky factor can be obtained from the factorization `F` with:
223224
The following functions are available for `Cholesky` objects: [`size`](@ref), [`\\`](@ref),
224225
[`inv`](@ref), [`det`](@ref), [`logdet`](@ref) and [`isposdef`](@ref).
225226
227+
When `check = true`, an error is thrown if the decomposition fails.
228+
When `check = false`, responsibility for checking the decomposition's
229+
validity (via [`issuccess`](@ref)) lies with the user.
230+
226231
# Examples
227232
```jldoctest
228233
julia> A = [4. 12. -16.; 12. 37. -43.; -16. -43. 98.]
@@ -256,12 +261,12 @@ true
256261
```
257262
"""
258263
cholesky(A::Union{StridedMatrix,RealHermSymComplexHerm{<:Real,<:StridedMatrix}},
259-
::Val{false}=Val(false)) = cholesky!(cholcopy(A))
264+
::Val{false}=Val(false); check::Bool = true) = cholesky!(cholcopy(A); check = check)
260265

261266

262267
## With pivoting
263268
"""
264-
cholesky(A, Val(true); tol = 0.0) -> CholeskyPivoted
269+
cholesky(A, Val(true); tol = 0.0, check = true) -> CholeskyPivoted
265270
266271
Compute the pivoted Cholesky factorization of a dense symmetric positive semi-definite matrix `A`
267272
and return a `CholeskyPivoted` factorization. The matrix `A` can either be a [`Symmetric`](@ref)
@@ -271,9 +276,14 @@ The following functions are available for `PivotedCholesky` objects:
271276
[`size`](@ref), [`\\`](@ref), [`inv`](@ref), [`det`](@ref), and [`rank`](@ref).
272277
The argument `tol` determines the tolerance for determining the rank.
273278
For negative values, the tolerance is the machine precision.
279+
280+
When `check = true`, an error is thrown if the decomposition fails.
281+
When `check = false`, responsibility for checking the decomposition's
282+
validity (via [`issuccess`](@ref)) lies with the user.
274283
"""
275284
cholesky(A::Union{StridedMatrix,RealHermSymComplexHerm{<:Real,<:StridedMatrix}},
276-
::Val{true}; tol = 0.0) = cholesky!(cholcopy(A), Val(true); tol = tol)
285+
::Val{true}; tol = 0.0, check::Bool = true) =
286+
cholesky!(cholcopy(A), Val(true); tol = tol, check = check)
277287

278288
## Number
279289
function cholesky(x::Number, uplo::Symbol=:U)
@@ -319,11 +329,11 @@ function getproperty(C::Cholesky, d::Symbol)
319329
Cuplo = getfield(C, :uplo)
320330
info = getfield(C, :info)
321331
if d == :U
322-
return @assertposdef UpperTriangular(Symbol(Cuplo) == d ? Cfactors : copy(Cfactors')) info
332+
return UpperTriangular(Symbol(Cuplo) == d ? Cfactors : copy(Cfactors'))
323333
elseif d == :L
324-
return @assertposdef LowerTriangular(Symbol(Cuplo) == d ? Cfactors : copy(Cfactors')) info
334+
return LowerTriangular(Symbol(Cuplo) == d ? Cfactors : copy(Cfactors'))
325335
elseif d == :UL
326-
return @assertposdef (Symbol(Cuplo) == :U ? UpperTriangular(Cfactors) : LowerTriangular(Cfactors)) info
336+
return (Symbol(Cuplo) == :U ? UpperTriangular(Cfactors) : LowerTriangular(Cfactors))
327337
else
328338
return getfield(C, d)
329339
end
@@ -335,16 +345,12 @@ function getproperty(C::CholeskyPivoted{T}, d::Symbol) where T<:BlasFloat
335345
Cfactors = getfield(C, :factors)
336346
Cuplo = getfield(C, :uplo)
337347
if d == :U
338-
chkfullrank(C)
339348
return UpperTriangular(Symbol(Cuplo) == d ? Cfactors : copy(Cfactors'))
340349
elseif d == :L
341-
chkfullrank(C)
342350
return LowerTriangular(Symbol(Cuplo) == d ? Cfactors : copy(Cfactors'))
343351
elseif d == :p
344-
chkfullrank(C)
345352
return getfield(C, :piv)
346353
elseif d == :P
347-
chkfullrank(C)
348354
n = size(C, 1)
349355
P = zeros(T, n, n)
350356
for i = 1:n
@@ -379,7 +385,7 @@ function show(io::IO, mime::MIME{Symbol("text/plain")}, C::CholeskyPivoted{<:Any
379385
end
380386

381387
ldiv!(C::Cholesky{T,<:AbstractMatrix}, B::StridedVecOrMat{T}) where {T<:BlasFloat} =
382-
@assertposdef LAPACK.potrs!(C.uplo, C.factors, B) C.info
388+
LAPACK.potrs!(C.uplo, C.factors, B)
383389

384390
function ldiv!(C::Cholesky{<:Any,<:AbstractMatrix}, B::StridedVecOrMat)
385391
if C.uplo == 'L'
@@ -390,11 +396,9 @@ function ldiv!(C::Cholesky{<:Any,<:AbstractMatrix}, B::StridedVecOrMat)
390396
end
391397

392398
function ldiv!(C::CholeskyPivoted{T}, B::StridedVector{T}) where T<:BlasFloat
393-
chkfullrank(C)
394399
invpermute!(LAPACK.potrs!(C.uplo, C.factors, permute!(B, C.piv)), C.piv)
395400
end
396401
function ldiv!(C::CholeskyPivoted{T}, B::StridedMatrix{T}) where T<:BlasFloat
397-
chkfullrank(C)
398402
n = size(C, 1)
399403
for i=1:size(B, 2)
400404
permute!(view(B, 1:n, i), C.piv)
@@ -429,7 +433,6 @@ end
429433
isposdef(C::Union{Cholesky,CholeskyPivoted}) = C.info == 0
430434

431435
function det(C::Cholesky)
432-
isposdef(C) || throw(PosDefException(C.info))
433436
dd = one(real(eltype(C)))
434437
@inbounds for i in 1:size(C.factors,1)
435438
dd *= real(C.factors[i,i])^2
@@ -438,8 +441,6 @@ function det(C::Cholesky)
438441
end
439442

440443
function logdet(C::Cholesky)
441-
# need to check first, or log will throw DomainError
442-
isposdef(C) || throw(PosDefException(C.info))
443444
dd = zero(real(eltype(C)))
444445
@inbounds for i in 1:size(C.factors,1)
445446
dd += log(real(C.factors[i,i]))
@@ -472,12 +473,11 @@ function logdet(C::CholeskyPivoted)
472473
end
473474

474475
inv!(C::Cholesky{<:BlasFloat,<:StridedMatrix}) =
475-
@assertposdef copytri!(LAPACK.potri!(C.uplo, C.factors), C.uplo, true) C.info
476+
copytri!(LAPACK.potri!(C.uplo, C.factors), C.uplo, true)
476477

477478
inv(C::Cholesky{<:BlasFloat,<:StridedMatrix}) = inv!(copy(C))
478479

479480
function inv(C::CholeskyPivoted)
480-
chkfullrank(C)
481481
ipiv = invperm(C.piv)
482482
copytri!(LAPACK.potri!(C.uplo, copy(C.factors)), C.uplo, true)[ipiv, ipiv]
483483
end

stdlib/LinearAlgebra/src/dense.jl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ julia> A
8989
2.0 6.78233
9090
```
9191
"""
92-
isposdef!(A::AbstractMatrix) = ishermitian(A) && isposdef(cholesky!(Hermitian(A)))
92+
isposdef!(A::AbstractMatrix) =
93+
ishermitian(A) && isposdef(cholesky!(Hermitian(A); check = false))
9394

9495
"""
9596
isposdef(A) -> Bool
@@ -109,7 +110,8 @@ julia> isposdef(A)
109110
true
110111
```
111112
"""
112-
isposdef(A::AbstractMatrix) = ishermitian(A) && isposdef(cholesky(Hermitian(A)))
113+
isposdef(A::AbstractMatrix) =
114+
ishermitian(A) && isposdef(cholesky(Hermitian(A); check = false))
113115
isposdef(x::Number) = imag(x)==0 && real(x) > 0
114116

115117
# the definition of strides for Array{T,N} is tuple() if N = 0, otherwise it is
@@ -1208,7 +1210,7 @@ function factorize(A::StridedMatrix{T}) where T
12081210
return UpperTriangular(A)
12091211
end
12101212
if herm
1211-
cf = cholesky(A)
1213+
cf = cholesky(A; check = false)
12121214
if cf.info == 0
12131215
return cf
12141216
else

stdlib/LinearAlgebra/src/factorization.jl

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,8 @@ eltype(::Type{<:Factorization{T}}) where {T} = T
88
size(F::Adjoint{<:Any,<:Factorization}) = reverse(size(parent(F)))
99
size(F::Transpose{<:Any,<:Factorization}) = reverse(size(parent(F)))
1010

11-
macro assertposdef(A, info)
12-
:($(esc(info)) == 0 ? $(esc(A)) : throw(PosDefException($(esc(info)))))
13-
end
14-
15-
macro assertnonsingular(A, info)
16-
:($(esc(info)) == 0 ? $(esc(A)) : throw(SingularException($(esc(info)))))
17-
end
11+
checkpositivedefinite(info) = info == 0 || throw(PosDefException(info))
12+
checknonsingular(info) = info == 0 || throw(SingularException(info))
1813

1914
"""
2015
issuccess(F::Factorization)
@@ -27,7 +22,7 @@ julia> F = cholesky([1 0; 0 1]);
2722
julia> LinearAlgebra.issuccess(F)
2823
true
2924
30-
julia> F = lu([1 0; 0 0]);
25+
julia> F = lu([1 0; 0 0]; check = false);
3126
3227
julia> LinearAlgebra.issuccess(F)
3328
false
@@ -101,4 +96,4 @@ end
10196

10297
# fallback methods for transposed solves
10398
\(F::Transpose{<:Any,<:Factorization{<:Real}}, B::AbstractVecOrMat) = adjoint(F.parent) \ B
104-
\(F::Transpose{<:Any,<:Factorization}, B::AbstractVecOrMat) = conj.(adjoint(F.parent) \ conj.(B))
99+
\(F::Transpose{<:Any,<:Factorization}, B::AbstractVecOrMat) = conj.(adjoint(F.parent) \ conj.(B))

0 commit comments

Comments
 (0)