Cholesky-methods.Rd
\name{Cholesky-methods}
\title{Methods for Cholesky Factorization}
%
\docType{methods}
\keyword{algebra}
\keyword{array}
\keyword{methods}
\concept{Choleski} % alternate English spelling
%
\alias{Cholesky}
\alias{Cholesky-methods}
%
\alias{Cholesky,ddiMatrix-method}
\alias{Cholesky,diagonalMatrix-method}
\alias{Cholesky,dsCMatrix-method}
\alias{Cholesky,dsRMatrix-method}
\alias{Cholesky,dsTMatrix-method}
\alias{Cholesky,dspMatrix-method}
\alias{Cholesky,dsyMatrix-method}
\alias{Cholesky,generalMatrix-method}
\alias{Cholesky,matrix-method}
\alias{Cholesky,symmetricMatrix-method}
\alias{Cholesky,triangularMatrix-method}
%
\description{
Computes the pivoted Cholesky factorization of an
\eqn{n \times n}{n-by-n} real, symmetric matrix \eqn{A},
which has the general form
\deqn{P_1 A P_1' = L_1 D L_1' \overset{D_{jj} \ge 0}{=} L L'}{P1 * A * P1' = L1 * D * L1' [ = L * L' ]}
or (equivalently)
\deqn{A = P_1' L_1 D L_1' P_1 \overset{D_{jj} \ge 0}{=} P_1' L L' P_1}{A = P1' L1 * D * L1' * P1 [ = P1' * L * L' * P1 ]}
where
\eqn{P_1}{P1} is a permutation matrix,
\eqn{L_1}{L1} is a unit lower triangular matrix,
\eqn{D} is a diagonal matrix, and
\eqn{L = L_1 \sqrt{D}}{L = L1 * sqrt(D)}.
The second equalities hold only for positive semidefinite \eqn{A},
for which the diagonal entries of \eqn{D} are non-negative
and \eqn{\sqrt{D}}{sqrt(D)} is well-defined.
Methods for \code{\linkS4class{denseMatrix}} are built on
LAPACK routines \code{dpstrf}, \code{dpotrf}, and \code{dpptrf}.
The latter two do not permute rows or columns,
so that \eqn{P_1}{P1} is an identity matrix.
Methods for \code{\linkS4class{sparseMatrix}} are built on
CHOLMOD routines \code{cholmod_analyze} and \code{cholmod_factorize_p}.
}
\usage{
Cholesky(A, \dots)
\S4method{Cholesky}{dsyMatrix}(A, perm = TRUE, tol = -1, \dots)
\S4method{Cholesky}{dspMatrix}(A, \dots)
\S4method{Cholesky}{dsCMatrix}(A, perm = TRUE, LDL = !super, super = FALSE,
Imult = 0, \dots)
\S4method{Cholesky}{ddiMatrix}(A, \dots)
\S4method{Cholesky}{generalMatrix}(A, uplo = "U", \dots)
\S4method{Cholesky}{triangularMatrix}(A, uplo = "U", \dots)
\S4method{Cholesky}{matrix}(A, uplo = "U", \dots)
}
\arguments{
\item{A}{a \link[=is.finite]{finite}, symmetric matrix or
\code{\linkS4class{Matrix}} to be factorized. If \code{A}
is square but not symmetric, then it will be \emph{treated}
as symmetric; see \code{uplo}.
Methods for dense \code{A} require positive definiteness
when \code{perm = FALSE} and positive semidefiniteness
when \code{perm = TRUE}.
Methods for sparse \code{A} require positive definiteness
when \code{LDL = TRUE} and nonzero leading principal minors
(after pivoting) when \code{LDL = FALSE}.
Methods for sparse, \emph{diagonal} \code{A} are an exception,
requiring positive semidefiniteness unconditionally.}
\item{perm}{a logical indicating if the rows and columns
of \eqn{A} should be pivoted. Methods for sparse \code{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 \code{perm = FALSE}.}
\item{tol}{a \link[=is.finite]{finite} numeric tolerance,
used only if \code{perm = TRUE}.
The factorization algorithm stops if the pivot is less than
or equal to \code{tol}. Negative \code{tol} is equivalent
to \code{nrow(A) * .Machine$double.eps * max(diag(A))}.}
\item{LDL}{a logical indicating if the simplicial factorization
should be computed as
\eqn{P_1' L_1 D L_1' P_1}{P1' * L1 * D * L1' * P1},
such that the result stores the lower triangular entries
of \eqn{L_1-I+D}{L1-I+D}.
The alternative is \eqn{P_1' L L' P_1}{P1' * L * L' * P1},
such that the result stores the lower triangular entries
of \eqn{L = L_1 \sqrt{D}}{L = L1 * sqrt(D)}. This argument
is ignored if \code{super = TRUE} (or if \code{super = NA}
and the supernodal algorithm is chosen), as the supernodal
code does not yet support the \code{LDL = TRUE} variant.}
\item{super}{a logical indicating if the factorization should
use the supernodal algorithm. The alternative is the simplicial
algorithm. Setting \code{super = NA} leaves the choice to
a CHOLMOD-internal heuristic.}
\item{Imult}{a \link[=is.finite]{finite} number. The matrix
that is factorized is \code{A + Imult * diag(nrow(A))},
i.e., \code{A} plus \code{Imult} times the identity matrix.
This argument is useful for symmetric, indefinite \code{A},
as \code{Imult > max(rowSums(abs(A)) - diag(abs(A)))} ensures
that \code{A + Imult * diag(nrow(A))} is diagonally dominant.
(Symmetric, diagonally dominant matrices are positive definite.)}
\item{uplo}{a string, either \code{"U"} or \code{"L"},
indicating which triangle of \code{A} should be used
to compute the factorization. The default is \code{"U"},
even for lower triangular \code{A}, to be consistent with
\code{\link[base]{chol}} from \pkg{base}.}
\item{\dots}{further arguments passed to or from methods.}
}
\value{
An object representing the factorization, inheriting from
virtual class \code{\linkS4class{CholeskyFactorization}}.
For a traditional matrix \code{A}, the specific class is
\code{\linkS4class{Cholesky}}.
For \code{A} inheriting from
\code{\linkS4class{unpackedMatrix}},
\code{\linkS4class{packedMatrix}}, and
\code{\linkS4class{sparseMatrix}},
the specific class is
\code{\linkS4class{Cholesky}},
\code{\linkS4class{pCholesky}}, and
\code{\linkS4class{dCHMsimpl}} or \code{\linkS4class{dCHMsuper}},
respectively.
}
\details{
Note that the result of a call to \code{Cholesky} inherits
from \code{\linkS4class{CholeskyFactorization}} but not
\code{\linkS4class{Matrix}}. Users who just want a matrix
should consider using \code{\link{chol}}, whose methods are
simple wrappers around \code{Cholesky} returning just the
upper triangular Cholesky factor \eqn{L'},
typically as a \code{\linkS4class{triangularMatrix}}.
However, a more principled approach would be to construct
factors as needed from the \code{CholeskyFactorization} object,
e.g., with \code{\link{expand1}(x, "L")}, if \code{x} is the
object.
The behaviour of \code{Cholesky(A, perm = TRUE)} for dense \code{A}
is somewhat exceptional, in that it expects \emph{without} checking
that \code{A} is positive semidefinite. By construction, if \eqn{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 \code{tol}, it signals a
warning instead of an error and zeros the trailing submatrix in
order to guarantee that \eqn{P' L L' P}{P' * L * L' * P} is positive
semidefinite even if \eqn{A} is not. It follows that one way to
test for positive semidefiniteness of \eqn{A} in the event of a
warning is to analyze the error
\deqn{\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 (\dQuote{LAWN}) 161
for details.
}
\seealso{
Classes \code{\linkS4class{Cholesky}}, \code{\linkS4class{pCholesky}},
\code{\linkS4class{dCHMsimpl}} and \code{\linkS4class{dCHMsuper}}
and their methods.
Classes \code{\linkS4class{dpoMatrix}}, \code{\linkS4class{dppMatrix}},
and \code{\linkS4class{dsCMatrix}}.
Generic function \code{\link{chol}},
for obtaining the upper triangular Cholesky factor \eqn{L'} as a
matrix or \code{\linkS4class{Matrix}}.
Generic functions \code{\link{expand1}} and \code{\link{expand2}},
for constructing matrix factors from the result.
Generic functions \code{\link{BunchKaufman}}, \code{\link{Schur}},
\code{\link{lu}}, and \code{\link{qr}},
for computing other factorizations.
}
\references{
The LAPACK source code, including documentation; see
\url{https://netlib.org/lapack/double/dpstrf.f},
\url{https://netlib.org/lapack/double/dpotrf.f}, and
\url{https://netlib.org/lapack/double/dpptrf.f}.
The CHOLMOD source code; see
\url{https://github.com/DrTimothyAldenDavis/SuiteSparse},
notably the header file \file{CHOLMOD/Include/cholmod.h}
defining \code{cholmod_factor_struct}.
Lucas, C. (2004).
\emph{LAPACK-style codes for level 2 and 3 pivoted Cholesky factorizations}.
LAPACK Working Note, Number 161.
\url{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.
\emph{ACM Transactions on Mathematical Software},
\emph{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.
\emph{ACM Transactions on Mathematical Software},
\emph{17}(4), 886-905.
\doi{10.1145/1024074.1024081}
Golub, G. H., & Van Loan, C. F. (2013).
\emph{Matrix computations} (4th ed.).
Johns Hopkins University Press.
\doi{10.56021/9781421407944}
}
\examples{
\dontshow{ % for R_DEFAULT_PACKAGES=NULL
library(stats, pos = "package:base", verbose = FALSE)
library(utils, pos = "package:base", verbose = FALSE)
}
showMethods("Cholesky", inherited = FALSE)
set.seed(0)
## ---- Dense ----------------------------------------------------------
## .... Positive definite ..............................................
n <- 6L
(A1 <- crossprod(Matrix(rnorm(n * n), n, n)))
(ch.A1.nopivot <- Cholesky(A1, perm = FALSE))
(ch.A1 <- Cholesky(A1))
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)
str(E.ch.A1 <- expand2(ch.A1, LDL = FALSE), max.level = 2L)
stopifnot(exprs = {
all.equal(as(A1, "matrix"), as(Reduce(`\%*\%`, e.ch.A1), "matrix"))
all.equal(as(A1, "matrix"), as(Reduce(`\%*\%`, E.ch.A1), "matrix"))
})
## .... Positive semidefinite but not positive definite ................
A2 <- A1
A2[1L, ] <- A2[, 1L] <- 0
A2
try(Cholesky(A2, perm = FALSE)) # fails as not positive definite
ch.A2 <- Cholesky(A2) # returns, with a warning and ...
A2.hat <- Reduce(`\%*\%`, expand2(ch.A2, LDL = FALSE))
norm(A2 - A2.hat, "2") / norm(A2, "2") # 7.670858e-17
## .... Not positive semidefinite ......................................
A3 <- A1
A3[1L, ] <- A3[, 1L] <- -1
A3
try(Cholesky(A3, perm = FALSE)) # fails as not positive definite
ch.A3 <- Cholesky(A3) # returns, with a warning and ...
A3.hat <- Reduce(`\%*\%`, expand2(ch.A3, LDL = FALSE))
norm(A3 - A3.hat, "2") / norm(A3, "2") # 1.781568
## Indeed, 'A3' is not positive semidefinite, but 'A3.hat' _is_
ch.A3.hat <- Cholesky(A3.hat)
A3.hat.hat <- Reduce(`\%*\%`, expand2(ch.A3.hat, LDL = FALSE))
norm(A3.hat - A3.hat.hat, "2") / norm(A3.hat, "2") # 1.777944e-16
## ---- 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 )))
ch.A4
s <- simplify2array
rapply2 <- function(object, f, ...) rapply(object, f, , , how = "list", ...)
s(rapply2(ch.A4, isLDL))
s(m.ch.A4 <- rapply2(ch.A4, expand1, "L")) # giving L = L1 sqrt(D)
## By design, the pivoted and simplicial factorizations
## are more sparse than the unpivoted and supernodal ones ...
s(rapply2(m.ch.A4, object.size))
## 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)
simpl1 <- ch.A4[[c("pivoted", "simpl1")]]
stopifnot(exprs = {
length(simpl1@perm) == ncol(A4)
isPerm(simpl1@perm, 0L)
is.unsorted(simpl1@perm) # typically not the identity permutation
})
## 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)
stopifnot(min(diag(simpl1)) >= 0)
str(e.ch.A4 <- expand2(simpl1, LDL = TRUE), max.level = 2L) # default
str(E.ch.A4 <- expand2(simpl1, LDL = FALSE), max.level = 2L)
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"))
})
## 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"]])
})
## 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")
stopifnot(all.equal(chol(A4, pivot = TRUE), E.ch.A4[["L."]]))
## 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)
(t.ev <- table(factor(sign(ev), -1:1))) # the matrix "inertia"
ch.A5 <- Cholesky(A5)
isLDL(ch.A5)
(d.A5 <- diag(ch.A5)) # diag(D) is partly negative
## 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)
try(expand2(ch.A5, LDL = FALSE)) # ditto
try(chol(A5, pivot = TRUE)) # ditto
## The default expansion is "square root free" and still works here
str(e.ch.A5 <- expand2(ch.A5, LDL = TRUE), max.level = 2L)
stopifnot(all.equal(A5, as(Reduce(`\%*\%`, e.ch.A5), "symmetricMatrix")))
## Version of the SuiteSparse library, which includes CHOLMOD
Mv <- Matrix.Version()
Mv[["suitesparse"]]
}