92 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			R
		
	
	
	
	
	
			
		
		
	
	
			92 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			R
		
	
	
	
	
	
#' 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 \dontrun{
 | 
						|
#' # 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))
 | 
						|
#' }
 | 
						|
#'
 | 
						|
#' @export
 | 
						|
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)))
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |