#' Generalized matrix power function for symmetric matrices. #' #' Using the SVD of the matrix \eqn{A = U D V'} where \eqn{D} is the #' diagonal matrix with the singular values of \eqn{A}, the powers are then #' computed as \deqn{A^p = U D^p V'} using the symmetrie of \eqn{A}. #' #' @details #' It is assumed that the argument \code{A} is symmeric and it is not checked #' for symmerie, the result will just be wrong. The actual formula for negative #' powers is \deqn{A^-p = V D^-p U'}. For symmetric matrices \eqn{U = V} which #' gives the formula used by this function. #' The reason is for speed, using the symmetrie propertie as described avoids #' two transpositions in the algorithm (one in \code{svd} using \code{La.svd}). #' #' @param A Matrix. #' @param pow numeric power. #' @param tol relative tolerance to detect zero singular values as well as the #' \code{qr} factorization tolerance. #' #' @return a matrix. #' #' @seealso \code{\link{solve}}, \code{\link{qr}}, \code{\link{svd}}. #' #' @examples #' # General full rank square matrices. #' A <- matrix(rnorm(121), 11, 11) #' all.equal(matpow(A, 1), A) #' all.equal(matpow(A, 0), diag(nrow(A))) #' all.equal(matpow(A, -1), solve(A)) #' #' # Roots of full rank symmetric matrices. #' A <- crossprod(A) #' B <- matpow(A, 0.5) #' all.equal(B %*% B, A) #' all.equal(matpow(A, -0.5), solve(B)) #' C <- matpow(A, -0.5) #' all.equal(C %*% C %*% A, diag(nrow(A))) #' all.equal(A %*% C %*% C, diag(nrow(A))) #' #' # General singular matrices. #' A <- matrix(rnorm(72), 12, 12) # rank(A) = 6 #' B <- matpow(A, -1) # B = A^+ #' # Check generalized inverse properties. #' all.equal(A %*% B %*% A, A) #' all.equal(B %*% A %*% B, B) #' all.equal(B %*% A, t(B %*% A)) #' all.equal(A %*% B, t(A %*% B)) #' #' # Roots of singular symmetric matrices. #' A <- crossprod(matrix(rnorm(72), 12, 12)) # rank(A) = 6 #' B <- matpow(A, -0.5) # B = (A^+)^1/2 #' # Check generalized inverse properties. #' all.equal(A %*% B %*% B %*% A, A) #' all.equal(B %*% B %*% A %*% B %*% B, B %*% B) #' all.equal(B %*% A, t(B %*% A)) #' all.equal(A %*% B, t(A %*% B)) #' matpow <- function(A, pow, tol = 1e-7) { if (nrow(A) != ncol(A)) { stop("Expected a square matix, but 'A' is ", nrow(A), " by ", ncol(A)) } # Case study for negative, zero or positive power. if (pow > 0) { if (pow == 1) { return(A) } # Perform SVD and return power as A^pow = U diag(d^pow) V'. svdA <- La.svd(A) return(svdA$u %*% ((svdA$d^pow) * svdA$vt)) } else if (pow == 0) { return(diag(nrow(A))) } else { # make QR decomposition. qrA <- qr(A, tol = tol) # Check rank of A. if (qrA$rank == nrow(A)) { # Full rank, calc inverse the classic way using A's QR decomposition return(matpow(solve(qrA), abs(pow), tol = tol)) } else { # For singular matrices use the SVD decomposition for the power svdA <- svd(A) # Get (numerically) positive singular values. positives <- svdA$d > tol * svdA$d[1] # Apply the negative power to positive singular values and augment # the rest with zero. d <- c(svdA$d[positives]^pow, rep(0, sum(!positives))) # The pseudo invers as A^pow = V diag(d^pow) U' for pow < 0. return(svdA$v %*% (d * t(svdA$u))) } } }