add: higher-order approx.kron, add: paper.tex - simulation plots and tensor normal MLE estimation
		
			
				
	
	
		
			176 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			R
		
	
	
	
	
	
			
		
		
	
	
			176 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			R
		
	
	
	
	
	
#' @examples
 | 
						|
#' nrows <- c(3, 2, 5)
 | 
						|
#' ncols <- c(2, 4, 8)
 | 
						|
#'
 | 
						|
#' A <- Reduce(kronecker, Map(matrix, Map(rnorm, nrows * ncols), nrows))
 | 
						|
#'
 | 
						|
#' all.equal(A, Reduce(kronecker, approx.kron(A, nrows, ncols)))
 | 
						|
#'
 | 
						|
#' @export
 | 
						|
approx.kron <- function(A, nrows, ncols) {
 | 
						|
 | 
						|
    # rearrange kronecker product `A` into outer product `R`
 | 
						|
    dim(A) <- c(rev(nrows), rev(ncols))
 | 
						|
    axis.perm <- as.vector(t(matrix(seq_along(dim(A)), ncol = 2L))[, rev(seq_along(nrows))])
 | 
						|
    R <- aperm(A, axis.perm, resize = FALSE)
 | 
						|
    dim(R) <- nrows * ncols
 | 
						|
 | 
						|
    # scaling factor for every product component
 | 
						|
    s <- sum(A^2)^(1 / length(dim(A)))
 | 
						|
 | 
						|
    # higher order SVD on `R` with one singular vector
 | 
						|
    Map(function(mode, nr, nc) {
 | 
						|
        s * `dim<-`(La.svd(mcrossprod(R, mode = mode), 1L, 0L)$u, c(nr, nc))
 | 
						|
    }, seq_along(nrows), nrows, ncols)
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#' Approximates Kronecker Product decomposition.
 | 
						|
#'
 | 
						|
#' Approximates the matrices `A` and `B` such that
 | 
						|
#'      C = A %x% B
 | 
						|
#' with `%x%` the kronecker product of the matrixes `A` and `B`
 | 
						|
#' of dimensions `dimA` and `dimB` respectively.
 | 
						|
#'
 | 
						|
#' @param C desired kronecker product result.
 | 
						|
#' @param dimA length 2 vector of dimensions of \code{A}.
 | 
						|
#' @param dimB length 2 vector of dimensions of \code{B}.
 | 
						|
#'
 | 
						|
#' @return list with attributes `A` and `B`.
 | 
						|
#'
 | 
						|
#' @examples
 | 
						|
#' A <- matrix(seq(14), 7, 2)
 | 
						|
#' B <- matrix(c(1, 0), 3, 4)
 | 
						|
#' C <- kronecker(A, B) # the same as 'C <- A %x% B'
 | 
						|
#' approx.kronecker(C, dim(A), dim(B))
 | 
						|
#'
 | 
						|
#' @seealso C.F. Van Loan / Journal of Computational and Applied Mathematics
 | 
						|
#'          123 (2000) 85-100 (pp. 93-95)
 | 
						|
#'
 | 
						|
#' @export
 | 
						|
approx.kronecker <- function(C, dimA, dimB = dim(C) / dimA) {
 | 
						|
 | 
						|
    dim(C) <- c(dimB[1L], dimA[1L], dimB[2L], dimA[2L])
 | 
						|
    R <- aperm(C, c(2L, 4L, 1L, 3L))
 | 
						|
    dim(R) <- c(prod(dimA), prod(dimB))
 | 
						|
 | 
						|
    if (requireNamespace("RSpectra", quietly = TRUE)) {
 | 
						|
        svdR <- RSpectra::svds(R, 1L)
 | 
						|
    } else {
 | 
						|
        svdR <- svd(R, 1L, 1L)
 | 
						|
    }
 | 
						|
 | 
						|
    list(
 | 
						|
        A = array(sqrt(svdR$d[1]) * svdR$u, dimA),
 | 
						|
        B = array(sqrt(svdR$d[1]) * svdR$v, dimB)
 | 
						|
    )
 | 
						|
}
 | 
						|
 | 
						|
#' Kronecker Product Decomposition.
 | 
						|
#'
 | 
						|
#' Computes the components summation components `A_i`, `B_i` of a sum of
 | 
						|
#' Kronecker products
 | 
						|
#'      C = sum_i A_i %x% B_i
 | 
						|
#' with the minimal estimated number of summands.
 | 
						|
#'
 | 
						|
#' @param C desired kronecker product result.
 | 
						|
#' @param dimA length 2 vector of dimensions of \code{A}.
 | 
						|
#' @param dimB length 2 vector of dimensions of \code{B}.
 | 
						|
#' @param tol tolerance of singular values of \code{C} to determin the number of
 | 
						|
#'  needed summands.
 | 
						|
#'
 | 
						|
#' @return list of lenghth with estimated number of summation components, each
 | 
						|
#'  entry consists of a list with named entries \code{"A"} and \code{"B"} of
 | 
						|
#'  dimensions \code{dimA} and \code{dimB}.
 | 
						|
#'
 | 
						|
#' @examples
 | 
						|
#' As <- replicate(3, matrix(rnorm(2 * 7), 2), simplify = FALSE)
 | 
						|
#' Bs <- replicate(3, matrix(rnorm(5 * 3), 5), simplify = FALSE)
 | 
						|
#' C <- Reduce(`+`, Map(kronecker, As, Bs))
 | 
						|
#'
 | 
						|
#' decomposition <- decompose.kronecker(C, c(2, 7))
 | 
						|
#'
 | 
						|
#' reconstruction <- Reduce(`+`, Map(function(summand) {
 | 
						|
#'     kronecker(summand[[1]], summand[[2]])
 | 
						|
#' }, decomposition), array(0, dim(C)))
 | 
						|
#'
 | 
						|
#' stopifnot(all.equal(C, reconstruction))
 | 
						|
#'
 | 
						|
#' @export
 | 
						|
decompose.kronecker <- function(C, dimA, dimB = dim(C) / dimA, tol = 1e-7) {
 | 
						|
    # convert the equivalent outer product
 | 
						|
    dim(C) <- c(dimB[1L], dimA[1L], dimB[2L], dimA[2L])
 | 
						|
    C <- aperm(C, c(2L, 4L, 1L, 3L), resize = FALSE)
 | 
						|
    dim(C) <- c(prod(dimA), prod(dimB))
 | 
						|
 | 
						|
    # Singular Valued Decomposition
 | 
						|
    svdC <- La.svd(C)
 | 
						|
 | 
						|
    # Sum of Kronecker Components
 | 
						|
    lapply(seq_len(sum(svdC$d > tol)), function(i) list(
 | 
						|
        A = matrix(svdC$d[i] * svdC$u[, i], dimA),
 | 
						|
        B = matrix(svdC$vt[i, ], dimB)
 | 
						|
    ))
 | 
						|
}
 | 
						|
 | 
						|
### Given that C is a Kronecker product this is a fast method but a bit
 | 
						|
### unreliable in full generality.
 | 
						|
# decompose.kronecker <- function(C, dimA, dimB = dim(C) / dimA) {
 | 
						|
#     dim(C) <- c(dimB[1L], dimA[1L], dimB[2L], dimA[2L])
 | 
						|
#     R <- aperm(C, c(2L, 4L, 1L, 3L))
 | 
						|
#     dim(R) <- c(prod(dimA), prod(dimB))
 | 
						|
#     max.index <- which.max(abs(R))
 | 
						|
#     row.index <- ((max.index - 1L) %% nrow(R)) + 1L
 | 
						|
#     col.index <- ((max.index - 1L) %/% nrow(R)) + 1L
 | 
						|
#     max.elem <- if (abs(R[max.index]) > .Machine$double.eps) R[max.index] else 1
 | 
						|
#     list(
 | 
						|
#         A = array(R[, col.index], dimA),
 | 
						|
#         B = array(R[row.index, ] / max.elem, dimB)
 | 
						|
#     )
 | 
						|
# }
 | 
						|
 | 
						|
 | 
						|
# kron <- function(A, B) {
 | 
						|
#     perm <- as.vector(t(matrix(seq_len(2 * length(dim(A))), ncol = 2)[, 2:1]))
 | 
						|
#     K <- aperm(outer(A, B), perm)
 | 
						|
#     dim(K) <- dim(A) * dim(B)
 | 
						|
#     K
 | 
						|
# }
 | 
						|
 | 
						|
# kronperm <- function(A) {
 | 
						|
#     # force A to have even number of dimensions
 | 
						|
#     dim(A) <- c(dim(A), rep(1L, length(dim(A)) %% 2L))
 | 
						|
#     # compute axis permutation
 | 
						|
#     perm <- as.vector(t(matrix(seq_along(dim(A)), ncol = 2)[, 2:1]))
 | 
						|
#     # permute elements of A
 | 
						|
#     K <- aperm(A, perm, resize = FALSE)
 | 
						|
#     # collapse/set dimensions
 | 
						|
#     dim(K) <- head(dim(A), length(dim(A)) / 2) * tail(dim(A), length(dim(A)) / 2)
 | 
						|
#     K
 | 
						|
# }
 | 
						|
 | 
						|
# p <- c(2, 3, 5)
 | 
						|
# q <- c(3, 4, 7)
 | 
						|
# A <- array(rnorm(prod(p)), p)
 | 
						|
# B <- array(rnorm(prod(q)), q)
 | 
						|
# all.equal(kronperm(outer(A, B)), kronecker(A, B))
 | 
						|
# all.equal(kron(A, B), kronecker(A, B))
 | 
						|
 | 
						|
 | 
						|
# dA <- c(2, 3, 5)
 | 
						|
# dB <- c(3, 4, 7)
 | 
						|
# A <- array(rnorm(prod(dA)), dA)
 | 
						|
# B <- array(rnorm(prod(dB)), dB)
 | 
						|
# X <- outer(A, B)
 | 
						|
 | 
						|
# r <- length(dim(X)) / 2
 | 
						|
# I <- t(do.call(expand.grid, Map(seq_len, head(dim(X), r) * tail(dim(X), r))))
 | 
						|
# K <- apply(rbind(
 | 
						|
#     (I - 1) %/% tail(dim(X), r) + 1,
 | 
						|
#     (I - 1) %% tail(dim(X), r) + 1
 | 
						|
# ), 2, function(i) X[sum(c(1, cumprod(head(dim(X), -1))) * (i - 1)) + 1])
 | 
						|
# dim(K) <- head(dim(X), r) * tail(dim(X), r)
 | 
						|
 | 
						|
# all.equal(kronecker(A, B), K)
 |