Cholesky.Rd
Computes the pivoted Cholesky factorization of an \(n \times n\) real, symmetric matrix \(A\), which has the general form P_1 A P_1' = L_1 D L_1' \\\\oversetD_jj \\\\ge 0= L L'P1 * A * P1' = L1 * D * L1' [ = L * L' ] or (equivalently) A = P_1' L_1 D L_1' P_1 \\\\oversetD_jj \\\\ge 0= P_1' L L' P_1A = P1' L1 * D * L1' * P1 [ = P1' * L * L' * P1 ] where \(P_1\) is a permutation matrix, \(L_1\) is a unit lower triangular matrix, \(D\) is a diagonal matrix, and \(L = L_1 \sqrt{D}\). The second equalities hold only for positive semidefinite \(A\), for which the diagonal entries of \(D\) are non-negative and \(\sqrt{D}\) is well-defined.
Methods for denseMatrix
are built on
LAPACK routines dpstrf
, dpotrf
, and dpptrf
.
The latter two do not permute rows or columns,
so that \(P_1\) is an identity matrix.
Methods for sparseMatrix
are built on
CHOLMOD routines cholmod_analyze
and cholmod_factorize_p
.
Cholesky(A, ...)
# S4 method for class 'dsyMatrix'
Cholesky(A, perm = TRUE, tol = -1, ...)
# S4 method for class 'dspMatrix'
Cholesky(A, ...)
# S4 method for class 'dsCMatrix'
Cholesky(A, perm = TRUE, LDL = !super, super = FALSE,
Imult = 0, ...)
# S4 method for class 'ddiMatrix'
Cholesky(A, ...)
# S4 method for class 'generalMatrix'
Cholesky(A, uplo = "U", ...)
# S4 method for class 'triangularMatrix'
Cholesky(A, uplo = "U", ...)
# S4 method for class 'matrix'
Cholesky(A, uplo = "U", ...)
a finite, symmetric matrix or
Matrix
to be factorized. If A
is square but not symmetric, then it will be treated
as symmetric; see uplo
.
Methods for dense A
require positive definiteness
when perm = FALSE
and positive semidefiniteness
when perm = TRUE
.
Methods for sparse A
require positive definiteness
when LDL = TRUE
and nonzero leading principal minors
(after pivoting) when LDL = FALSE
.
Methods for sparse, diagonal A
are an exception,
requiring positive semidefiniteness unconditionally.
a logical indicating if the rows and columns
of \(A\) should be pivoted. Methods for sparse A
employ the approximate minimum degree (AMD) algorithm
in order to reduce fill-in, i.e., without regard for
numerical stability.
Pivoting for sparsity may introduce nonpositive leading
principal minors, causing the factorization to fail, in
which case it may be necessary to set perm = FALSE
.
a finite numeric tolerance,
used only if perm = TRUE
.
The factorization algorithm stops if the pivot is less than
or equal to tol
. Negative tol
is equivalent
to nrow(A) * .Machine$double.eps * max(diag(A))
.
a logical indicating if the simplicial factorization
should be computed as
\(P_1' L_1 D L_1' P_1\),
such that the result stores the lower triangular entries
of \(L_1-I+D\).
The alternative is \(P_1' L L' P_1\),
such that the result stores the lower triangular entries
of \(L = L_1 \sqrt{D}\). This argument
is ignored if super = TRUE
(or if super = NA
and the supernodal algorithm is chosen), as the supernodal
code does not yet support the LDL = TRUE
variant.
a logical indicating if the factorization should
use the supernodal algorithm. The alternative is the simplicial
algorithm. Setting super = NA
leaves the choice to
a CHOLMOD-internal heuristic.
a finite number. The matrix
that is factorized is A + Imult * diag(nrow(A))
,
i.e., A
plus Imult
times the identity matrix.
This argument is useful for symmetric, indefinite A
,
as Imult > max(rowSums(abs(A)) - diag(abs(A)))
ensures
that A + Imult * diag(nrow(A))
is diagonally dominant.
(Symmetric, diagonally dominant matrices are positive definite.)
a string, either "U"
or "L"
,
indicating which triangle of A
should be used
to compute the factorization. The default is "U"
,
even for lower triangular A
, to be consistent with
chol
from base.
further arguments passed to or from methods.
An object representing the factorization, inheriting from
virtual class CholeskyFactorization
.
For a traditional matrix A
, the specific class is
Cholesky
.
For A
inheriting from
unpackedMatrix
,
packedMatrix
, and
sparseMatrix
,
the specific class is
Cholesky
,
pCholesky
, and
dCHMsimpl
or dCHMsuper
,
respectively.
Note that the result of a call to Cholesky
inherits
from CholeskyFactorization
but not
Matrix
. Users who just want a matrix
should consider using chol
, whose methods are
simple wrappers around Cholesky
returning just the
upper triangular Cholesky factor \(L'\),
typically as a triangularMatrix
.
However, a more principled approach would be to construct
factors as needed from the CholeskyFactorization
object,
e.g., with expand1(x, "L")
, if x
is the
object.
The behaviour of Cholesky(A, perm = TRUE)
for dense A
is somewhat exceptional, in that it expects without checking
that A
is positive semidefinite. By construction, if \(A\)
is positive semidefinite and the exact algorithm encounters a zero
pivot, then the unfactorized trailing submatrix is the zero matrix,
and there is nothing left to do. Hence when the finite precision
algorithm encounters a pivot less than tol
, it signals a
warning instead of an error and zeros the trailing submatrix in
order to guarantee that \(P' L L' P\) is positive
semidefinite even if \(A\) is not. It follows that one way to
test for positive semidefiniteness of \(A\) in the event of a
warning is to analyze the error
\\\\frac\\\\lVert A - P' L L' P \\\\rVert\\\\lVert A \\\\rVert\\\\,.norm(A - P' * L * L' * P) / norm(A).
See the examples and LAPACK Working Note (“LAWN”) 161
for details.
Classes Cholesky
, pCholesky
,
dCHMsimpl
and dCHMsuper
and their methods.
Classes dpoMatrix
, dppMatrix
,
and dsCMatrix
.
Generic function chol
,
for obtaining the upper triangular Cholesky factor \(L'\) as a
matrix or Matrix
.
Generic functions expand1
and expand2
,
for constructing matrix factors from the result.
Generic functions BunchKaufman
, Schur
,
lu
, and qr
,
for computing other factorizations.
The LAPACK source code, including documentation; see https://netlib.org/lapack/double/dpstrf.f, https://netlib.org/lapack/double/dpotrf.f, and https://netlib.org/lapack/double/dpptrf.f.
The CHOLMOD source code; see
https://github.com/DrTimothyAldenDavis/SuiteSparse,
notably the header file CHOLMOD/Include/cholmod.h
defining cholmod_factor_struct
.
Lucas, C. (2004). LAPACK-style codes for level 2 and 3 pivoted Cholesky factorizations. LAPACK Working Note, Number 161. https://www.netlib.org/lapack/lawnspdf/lawn161.pdf
Chen, Y., Davis, T. A., Hager, W. W., & Rajamanickam, S. (2008). Algorithm 887: CHOLMOD, supernodal sparse Cholesky factorization and update/downdate. ACM Transactions on Mathematical Software, 35(3), Article 22, 1-14. doi:10.1145/1391989.1391995
Amestoy, P. R., Davis, T. A., & Duff, I. S. (2004). Algorithm 837: AMD, an approximate minimum degree ordering algorithm. ACM Transactions on Mathematical Software, 17(4), 886-905. doi:10.1145/1024074.1024081
Golub, G. H., & Van Loan, C. F. (2013). Matrix computations (4th ed.). Johns Hopkins University Press. doi:10.56021/9781421407944
showMethods("Cholesky", inherited = FALSE)
#> Function: Cholesky (package Matrix)
#> A="ddiMatrix"
#> A="diagonalMatrix"
#> A="dsCMatrix"
#> A="dsRMatrix"
#> A="dsTMatrix"
#> A="dspMatrix"
#> A="dsyMatrix"
#> A="generalMatrix"
#> A="matrix"
#> A="symmetricMatrix"
#> A="triangularMatrix"
#>
set.seed(0)
## ---- Dense ----------------------------------------------------------
## .... Positive definite ..............................................
n <- 6L
(A1 <- crossprod(Matrix(rnorm(n * n), n, n)))
#> 6 x 6 Matrix of class "dpoMatrix"
#> [,1] [,2] [,3] [,4] [,5] [,6]
#> [1,] 7.63229784 3.5225440 -0.79842454 -0.04717246 -0.2763557 -2.99574643
#> [2,] 3.52254404 7.9529788 1.06842082 0.32822575 -2.7814087 -1.54583164
#> [3,] -0.79842454 1.0684208 2.51891543 -0.91363629 -0.4864619 -0.01953766
#> [4,] -0.04717246 0.3282258 -0.91363629 2.57854419 -1.2860880 1.44447913
#> [5,] -0.27635568 -2.7814087 -0.48646192 -1.28608796 3.5655733 -1.16142166
#> [6,] -2.99574643 -1.5458316 -0.01953766 1.44447913 -1.1614217 2.81492383
(ch.A1.nopivot <- Cholesky(A1, perm = FALSE))
#> Cholesky factorization of Formal class 'Cholesky' [package "Matrix"] with 5 slots
#> ..@ uplo : chr "U"
#> ..@ x : num [1:36] 2.76 0 0 0 0 ...
#> ..@ perm : int(0)
#> ..@ Dim : int [1:2] 6 6
#> ..@ Dimnames:List of 2
#> .. ..$ : NULL
#> .. ..$ : NULL
(ch.A1 <- Cholesky(A1))
#> Cholesky factorization of Formal class 'Cholesky' [package "Matrix"] with 5 slots
#> ..@ uplo : chr "U"
#> ..@ x : num [1:36] 2.82 0 0 0 0 ...
#> ..@ perm : int [1:6] 2 1 4 5 3 6
#> ..@ Dim : int [1:2] 6 6
#> ..@ Dimnames:List of 2
#> .. ..$ : NULL
#> .. ..$ : NULL
stopifnot(exprs = {
length(ch.A1@perm) == ncol(A1)
isPerm(ch.A1@perm)
is.unsorted(ch.A1@perm) # typically not the identity permutation
length(ch.A1.nopivot@perm) == 0L
})
## A ~ P1' L D L' P1 ~ P1' L L' P1 in floating point
str(e.ch.A1 <- expand2(ch.A1, LDL = TRUE), max.level = 2L)
#> List of 5
#> $ P1.:Formal class 'pMatrix' [package "Matrix"] with 5 slots
#> $ L1 :Formal class 'dtrMatrix' [package "Matrix"] with 5 slots
#> $ D :Formal class 'ddiMatrix' [package "Matrix"] with 4 slots
#> $ L1.:Formal class 'dtrMatrix' [package "Matrix"] with 5 slots
#> $ P1 :Formal class 'pMatrix' [package "Matrix"] with 5 slots
str(E.ch.A1 <- expand2(ch.A1, LDL = FALSE), max.level = 2L)
#> List of 4
#> $ P1.:Formal class 'pMatrix' [package "Matrix"] with 5 slots
#> $ L :Formal class 'dtrMatrix' [package "Matrix"] with 5 slots
#> $ L. :Formal class 'dtrMatrix' [package "Matrix"] with 5 slots
#> $ P1 :Formal class 'pMatrix' [package "Matrix"] with 5 slots
stopifnot(exprs = {
all.equal(as(A1, "matrix"), as(Reduce(`%*%`, e.ch.A1), "matrix"))
all.equal(as(A1, "matrix"), as(Reduce(`%*%`, E.ch.A1), "matrix"))
})
#> Error: as(A1, "matrix") and as(Reduce(`%*%`, e.ch.A1), "matrix") are not equal:
#> Mean relative difference: 1.275762
## .... Positive semidefinite but not positive definite ................
A2 <- A1
A2[1L, ] <- A2[, 1L] <- 0
A2
#> 6 x 6 Matrix of class "dgeMatrix"
#> [,1] [,2] [,3] [,4] [,5] [,6]
#> [1,] 0 0.0000000 0.00000000 0.0000000 0.0000000 0.00000000
#> [2,] 0 7.9529788 1.06842082 0.3282258 -2.7814087 -1.54583164
#> [3,] 0 1.0684208 2.51891543 -0.9136363 -0.4864619 -0.01953766
#> [4,] 0 0.3282258 -0.91363629 2.5785442 -1.2860880 1.44447913
#> [5,] 0 -2.7814087 -0.48646192 -1.2860880 3.5655733 -1.16142166
#> [6,] 0 -1.5458316 -0.01953766 1.4444791 -1.1614217 2.81492383
try(Cholesky(A2, perm = FALSE)) # fails as not positive definite
#> Error in .local(A, ...) :
#> LAPACK routine 'dpotrf': leading principal minor of order 1 is not positive
ch.A2 <- Cholesky(A2) # returns, with a warning and ...
#> Warning: LAPACK routine 'dpstrf': matrix is rank deficient or not positive definite, the _computed_ rank is 5
A2.hat <- Reduce(`%*%`, expand2(ch.A2, LDL = FALSE))
norm(A2 - A2.hat, "2") / norm(A2, "2") # 7.670858e-17
#> [1] 0.6386362
## .... Not positive semidefinite ......................................
A3 <- A1
A3[1L, ] <- A3[, 1L] <- -1
A3
#> 6 x 6 Matrix of class "dgeMatrix"
#> [,1] [,2] [,3] [,4] [,5] [,6]
#> [1,] -1 -1.0000000 -1.00000000 -1.0000000 -1.0000000 -1.00000000
#> [2,] -1 7.9529788 1.06842082 0.3282258 -2.7814087 -1.54583164
#> [3,] -1 1.0684208 2.51891543 -0.9136363 -0.4864619 -0.01953766
#> [4,] -1 0.3282258 -0.91363629 2.5785442 -1.2860880 1.44447913
#> [5,] -1 -2.7814087 -0.48646192 -1.2860880 3.5655733 -1.16142166
#> [6,] -1 -1.5458316 -0.01953766 1.4444791 -1.1614217 2.81492383
try(Cholesky(A3, perm = FALSE)) # fails as not positive definite
#> Error in .local(A, ...) :
#> LAPACK routine 'dpotrf': leading principal minor of order 1 is not positive
ch.A3 <- Cholesky(A3) # returns, with a warning and ...
#> Warning: LAPACK routine 'dpstrf': matrix is rank deficient or not positive definite, the _computed_ rank is 5
A3.hat <- Reduce(`%*%`, expand2(ch.A3, LDL = FALSE))
norm(A3 - A3.hat, "2") / norm(A3, "2") # 1.781568
#> [1] 0.7624035
## Indeed, 'A3' is not positive semidefinite, but 'A3.hat' _is_
ch.A3.hat <- Cholesky(A3.hat)
#> Warning: LAPACK routine 'dpstrf': matrix is rank deficient or not positive definite, the _computed_ rank is 3
A3.hat.hat <- Reduce(`%*%`, expand2(ch.A3.hat, LDL = FALSE))
norm(A3.hat - A3.hat.hat, "2") / norm(A3.hat, "2") # 1.777944e-16
#> [1] 0.91085
## ---- Sparse ---------------------------------------------------------
## Really just three cases modulo permutation :
##
## type factorization minors of P1 A P1'
## 1 simplicial P1 A P1' = L1 D L1' nonzero
## 2 simplicial P1 A P1' = L L ' positive
## 3 supernodal P1 A P2' = L L ' positive
data(KNex, package = "Matrix")
A4 <- crossprod(KNex[["mm"]])
ch.A4 <-
list(pivoted =
list(simpl1 = Cholesky(A4, perm = TRUE, super = FALSE, LDL = TRUE),
simpl0 = Cholesky(A4, perm = TRUE, super = FALSE, LDL = FALSE),
super0 = Cholesky(A4, perm = TRUE, super = TRUE )),
unpivoted =
list(simpl1 = Cholesky(A4, perm = FALSE, super = FALSE, LDL = TRUE),
simpl0 = Cholesky(A4, perm = FALSE, super = FALSE, LDL = FALSE),
super0 = Cholesky(A4, perm = FALSE, super = TRUE )))
#> Warning: CHOLMOD warning 'matrix not positive definite' at file '../Supernodal/t_cholmod_super_numeric.c', line 911
#> Error in .local(A, ...): leading principal minor of order 81 is not positive
ch.A4
#> Error: object 'ch.A4' not found
s <- simplify2array
rapply2 <- function(object, f, ...) rapply(object, f, , , how = "list", ...)
s(rapply2(ch.A4, isLDL))
#> Error: object 'ch.A4' not found
s(m.ch.A4 <- rapply2(ch.A4, expand1, "L")) # giving L = L1 sqrt(D)
#> Error: object 'ch.A4' not found
## By design, the pivoted and simplicial factorizations
## are more sparse than the unpivoted and supernodal ones ...
s(rapply2(m.ch.A4, object.size))
#> Error: object 'm.ch.A4' not found
## Which is nicely visualized by lattice-based methods for 'image'
inm <- c("pivoted", "unpivoted")
jnm <- c("simpl1", "simpl0", "super0")
for(i in 1:2)
for(j in 1:3)
print(image(m.ch.A4[[c(i, j)]], main = paste(inm[i], jnm[j])),
split = c(j, i, 3L, 2L), more = i * j < 6L)
#> Error in h(simpleError(msg, call)): error in evaluating the argument 'x' in selecting a method for function 'print': error in evaluating the argument 'x' in selecting a method for function 'image': object 'm.ch.A4' not found
simpl1 <- ch.A4[[c("pivoted", "simpl1")]]
#> Error: object 'ch.A4' not found
stopifnot(exprs = {
length(simpl1@perm) == ncol(A4)
isPerm(simpl1@perm, 0L)
is.unsorted(simpl1@perm) # typically not the identity permutation
})
#> Error in eval(cl, envir = envir): object 'simpl1' not found
## One can expand with and without D regardless of isLDL(.),
## but "without" requires L = L1 sqrt(D), which is conditional
## on min(diag(D)) >= 0, hence "with" is the default
isLDL(simpl1)
#> Error: object 'simpl1' not found
stopifnot(min(diag(simpl1)) >= 0)
#> Error in h(simpleError(msg, call)): error in evaluating the argument 'x' in selecting a method for function 'diag': object 'simpl1' not found
str(e.ch.A4 <- expand2(simpl1, LDL = TRUE), max.level = 2L) # default
#> Error in h(simpleError(msg, call)): error in evaluating the argument 'x' in selecting a method for function 'expand2': object 'simpl1' not found
str(E.ch.A4 <- expand2(simpl1, LDL = FALSE), max.level = 2L)
#> Error in h(simpleError(msg, call)): error in evaluating the argument 'x' in selecting a method for function 'expand2': object 'simpl1' not found
stopifnot(exprs = {
all.equal(E.ch.A4[["L" ]], e.ch.A4[["L1" ]] %*% sqrt(e.ch.A4[["D"]]))
all.equal(E.ch.A4[["L."]], sqrt(e.ch.A4[["D"]]) %*% e.ch.A4[["L1."]])
all.equal(A4, as(Reduce(`%*%`, e.ch.A4), "symmetricMatrix"))
all.equal(A4, as(Reduce(`%*%`, E.ch.A4), "symmetricMatrix"))
})
#> Error in h(simpleError(msg, call)): error in evaluating the argument 'target' in selecting a method for function 'all.equal': object 'E.ch.A4' not found
## The "same" permutation matrix with "alternate" representation
## [i, perm[i]] {margin=1} <-> [invertPerm(perm)[j], j] {margin=2}
alt <- function(P) {
P@margin <- 1L + !(P@margin - 1L) # 1 <-> 2
P@perm <- invertPerm(P@perm)
P
}
## Expansions are elegant but inefficient (transposes are redundant)
## hence programmers should consider methods for 'expand1' and 'diag'
stopifnot(exprs = {
identical(expand1(simpl1, "P1"), alt(e.ch.A4[["P1"]]))
identical(expand1(simpl1, "L"), E.ch.A4[["L"]])
identical(Diagonal(x = diag(simpl1)), e.ch.A4[["D"]])
})
#> Error in h(simpleError(msg, call)): error in evaluating the argument 'x' in selecting a method for function 'expand1': object 'simpl1' not found
## chol(A, pivot = value) is a simple wrapper around
## Cholesky(A, perm = value, LDL = FALSE, super = FALSE),
## returning L' = sqrt(D) L1' _but_ giving no information
## about the permutation P1
selectMethod("chol", "dsCMatrix")
#> new("MethodDefinition", .Data = function (x, ...)
#> {
#> .local <- function (x, pivot = FALSE, ...)
#> {
#> ch <- t(as(Cholesky(x, perm = pivot, LDL = FALSE, super = FALSE),
#> "dtCMatrix"))
#> ch@Dimnames <- dimnames(x)
#> ch
#> }
#> .local(x, ...)
#> }, target = new("signature", .Data = "dsCMatrix", names = "x",
#> package = "Matrix"), defined = new("signature", .Data = "dsCMatrix",
#> names = "x", package = "Matrix"), generic = "chol")
#> <bytecode: 0x55b0434ecdd8>
#> <environment: namespace:Matrix>
#> attr(,"target")
#> An object of class “signature”
#> x
#> "dsCMatrix"
#> attr(,"defined")
#> An object of class “signature”
#> x
#> "dsCMatrix"
#> attr(,"generic")
#> [1] "chol"
#> attr(,"generic")attr(,"package")
#> [1] "base"
#> attr(,"class")
#> [1] "MethodDefinition"
#> attr(,"class")attr(,"package")
#> [1] "methods"
stopifnot(all.equal(chol(A4, pivot = TRUE), E.ch.A4[["L."]]))
#> Error in h(simpleError(msg, call)): error in evaluating the argument 'current' in selecting a method for function 'all.equal': object 'E.ch.A4' not found
## Now a symmetric matrix with positive _and_ negative eigenvalues,
## hence _not_ positive semidefinite
A5 <- new("dsCMatrix",
Dim = c(7L, 7L),
p = c(0:1, 3L, 6:7, 10:11, 15L),
i = c(0L, 0:1, 0:3, 2:5, 3:6),
x = c(1, 6, 38, 10, 60, 103, -4, 6, -32, -247, -2, -16, -128, -2, -67))
(ev <- eigen(A5, only.values = TRUE)$values)
#> [1] 1.398039e+02 3.058801e+00 2.680774e-01 -8.437545e-03 -1.396242e-01
#> [6] -3.356906e+00 -3.176258e+02
(t.ev <- table(factor(sign(ev), -1:1))) # the matrix "inertia"
#>
#> -1 0 1
#> 4 0 3
ch.A5 <- Cholesky(A5)
isLDL(ch.A5)
#> [1] TRUE
(d.A5 <- diag(ch.A5)) # diag(D) is partly negative
#> [1] -2 -4 -1 9 1 2 -1
## Sylvester's law of inertia holds here, but not in general
## in finite precision arithmetic
stopifnot(identical(table(factor(sign(d.A5), -1:1)), t.ev))
try(expand1(ch.A5, "L")) # unable to compute L = L1 sqrt(D)
#> Error in expand1(ch.A5, "L") : D[i,i] is negative, i=1
try(expand2(ch.A5, LDL = FALSE)) # ditto
#> Error in .local(x, ...) : D[i,i] is negative, i=1
try(chol(A5, pivot = TRUE)) # ditto
#> Warning: CHOLMOD warning 'not positive definite' at file '../Cholesky/t_cholmod_rowfac.c', line 430
#> Error in h(simpleError(msg, call)) :
#> error in evaluating the argument 'x' in selecting a method for function 't': leading principal minor of order 1 is not positive
## The default expansion is "square root free" and still works here
str(e.ch.A5 <- expand2(ch.A5, LDL = TRUE), max.level = 2L)
#> List of 5
#> $ P1.:Formal class 'pMatrix' [package "Matrix"] with 5 slots
#> $ L1 :Formal class 'dtCMatrix' [package "Matrix"] with 7 slots
#> $ D :Formal class 'ddiMatrix' [package "Matrix"] with 4 slots
#> $ L1.:Formal class 'dtCMatrix' [package "Matrix"] with 7 slots
#> $ P1 :Formal class 'pMatrix' [package "Matrix"] with 5 slots
stopifnot(all.equal(A5, as(Reduce(`%*%`, e.ch.A5), "symmetricMatrix")))
## Version of the SuiteSparse library, which includes CHOLMOD
Mv <- Matrix.Version()
Mv[["SuiteSparse"]]
#> NULL