tests/testthat/mniw-testfunctions.R

########################

# R functions for the mniw package

############################

# matrix of iid N(0,1)
rMnorm <- function(p, q) {
  if(missing(q)) q <- p
  matrix(rnorm(p*q), p, q)
}

# log-determinant
ldet <- function(X) {
  determinant(X, log = TRUE)$modulus[1]
}
ldetV <- function(V) {
  solveV(V, x = rep(1, nrow(V)), ldV = TRUE)$ldV
}

# log multi-gamma function
lmgamma <- function(x, p) p*(p-1)/4 * log(pi) + sum(lgamma(x + (1-1:p)/2))

# density of wishart and inverse wishart
dwishR <- function(X, Psi, nu, inverse = FALSE, log = FALSE) {
  if(length(X) == 1) {
    X <- as.matrix(X)
    Psi <- as.matrix(Psi)
  }
  d <- nrow(X)
  if(!inverse) {
    ans <- (nu-d-1) * ldetV(X)
    ans <- ans - nu * ldetV(Psi)
    ans <- ans - sum(diag(solveV(Psi, X)))
  } else {
    ans <- -(nu+d+1) * ldetV(X)
    ans <- ans + nu * ldetV(Psi)
    ans <- ans - sum(diag(solveV(X, Psi)))
  }
  ans <- ans - nu*d * log(2)
  ans <- 0.5 * ans - lmgamma(nu/2, d)
  if(!log) ans <- exp(ans)
  ans
}

# simulation of wishart/inverse wishart
rwishR <- function(Psi, nu, inverse = FALSE) {
  q <- nrow(Psi)
  XL <- matrix(0, q,q)
  # populate normals
  for(ii in 1:q) {
    XL[ii,ii] <- sqrt(rchisq(1, df = nu-ii+1))
    if(ii > 1) {
      for(jj in 1:(ii-1)) {
        XL[ii,jj] <- rnorm(1)
      }
    }
  }
  if(!inverse) {
    PsiL <- t(chol(Psi))
    XL <- PsiL %*% XL
    X <- XL %*% t(XL)
  } else {
    PsiL <- solve(t(chol(solveV(Psi))))
    XL <- solve(PsiL, XL)
    X <- solve(XL %*% t(XL))
  }
  X
}

# density of matrix normal
dMNormR <- function(X, Lambda, SigmaU, SigmaV, log = FALSE) {
  X <- as.matrix(X)
  Lambda <- as.matrix(Lambda)
  SigmaU <- as.matrix(SigmaU)
  SigmaV <- as.matrix(SigmaV)
  n <- nrow(X)
  p <- ncol(X)
  ans <- n*p*log(2*pi) + sum(diag(solveV(SigmaV, t(X-Lambda)) %*% solveV(SigmaU, X-Lambda)))
  ans <- ans + n*ldetV(SigmaV) + p*ldetV(SigmaU)
  ans <- -ans/2
  if(!log) ans <- exp(ans)
  ans
}

# simulation of matrix normal
rMNormR <- function(Lambda, SigmaU, SigmaV) {
  p <- nrow(Lambda)
  q <- ncol(Lambda)
  Z <- t(rMnorm(q, p))
  X <- Z %*% chol(SigmaV)
  X <- t(chol(SigmaU)) %*% X
  X + Lambda
}

# density of matrix t
dMTR <- function(X, Lambda, SigmaU, SigmaV, nu, log = FALSE) {
  X <- as.matrix(X)
  Lambda <- as.matrix(Lambda)
  SigmaU <- as.matrix(SigmaU)
  SigmaV <- as.matrix(SigmaV)
  n <- nrow(X)
  p <- ncol(X)
  Z <- X - Lambda
  xi <- nu+n+p-1
  ans <- diag(n) + solveV(SigmaU, Z) %*% solveV(SigmaV, t(Z))
  ## .5 * ldet(ans)
  ans <- xi * ldet(ans) + n * ldetV(SigmaV) + p * ldetV(SigmaU) + n*p * log(pi)
  ans <- -.5 * ans + lmgamma(.5*xi, p) - lmgamma(.5*(xi-n), p)
  ## ans <- ans + n*p * log(pi) + lmgamma(.5*(xi-n), p) - lmgamma(.5*xi, p)
  ## ans <- -2 * ans
  if(!log) ans <- exp(ans)
  ans
}

# simulation of matrix T
rMTR <- function(Lambda, SigmaU, SigmaV, nu, prec = FALSE) {
  p <- nrow(Lambda)
  q <- ncol(Lambda)
  V <- rwishR(Psi = SigmaV, nu = nu, inverse = TRUE)
  CL <- t(chol(solveV(V)))
  Z <- t(rMnorm(q, p))
  Z <- Z %*% solve(CL)
  if(!prec) {
    X <- t(chol(SigmaU)) %*% Z + Lambda
  } else {
    X <- solve(chol(SigmaU)) %*% Z + Lambda
  }
  X
}

# density of mniw distribution
dmniwR <- function(X, V, Lambda, Sigma, Psi, nu, log = FALSE) {
  ans <- diwish(V = V, Psi = Psi, nu = nu, log = TRUE)
  if(!all(Sigma == 0)) ans <- ans + dMnorm(X, Lambda, Sigma, V, log = TRUE)
  if(!log) ans <- exp(ans)
  ans
}

# simulation of mniw.
# row variance can be on precision scale by seting prec = TRUE.
rmniwR <- function(Lambda, Sigma, Psi, nu, prec = FALSE) {
  p <- nrow(Lambda)
  q <- ncol(Lambda)
  V <- rwishR(Psi = Psi, nu = nu, inverse = TRUE)
  CL <- t(chol(solveV(V)))
  Z <- t(rMnorm(q, p))
  Z <- Z %*% solve(CL)
  if(!prec) {
    X <- t(chol(Sigma)) %*% Z + Lambda
  } else {
    X <- solve(chol(Sigma)) %*% Z + Lambda
  }
  list(X = X, V = V)
  #PsiL <- solve(t(chol(solveV(Psi))))
  #q <- nrow(PsiL)
  #for(ii in 1:q) {
  #  XL[ii,ii] <- sqrt(rchisq(1, df = nu-ii+1))
  #  if(ii > 1) {
  #    for(jj in 1:(ii-1)) {
  #      XL[ii,jj] <- rnorm(1)
  #    }
  #  }
  #}
  #XL <- solve(XiL, XL)
}

# random effects normal simulation in R
rmNormRER <- function(y, V, lambda, A) {
  C <- solveV(V)
  Q <- solveV(A)
  G <- C + Q
  GU <- chol(G)
  z <- rnorm(length(y))
  mu <- backsolve(r = GU, x = Q %*% (lambda - y), transpose = TRUE)
  drop(backsolve(r = GU, x = z + mu) + y)
}

# multivariate normal simulation in R
rmNormR <- function(mu, V) {
  z <- rnorm(length(mu))
  c(t(chol(V)) %*% z) + mu
}

# multivariate normal density in R
dmNormR <- function(x, mu, V, log = FALSE) {
  p <- length(x)
  ans <- p*log(2*pi) + ldetV(V)
  ans <- ans + crossprod((x-mu), solveV(V, x-mu))[1]
  ans <- -ans/2
  if(!log) ans <- exp(ans)
  ans
}

# unlist to matrices/vectors/scalars to array/matrix/vector.
# dropLast indicates that the last dimension = 1 should be dropped.
unlistMV <- function(X, vtype, dropLast) {
  vtype <- match.arg(vtype, c("matrix", "vector", "scalar"))
  if(vtype == "matrix") {
    PQ <- dim(X[[1]])
    X <- array(unlist(X), dim = c(PQ, length(X)))
    if(dropLast && dim(X)[3] == 1) X <- array(X, dim = PQ)
  } else if(vtype == "vector") {
    X <- do.call(rbind, X)
  } else X <- unlist(X)
  X
}

# create n random p x q matrices.
# If only one of p or q provided the random matrix is a variance.
# rtype is one of "none", "single", "multi".
# if "none", defaults to a matrix of zeros or identity.
# if "single", keep only first entry
# vtype is one of "matrix", "vector", "scalar"
rMM <- function(n, p, q, rtype, vtype) {
  rtype <- match.arg(rtype, c("none", "single", "multi"))
  vtype <- match.arg(vtype, c("matrix", "vector", "scalar"))
  varX <- missing(p) || missing(q) # is X a variance matrix?
  if(rtype != "multi") n <- 1
  if(missing(p)) p <- q
  if(missing(q)) q <- p
  if(vtype == "scalar") {
    X <- lapply(runif(n, 2*q, 3*q),c)
  } else {
    if(rtype == "none") {
      if(!varX) {
        X <- replicate(n, matrix(0,p,q), simplify = FALSE)
      } else {
        X <- replicate(n, diag(p), simplify = FALSE)
      }
    } else {
      if(!varX) {
        X <- replicate(n, rMnorm(p,q), simplify = FALSE)
      } else {
        X <- replicate(n, crossprod(rMnorm(p)) + 5 * diag(p), simplify = FALSE)
      }
    }
    if(vtype == "vector") X <- lapply(X, drop)
  }
  ## if(type != "multi") X <- X[1]
  X
}

# lower of maximum abs/rel error
arDiff <- function(x0, xhat) {
  mx <- abs(xhat - x0)
  min(max(mx), max(mx/abs(x0)))
}

# solve for variance matrices
solveV <- function(V, x, ldV = FALSE) {
  C <- chol(V)
  if (missing(x)) x <- diag(nrow(V))
  ans <- backsolve(r = C, x = backsolve(r = C, x = x, transpose = TRUE))
  if (ldV) {
    ldV <- 2 * sum(log(diag(C)))
    ans <- list(y = ans, ldV = ldV)
  }
  ans
}

# check R vs cpp code
expect_Rcpp_equal <- function(fun, icase, mx, ...) {
  Rcpp_comp <- function(fun, icase) mx
  eval(bquote({
    expect_equal(object = Rcpp_comp(.(fun), .(icase)),
                 expected = .(rep(0, length(mx))), ...)
  }))
}

# automatically deduce R-style and cpp-style function arguments from
# case.par specification
# p,q, and drop are common to all the remaining arguments,
# the names of which can be deduced.
# R arguments consist of lists of single arguments, repeated if necessary.
# cpp arguments have appropriate dimensions, and are dropped if necessary.
# args is a named list with named elements p, q, and
# type %in% c("none", "single", "multi")
# drop same as in case.par
get_args <- function(n, args, drop) {
  anames <- names(args)
  # simulate argument values
  sargs <- sapply(args, function(vargs) {
    do.call(rMM, c(list(n = n), vargs))
  }, simplify = FALSE)
  # R format
  rargs <- sapply(anames, function(nm) {
    if(args[[nm]]$rtype != "multi") {
      rep(sargs[[nm]], n)
    } else sargs[[nm]]
  }, simplify = FALSE)
  # cpp format
  cargs <- sapply(anames, function(nm) {
    unlistMV(sargs[[nm]],
             vtype = args[[nm]]$vtype, drop = drop)
  }, simplify = FALSE)
  cargs <- cargs[sapply(args, function(aa) aa$rtype != "none")]
  list(R = rargs, cpp = cargs)
}

# determine if all arguments are single
all_single <- function(cp) {
  nignore <- c("drop", "inverse", "prec", "p", "q", "r")
  single <- sapply(cp[!names(cp) %in% nignore],
                   function(x) {
                     if(!x %in% c("none", "single", "multi")) {
                       stop("wrong input to all_single.")
                     }
                     x != "multi"
                   })
  all(single)
}

Try the mniw package in your browser

Any scripts or data that you put into this service are public.

mniw documentation built on Sept. 23, 2024, 1:09 a.m.