R/structuredSparseMatrices.R

## ----------------------------------------------------------------------
## Structured sparse matrices
##
## used by: formulaParsing.R, randomEffectsStructures.R, outputAndInference.R
## ----------------------------------------------------------------------


## TOC:  grep -A 1 "## \-\-\-" * | grep "\-##" | grep strucMatrix

##' Structured sparse matrices
##'
##' A sparse matrix class for matrices whose elements come from a set
##' of a relatively small number of values or computable from a
##' relatively small parameter vector.
##'
##' These structured matrix objects are useful for updating matrices
##' at each iteration of a general nonlinear optimizer.  This
##' \code{\link{strucMatrix-class}} is designed to work well with the
##' \code{\link{Matrix}} package, in that they can be quickly coerced
##' to \code{\link{sparseMatrix}} objects.  The
##' \code{\link{mkSparseTrans}} function can be used to automatically
##' generate a function for updating the values of the
##' \code{sparseMatrix} object from a relatively small number of
##' parameters or repeated values.
##'
##' Such matrices are best constructed by \code{\link{bind}}ing,
##' \code{\link{kron}}ing, and \code{\link{kr}}ing together a series
##' of simpler \code{strucMatrix} matrices, which can be constructed
##' with the \code{strucMatrix} function or a function for constructing
##' special \code{strucMatrix} objects
##' (e.g. \code{\link{strucMatrixDiag}}).
##'
##' Note that the ordering of the indices supplied to \code{rowInds},
##' \code{colInds}, and \code{valInds} may not appear in the same
##' order in the output.  This is because \code{sortFun} is used to
##' process the order, which by default is \code{\link{standardSort}}.
##' To suppress this behaviour a different \code{sortFun} may be
##' supplied, but this is generally not recommended because certain
##' functions assume certain orderings (e.g. \code{\link{kr}}).
##'
##' @param rowInds 1-based row indices (converted to 0-based in
##' output)
##' @param colInds 1-based column indices (converted to 0-based in
##' output)
##' @param valInds indices for the nonzero values
##' @param vals nonzero values
##' @param trans function for transforming some parameters into
##' \code{vals}.  the \code{\link{environment}} of this function
##' should contain a vector of these parameters (called \code{init}),
##' which could be taken as the arguments to \code{trans}.  these
##' initial values can be obtained by \code{\link{getInit}} and set by
##' \code{\link{setInit}}.  an \code{\link{update.strucMatrix}} method
##' will update these parameters.
##' @param Dim matrix dimensions
##' @param sortFun a function with which to sort the indices of the
##' resulting matrix.  please note that by default the
##' \code{standardSort} function is used, which provides a convenient
##' ordering for computing Khatri-Rao products (with
##' \code{\link{kr}}).  see details.
##' @return A member of the \code{\link{strucMatrix-class}}.
##' @rdname strucMatrix
##' @export
##' @examples
##' strucMatrix(1:4, 4:1, rep(1:2, 2), c(-pi, pi))
strucMatrix <- function(rowInds, colInds, valInds, vals, trans, Dim,
                      sortFun = standardSort) {
    maxValInds <- ifelse(length(valInds), max(valInds), 0L)
    if(missing(Dim)) Dim <- c(max(rowInds), max(colInds))
    if(!all(seq_len(maxValInds) %in% valInds)) stop("max(valInds) unnecessarily large")
    if(length(vals)    != maxValInds)          stop("mismatch between vals and valInds")
    if(length(rowInds) != length(colInds))     stop("row and column index mismatch")
    if(length(rowInds) != length(valInds))     stop("row and value index mismatch")
    if(missing(trans)) trans <- mkIdentityTrans(vals)
    sortFun(structure(list(rowInds = as.integer(rowInds - 1),
                           colInds = as.integer(colInds - 1),
                           valInds = as.integer(valInds),
                           vals = vals,
                           trans = trans),
                      class = "strucMatrix",
                      Dim = Dim))
}

setOldClass("strucMatrix")



## ----------------------------------------------------------------------
## strucMatrix-class
## ----------------------------------------------------------------------

##' A structured sparse matrix class
##'
##' An \code{S3} class for structured sparse matrices, which are lists
##' with a \code{Dim} attribute and with the following elements:
##' \describe{
##'   \item{\code{rowInds}}{0-based row indices}
##'   \item{\code{colInds}}{0-based column indices}
##'   \item{\code{valInds}}{1-based indices for associating each \code{rowInds} and
##'                         \code{colInds} pair with the elements in \code{vals}}
##'   \item{\code{trans}}{A function for transforming a parameter vector
##'                       into \code{vals}.  This function is used by
##'                       \code{\link{update.strucMatrix}}}
##' }
##' Objects in this class can be constructed with
##' \code{\link{strucMatrix}} (and, for example,
##' \code{\link{strucMatrixDiag}}), and has several methods including
##' the following.
##'
##' The \code{...} arguments for the \code{update.strucMatrix} method
##' can be used to specify special parameter arguments, if
##' \code{object$mkNewPars} exits (which is often the case for special
##' matrices such as \code{\link{strucMatrixTri}}.  This is a more
##' explicit approach and can therefore be more convenient than having
##' to figure out what order the parameters should appear in
##' \code{newPars}.  For example, \code{update(., diagVals = c(1, 1),
##' offDiagVals = -0.2)} is more explicit than \code{update(., c(1,
##' -0.2, 1))}.
##'
##' @name strucMatrix-class
##' @rdname strucMatrix-class
##' @exportClass strucMatrix
##' @examples
##' (X <- strucMatrix(rowInds = 1:6,
##'                 colInds = rep(1:2, 3),
##'                 valInds = rep(1:2, each = 3),
##'                 vals    = c(-pi, pi)))
##' (Y <- update(X, c(0.2, 0.8)))
##' as.matrix(X, sparse = TRUE)
##' as.matrix(Y, sparse = TRUE)
##' image(kron(t(X), Y))
##' image(bind(X, Y, type = "diag"))
setOldClass("strucMatrix")

##' @param x \code{strucMatrix} object
##' @param n how many indices and repeated values to print?
##' @param ... passed to subsequent functions
##' @rdname strucMatrix-class
##' @method print strucMatrix
##' @export
print.strucMatrix <- function(x, n = 6L, ...) {

    init <- getInit(x)
    parsEqualRepVals <- identical(x$vals, init)
    headVals <- head(x$vals, n = n)
    inds <- as.data.frame(x)
    headInds <- head(inds, n = n)
    reportTruncVals <- length(x$vals) > n
    reportTruncInds <- nrow(inds) > n
    reportTruncPars <- length(init) > n
    reportNumUnique <- any(duplicated(x$valInds))

    cat("Structured sparse matrix\n")
    cat("------------------------\n")
    cat("dimensions:\n")
    cat(nrow(x), "rows and", ncol(x), "columns\n")
    cat(nrow(inds), "structural nonzero values\n")
    if(reportNumUnique) cat(length(x$vals), "structural unique values\n")
    if(!parsEqualRepVals) cat(length(init), "parameters\n")
    cat("\n")
    if(reportTruncVals) cat("first", n, "")
    cat("structural unique values:\n")
    print(headVals)
    if(!parsEqualRepVals) {
        cat("\n")
        if(reportTruncPars) cat("first", n, "")
        cat("initial parameters:\n")
        print(head(getInit(x), n))
    }
    cat("\n")
    if(reportTruncInds) cat("first", n, "")
    cat ("row, column, and value indices (with associated values):\n")
    print(headInds)
    if(length(class(x)) > 1L) {
        cat("\nspecial structured sparse matrix, inheriting from:\n")
        cat(class(x), "\n")
    }
    cat("\n")
}

##' @param object \code{strucMatrix} object
##' @param newPars optional new parameter values
##' @param newTrans optional new transformation function
##' @importFrom stats update
##' @rdname strucMatrix-class
##' @method update strucMatrix
##' @export
update.strucMatrix <- function(object, newPars, newTrans, ...) {
    if(!missing(newTrans)) {
        stop("not working yet ... FIXME ... see mkTrans")
        object$trans <- newTrans
        if(!missing(newPars)) environment(newTrans)$init <- newPars
    }
    l... <- list(...)
    if(length(l...) > 0L) {
        mkNewPars <- object$mkNewPars
        if(is.null(mkNewPars)) stop("special arguments given, ",
                                    "but no function available for ",
                                    "constructing a parameter vector")
        newPars <- do.call(mkNewPars, l...)
    }
    if(missing(newPars)) {
        if(length(gi <- getInit(object)) == 0L) {
            return(object)
        }
        newPars <- gi
    }
    if(length(newPars) != (np <- length(getInit(object))))
        stop("newPars must have the same length as getInit(object), ",
             "which in this case is ", np)
    object$vals <- object$trans(newPars)
    return(object)
}

.removeLatticeWhitespace <- function() {
    lh <- lattice::trellis.par.get("layout.heights")
    lw <- lattice::trellis.par.get("layout.widths" )
    lh[grep("padding", names(lh))] <-
    lw[grep("padding", names(lw))] <- 0
    lh$axis.bottom <- 0 ## huh? alright i guess? whatever...
    ac <- lattice::trellis.par.get("axis.components")
    for(i in 1:4) ac[[i]][c("pad1", "pad2")] <- 0
    lattice::trellis.par.set(layout.heights  = lh,
                             layout.widths   = lw,
                             axis.components = ac)
}

.xscaleComponents <- function(...) {
    ans <- lattice::xscale.components.default(...)
    ans$bottom$labels$labels <- rep("", length(ans$bottom$labels$labels))
    ans$bottom$ticks$tck <- 0
    #ans$bottom$ticks$at <- c()
    #ans$bottom$labels$check.overlap <- FALSE
    #ans$top <- FALSE
    ans
}

.yscaleComponents <- function(...) {
    ans <- lattice::yscale.components.default(...)
    ans$left$labels$labels <- rep("", length(ans$left$labels$labels))
    ans$left$ticks$tck <- 0
    #ans$left$ticks$at <- c()
    #ans$left$labels$check.overlap <- FALSE
    #ans$right <- FALSE
    ans
}

##' @param plain should a completely plain plot be used? (try and see)
##' @importFrom Matrix image
##' @rdname strucMatrix-class
##' @method image strucMatrix
##' @export
image.strucMatrix <- function(x, plain = FALSE, ...) {
    blank <- length(x$vals) == 0
    x <- as.matrix(x, sparse = TRUE)
    if(plain) {
        if(blank) { # hack to give something sensible for
                    # image(strucMatrixBlank(...))
            grid::pushViewport(grid::viewport())
            bx <- grid::unit(0.99, "npc")
            grid::grid.rect(width = bx, height = bx)
        } else {
            .removeLatticeWhitespace()
            image(x, sub = "", xlab = "", ylab = "", colorkey = FALSE,
                  xscale.components = .xscaleComponents,
                  yscale.components = .yscaleComponents)
        }
    } else {
        if(blank) {
            stop("can't plot blank matrix in this way, ",
                 "try image(..., plain = TRUE)")
        } else {
        ## not sure why i can't pass ... directly, but apparently this
        ## doesn't work
            do.call(image, c(list(x), list(...)))
        }
    }
}

##' @param y not used
##' @rdname strucMatrix-class
##' @method plot strucMatrix
##' @export
plot.strucMatrix <- function(x, y, plain = FALSE, ...) image(x, plain = plain, ...)

##' @rdname strucMatrix-class
##' @method t strucMatrix
##' @export
t.strucMatrix <- function(x) {
    tx <- x
    tx$rowInds <- x$colInds
    tx$colInds <- x$rowInds
    attr(tx, "Dim") <- rev(dim(x))
    return(standardSort(tx))
}



##' @rdname strucMatrix-class
##' @export
setMethod("diag", signature(x = "strucMatrix"), {
    function(x) {
        with(x, {
            diagInds <- which(rowInds == colInds)
            rowDiagInds <- rowInds[diagInds] + 1L
            fullInds <- match(seq_len(min(dim(x))), rowDiagInds)
            ans <- vals[valInds[diagInds]][fullInds]
            ifelse(is.na(ans), 0, ans)
        })
    }
})

##' @param value replacement value for diagonal
##' @rdname strucMatrix-class
##' @export
setMethod("diag<-", signature(x = "strucMatrix"), {
    function(x, value) {
        diagInds <- with(x, {
            diagInds <- unique(valInds[  rowInds == colInds])
            offDiagInds <- unique(valInds[!(rowInds == colInds)])
            if(length(diagInds) != length(value)) { # length mismatch
                stop("too many replacement values for the number of repeated diagonal values")
            }
            if(any(diagInds %in% offDiagInds)) { # complex pattern
                stop(" the repetition pattern is too complicated to replace the diagonal.\n",
                     "some repeated values are both on the diagonal and off of it")
            }
            diagInds
        })
        x$vals[diagInds] <- value
        return(x)
    }
})


##' @rdname strucMatrix-class
##' @method dim strucMatrix
##' @export
dim.strucMatrix <- function(x) attr(x, "Dim")


##' Sort the indices of a structured sparse matrix
##'
##' The ordering of the indices is arbitrary, but some orders are more
##' computationally efficient than others in certain circumstances.
##' \code{standardSort} is defined as \code{sort(sort(., type =
##' "row"), type = "col")}, which is often the 'best' order.  It is
##' used throughout, and in particular is required for using the
##' \code{\link{kr}} function.
##'
##' @param x \code{\link{strucMatrix}} object
##' @param decreasing see \code{\link{sort}}
##' @param type sort by column, row, or value indices?
##' @param ... for consistency with generic
##' @rdname sort.strucMatrix
##' @method sort strucMatrix
##' @export
sort.strucMatrix <- function(x, decreasing = FALSE,
                           type = c("col", "row", "val"), ...) {
    inds <- switch(type[1],
                   col = x$colInds,
                   row = x$rowInds,
                   val = x$valInds)
    ord <- order(inds, decreasing = decreasing, ...)
    x$rowInds <- x$rowInds[ord]
    x$colInds <- x$colInds[ord]
    x$valInds <- x$valInds[ord]
    return(x)
}

##' @rdname sort.strucMatrix
##' @export
standardSort <- function(x) {
    sort(sort(x, type = "row"), type = "col")
}


## ----------------------------------------------------------------------
## Coercion -- as...
## ----------------------------------------------------------------------

##' Coerce to and from structured sparse matrices
##'
##' @param x an object
##' @param ... dots
##' @rdname as.strucMatrix
##' @export
##' @examples
##' set.seed(1)
##' m1 <- as.strucMatrix(matrix(rnorm(6), 2, 3))
##' m2 <- as.strucMatrix(matrix(rbinom(6, 1, 0.5), 2, 3))
##' m3 <- sparseMatrix(i = 1:10, j = rep(1:5, 2),
##'                    x = as.integer(rbinom(10, 1, 0.5)),
##'                    giveCsparse = FALSE)
##' as.strucMatrix(m1)
##' as.strucMatrix(m2)
##' as.strucMatrix(m3)
##' as(m1, "TsparseMatrix")
##' as(m2, "dgCMatrix")
##' as(m2, "sparseMatrix")
as.strucMatrix <- function(x, ...) {
    UseMethod("as.strucMatrix")
}

##' @rdname as.strucMatrix
##' @export
as.strucMatrix.strucMatrix <- function(x, ...) {
    class(x) <- "strucMatrix"
    return(x)
}

##' @rdname as.strucMatrix
##' @export
as.strucMatrix.dsparseMatrix <- function(x, ...) {
    x <- as(x, "TsparseMatrix")
                                        # try to find detectable
                                        # repeated structure.
    va <- sort(unique(x@x))
    vi <- match(x@x, va)

                                        # return value
    ans <- strucMatrix(rowInds = x@i + 1L,
                     colInds = x@j + 1L,
                     valInds = vi,
                     vals = va,
                     trans = mkIdentityTrans(va),
                     Dim = dim(x))
    return(sort(ans))
}

##' @rdname as.strucMatrix
##' @export
as.strucMatrix.matrix <- function(x, ...) {
    vecx <- as.numeric(x)
    va <- sort(unique(vecx))
    vi <- match(vecx, va)
    ii <- rep.int(1:nrow(x), ncol(x))
    jj <- rep.int(1:ncol(x), rep.int(nrow(x), ncol(x)))
    ans <- strucMatrix(rowInds = ii, colInds = jj,
                     valInds = vi, vals = va,
                     trans = mkIdentityTrans(va),
                     Dim = dim(x))
    return(sort(ans))
}

##' @rdname as.strucMatrix
##' @export
as.strucMatrix.factor <- function(x, ...) {
    as.strucMatrix(as(x, "sparseMatrix"))
}

##' @rdname as.strucMatrix
##' @export
as.strucMatrix.dist <- function(x, ...) {
    matSize <- attr(x, "Size")
    lowTriSize <- matSize - 1
    diagVals <- rep(0, matSize)
    offDiagInds <- triInds(rep(1:lowTriSize, 1:lowTriSize),
                           sequence(1:lowTriSize),
                           lowTriSize)
    strucMatrixSymm(diagVals, x[offDiagInds])
}

##' @rdname as.strucMatrix
##' @export
as.strucMatrix.dCHMsimpl <- function(x, ...) {
    matSize <- x@Dim[1]
    rowInds <- rep(1:matSize, 1:matSize)
    colInds <- sequence(1:matSize)
    valInds <- triInds(rowInds, colInds, matSize)
    vals <- as(x, "sparseMatrix")@x
    strucMatrix(rowInds, colInds, valInds, vals)
}


##' @rdname as.strucMatrix
##' @method as.data.frame strucMatrix
##' @export
as.data.frame.strucMatrix <- function(x, ...) {
    with(x, {
        data.frame(rowInds = rowInds,
                   colInds = colInds,
                   valInds = valInds,
                   vals = vals[valInds])
    })
}


##' @importFrom Matrix sparseMatrix
##' @rdname as.strucMatrix
##' @param sparse return \code{sparseMatrix}?
##' @method as.matrix strucMatrix
##' @export
as.matrix.strucMatrix <- function(x, sparse = FALSE, ...) {
    ans <- with(x, {
        sparseMatrix(i = rowInds + 1L,
                     j = colInds + 1L,
                     x = vals[valInds],
                     dims = dim(x), ...)
    })
    if(sparse) return(ans)
    return(as.matrix(ans))
}

##' @rdname as.strucMatrix
##' @method as.matrix strucMatrixChol
##' @export
as.matrix.strucMatrixChol <- function(x, sparse = FALSE, ...) {
    ans <- as.matrix.strucMatrix(x, sparse = sparse, ...)
    return(as(ans, "dtCMatrix"))
}


strucMatrix2gCsparse <- function(from) {
    ans <- new("dgCMatrix")
    ans@i <- as.integer(from$rowInds)
    ans@p <- as.integer(ind2point(from$colInds, ncol(from)))
    ans@x <- as.numeric(with(from, vals[valInds]))
    ans@Dim <- as.integer(dim(from))
    return(ans)
}

strucMatrix2gTsparse <- function(from) {
    ans <- new("dgTMatrix")
    ans@i <- as.integer(from$rowInds)
    ans@j <- as.integer(from$colInds)
    ans@x <- as.numeric(with(from, vals[valInds]))
    ans@Dim <- as.integer(dim(from))
    return(ans)
}

strucMatrix2tCsparse <- function(from) {
    as(as(from, "dgCMatrix"), "dtCMatrix")
}

strucMatrix2tTsparse <- function(from) {
    as(as(from, "dgTMatrix"), "dtTMatrix")
}

##' as("strucMatrix", "sparseMatrix")
##' @name strucMatrix2sparseMatrix
##' @rdname as.strucMatrix
##' @importClassesFrom Matrix sparseMatrix
##' @importFrom methods coerce
##' @importFrom methods as
setAs("strucMatrix",  "sparseMatrix", def = strucMatrix2gCsparse)

##' as("strucMatrix", "CsparseMatrix")
##' @name strucMatrix2gCsparseMatrix
##' @rdname as.strucMatrix
##' @importClassesFrom Matrix CsparseMatrix
setAs("strucMatrix", "CsparseMatrix", def = strucMatrix2gCsparse)

##' as("strucMatrix", "dgCMatrix")
##' @name strucMatrix2dgCMatrix
##' @rdname as.strucMatrix
##' @importClassesFrom Matrix dgCMatrix
setAs("strucMatrix",     "dgCMatrix", def = strucMatrix2gCsparse)

##' as("strucMatrix", "dtCMatrix")
##' @name strucMatrix2dtCMatrix
##' @rdname as.strucMatrix
##' @importClassesFrom Matrix dtCMatrix
setAs("strucMatrix",     "dtCMatrix", def = strucMatrix2tCsparse)

##' as("strucMatrix", "TsparseMatrix")
##' @name strucMatrix2gTsparseMatrix
##' @rdname as.strucMatrix
##' @importClassesFrom Matrix TsparseMatrix
setAs("strucMatrix", "TsparseMatrix", def = strucMatrix2gTsparse)

##' as("strucMatrix", "dgTMatrix")
##' @name strucMatrix2dgTMatrix
##' @rdname as.strucMatrix
##' @importClassesFrom Matrix dgTMatrix
setAs("strucMatrix", "dgTMatrix", def = strucMatrix2gTsparse)

##' as("strucMatrix", "dtTMatrix")
##' @name strucMatrix2dtTMatrix
##' @rdname as.strucMatrix
##' @importClassesFrom Matrix dtTMatrix
setAs("strucMatrix",     "dtTMatrix", def = strucMatrix2tTsparse)



##' Get repetition pattern of a structured sparse matrix
##'
##' @param object a \code{\link{strucMatrix}} object
##' @export
getRepPattern <- function(object) {
    object$vals <- seq_along(object$vals)
    return(as.matrix(object, sparse = TRUE))
}




## ----------------------------------------------------------------------
## Matrix operations -- kron (Kronecker product), kr (Khatri-Rao
## product)
## ----------------------------------------------------------------------

##' Kronecker and Khatri-Rao products for structured sparse matrices
##'
##' @param X,Y structured sparse matrices (\code{\link{strucMatrix-class}})
##' @param trans see argument \code{FUN} in \code{\link{outer}}
##' @param makedimnames ignored
##' @param ... ignored
##' @family matrixCombining
##' @export
##' @examples
##' set.seed(1)
##' X <- strucMatrix(c(1, 2, 1, 2), c(1, 1, 2, 2), 1:4, rnorm(4))
##' Y <- strucMatrix(c(1, 2, 1), c(1, 1, 2), 1:3, rnorm(3))
##' (kronExample <- kron(X, Y))
kron <- function(X, Y, trans = "*",
                 makedimnames = FALSE,  ...) {

    lenX <- length(X$rowInds)
    lenY <- length(Y$rowInds)
    lenRep <- rep.int(lenY, lenX)
    XYrowInds <- rep.int(Y$rowInds, lenX) + dim(Y)[1] * rep.int(X$rowInds, lenRep)
    XYcolInds <- rep.int(Y$colInds, lenX) + dim(Y)[2] * rep.int(X$colInds, lenRep)
    XYvalInds <- as.vector(outer(Y$valInds, max(Y$valInds) * (X$valInds - 1), "+"))
    XYtrans <- mkOuterTrans(Y$trans, X$trans, trans)
    XYvals <- as.vector(outer(Y$vals, X$vals, FUN = trans))
    structure(list(rowInds = XYrowInds,
                   colInds = XYcolInds,
                   valInds = XYvalInds,
                   vals = XYvals,
                   trans = XYtrans),
              class = c("strucMatrixKron", "strucMatrix"),
              Dim = dim(X) * dim(Y))
}

setOldClass(c("strucMatrixKron", "strucMatrix"))
setIs("strucMatrixKron", "strucMatrix")


##' @rdname kron
##' @family matrixCombining
##' @export
##' @examples
##' (krExample <- kr(X, Y))
kr <- function(X, Y, trans = "*") {

    ## modified Matrix::KhatriRao to allow for structured sparse case

    X <- standardSort(X)
    Y <- standardSort(Y)

    p <- ncol(X)
    xp <- as.integer(ind2point(X$colInds, p))
    yp <- as.integer(ind2point(Y$colInds, ncol(Y)))
    xn <- diff(xp)
    yn <- diff(yp)
    newp <- as.integer(diffinv(xn * yn))

    xn.yp <- xn[as.logical(yn)]
    yj <- factor(Y$colInds)
    rep.yn <- rep.int(yn, xn)
    i1 <- rep.int(X$rowInds, rep.yn)
    i2 <- unlist(rep(split.default(Y$rowInds, yj), xn.yp))
    n1 <- nrow(X); n2 <- nrow(Y)
    dim <- as.integer(c(n1 * n2, p))

    v1 <- rep.int(X$valInds, rep.yn)
    v2 <- unlist(rep(split.default(Y$valInds, yj), xn.yp))

    newRowInds <- i1 * n2 + i2
    newColInds <- point2ind(newp) - 1L
    newValInds <- (v1 - 1L) * length(Y$vals) + v2
    newVals <- as.vector(outer(Y$vals, X$vals, FUN = trans))
    newTrans <- mkOuterTrans(Y$trans, X$trans, trans)

    structure(list(rowInds = newRowInds,
                   colInds = newColInds,
                   valInds = newValInds,
                   vals = newVals,
                   trans = newTrans),
              class = c("strucMatrixKr", "strucMatrix"),
              Dim = c(dim(X)[1] * dim(Y)[1], dim(X)[2]))
}

setOldClass(c("strucMatrixKr", "strucMatrix"))
setIs("strucMatrixKr", "strucMatrix")

##' @rdname kron
##' @export
setMethod("kronecker", signature(X = "strucMatrix", Y = "strucMatrix"), {
    function(X, Y, FUN = "*", make.dimnames = FALSE, ...) kron(X, Y)
})

##' Structured sparse matrix multiplication (and other binary operations)
##'
##' Because of the marginal summations involved, the results of a
##' matrix multiplication of \code{\link{strucMatrix}} objects is not
##' profitably stored as a \code{strucMatrix}. Therefore, the output is
##' a \code{\link{sparseMatrix}}.
##'
##' @param e1,e2 \code{strucMatrix} objects
##' @note The \code{*} operator is matrix multiplication, not
##' element-wise multiplication.
##' @rdname Ops.strucMatrix
##' @export
Ops.strucMatrix <- function(e1, e2) {
    FUN = .Generic
    if(FUN == "*") {
        return(as.matrix(e1, sparse = TRUE) %*% as.matrix(e2, sparse = TRUE))
    } else {
        return(getGeneric(FUN)(as.matrix(e1, sparse = TRUE), as.matrix(e2, sparse = TRUE)))
    }
}


##' @param x,y \code{\link{strucMatrix}} matrix objects
##' @param ... not used (only for consistency with generic)
##' @rdname Ops.strucMatrix
##' @importFrom Matrix crossprod
##' @export
setMethod("crossprod", signature(x = "strucMatrix", y = "missing"), {
    function(x, y = NULL, ...) {
        crossprod(as.matrix(x, sparse = TRUE), ...)
    }
})

##' @rdname Ops.strucMatrix
##' @importFrom Matrix tcrossprod
##' @export
setMethod("tcrossprod", signature(x = "strucMatrix", y = "missing"), {
    function(x, y = NULL, ...) {
        tcrossprod(as.matrix(x, sparse = TRUE), ...)
    }
})

##' @rdname Ops.strucMatrix
##' @importFrom Matrix crossprod
##' @export
setMethod("crossprod", signature(x = "strucMatrix", y = "strucMatrix"), {
    function(x, y = NULL, ...) {
        crossprod(as.matrix(x, sparse = TRUE),
                  as.matrix(y, sparse = TRUE), ...)
    }
})

##' @rdname Ops.strucMatrix
##' @importFrom Matrix tcrossprod
##' @export
setMethod("tcrossprod", signature(x = "strucMatrix", y = "strucMatrix"), {
    function(x, y = NULL, ...) {
        tcrossprod(as.matrix(x, sparse = TRUE),
                   as.matrix(y, sparse = TRUE), ...)
    }
})

## ----------------------------------------------------------------------
## Make trans functions
## ----------------------------------------------------------------------

##' Construct functions for transforming a parameter vector to the
##' non-zero values of a structured sparse matrix
##'
##' These functions return a 'trans function', for transforming a
##' parameter vector to the repeated non-zero values of a structured
##' sparse matrix.  Each trans function takes one vector argument
##' called \code{matPars}, which are the parameters of the matrix.
##' The environment of these transformation functions must contain a
##' vector called \code{init}, which contains the initial values (aka,
##' the prototype) of \code{matPars}.
##'
##' @param trans transformation function (without an environment) that
##' takes parameter values and returns a vector containing the
##' structural non-zero unique values of a structured sparse matrix
##' @param init initial parameter values
##' @param env a named list or environment containing objects required
##' by \code{trans}
##' @param .safe should an error be thrown if \code{trans} has an
##' environment? if \code{FALSE} then \code{mkTrans} will delete any
##' such environment, and you might not like this so be careful.
##' @rdname mkTrans
##' @aliases mkTrans
##' @export
mkTrans <- function(init, trans, env = new.env(), .safe = TRUE) {
    ## if(!is.null(environment(trans)) && .safe) stop("\ntrans cannot have an environment.",
    ##                                                "\nif you really want to do this,",
    ##                                                "\nuse .safe = FALSE and the environment",
    ##                                                "\nwill be removed for you.")
    ## if(!.safe) environment(trans) <- NULL
    if(!is.environment(env)) env <- list2env(env)
    env$init <- init
    env$trans <- trans
    environment(trans) <- env
    return(trans)
    local(function(matPars) {
        stopifnot(length(init) == length(matPars)) ## FIXME: add
                                                   ## switch to turn
                                                   ## off this check
        trans(matPars)
    }, env)
}

##' @rdname mkTrans
##' @aliases mkTrans
##' @export
mkIdentityTrans <- function(init) {
    ## mkTrans(init, I)
    local({
        init <- init
        function(matPars) matPars
    })
}

##' @rdname mkTrans
##' @export
mkCholOneOffDiagTrans <- function(init) {
    local({
        init <- init
        function(matPars) {
            with(setNames(as.list(matPars), c("diagVal", "offDiagVal")), {
                c(sqrt(diagVal),
                  offDiagVal/sqrt(diagVal),
                  sqrt(diagVal - ((offDiagVal^2) / diagVal)))
            })
        }
    })
}


##' @rdname mkTrans
##' @export
mkGenCholInPlaceTrans <- function(init) {
    local({
        init <- init
        m <- length(init)
        n <- nChoose2Inv(m) - 1 # as.integer((sqrt(1 + 8 * m) - 1)/2)
        diagInds <- choose(3:(n + 1), 2)
        col1Inds <- choose(1:n, 2) + 1L
        function(matPars) {
            print(matPars[col1Inds] <- matPars[col1Inds]/sqrt(matPars[1]))
            matPars
            k <- 3
            for(i in 3:n) {
                for(j in 1:(i-1)) {
                    k <- k + 1
                    matPars[k] <-
                        (matPars[k] - sum(matPars[col1Inds[i-1] + (0:(j-1))] *
                                          matPars[col1Inds[j-1] + (0:(j-1))])) /
                        matPars[diagInds[i]]
                }
                k <- k + 1
                matPars[k] <- sqrt(matPars[k] - sum(matPars[(k-i-1):(k-1)]^2))
            }
            return(matPars)
        }
    })
}


##' @rdname mkTrans
##' @export
mkConstVarCholTrans <- function(init) {
    local({
        init <- init
        matSize <- nChoose2Inv(length(init) - 1L) # minus one for the
                                                  # standard deviation
                                                  # parameter
        diagIndices <- 1:matSize
        rowIndices <- rep(diagIndices, diagIndices)
        colIndices <- sequence(diagIndices)
        diagIndices <- rowIndices == colIndices
        vals <- numeric(length(diagIndices))
        function(matPars) {
            sdVal <- matPars[1]
            offDiagVals <- matPars[-1]
            innProd <- (sdVal^2) -
                c(0, tapply(offDiagVals^2, rowIndices[!diagIndices], sum))
            if(any(innProd < 0L)) {
                stop("resulting matrix not positive definite")
            }
            diagVals <- sqrt(innProd)
            vals[ diagIndices] <- diagVals
            vals[!diagIndices] <- offDiagVals
            return(vals)
        }
    })
}

##' @rdname mkTrans
##' @export
mkCorMatCholTrans <- function(init) {
    local({
        init <- init
        matSize <- nChoose2Inv(length(init))
        diagIndices <- 1:matSize
        rowIndices <- rep(diagIndices, diagIndices)
        colIndices <- sequence(diagIndices)
        diagIndices <- rowIndices == colIndices
        vals <- numeric(length(diagIndices))
        sdVal <- 1
        offDiagFun <- function(x) {
            x <- x^2
            sqrt(x / (sum(x) + 1))
        }
        innProdFun <- function(x) sum(x^2)
        function(matPars) {
            splitPars <- split(matPars, rowIndices[!diagIndices])
            offDiagValsList <- lapply(splitPars, offDiagFun)
            innProd <- 1 - c(0, sapply(offDiagValsList, innProdFun))
            if(any(innProd < 0L)) stop("resulting matrix not positive definite") ## shouldn't ever happen
            diagVals <- sqrt(innProd)
            ## a little DRY
            vals[ diagIndices] <- diagVals
            vals[!diagIndices] <- sign(matPars) * unlist(offDiagValsList)
            return(vals)
        }
    })
}

##' @param cholObj,symmObj corresponding triangular cholesky and
##' symmetric \code{\link{strucMatrix}} matrix objects
##' @param vecDist distances as a vector
##' @rdname mkTrans
##' @export
mkExpCholTrans <- function(init, cholObj, symmObj, vecDist) {
    local({
        init <- init
        cholObj <- cholObj
        symmObj <- symmObj
        vecDist <- as.numeric(vecDist) + 0
        symmObj@x <- exp(-init * vecDist)
        function(matPars) {
            symmObj@x <- exp(-matPars * vecDist)
            as(update(cholObj, symmObj), "sparseMatrix")@x
        }
    })
}


##' @rdname mkTrans
##' @export
##' @param defaultOutput default vector for diagonal elements
##' @param devfunEnv environment of the deviance function
mkFlexDiagTrans <- function(init, defaultOutput,
                            devfunEnv) {

    ## silence no visible binding for global variable notes
    pp <- resp <- indsObsLevel <- ns <- NULL
    
    local({
        init <- init
        nBasis <- length(init)
        if(nBasis == 0L) stop("initial parameter vector must have length greater than zero")
        defaultOutput <- defaultOutput
        devfunEnv <- devfunEnv
        Xspline <- NULL
        if(nBasis == 1) {
            function(matPars) {
                return(as.numeric(rep(exp(matPars), length(defaultOutput))))
            }
        } else {
            function(matPars) {
                lp <- try(evalq(pp$linPred(1) + resp$offset, devfunEnv), silent = TRUE)
                b1 <- try(evalq(pp$b      (1),               devfunEnv), silent = TRUE)
                if(inherits(lp, "try-error")) return(defaultOutput)
                lp <- try(lp - b1[indsObsLevel], silent = TRUE)
                if(inherits(lp, "try-error")) return(defaultOutput)
                Xspline <- try(ns(lp, nBasis, intercept = TRUE), silent = TRUE)
                assign("Xspline", Xspline, envir = parent.env(environment()))
                if(inherits(Xspline, "try-error")) return(defaultOutput)
                return(as.numeric(exp(Xspline %*% matPars)))
            }
        }
    })
}


##' @param Atrans,Btrans functions for transforming two structured
##' sparse matrices, \code{A} and \code{B} say
##' @param ABtrans function to pass as \code{FUN} in
##' \code{\link{outer}}
##' @rdname mkTrans
##' @export
mkOuterTrans <- function(Atrans, Btrans, ABtrans) {
    A <- Atrans(getInit(Atrans))
    B <- Btrans(getInit(Btrans))

                                        # check to see if one of the
                                        # parameter sets is a single
                                        # 1L, in which case just
                                        # return an identity
                                        # transformation (because an
                                        # outer product involving a
                                        # scalar of 1L is boring)
    checkForBordom <- function(xx) (length(xx) == 1L) & (xx[1] == 1L)
    ## if(checkForBordom(A) && !checkForBordom(B)) return(mkIdentityTrans(getInit(Btrans)))
    ## if(!checkForBordom(A) && checkForBordom(B)) return(mkIdentityTrans(getInit(Atrans)))
    ## if(checkForBordom(A) && checkForBordom(B)) return(mkIdentityTrans(1)) ## FIXME: too
                                                                             ## presumptuous?

                                        # construct the environment of
                                        # the transformation function
    local({
        Atrans <- Atrans
        Btrans <- Btrans
        ABtrans <- ABtrans
        Ainit <- getInit(Atrans)
        Binit <- getInit(Btrans)
        Aind <- seq_along(Ainit)
        Bind <- seq_along(Binit) + length(Ainit)
        init <- c(Ainit, Binit)
        function(matPars) as.vector(outer(Atrans(matPars[Aind]),
                                          Btrans(matPars[Bind]),
                                          FUN = ABtrans))
    })
}

##' @param transList list of transformation functions
##' @rdname mkTrans
##' @export
mkListTrans <- function(transList) {
    local({
        transList <- transList
        parList <- lapply(transList, getInit)
        indList <- lapply(parList, seq_along)
        for(ii in 2:length(indList)) {
            indList[[ii]] <- indList[[ii]] + max(0L, indList[[ii-1]])
        }
        init <- unlist(parList)
        function(matPars) {
            unlist(lapply(seq_along(indList),
                          function(ii) transList[[ii]](matPars[indList[[ii]]])))
        }
    })
}


##' @param covariate covariate
##' @param grpFac a grouping factor (or anything coercible to
##' \code{numeric} really) with the number of levels equal to the
##' length of \code{init}
##' @rdname mkTrans
##' @export
mkVarExpTrans <- function(init, covariate, grpFac) {
    local({
        init <- init
        covariate <- covariate
        grpFac <- grpFac
        function(matPars) {
            parsExpand <- matPars[as.numeric(grpFac)]
            return(exp(2 * parsExpand * covariate))
        }
    })
}

##' @importFrom Matrix KhatriRao
##' @param modMat model matrix
##' @rdname mkTrans
##' @export
mkMultiVarExpTrans <- function(init, modMat, grpFac) {
    local({
        init <- init
        if(!is.na(grpFac)) {
            termMat <- t(KhatriRao(as(grpFac, "sparseMatrix"),
                                   t(as(modMat, "sparseMatrix"))))
        } else {
            termMat <- modMat
        }
        function(matPars) {
            return(exp(2 * as.numeric(termMat %*% matPars)))
        }
    })
}

##' @rdname mkTrans
##' @export
mkVarPowerTrans <- function(init, covariate, grpFac) {
    local({
        init <- init
        covariate <- covariate
        grpFac <- grpFac
        function(matPars) {
            parsExpand <- matPars[as.numeric(grpFac)]
            return(abs(covariate)^(2 * parsExpand))
        }
    })
}


##' @rdname mkTrans
##' @export
mkVarIdentTrans <- function(init, covariate, grpFac) {
    local({
        init <- init
        grpFac <- grpFac
        function(matPars) {
            return(matPars[as.numeric(grpFac)])
        }
    })
}


##' @rdname mkTrans
##' @export
mkVarFixedTrans <- function(init, covariate, grpFac) {
    local({
        init <- init
        covariate <- covariate
        function(matPars) {
            return(abs(covariate))
        }
    })
}


##' @param matSize number of rows (or columns) of the matrix
##' @rdname mkTrans
##' @export
mkCholCompSymmTrans <- function(init, matSize) {
    local({
        init <- init
        matSize <- matSize
        function(matPars) {
            md0 <- matPars[1]
            od0 <- matPars[2]
            md <- numeric(matSize) # main diagonal
            od <- numeric(matSize-1) # off diagonal
            md[1] <- md0
            od[1] <- od0/sqrt(md0)
            for(i in 2:matSize) {
                md[i] <- md[i-1] - od[i-1]^2
                if(i == matSize) break
                od[i] <- (od0 - md0 + md[i])/sqrt(md[i])
            }
            return(c(sqrt(md), od))
        }
    })
}


## ----------------------------------------------------------------------
## Special modifications of structured sparse matrices
## ----------------------------------------------------------------------

##' Constant structured sparse matrix
##'
##' Convert a parameterized structured sparse matrix into a constant
##' structured sparse matrix (i.e. \code{getInit(object)} has length
##' zero).
##'
##' @param object structured sparse matrix
##' @family modifications
##' @export
resetTransConst <- function(object) {
    object$trans <- local({
        baseline <- object$vals
        init <- numeric(0)
        function(matPars = NULL) {
            return(baseline)
        }
    })
    ## FIXME: return(simplifyStrucMatrix(object)) ???
    return(object)
}


##' Simplify structured sparse matrices
##'
##' Simplify structured sparse matrices by (1) forcing non structural
##' zeros to be structural zeros, (2) eliminate duplicates in the
##' repeated values, and (3) resetting the transformation function to
##' be the identity.
##'
##' @param object structured sparse matrix
##' @param force force non structural zeros to be structural zeros?
##' @param eliminate eliminate duplicates in the repeated values?
##' @param reset reset the transformation function to be the identity?
##' @param ... not yet used
##' @family modifications
##' @export
simplifyStrucMatrix <- function(object,
                              force = TRUE,
                              eliminate = TRUE,
                              reset = TRUE, ...) {
                                        # force non structural zeros
                                        # to be structural
    if(force) {
        valsToKeep <- which(object$vals != 0L)
        elementsToKeep <- with(object, valInds %in% valsToKeep)
        object$vals <- object$vals[valsToKeep]
        object$rowInds <- object$rowInds[elementsToKeep]
        object$colInds <- object$colInds[elementsToKeep]
        object$valInds <- flattenIntVec(object$valInds[elementsToKeep])
    }
                                        # eliminate duplicates in the
                                        # repeated values
    if(eliminate) {
        newVals <- with(object, vals[which(!duplicated(vals))])
        newValInds <- with(object, match(vals[valInds], newVals))
        object$vals <- newVals
        object$valInds <- newValInds
    }
                                        # reset to identity trans
    if(reset) {
        object$trans <- mkIdentityTrans(object$vals)
    }
    return(object)
}

##' Add scalar multiple to a structured sparse matrix
##'
##' Adjust the \code{trans} function of \code{object} such that a
##' scalar multiplier parameter is concatenated at the beginning of
##' the parameter vector.
##'
##' @param object structured sparse matrix
##' @param mult initial value for the multiplier parameter
##' @family modifications
##' @export
scalarMult <- function(object, mult) {
    object$trans <- local({
        init <- c(mult, getInit(object))
        unscaledTrans <- object$trans
        function(matPars) {
            matPars[1] * unscaledTrans(matPars[-1])
        }
    })
    ans <- update(object)
    class(ans) <- c("strucMatrixScalarMult", class(ans))
    return(ans)
}


## ----------------------------------------------------------------------
## Matrix binding and repeating
## ----------------------------------------------------------------------

##' Row, column, and block-diagonal binding for structured sparse
##' matrices
##'
##' @param ... list of \code{strucMatrix} objects (but not used for
##' \code{rep.strucMatrix})
##' @param type type of binding
##' @rdname bind
##' @family matrixCombining
##' @export
##' @examples
##' set.seed(1)
##' X <- strucMatrix(c(1, 2, 1, 2), c(1, 1, 2, 2), 1:4, rnorm(4))
##' Y <- strucMatrix(c(1, 2, 1), c(1, 1, 2), 1:3, rnorm(3))
##' Z <- strucMatrix(c(1, 2), c(1, 2), 1:2, rnorm(2))
##' (bindDiag <- bind(X, Y, Z, type = "diag"))
##' (bindRow  <- bind(X, Y, Z, type = "row"))
##' (bindCol  <- bind(X, Y, Z, type = "col"))
bind <- function(..., type = c("row", "col", "diag")) {
    mats <- list(...)
    nmat <- length(mats)
    type <- type[[1]]
    if(nmat == 1L) return(mats[[1]])
    with(listTranspose(mats), {
        rowOff <- colOff <- 0
        vilen <- sapply(valInds, length)
        valen <- sapply(vals[-nmat], length) # don't need value lengths for last matrix
        valOff <- rep.int(c(0, cumsum(valen)), vilen)
        if((type == "row") || (type == "diag")) {
            rlen <- sapply(rowInds, length)
            nrows <- sapply(mats, nrow)
            rowOff <- rep.int(c(0, cumsum(nrows[-nmat])), rlen)
        }
        if((type == "col") || (type == "diag")) {
            clen <- sapply(colInds, length)
            ncols <- sapply(mats, ncol)
            colOff <- rep.int(c(0, cumsum(ncols[-nmat])), clen)
        }
        if((type == "row") && (type != "diag")) ncols <- ncol(mats[[1]])
        if((type == "col") && (type != "diag")) nrows <- nrow(mats[[1]])
        ans <- strucMatrix(rowInds = unlist(rowInds) + rowOff + 1,
                         colInds = unlist(colInds) + colOff + 1,
                         valInds = unlist(valInds) + valOff,
                         vals = unlist(vals),
                         trans = mkListTrans(trans),
                         Dim = c(sum(nrows), sum(ncols)))
        class(ans) <- c("strucMatrixBind", class(ans))
        return(ans)
    })
}

setOldClass(c("strucMatrixBind", "strucMatrix"))
setIs("strucMatrixBind", "strucMatrix")


##' @param mats list of \code{strucMatrix} matrix objects
##' @rdname bind
##' @export
.bind <- function(mats, type = c("row", "col", "diag")) {
    do.call(bind, c(mats, list(type = type)))
}

##' Repeat structured sparse matrix
##'
##' @param x \code{strucMatrix} object
##' @param times like \code{rep}
##' @param repVals should the unique values (\code{x$vals}) be
##' repeated too?  in other words, do you want to have each replicate
##' to have guaranteed identical values (\code{FALSE}, the default),
##' or do you want each replicate to be settable to it's own values
##' (\code{TRUE})?
##' @rdname bind
##' @family matrixCombining
##' @export
##' @examples
##' set.seed(1)
##' X <- strucMatrix(c(1, 2, 1, 2), c(1, 1, 2, 2), 1:4, rnorm(4))
##' Y <- strucMatrix(c(1, 2, 1), c(1, 1, 2), 1:3, rnorm(3))
##' Z <- strucMatrix(c(1, 2), c(1, 2), 1:2, rnorm(2))
##' (repDiag <- rep(X, 3, type = "diag"))
##' (repRow  <- rep(X, 3, type = "row"))
##' (repCol  <- rep(X, 3, type = "col"))
rep.strucMatrix <- function(x, times,
                          type = c("row", "col", "diag"),
                          repVals = FALSE,
                          ...) {

    type = type[[1]]
    len <- length(x$rowInds)
    rowInds <- rep.int(x$rowInds, times)
    colInds <- rep.int(x$colInds, times)
    rowMult <- colMult <- 1
    if((type == "row") || (type == "diag")) {
        off <- rep.int((0:(times - 1)) * nrow(x), rep.int(len, times))
        rowInds <- rowInds + off
        rowMult <- times
    }
    if((type == "col") || (type == "diag")) {
        off <- rep.int((0:(times - 1)) * ncol(x), rep.int(len, times))
        colInds <- colInds + off
        colMult <- times
    }
    if(repVals) {
        lenXvals <- length(x$vals)
        vals <- rep(x$vals, times)
        off <- rep.int((0:(times - 1)) * lenXvals,
                       rep.int(len, times))
        valInds <- x$valInds + off
        trans <- mkListTrans(rep(list(x$trans), times))
    } else {
        vals <- x$vals
        valInds <- rep(x$valInds, times = times)
        trans <- x$trans
    }
    ans <- strucMatrix(rowInds = rowInds + 1,
                     colInds = colInds + 1,
                     valInds = valInds,
                     vals = vals,
                     trans = trans,
                     Dim = c(rowMult, colMult) * dim(x))
    class(ans) <- c("strucMatrixRep", class(ans))
    ans$mkNewPars <- x$mkNewPars
    return(ans)
}

setOldClass(c("strucMatrixRep", "strucMatrix"))
setIs("strucMatrixRep", "strucMatrix")


## ----------------------------------------------------------------------
## Subsetting
## ----------------------------------------------------------------------

##' Subsetting structured sparse matices
##'
##' @param x a \code{\link{strucMatrix-class}} object
##' @param rowInds,colInds 1-based integer indices for rows and
##' columns
##' @param ... unused
##' @family matrixCombining
##' @rdname subset
##' @export
##' @examples
##' set.seed(1)
##' X <- strucMatrix(c(1, 2, 1, 2), c(1, 1, 2, 2), 1:4, rnorm(4))
##' (subsetExample <- subset(X, c(1, 2, 2, 2, 1, 1)))
subset.strucMatrix <- function(x, rowInds = NULL, colInds = NULL, ...) {
    if(!is.null(rowInds)) x <-   strucMatrixRowSubset(  x , rowInds)
    if(!is.null(colInds)) x <- t(strucMatrixRowSubset(t(x), colInds))
    return(x)
}


##' @rdname subset
##' @export
strucMatrixRowSubset <- function(x, rowInds) {

                                        # save the original indices
    ri <- x$rowInds + 1L
    ci <- x$colInds + 1L
    vi <- x$valInds

                                        # find out about the indices
    coir <- countInRange(ri)
    rowIndsCounts <- coir$counts[rowInds]
    tripletsToKeep <- rowInds[rowInds %in% which(coir$counts > 0L)]
    riFac <- factor(ri, levels = coir$vals)

                                        # modify the indices
    x$rowInds <- as.integer(rep(seq_along(rowIndsCounts), rowIndsCounts) - 1L)
    x$colInds <- as.integer(unlist(split(ci, riFac)[tripletsToKeep]) - 1L)
    x$valInds <- as.integer(unlist(split(vi, riFac)[tripletsToKeep]))
    attr(x, "Dim")[1] <- as.integer(length(rowInds))

    return(x)

    ## FIXME: decided not to use this code to remove any possible
    ## unused repeated values, mostly because it hurts my head to
    ## think about how to deal with special parameterized matrics

    ## valsToKeep <- seq_along(va) %in% viNew
    ## vaNew <- va[valsToKeep]
    ## viNew <- flattenIntVec(viNew)

    ## strucMatrix(riNew, ciNew, viNew, vaNew,
    ##           Dim = c(length(rowInds), ncol(x)),
    ##           trans = x$trans)

}



## ----------------------------------------------------------------------
## Changing sparse formats
## ----------------------------------------------------------------------

##' Changing sparse format
##'
##' \code{point2ind} takes a vector of column pointers, as used in
##' sparse matrices of class \code{dgCMatrix}, and returns a vector of
##' column indices, as used in sparse matrices of class
##' \code{dgTMatrix}.  \code{ind2point} is the approximate inverse of
##' \code{point2ind}.  See \code{\link{sparseMatrix}} for more
##' information about these conversions.
##'
##' @param point vector of column pointers
##' @rdname changeSparseFormat
##' @export
point2ind <- function(point) {
                                        # ?Matrix::sparseMatrix
    dp <- diff(sort(point))
    rep.int(seq_along(dp), dp)
}

##' @param ind vector of column indices
##' @param maxInd number of rows or columns (could be larger than
##' \code{max(ind)})
##' @param fillNA fill in \code{NA}'s with repeated column numbers (as
##' in \code{Matrix} package)?
##' @rdname changeSparseFormat
##' @export
ind2point <- function(ind, maxInd, fillNA = TRUE) {
    point <- match(0:maxInd, ind) - 1L
    if(!fillNA) return(point)
    point[maxInd + 1] <- max(maxInd, length(ind))
    isNaPoint <- is.na(point)
    for(j in maxInd:1) {
        point[j] <- ifelse(isNaPoint[j],
                           point[j + 1],
                           point[j])
    }
    return(point)
}


## ----------------------------------------------------------------------
## Construct special matrices -- strucMatrixCompSymm, strucMatrixDiag,
## strucMatrixIdent, rStrucMatrix
## ----------------------------------------------------------------------

##' Blank structured sparse matrix
##'
##' @param nrow,ncol number of rows and columns
##' @export
##' @family strucMatrixSpecial
##' @examples
##' (xBlank <- strucMatrixBlank(5, 5))
strucMatrixBlank <- function(nrow, ncol) {
    ## MATNAME: Blank
    ans <- strucMatrix(integer(0), integer(0), integer(0), numeric(0), Dim = c(nrow, ncol))
    class(ans) <- c("strucMatrixBlank", class(ans))
    return(ans)
}

setOldClass(c("strucMatrixBlank", "strucMatrix"))
setIs("strucMatrixBlank", "strucMatrix")


##' Identity structured sparse matrix
##'
##' @param matSize matrix size
##' @param val value to be repeated on the diagonal (defaults to
##' \code{1})
##' @family strucMatrixSpecial
##' @export
##' @examples
##' (xIdent <- strucMatrixIdent(5))
strucMatrixIdent <- function(matSize, val) {
    ## MATNAME: Identity
    ans <- strucMatrixDiag(1, rep(1, matSize))
    class(ans) <- c("strucMatrixIdent", class(ans))
    if(!missing(val)) ans <- update(ans, val)
    return(ans)
}

setOldClass(c("strucMatrixIdent", "strucMatrix"))
setIs("strucMatrixIdent", "strucMatrix")


##' Diagonal structured sparse matrix
##'
##' @param vals vector of values
##' @param valInds vector of value indices
##' @family strucMatrixSpecial
##' @export
##' @examples
##' set.seed(1)
##' (xDiag <- strucMatrixDiag(rnorm(5)))
strucMatrixDiag <- function(vals, valInds) {
    ## MATNAME: Diagonal
    if(missing(valInds)) valInds <- seq_along(vals)
    matSize <- length(valInds)
    ans <- strucMatrix(1:matSize, 1:matSize, valInds, vals)
    class(ans) <- c("strucMatrixDiag", class(ans))
    return(ans)
}

setOldClass(c("strucMatrixDiag", "strucMatrix"))
setIs("strucMatrixDiag", "strucMatrix")



##' Full structured sparse matrix
##' 
##' @param nrow,ncol numbers of rows and columns
##' @param vals vector of values
##' @family strucMatrixSpecial
##' @export
##' @examples
##' set.seed(1)
##' (xFull <- strucMatrixFull(5, 5, rnorm(25)))
strucMatrixFull <- function(nrow, ncol, vals) {
    ## MATNAME: Full
    ans <- strucMatrix(rep(1:nrow, ncol),
                     rep(1:ncol, each = nrow),
                     1:(nrow * ncol),
                     vals)
    class(ans) <- c("strucMatrixFull", class(ans))
    return(ans)       
}

setOldClass(c("strucMatrixFull", "strucMatrix"))
setIs("strucMatrixFull", "strucMatrix")


##' Column vector as structured sparse matrix
##'
##' @param vals vector of values
##' @param valInds vector of value indices
##' @family strucMatrixSpecial
##' @export
##' @examples
##' set.seed(1)
##' (xCol <- strucMatrixCol(rnorm(5)))
strucMatrixCol <- function(vals, valInds) {
    ## MATNAME: Column vector
    if(missing(valInds)) valInds <- seq_along(vals)
    matSize <- length(valInds)
    ans <- strucMatrix(1:matSize, rep(1, matSize), valInds, vals)
    class(ans) <- c("strucMatrixCol", class(ans))
    return(ans)
}

setOldClass(c("strucMatrixCol", "strucMatrix"))
setIs("strucMatrixCol", "strucMatrix")


##' Structured sparse indicator matrix
##'
##' @param fac vector coercible to factor
##' @family strucMatrixSpecial
##' @export
##' @examples
##' (xInd <- strucMatrixInd(rep(1:3, c(2, 2, 1))))
strucMatrixInd <- function(fac) {
    ## MATNAME: Indicator
    ans <- as.strucMatrix(as.factor(fac))
    class(ans) <- c("strucMatrixInd", class(ans))
    return(ans)
}

setOldClass(c("strucMatrixInd", "strucMatrix"))
setIs("strucMatrixInd", "strucMatrix")


##' Triangular structured sparse matrix
##'
##' @param diagVals values for the diagonal
##' @param offDiagVals values for the off-diagonal
##' @param low lower triangular?
##' @family strucMatrixSpecial
##' @export
##' @examples
##' set.seed(1)
##' (xTri <- strucMatrixTri(rnorm(5), rnorm(choose(5, 2))))
strucMatrixTri <- function(diagVals, offDiagVals, low = TRUE) {
    ## MATNAME: Triangular
    matSize <- length(diagVals)
    diagIndices <- 1:matSize
    rowIndices <- rep(diagIndices, diagIndices)
    colIndices <- sequence(diagIndices)
    diagIndices <- rowIndices == colIndices
    vals <- numeric(length(diagIndices))
    vals[ diagIndices] <- diagVals
    vals[!diagIndices] <- offDiagVals
    ans <- strucMatrix(rowIndices, colIndices, seq_along(vals), vals)
    if(!low) ans <- t(ans)
    class(ans) <- c("strucMatrixTri", class(ans))
    ans$mkNewPars <- local({
        diagIndices
        function(diagVals, offDiagVals) {
            vals <- numeric(length(diagIndices))
            vals[ diagIndices] <- diagVals
            vals[!diagIndices] <- offDiagVals
            return(vals)
        }
    })
    return(ans)
}

setOldClass(c("strucMatrixTri", "strucMatrix"))
setIs("strucMatrixTri", "strucMatrix")

##' General and full triangular structured sparse matrix
##'
##' @note Beware there appear to be bugs here.
##'
##' @param nrow,ncol number of rows and columns
##' @param vals values for the nonzero values
##' @param diag include diagonal?
##' @param low lower triangular?
##' @family strucMatrixSpecial
##' @export
##' @examples
##' set.seed(1)
##' (xGenFullTri <- strucMatrixGenFullTri(5, 5, rnorm(choose(6, 2))))
strucMatrixGenFullTri <- function(nrow, ncol, vals, diag = TRUE, low = TRUE) {
    ## MATNAME: General full triangular
    rowIndices <- rev(nrow - sequence(nrow - (ncol:1) + 1)) + 1
    colIndices <- rep(0:(ncol - 1), nrow:(nrow - ncol + 1)) + 1
    ans <- strucMatrix(rowIndices, colIndices, seq_along(vals), vals)
    class(ans) <- c("strucMatrixGenFullTri", class(ans))
    return(ans)
}

setOldClass(c("strucMatrixGenFullTri", "strucMatrix"))
setIs("strucMatrixGenFullTri", "strucMatrix")


##' Structured sparse matrix of ones
##'
##' @param nrow,ncol numbers of rows and columns
##' @family strucMatrixSpecial
##' @export
##' @examples
##' (xOnes <- strucMatrixOnes(5, 5))
strucMatrixOnes <- function(nrow, ncol) {
    ## MATNAME: Ones
    rc <- expand.grid(seq_len(nrow), seq_len(ncol))
    vi <- rep(1, nrow(rc))
    ans <- strucMatrix(rc[, 1], rc[, 2], vi, 1)
    class(ans) <- c("strucMatrixOnes", class(ans))
    return(ans)
}

setOldClass(c("strucMatrixOnes", "strucMatrix"))
setIs("strucMatrixOnes", "strucMatrix")


##' Symmetric structured sparse matrix
##'
##' @param diagVals values for the diagonal
##' @param offDiagVals values for the off-diagonal
##' @family strucMatrixSpecial
##' @export
##' @examples
##' set.seed(1)
##' (xSymm <- strucMatrixSymm(rnorm(5), rnorm(choose(5, 2))))
strucMatrixSymm <- function(diagVals, offDiagVals) {
    ## MATNAME: Symmetric
    matSize <- length(diagVals)
    diagIndices <- 1:matSize
    repIndices <- rep(diagIndices, diagIndices)
    seqIndices <- sequence(diagIndices)
    diagIndices <- repIndices == seqIndices
    rowIndices <- c(repIndices[ diagIndices],
                    repIndices[!diagIndices],
                    seqIndices[!diagIndices])
    colIndices <- c(seqIndices[ diagIndices],
                    seqIndices[!diagIndices],
                    repIndices[!diagIndices])
    vals <- numeric(length(rowIndices))
    vals <- c(diagVals, offDiagVals)
    valIndices <- c(1:matSize,
                    matSize + (1:sum(!diagIndices)),
                    matSize + (1:sum(!diagIndices)))
    ans <- strucMatrix(rowIndices, colIndices, valIndices, vals)
    class(ans) <- c("strucMatrixSymm", class(ans))
    return(ans)
}

setOldClass(c("strucMatrixSymm", "strucMatrix"))
setIs("strucMatrixSymm", "strucMatrix")


##' Structured sparse matrix with compound symmetry
##'
##' @param diagVal value for the diagonal
##' @param offDiagVal value for the off-diagonal
##' @param matSize size of the resulting matrix
##' @family strucMatrixSpecial
##' @export
##' @examples
##' (xCompSymm <- strucMatrixCompSymm(1, -0.2, 5))
strucMatrixCompSymm <- function(diagVal, offDiagVal, matSize) {
    ## MATNAME: Compound symmetry
    if((!(diagVal > offDiagVal)) || (!(offDiagVal > (-diagVal)/(matSize-1))))
        warning("resulting matrix not positive definite")
    iii <- rep.int(1:(matSize-1), 1:(matSize-1)) + 1L
    jjj <- sequence(1:(matSize-1))
    ii <- c(1:matSize, iii, jjj)
    jj <- c(1:matSize, jjj, iii)
    vi <- rep.int(1:2, c(matSize, 2 * choose(matSize, 2)))
    va <- setNames(c( diagVal ,  offDiagVal ),
                   c("diagVal", "offDiagVal"))
    ans <- strucMatrix(ii, jj, vi, va, Dim = c(matSize, matSize))
    class(ans) <- c("strucMatrixCompSymm", class(ans))
    return(ans)
}

setOldClass(c("strucMatrixCompSymm", "strucMatrix"))
setIs("strucMatrixCompSymm", "strucMatrix")


##' Structured sparse matrix with only one non-zero value off the
##' diagonal
##'
##' @param diagVal unique value for the diagonal
##' @param offDiagVal unique value for the off-diagonal
##' @param offDiagInds indices for the two correlated objects
##' @param matSize size of the matrix
##' @family strucMatrixSpecial
##' @export
##' @examples
##' (xOneOffDiag <- strucMatrixOneOffDiag(1, -0.2, c(5, 2), 5))
strucMatrixOneOffDiag <- function(diagVal, offDiagVal, offDiagInds, matSize) {
    ## MATNAME: One off diagonal
    if(length(offDiagInds) != 2L) stop("only one off diagonal element please")
    if(offDiagInds[1] == offDiagInds[2]) stop("off diagonal must be off the diagonal")
    iii <- jjj <- 1:matSize
    ii <- c(iii,     offDiagInds )
    jj <- c(jjj, rev(offDiagInds))
    vi <- c(rep(1, matSize), rep(2, 2))
    va <- setNames(c( diagVal ,  offDiagVal ),
                   c("diagVal", "offDiagVal"))
    ans <- strucMatrix(ii, jj, vi, va, Dim = c(matSize, matSize))
    class(ans) <- c("strucMatrixOneOffDiag", class(ans))
    return(ans)
}

setOldClass(c("strucMatrixOneOffDiag", "strucMatrix"))
setIs("strucMatrixOneOffDiag", "strucMatrix")


##' Structured sparse Cholesky factor leading to constant variance
##'
##' @param sdVal standard deviation of crossproduct of the result
##' factor
##' @param offDiagVals off diagonal values of the Cholesky factor
##' @family strucMatrixSpecial
##' @export
##' @examples
##' set.seed(1)
##' (xConstVarChol <- strucMatrixConstVarChol(3, rnorm(choose(5, 2))))
strucMatrixConstVarChol <- function(sdVal, offDiagVals) {
    ## MATNAME: Constant variance Cholesky
    matSize <- nChoose2Inv(length(offDiagVals))
    diagIndices <- 1:matSize
    rowIndices <- rep(diagIndices, diagIndices)
    colIndices <- sequence(diagIndices)
    diagIndices <- rowIndices == colIndices
    vals <- numeric(length(diagIndices))
    innProd <- (sdVal^2) - c(0, tapply(offDiagVals^2, rowIndices[!diagIndices], sum))
    if(any(innProd < 0L)) stop("resulting matrix not positive definite")
    diagVals <- sqrt(innProd)
    ## a little DRY
    vals[ diagIndices] <- diagVals
    vals[!diagIndices] <- offDiagVals

    ans <- strucMatrix(rowIndices, colIndices,
                     seq_along(vals), vals,
                     mkConstVarCholTrans(c(sdVal, offDiagVals)))
    class(ans) <- c("strucMatrixConstVarChol", class(ans))
    return(ans)
}

setOldClass(c("strucMatrixConstVarChol", "strucMatrix"))
setIs("strucMatrixConstVarChol", "strucMatrix")


##' Structured sparse Cholesky factor of a correlation matrix
##'
##' @param offDiagPars parameters determining the off-diagonal of the
##' Cholesky factor
##' @family strucMatrixSpecial
##' @export
##' @examples
##' set.seed(1)
##' (xCorMatChol <- strucMatrixCorMatChol(rnorm(choose(5, 2))))
strucMatrixCorMatChol <- function(offDiagPars) {
    ## MATNAME: Correlation matrix Cholesky
    matSize <- nChoose2Inv(length(offDiagPars))
    diagIndices <- 1:matSize
    rowIndices <- rep(diagIndices, diagIndices)
    colIndices <- sequence(diagIndices)
    diagIndices <- rowIndices == colIndices
    vals <- numeric(length(diagIndices))
    splitPars <- split(offDiagPars, rowIndices[!diagIndices])
    offDiagValsList <- lapply(splitPars, function(xx) {
        xx <- xx^2
        sqrt(xx / (sum(xx) + 1))
    })
    innProd <- 1 - c(0, sapply(offDiagValsList, function(xx) sum(xx^2)))
    if(any(innProd < 0L)) stop("resulting matrix not positive definite") ## shouldn't ever happen
    diagVals <- sqrt(innProd)
    ## a little DRY
    vals[ diagIndices] <- diagVals
    vals[!diagIndices] <- sign(offDiagPars) * unlist(offDiagValsList)

    ans <- strucMatrix(rowIndices, colIndices,
                     seq_along(vals), vals,
                     mkCorMatCholTrans(offDiagPars))
    class(ans) <- c("strucMatrixCorMatChol", class(ans))
    return(ans)
}

setOldClass(c("strucMatrixCorMatChol", "strucMatrix"))
setIs("strucMatrixCorMatChol", "strucMatrix")


##' Structured sparse diagonal covariance matrix with a covariate
##' determining the diagonal
##'
##' @param varPars vector of variance parameters (one per level of
##' \code{grpFac})
##' @param covariate covariate
##' @param grpFac a grouping factor (or anything coercible to
##' \code{numeric} really) with the number of levels equal to the
##' length of \code{varPars}
##' @param mkTransFunc a function taking arguments \code{varPars},
##' \code{covariate}, and \code{grpFac} for constructing a
##' \code{trans} function (see \code{\link{mkVarExpTrans}} for an
##' example).
##' @family strucMatrixSpecial
##' @export
##' @examples
##' (xVarWithCovariate <- strucMatrixVarWithCovariate(rep(1, 5), 0.05*(4:0)))
strucMatrixVarWithCovariate <- function(varPars, covariate, grpFac,
                                      mkTransFunc = mkVarExpTrans) {
    ## MATNAME: Structured covariance matrix
    if(missing(grpFac)) grpFac <- factor(seq_along(covariate))
    if(missing(covariate)) covariate <- NA
    trans <- mkTransFunc(varPars, covariate, grpFac)
    matSize <- length(grpFac)
    vals <- trans(varPars)
    ans <- strucMatrix(1:matSize, 1:matSize, 1:matSize, vals,
                     trans = trans, Dim = c(matSize, matSize))
    class(ans) <- c("strucMatrixVarWithCovariate", class(ans))
    return(ans)
}

setOldClass(c("strucMatrixVarWithCovariate", "strucMatrix"))
setIs("strucMatrixVarWithCovariate", "strucMatrix")



##' Cholesky factor of a structured sparse covariance matrix obeying
##' exponential distance decay in covariance
##'
##' @importFrom Matrix Cholesky
##' @param distObj distance matrix object
##' @param cutOffDist maximum distance with nonzero correlations
##' @family strucMatrixSpecial
##' @export
##' @examples
##' (xExpChol <- strucMatrixExpChol(dist(matrix(rnorm(10), 5, 2))))
strucMatrixExpChol <- function(distObj, cutOffDist = Inf) {
    ## MATNAME: Exponential distance Cholesky
    matSize <- as.integer(attr(distObj, "Size"))
    ri <- c(0:(matSize - 1L), matSize - rev(sequence(1:(matSize - 1))))
    ci <- c(1:matSize, rep(1:(matSize - 1), (matSize - 1):1)) - 1L
    va <- c(rep(0, matSize), as.numeric(distObj))

    ## FIXME: DRY -- order takes more than one argument?
    ord <- order(ri, decreasing = FALSE)
    ri <- ri[ord]; ci <- ci[ord]; va <- va[ord]
    ord <- order(ci, decreasing = FALSE)
    ri <- ri[ord]; ci <- ci[ord]; va <- va[ord]

    symmObj <- new("dsCMatrix", uplo = "L",
                   i = ri, p = ind2point(ci, matSize),
                   x = va,
                   Dim = rep(matSize, 2))

    vecDist <- symmObj@x
    symmObj@x <- exp(-vecDist)
    cholObj <- Cholesky(symmObj)

    cholMat <- as(cholObj, "sparseMatrix")
    ans <- strucMatrix(rowInds = cholMat@i + 1L,
                     colInds = point2ind(cholMat@p),
                     valInds = seq_along(vecDist),
                     vals = cholMat@x,
                     trans = mkExpCholTrans(1, cholMat, symmObj, vecDist))
    class(ans) <- c("strucMatrixExpChol", class(ans))
    return(ans)
}

setOldClass(c("strucMatrixExpChol", "strucMatrix"))
setIs("strucMatrixExpChol", "strucMatrix")

##' Construct a structured sparse upper Cholesky factor from an
##' \code{nlme}-style \code{corStruct} object
##'
##' @importFrom nlme Dim
##' @importFrom nlme "coef<-"
##' @param object a \code{corStruct} object
##' @param sig initial standard deviation
##' @family strucMatrixSpecial
##' @export
##' @examples
##' if(require("nlme")) {
##'     corObj <- Initialize(corAR1(0.5, form = ~ 1 | Subject),
##'                          data = Orthodont)
##' }
##' (xCorFactor <- strucMatrixCorFactor(corObj))
strucMatrixCorFactor <- function(object, sig = 1) {
    ## MATNAME: Cholesky from corStruct object

    
    sigExists <- !is.null(sig)
    coefExists <- length(coef(object)) != 0

    corFac <- corFactor(object)
    lens <- Dim(object)$len
    vecLens <- 2 * choose(lens, 2) + lens
    vecList <- subRagByLens(corFactor(object), vecLens)

    invList <- mapplyInvList(vecList, lens)
    upperInds <- lapply(invList, upper.tri)

    oneBlock <- length(invList) == 1L

    ans <- .bind(mapply(strucMatrixTri,
                        lapply(invList, diag),
                        mapply("[", invList, upperInds, SIMPLIFY = FALSE),
                        MoreArgs = list(low = FALSE),
                        SIMPLIFY = FALSE), "diag")

    if(!coefExists) {
        ans <- resetTransConst(ans)
        if(!sigExists) {
            assign("object", object, envir = environment(ans$trans))
            class(ans) <- c("strucMatrixCorFactor", class(ans))
            return(ans)
        }
    }
    if(coefExists) {
        diagIndices <- lapply(lens, seq, from = 1, by = 1)
        rowIndices <- mapply(rep, diagIndices, diagIndices, SIMPLIFY = FALSE)
        colIndices <- lapply(diagIndices, sequence)
        diagIndices <- mapply("==", rowIndices, colIndices, SIMPLIFY = FALSE)
        
        transEnv <- environment(ans$trans)
        list4env <- list(object = object,
                         init = coef(object),
                         lens = lens,
                         diagIndices = diagIndices,
                         rowIndices = rowIndices,
                         colIndices = colIndices,
                         vecLens = vecLens,
                         upperInds = upperInds)
        if(oneBlock) list4env$parList <- list(getInit(ans))
        list2env(list4env, transEnv)

        ans$trans <- local({
            function(matPars) {
                coef(object) <- matPars
                vecList <- subRagByLens(corFactor(object), vecLens)
                invList <- mapplyInvList(vecList, lens)
                diagVals <- lapply(invList, diag)
                upperVals <- mapply("[", invList, upperInds, SIMPLIFY = FALSE)
                for(i in seq_along(diagVals)) {
                    parList[[i]][ diagIndices[[i]]] <-  diagVals[[i]]
                    parList[[i]][!diagIndices[[i]]] <- upperVals[[i]]
                }
                unlist(parList)
            }
        }, transEnv)
    }

    if(sigExists) ans <- scalarMult(ans, sig)
    class(ans) <- c("strucMatrixCorFactor", class(ans))
    assign( "sigExists",  sigExists, envir = environment(ans$trans))
    assign("coefExists", coefExists, envir = environment(ans$trans))
    assign("object",     object,     envir = environment(ans$trans))
    return(ans)
}

setOldClass(c("strucMatrixCorFactor", "strucMatrix"))
setIs("strucMatrixCorFactor", "strucMatrix")

## ----------------------------------------------------------------------
## Random structured sparse matrices
## ----------------------------------------------------------------------

##' Random structured sparse matrices
##'
##' @param nrows,ncols numbers of rows and columns
##' @param nvals number of values
##' @param nnonzeros number of nonzero elements
##' @param rfunc random number function
##' @param ... dots
##' @export
rStrucMatrix <- function(nrows, ncols, nvals, nnonzeros, rfunc = rnorm, ...) {
    ## Random structured sparse matrix
    if(nnonzeros < nvals)
        stop("number of nonzeros must be at least the number of values")
    if(nnonzeros > (nrows * ncols))
        stop("too many nonzeros for matrix of this size")
    valInds <- sample(c(1:nvals, sample(1:nvals, nnonzeros - nvals, TRUE)))
    indChoose <- sample(nrows * ncols, length(valInds))
    inds <- expand.grid(1:nrows, 1:ncols)[indChoose, ]
    vals <- rfunc(max(valInds), ...)
    strucMatrix(rowInds = inds$Var1,
              colInds = inds$Var2,
              valInds = valInds, vals = vals,
              Dim = c(nrows, ncols))
}




## ----------------------------------------------------------------------
## Cholesky --
## ----------------------------------------------------------------------

##' Cholesky decomposition of structured sparse matrices
##'
##' @note These are often just bailout methods, but some special
##' \code{strucMatrix} matrices have exploitable structure, which can be
##' used to keep the number of repeated values down.
##' @param x an object that inherits from class
##' \code{\link{strucMatrix}}
##' @param ... passed to subsequent functions
##' @rdname chol
##' @export
setMethod("chol", signature(x = "strucMatrix"), {
    function(x, ...) {
        ans <- as.strucMatrix(as(chol(as.matrix(x, sparse = TRUE)), "dgCMatrix"))
        class(ans) <- c("strucMatrixChol", class(x))
        return(ans)
    }
})

##' @rdname chol
##' @export
setMethod("chol", signature(x = "strucMatrixOneOffDiag"), {
    function(x, ...) {
        offRow <- sort(x$rowInds[c(0, -1) + length(x$valInds)]) + 1L
        va <- c(sqrt(x$vals[1]),
                x$vals[2]/sqrt(x$vals[1]),
                sqrt(x$vals[1] - ((x$vals[2]^2) / x$vals[1])))
        ni <- length(x$valInds)
        vi <- x$valInds[-ni]
        ri <- x$rowInds[-ni] + 1L
        ci <- x$colInds[-ni] + 1L
        vi[offRow[2]] <- 3
        vi[length(vi)] <- 2
        ans <- strucMatrix(ri, ci, vi, va,
                         trans = mkCholOneOffDiagTrans(x$vals),
                         Dim = dim(x))
        class(ans) <- c("strucMatrixChol", class(x))
        return(ans)
    }
})

##' @rdname chol
##' @export
##' @examples
##' x <- strucMatrixCompSymm(1.2, -0.11, 4)
##' as.matrix(chol(x))
##' t(chol(as.matrix(x)))
setMethod("chol", signature(x = "strucMatrixCompSymm"), {
    function(x, ...) {
        n <- nrow(x)
        trans <- mkCholCompSymmTrans(x$vals, n)
        sa <- seq_len(n)
        ii <- rep.int(1:(n-1), (n-1):1)
        ri <- c(sa, unlist(lapply(2:n, ":", n)))
        ci <- c(sa, ii)
        vi <- c(sa, ii + n)
        ans <- strucMatrix(ri, ci, vi,
                         trans(x$vals), trans = trans,
                         Dim = dim(x))
        class(ans) <- c("strucMatrixChol", class(x))
        return(ans)
    }
})
stevencarlislewalker/lme4ord documentation built on May 30, 2019, 4:43 p.m.