#### Tools for Package Testing --- in Matrix, sourced by ./test-tools.R #### ------------------------- ### ------- Part III -- "Matrix" (classes) specific ---------------------- ## lower.tri() and upper.tri() -- masking base definitions ## R/src/library/base/R/lower.tri.R ## R/src/library/base/R/upper.tri.R ## but we do __not__ want to coerce to "base R" 'matrix' via as.matrix(): ## lower.tri <- function(x, diag = FALSE) if(diag) row(x) >= col(x) else row(x) > col(x) upper.tri <- function(x, diag = FALSE) if(diag) row(x) <= col(x) else row(x) < col(x) lsM <- function(...) { for(n in ls(..., envir=parent.frame())) if(is((. <- get(n)),"Matrix")) cat(sprintf("%5s: '%s' [%d x %d]\n",n,class(.), nrow(.),ncol(.))) } asD <- function(m) { ## as "Dense" if(canCoerce(m, "denseMatrix")) as(m, "denseMatrix") else if(canCoerce(m, (cl <- paste(.M.kind(m), "denseMatrix", sep='')))) as(m, cl) else if(canCoerce(m, "dgeMatrix")) as(m, "dgeMatrix") else stop("cannot coerce to a typical dense Matrix") } ## "normal" sparse Matrix: Csparse, no diag="U" asCsp <- function(x) diagU2N(as(x, "CsparseMatrix")) ##' @title quasi-identical dimnames Qidentical.DN <- function(dx, dy) { stopifnot(is.list(dx) || is.null(dx), is.list(dy) || is.null(dy)) ## "empty" (is.null.DN(dx) && is.null.DN(dy)) || identical(dx, dy) } ##' quasi-identical() for 'Matrix' matrices Qidentical <- function(x,y, strictClass = TRUE) { if(!identical(class(x), cy <- class(y))) { if(strictClass || !is(x, cy)) return(FALSE) ## else try further } slts <- slotNames(x) ## MJ: should be slotNames(y), since is(x, class(y)) ?? if("Dimnames" %in% slts) { ## always (or we have no 'Matrix') slts <- slts[slts != "Dimnames"] if(!Qidentical.DN(x@Dimnames, y@Dimnames) && ## allow for "completion" of (NULL, ) dimnames of symmetricMatrix: !Qidentical.DN(dimnames(x), dimnames(y))) return(FALSE) } if("factors" %in% slts) { ## allow one empty and one non-empty 'factors' slts <- slts[slts != "factors"] ## if both are not empty, they must be the same: if(length(xf <- x@factors) && length(yf <- y@factors)) if(!identical(xf, yf)) return(FALSE) } for(sl in slts) if(!identical(slot(x,sl), slot(y,sl))) return(FALSE) TRUE } ## MJ: It seems intuitive to allow either of is(x, class(y)) ## and is(y, class(x)) when strictClass=FALSE ... .MJ.Qidentical <- function(x, y, strictClass = TRUE, skipSlots = NULL) { isxy <- identical(cx <- class(x), cy <- class(y)) if (!isxy) { if (strictClass) return(FALSE) isxy <- is(x, cy) if (!(isxy || is(y, cx))) return(FALSE) ## else try further } slts <- slotNames(if (isxy) y else x) if (length(skipSlots)) slts <- setdiff(slts, skipSlots) if ("Dimnames" %in% slts) { ## always, or we have no "Matrix" ## allow symmetrization of 'Dimnames' for "symmetricMatrix" : slts <- slts[slts != "Dimnames"] if(!Qidentical.DN(x@Dimnames, y@Dimnames) && !Qidentical.DN(dimnames(x), dimnames(y))) return(FALSE) } if ("factors" %in% slts) { ## allow one empty and one non-empty 'factors' : slts <- slts[slts != "factors"] ## if both are not empty, they must be the same: if (length(xf <- x@factors) && length(yf <- y@factors) && !identical(xf, yf)) return(FALSE) } for (slt in slts) if (!identical(slot(x, slt), slot(y, slt))) return(FALSE) TRUE } ##' quasi-identical() for traditional ('matrix') matrices mQidentical <- function(x,y, strictClass = TRUE) { if(!identical(class(x), cy <- class(y))) { if(strictClass || !is(x, cy)) return(FALSE) ## else try further } if(!Qidentical.DN(dimnames(x), dimnames(y))) return(FALSE) identical(unname(x), unname(y)) } Q.C.identical <- function(x,y, sparse = is(x,"sparseMatrix"), checkClass = TRUE, strictClass = TRUE) { if(checkClass && class(x) != class(y)) { if(strictClass || !is(x, class(y))) return(FALSE) ## else try further } if(sparse) Qidentical(as(x,"CsparseMatrix"), as(y,"CsparseMatrix"), strictClass=strictClass) else Qidentical(x,y, strictClass=strictClass) } ##' ##' ##'
##' @title Quasi-equal for 'Matrix' matrices ##' @param x Matrix ##' @param y Matrix ##' @param superclasses x and y must coincide in (not) extending these; set to empty, ##' if no class/inheritance checks should happen. ##' @param dimnames.check logical indicating if dimnames(.) much match ##' @param tol NA (--> use "==") or numerical tolerance for all.equal() ##' @return logical: Are x and y (quasi) equal ? Q.eq <- function(x, y, superclasses = c("sparseMatrix", "denseMatrix", "dMatrix", "lMatrix", "nMatrix"), dimnames.check = TRUE, tol = NA) { ## quasi-equal - for 'Matrix' matrices if(any(dim(x) != dim(y))) return(FALSE) if(dimnames.check && !identical(dimnames(x), dimnames(y))) return(FALSE) xcl <- getClassDef(class(x)) ycl <- getClassDef(class(y)) for(SC in superclasses) { if( extends(xcl, SC) && !extends(ycl, SC)) return(FALSE) } asC <- ## asCommon if((isDense <- extends(xcl,"denseMatrix"))) function(m) as(m, "matrix") else function(m) as(as(as(m,"CsparseMatrix"), "dMatrix"), "generalMatrix") # => "dgC" if(is.na(tol)) { if(isDense) all(x == y | (is.na(x) & is.na(y))) else ## 'x == y' blows up for large sparse matrices: isTRUE(all.equal(asC(x), asC(y), tolerance = 0., check.attributes = dimnames.check)) } else if(is.numeric(tol) && tol >= 0) { isTRUE(all.equal(asC(x), asC(y), tolerance = tol, check.attributes = dimnames.check)) } else stop("'tol' must be NA or non-negative number") } Q.eq2 <- function(x, y, superclasses = c("sparseMatrix", "denseMatrix"), dimnames.check = FALSE, tol = NA) Q.eq(x,y, superclasses=superclasses, dimnames.check=dimnames.check, tol=tol) ##' ##' ##'
##' @title Quasi-equality of symmpart(m) + skewpart(m) with m ##' @param m Matrix ##' @param tol numerical tolerance for all.equal() ##' @return logical ##' @author Martin Maechler Q.eq.symmpart <- function(m, tol = 8 * .Machine$double.eps) { ss <- symmpart(m) + skewpart(m) if(hasNA <- any(iNA <- is.na(ss))) { ## ss has the NA's symmetrically, but typically m has *not* iiNA <- which(iNA) # <- useful! -- this tests which() methods! ## assign NA's too -- using correct kind of NA: m[iiNA] <- as(NA, Matrix:::.type.kind[Matrix:::.M.kind(m)]) } Q.eq2(m, ss, tol = tol) } ##' sample.int(n, size, replace=FALSE) for really large n: sampleL <- function(n, size) { if(n < .Machine$integer.max) sample.int(n, size) else { i <- unique(round(n * runif(1.8 * size))) while(length(i) < size) { i <- unique(c(i, round(n * runif(size)))) } i[seq_len(size)] } } ## Useful Matrix constructors for testing: ##' @title Random Sparse Matrix ##' @param n ##' @param m number of columns; default (=n) ==> square matrix ##' @param density the desired sparseness density: ##' @param nnz number of non-zero entries; default from \code{density} ##' @param repr character string specifying the sparseness kind of the result. ##' @param giveCsparse *deprecated* logical specifying if result should be CsparseMatrix ##' @return a [CTR]sparseMatrix, n x m ##' @author Martin Maechler, Mar 2008; July 2020 ('repr' instead og 'giveCsparse') rspMat <- function(n, m = n, density = 1/4, nnz = round(density * n*m), repr = c("C","T","R"), giveCsparse) { stopifnot(length(n) == 1, n == as.integer(n), length(m) == 1, m == as.integer(m), 0 <= density, density <= 1, 0 <= nnz, nnz <= (N <- n*m)) in0 <- sampleL(N, nnz) x <- sparseVector(i = in0, x = as.numeric(1L + seq_along(in0)), length = N) dim(x) <- c(n,m)#-> sparseMatrix ## silent, back compatible (not yet warning about 'giveCsparse' deprecation): repr <- if(missing(repr) && !missing(giveCsparse)) if(giveCsparse) "C" else "T" else match.arg(repr) switch(repr, "C" = as(x, "CsparseMatrix"), "T" = x,# TsparseMatrix "R" = as(x, "RsparseMatrix")) } ## __DEPRECATED__ !! rSparseMatrix <- function(nrow, ncol, nnz, rand.x = function(n) round(rnorm(nnz), 2), ...) { stopifnot((nnz <- as.integer(nnz)) >= 0, nrow >= 0, ncol >= 0, nnz <= nrow * ncol) .Deprecated("rsparsematrix") ##========= sparseMatrix(i = sample(nrow, nnz, replace = TRUE), j = sample(ncol, nnz, replace = TRUE), x = rand.x(nnz), dims = c(nrow, ncol), ...) } rUnitTri <- function(n, upper = TRUE, ...) { ## Purpose: random unit-triangular sparse Matrix .. built from rspMat() ## ---------------------------------------------------------------------- ## Arguments: n: matrix dimension ## upper: logical indicating if upper or lower triangular ## ... : further arguments passed to rspMat(), eg. 'density' ## ---------------------------------------------------------------------- ## Author: Martin Maechler, Date: 5 Mar 2008, 11:35 r <- (if(upper) triu else tril)(rspMat(n, ...)) ## make sure the diagonal is empty diag(r) <- 0 r <- drop0(r) r@diag <- "U" r } ##' Construct a nice (with exact numbers) random artificial \eqn{A = L D L'} ##' decomposition with a sparse \eqn{n \times n}{n x n} matrix \code{A} of ##' density \code{density} and square root \eqn{D} determined by \code{d0}. ##' ##' If one of \code{rcond} or \code{condest} is true, \code{A} must be ##' non-singular, both use an \eqn{LU} decomposition requiring ##' non-singularity. ##' @title Make Nice Artificial A = L D L' (With Exact Numbers) Decomposition ##' @param n matrix dimension \eqn{n \times n}{n x n} ##' @param density ratio of number of non-zero entries to total number ##' @param d0 The sqrt of the diagonal entries of D default \code{10}, to be ##' \dQuote{different} from \code{L} entries. More generally these can be negative ##' @param rcond logical indicating if \code{\link{rcond}(A, useInv=TRUE)} ##' should be returned which requires non-singular A and D. ##' @param condest logical indicating if \code{\link{condest}(A)$est} ##' should be returned which requires non-singular A and D. ##' @return list with entries A, L, d.half, D, ..., where A inherits from ##' class \code{"\linkS4class{symmetricMatrix}"} and should be equal to ##' \code{as(L \%*\% D \%*\% t(L), "symmetricMatrix")}. ##' @author Martin Maechler, Date: 15 Mar 2008 mkLDL <- function(n, density = 1/3, d0 = 10, d.half = d0 * sample.int(n), # random permutation rcond = (n < 99), condest = (n >= 100)) { stopifnot(n == round(n), density <= 1) n <- as.integer(n) stopifnot(n >= 1, is.numeric(d.half), length(d.half) == n)# no longer (2023-05-24): d.half >= 0 L <- Matrix(0, n,n) nnz <- round(density * n*n) L[sample(n*n, nnz)] <- seq_len(nnz) L <- tril(L, -1L) diag(L) <- 1 ### FIXME: allow *negative* d.half[] entries! dh2 <- d.half^2 non.sing <- sum(dh2 > 0) == n D <- Diagonal(x = dh2) A <- tcrossprod(L * rep(d.half, each=n)) ## = as(L %*% D %*% t(L), "symmetricMatrix") list(A = A, L = L, d.half = d.half, D = D, rcond.A = if (rcond && non.sing) rcond(A, useInv=TRUE), cond.A = if(condest && non.sing) condest(A)$est) } eqDeterminant <- function(m1, m2, NA.Inf.ok=FALSE, tol=.Machine$double.eps^0.5, ...) { d1 <- determinant(m1) ## logarithm = TRUE d2 <- determinant(m2) d1m <- as.vector(d1$modulus)# dropping attribute d2m <- as.vector(d2$modulus) if((identical(d1m, -Inf) && identical(d2m, -Inf)) || ## <==> det(m1) == det(m2) == 0, then 'sign' may even differ ! (is.na(d1m) && is.na(d2m))) ## if both are NaN or NA, we "declare" that's fine here return(TRUE) else if(NA.Inf.ok && ## first can be NA, second infinite: ## wanted: base::determinant.matrix() sometimes gives -Inf instead ## of NA,e.g. for matrix(c(0,NA,0,0,NA,NA,0,NA,0,0,1,0,0,NA,0,1), 4,4)) is.na(d1m) && is.infinite(d2m)) return(TRUE) ## else if(is.infinite(d1m)) d1$modulus <- sign(d1m)* .Machine$double.xmax if(is.infinite(d2m)) d2$modulus <- sign(d2m)* .Machine$double.xmax ## now they are finite or *one* of them is NA/NaN, and all.equal() will tell so: all.equal(d1, d2, tolerance=tol, ...) } ##' @param A a non-negative definite sparseMatrix, typically "dsCMatrix" ##' ##' @return a list with components resulting from calling ##' Cholesky(., perm = .P., LDL = .L., super = .S.) ##' ##' for all 2*2*3 combinations of (.P., .L., .S.) allCholesky <- function(A, verbose = FALSE, silentTry = FALSE) { ## Author: Martin Maechler, Date: 16 Jul 2009 ##' @param r list of CHMfactor objects, typically with names() as '. | .' ##' ##' @return an is(perm,LDL,super) matrix with interesting and *named* rownames CHM_to_pLs <- function(r) { is.perm <- function(.) if(inherits(., "try-error")) NA else .@type[1L] != 0L is.LDL <- function(.)if(inherits(., "try-error")) NA else isLDL(.) r.st <- cbind(perm = sapply(r, is.perm), LDL = sapply(r, is.LDL), super = sapply(r, class) == "dCHMsuper") names(dimnames(r.st)) <- list(" p L s", "") r.st } my.Cholesky <- { if(verbose) function (A, perm = TRUE, LDL = !super, super = FALSE, Imult = 0, ...) { cat(sprintf("Chol..(*, perm= %1d, LDL= %1d, super=%1d):", perm, LDL, super)) r <- Cholesky(A, perm=perm, LDL=LDL, super=super, Imult=Imult, ...) cat(" [Ok]\n") r } else Cholesky } logi <- c(FALSE, TRUE) d12 <- expand.grid(perm = logi, LDL = logi, super = c(logi,NA), KEEP.OUT.ATTRS = FALSE) r1 <- lapply(seq_len(nrow(d12)), function(i) try(do.call(my.Cholesky, c(list(A = A), as.list(d12[i,]))), silent=silentTry)) names(r1) <- apply(d12, 1, function(.) paste(symnum(.), collapse=" ")) dup.r1 <- duplicated(r1) r.all <- CHM_to_pLs(r1) if(!identical(dup.r1, duplicated(r.all))) warning("duplicated( ) differs from duplicated( )", immediate. = TRUE) list(Chol.A = r1, dup.r.all = dup.r1, r.all = r.all, r.uniq = CHM_to_pLs(r1[ ! dup.r1])) } ##' Cheap Boolean Arithmetic Matrix product ##' Should be equivalent to %&% which is faster [not for large dense!]. ##' Consequently mainly used in checkMatrix() ## The first version (up to Aug.2022) -- possibly what we should use for dense case (!?) boolProd0 <- function(x,y) as((abs(x) %*% abs(y)) > 0, "nMatrix") ## New since Aug.13, 2022, ensuring that zeros are dropped isCRT <- function(x, cl = getClass(class(x))) extends(cl, "CsparseMatrix") || extends(cl, "TsparseMatrix") || extends(cl, "RsparseMatrix") boolProd <- function(x,y) { ## treat x & y, drop0() & coercing to "n" -- this treats NA <==> 1 (!) x <- if(isCRT(x)) .sparse2kind(x, kind="n", drop0=TRUE) else as(drop0(x), "nMatrix") y <- if(isCRT(y)) .sparse2kind(y, kind="n", drop0=TRUE) else as(drop0(y), "nMatrix") r <- (abs(x) %*% abs(y)) > 0 if(isCRT(r)) .sparse2kind(r, kind="n", drop0=TRUE) else # also for "sparseMatrix" cases "indMatrix" (incl "pMatrix") or "diagonalMatrix" ## NB: "diagonalMatrix already *does* drop0(.) when coerced to "nMatrix" as(r, "nMatrix") } .sparse2kind <- Matrix:::.sparse2kind # (FIXME -- a version of this should be exported!) ###----- Checking a "Matrix" ----------------------------------------- ##' Check the compatibility of \pkg{Matrix} package Matrix with a ##' \dQuote{traditional} \R matrix and perform a host of internal consistency ##' checks. ##' ##' @title Check Compatibility of Matrix Package Matrix with Traditional R Matrices ##' ##' @param m a "Matrix" ##' @param m.m as(m, "matrix") {if 'do.matrix' } ##' @param do.matrix logical indicating if as(m, "matrix") should be applied; ##' typically false for large sparse matrices ##' @param do.t logical: is t(m) "feasible" ? ##' @param doNorm ##' @param doOps ##' @param doSummary ##' @param doCoerce ##' @param doCoerce2 ##' @param do.prod ##' @param verbose logical indicating if "progress output" is produced. ##' @param catFUN (when 'verbose' is TRUE): function to be used as generalized cat() ##' @return TRUE (invisibly), unless an error is signalled ##' @author Martin Maechler, since 11 Apr 2008 checkMatrix <- function(m, m.m = if(do.matrix) as(m, "matrix"), do.matrix = !isSparse || prod(dim(m)) < 1e6, do.t = TRUE, doNorm = TRUE, doOps = TRUE, doSummary = TRUE, doCoerce = TRUE, doCoerce2 = doCoerce && !isRsp, doDet = do.matrix, do.prod = do.t && do.matrix && !isRsp, verbose = TRUE, catFUN = cat, MSG = if(interactive() || capabilities("long.double") || isTRUE(get0("doExtras"))) message else function(...) {} ) { ## is also called from dotestMat() in ../tests/Class+Meth.R stopifnot(is(m, "Matrix")) validObject(m) # or error(....) clNam <- class(m) cld <- getClassDef(clNam) ## extends(cld, FOO) is faster than is(m, FOO) isGen <- extends(cld, "generalMatrix") isSym <- extends(cld, "symmetricMatrix") isTri <- extends(cld, "triangularMatrix") isCor <- isSym && (extends(cld, "corMatrix") || extends(cld, "copMatrix")) if(isSparse <- extends(cld, "sparseMatrix")) { # also true for these isCsp <- extends(cld, "CsparseMatrix") isRsp <- extends(cld, "RsparseMatrix") isTsp <- extends(cld, "TsparseMatrix") isDiag <- extends(cld, "diagonalMatrix") isInd <- extends(cld, "indMatrix") isPerm <- extends(cld, "pMatrix") } else isCsp <- isRsp <- isTsp <- isDiag <- isInd <- isPerm <- FALSE is.n <- extends(cld, "nMatrix") nonMatr <- clNam != (Mcl <- MatrixClass(clNam, cld)) Cat <- function(...) if(verbose) cat(...) CatF <- function(...) if(verbose) catFUN(...) ## warnNow <- function(...) warning(..., call. = FALSE, immediate. = TRUE) DO.m <- function(expr) if(do.matrix) eval(expr) else TRUE vec <- function(x) { dim(x) <- c(length(x), 1L) dimnames(x) <- list(NULL,NULL) x } eps16 <- 16 * .Machine$double.eps ina <- is.na(m) if(do.matrix) { stopifnot(all(ina == is.na(m.m)), all(is.nan(m) == is.nan(m.m)), all(is.finite(m) == is.finite(m.m)), all(is.infinite(m) == is.infinite(m.m)), all(m == m | ina), ## check all() , "==" [Compare], "|" [Logic] if(ncol(m) > 0) identical3(unname(m[,1]), unname(m.m[,1]), as(m[,1,drop=FALSE], "vector")) else identical(as(m, "vector"), as.vector(m.m))) if(any(m != m & !ina)) stop(" any (m != m) should not be true") } else { if(any(m != m)) stop(" any (m != m) should not be true") if(ncol(m) > 0) stopifnot(identical(unname(m[,1]), as(m[,1,drop=FALSE], "vector"))) else stopifnot(identical(as(m, "vector"), as.vector(as(m, "matrix")))) } if(do.t) { tm <- t(m) if(isSym) ## check that t() swaps 'uplo' L <--> U : stopifnot(c("L","U") == sort(c(m@uplo, tm@uplo))) ttm <- t(tm) ## notInd: "pMatrix" ok, but others inheriting from "indMatrix" are not notInd <- (!isInd || isPerm) if(notInd && (isCsp || isGen || isDiag)) stopifnot(Qidentical(m, ttm, strictClass = !nonMatr)) else if(do.matrix) { if(notInd) stopifnot(nonMatr || class(ttm) == clNam) stopifnot(all(m == ttm | ina)) ## else : not testing } ## crossprod() %*% etc if(do.prod) { c.m <- crossprod(m, boolArith = FALSE) tcm <- tcrossprod(m, boolArith = FALSE) tolQ <- if(isSparse) NA else eps16 stopifnot(dim(c.m) == rep.int(ncol(m), 2), dim(tcm) == rep.int(nrow(m), 2), ## FIXME: %*% drops dimnames Q.eq2(c.m, tm %*% m, tol = tolQ), Q.eq2(tcm, m %*% tm, tol = tolQ), ## should work with dimnames: Q.eq(m %&% tm, boolProd(m, tm), superclasses=NULL, tol = 0) , Q.eq(tm %&% m, boolProd(tm, m), superclasses=NULL, tol = 0) ) } } if(!do.matrix) { CatF(" will *not* coerce to 'matrix' since do.matrix is FALSE\n") } else if(doNorm) { CatF(sprintf(" norm(m [%d x %d]) :", nrow(m), ncol(m))) for(typ in c("1","I","F","M")) { Cat('', typ, '') stopifnot(all.equal(norm(m,typ), norm(m.m,typ))) } Cat(" ok\n") } if(do.matrix && doSummary) { summList <- lapply(getGroupMembers("Summary"), get, envir = asNamespace("Matrix")) CatF(" Summary: ") for(f in summList) { ## suppressWarnings(): e.g. any() would warn here: r <- suppressWarnings(identical(f(m), f(m.m))) if(!isTRUE(r)) { ## typically for prod() f.nam <- sub("..$", '', sub("^\\.Primitive..", '', format(f))) ## sum() and prod() are sensitive to order of f. p. operations ## particularly on systems where sizeof(long double) == sizeof(double) (if(any(f.nam == c("sum", "prod"))) MSG else stop)( sprintf("%s(m) [= %g] differs from %s(m.m) [= %g]", f.nam, f(m), f.nam, f(m.m))) } } if(verbose) cat(" ok\n") } ## and test 'dim()' as well: d <- dim(m) isSqr <- d[1] == d[2] if(do.t) stopifnot(identical(diag(m), diag(t(m)))) ## TODO: also === diag(band(m,0,0)) if(prod(d) < .Machine$integer.max && !extends(cld, "modelMatrix")) { vm <- vec(m) stopifnot(is(vm, "Matrix"), validObject(vm), dim(vm) == c(d[1]*d[2], 1)) } if(!isInd) m.d <- local({ m. <- m diag(m.) <- diag(m) ## << *assigning* to 'm.' now typically annihilates @factor if(.hasSlot(m, "factors") && length(f <- m@factors)) m.@factors <- f m. }) if(do.matrix) stopifnot(identical(dim(m.m), dim(m)), ## now that "pMatrix" subsetting gives *LOGICAL* ## if(isPerm) { ## identical(as.integer(unname(diag(m))), unname(diag(m.m))) ## } else identical(diag(m), # base:: *and* Matrix diag() now keep names diag(m.m)),## not for NA: diag(m) == diag(m.m), identical(nnzero(m), sum(m.m != 0)), identical(nnzero(m, na.counted = FALSE), sum(m.m != 0, na.rm = TRUE)), identical(nnzero(m, na.counted = TRUE), sum(m.m != 0 | is.na(m.m))) ) if(isSparse) { n0m <- drop0(m) #==> n0m is Csparse has0 <- !Qidentical(n0m, as(m,"CsparseMatrix")) } if(isDiag) stopifnot(exprs = { .MJ.Qidentical(m, m.d, strictClass = FALSE, skipSlots = if(m@diag != "N") c("diag", "x")) m@diag == "N" || (m.d@diag == "N" && identical(m.d@x, diag(m, names = FALSE))) }) else if(isTri && m@diag != "N") stopifnot(exprs = { is(m.d, "triangularMatrix") && m.d@diag == "N" .MJ.Qidentical(m, m.d, strictClass = FALSE, skipSlots = c("diag", "p", "i", "j", "x")) isSparse || all(m == m.d) }) else if(!isInd) stopifnot(.MJ.Qidentical(m, m.d, strictClass = FALSE, skipSlots = if(((isCsp || isRsp) && has0) || isTsp) c("p", "i", "j", "x"))) ## use non-square matrix when "allowed": ## m12: sparse and may have 0s even if this is not: if(isSparse && has0) m12 <- as(as( m, "lMatrix"),"CsparseMatrix") m12 <- drop0(m12) if(do.matrix) { ## "!" should work (via as(*, "l...")) : m11 <- as(as(!!m,"CsparseMatrix"), "lMatrix") if(!Qidentical(m11, m12)) stopifnot(Qidentical(as(m11, "generalMatrix"), as(m12, "generalMatrix"))) } if(isSparse && !isDiag && !is.n) { ## ensure that as(., "nMatrix") gives nz-pattern CatF("as(., \"nMatrix\") giving full nonzero-pattern: ") n1 <- as(m, "nMatrix") ns <- as(m, "nsparseMatrix") stopifnot(identical(n1,ns), ## only testing [CR]sparseMatrix and indMatrix here ... ## sum() excludes duplicated (i,j) pairs whereas ## length(diagU2N(<[^n].T>)) includes them ... isTsp || (if(isSym) length(if(.hasSlot(n1, "i")) n1@i else n1@j) else sum(n1)) == length(if(isInd) m@perm else diagU2N(m)@x)) Cat("ok\n") } if(doOps) { ## makes sense with non-trivial m (!) CatF("2*m =?= m+m: ") if(identical(2*m, m+m)) Cat("identical\n") else if(do.matrix) { eq <- as(2*m,"matrix") == as(m+m, "matrix") # but work for NA's: stopifnot(all(eq | (is.na(m) & is.na(eq)))) Cat("ok\n") } else {# !do.matrix stopifnot(identical(as(2*m, "CsparseMatrix"), as(m+m, "CsparseMatrix"))) Cat("ok\n") } if(do.matrix) { ## m == m etc, now for all, see above CatF("m >= m for all: "); stopifnot(all(m >= m | ina)); Cat("ok\n") } if(prod(d) > 0) { CatF("m < m for none: ") mlm <- m < m if(!any(ina)) stopifnot(!any(mlm)) else if(do.matrix) stopifnot(!any(mlm & !ina)) else { ## !do.matrix & any(ina) : !ina can *not* be used mlm[ina] <- FALSE stopifnot(!any(mlm)) } Cat("ok\n") } if(isSqr) { if(do.matrix) { ## determinant() "fails" for triangular with NA such as ## (m <- matrix(c(1:0,NA,1), 2)) CatF("symmpart(m) + skewpart(m) == m: ") Q.eq.symmpart(m) CatF("ok; determinant(): ") if(!doDet) Cat(" skipped (!doDet): ") else if(any(is.na(m.m)) && isTri) Cat(" skipped: is triang. and has NA: ") else stopifnot(eqDeterminant(m, m.m, NA.Inf.ok=TRUE)) Cat("ok\n") } } else assertError(determinant(m)) }# end{doOps} if(doCoerce && do.matrix && canCoerce("matrix", clNam)) { CatF("as(, ",clNam,"): ", sep='') m3 <- as(m.m, clNam) Cat("valid:", validObject(m3), "\n") ## m3 should ``ideally'' be identical to 'm' } if(doCoerce2 && do.matrix) { ## not for large m: !m will be dense if(is.n) { mM <- if(nonMatr) as(m, Mcl) else m stopifnot(identical(mM, as(as(m, "dMatrix"),"nMatrix")), identical(mM, as(as(m, "lMatrix"),"nMatrix")), identical(which(m), which(m.m))) } else if(extends(cld, "lMatrix")) { ## should fulfill even with NA: stopifnot(all(m | !m | ina), !any(!m & m & !ina)) if(isTsp) # allow modify, since at end here m <- asUniqueT(m, isT = TRUE) stopifnot(identical(m, m & TRUE), identical(m, FALSE | m)) ## also check the coercions to [dln]Matrix m. <- if(isSparse && has0) n0m else m m1. <- m. # replace NA by 1 in m1. , carefully not changing class: if(any(ina)) m1.@x[is.na(m1.@x)] <- TRUE stopifnot(identical(m. , as(as(m. , "dMatrix"),"lMatrix")), clNam == "ldiMatrix" || # <- there's no "ndiMatrix" ## coercion to n* and back: only identical when no extra 0s: identical(m1., as(as(m1., "nMatrix"),"lMatrix")), identical(which(m), which(m.m))) } else if(extends(cld, "dMatrix")) { m. <- if(isSparse && has0) n0m else m m1 <- m1. <- (m. != 0)*1 ## replace NA by 1 in m1. , carefully not changing class: if(any(ina)) m1.@x[is.na(m1.@x)] <- 1 ## coercion to n* (nz-pattern!) and back: only identical when no extra 0s and no NAs: stopifnot(Q.C.identical(m1., as(as(m., "nMatrix"),"dMatrix"), isSparse, checkClass = FALSE), Q.C.identical(m1 , as(as(m., "lMatrix"),"dMatrix"), isSparse, checkClass = FALSE)) } maybeDense <- if(isSparse) identity else function(.) as(., "denseMatrix") if(isTri) { mm. <- m i0 <- if(m@uplo == "L") upper.tri(mm.) else lower.tri(mm.) n.catchWarn <- if(is.n) suppressWarnings else identity n.catchWarn( mm.[i0] <- 0 ) # ideally, mm. remained triangular, but can be dge* ## Aug.2022 - Coercion deprecations: No longer do as(*, clNam): CatF("as(mm., \"triangularMatrix\"): ") tm <- as(mm., "triangularMatrix") Cat("valid:", validObject(tm), "\n") if(m@uplo == tm@uplo) { ## otherwise, the matrix effectively was *diagonal* if(!isSparse && Matrix:::.isPacked(m)) m <- unpack(m) # to match tm ## note that diagU2N() |-> dtC, now dtT: stopifnot(Qidentical(tm, maybeDense(diagU2N(m)))) } } else if(isDiag) { ## TODO } else { ## TODO } }# end {doCoerce2 && ..} if(doCoerce && isSparse) { ## coerce to sparseVector and back : v <- as(m, "sparseVector") stopifnot(length(v) == prod(d)) dim(v) <- d stopifnot(Q.eq2(m, v)) } invisible(TRUE) } ## {checkMatrix} ### --- These use ##' Check QR-consistency of dense and sparse chk.qr.D.S <- function(d., s., y, Y = Matrix(y), force = FALSE, tol = 1e-10) { stopifnot(is.qr(d.), is(s., "sparseQR")) cc <- qr.coef(d.,y) rank.def <- any(is.na(cc)) && d.$rank < length(d.$pivot) if(rank.def && force) cc <- mkNA.0(cc) ## set NA's to 0 .. ok, in some case ## when system is rank deficient, have differing cases, not always just NA <-> 0 coef ## FIXME though: resid & fitted should be well determined if(force || !rank.def) stopifnot( is.all.equal3( cc , qr.coef (s.,y), drop(qr.coef (s.,Y)), tol=tol), is.all.equal3(qr.resid (d.,y), qr.resid (s.,y), drop(qr.resid (s.,Y)), tol=tol), is.all.equal3(qr.fitted(d.,y), qr.fitted(s.,y), drop(qr.fitted(s.,Y)), tol=tol) ) } ##' "Combi" calling chkQR() on both "(sparse)Matrix" and 'traditional' version ##' ------ and combine the two qr decompositions using chk.qr.D.S() ##' [ chkQR() def. in >>>>> ./test-tools-1.R <<<<< ] ##' ##' @title check QR-decomposition, and compare sparse and dense one ##' @param A a 'Matrix' , typically 'sparseMatrix' ##' @param Qinv.chk ##' @param QtQ.chk ##' @param quiet ##' @return list with 'qA' (sparse QR) and 'qa' (traditional (dense) QR) ##' @author Martin Maechler checkQR.DS.both <- function(A, Qinv.chk, QtQ.chk=NA, quiet=FALSE, giveRE=TRUE, tol = 1e-13) { stopifnot(is(A,"Matrix")) if(!quiet) cat("classical: ") qa <- chkQR(as(A, "matrix"), Qinv.chk=TRUE, QtQ.chk=TRUE, tol=tol, giveRE=giveRE)# works always if(!quiet) cat("[Ok] --- sparse: ") qA <- chkQR(A, Qinv.chk=Qinv.chk, QtQ.chk=QtQ.chk, tol=tol, giveRE=giveRE) validObject(qA) if(!quiet) cat("[Ok]\n") chk.qr.D.S(qa, qA, y = 10 + 1:nrow(A), tol = 256*tol)# ok [not done in rank deficient case!] invisible(list(qA=qA, qa=qa)) } non0.ij <- function(M) Matrix:::non0.i(as(M, "sparseMatrix")) triuChk <- function(x, k) { ans <- triu(x, k) ij <- non0.ij(ans) stopifnot(identical(dim(x), dim(ans)), (ij %*% c(-1,1)) >= k) ans } trilChk <- function(x, k) { ans <- tril(x, k) ij <- non0.ij(ans) stopifnot(identical(dim(x), dim(ans)), (ij %*% c(-1,1)) <= k) ans }