## various tests around mixture distributions set.seed(234534) ## precision at which reference and tested must match eps_lower <- 1e-5 ## sample based match requirements eps <- 1E-2 ## number of samples used for sampling method Nsamp_quant <- 3 ## number of samples used to compare sampled vs estimated quantiles Nsamp_equant <- 1E6 ## define the different test cases beta <- mixbeta(c(1, 11, 4)) betaMix <- mixbeta(c(0.8, 11, 4), c(0.2, 1, 1)) gamma <- mixgamma(c(1, 5, 10), param="mn") gammaMix <- mixgamma(rob=c(0.25, 8, 0.5), inf=c(0.75, 8, 10), param="mn") norm <- mixnorm(c(1, 0, sqrt(2)), sigma=1) normMix <- mixnorm(c(0.2, 0, 2), c(0.8, 2, 2), sigma=1) normMixWeak <- mixnorm(c(0.2, 0, 2), c(0.8, 2, 2), c(0, 0, 1), sigma=1) pmix_lower_tail_test <- function(mix, N=Nsamp_quant) { ## sample some random quantiles do_test <- function(mix) { q <- rmix(mix, N) pl <- pmix(mix, q, lower.tail=TRUE) pu <- pmix(mix, q, lower.tail=FALSE) res <- abs(pl - (1-pu) ) expect_true(all(res < eps_lower)) } ## now also test the respective predictive do_test(mix) do_test(preddist(mix, n=100)) } test_that("Cumulative beta distribution function evaluates lower.tail correctly", { pmix_lower_tail_test(beta) }) test_that("Cumulative beta mixture distribution function evaluates lower.tail correctly", { pmix_lower_tail_test(betaMix) }) test_that("Cumulative normal distribution function evaluates lower.tail correctly", { pmix_lower_tail_test(norm) }) test_that("Cumulative normal mixture distribution function evaluates lower.tail correctly", { pmix_lower_tail_test(normMix) }) test_that("Cumulative gamma distribution function evaluates lower.tail correctly", { pmix_lower_tail_test(gamma) }) test_that("Cumulative gamma mixture distribution function evaluates lower.tail correctly", { pmix_lower_tail_test(gammaMix) }) ## tests the quantile and distribution function against simulated samples mix_simul_test <- function(mix, eps, qtest, ptest = seq(0.1, 0.9, by=0.1), S=Nsamp_equant) { samp <- rmix(mix, S) qtest_samp <- quantile(samp, ptest) qref_qmix <- qmix(mix, ptest) res_quants <- abs(qref_qmix - qtest_samp) expect_true(all(res_quants < eps)) ptest_samp <- vapply(qtest, function(q) mean(samp < q), c(0.1)) pref_pmix <- pmix(mix, qtest) res_probs <- abs(pref_pmix - ptest_samp) expect_true(all(res_probs < eps)) } test_that("Beta quantile function is correct", { mix_simul_test(beta, eps, c(0.1, 0.9)) }) test_that("Beta mixture quantile function is correct", { mix_simul_test(betaMix, eps, c(0.1, 0.9)) }) test_that("Normal quantile function is correct", { mix_simul_test(norm, eps, c(-1, 0)) }) test_that("Normal mixture quantile function is correct", { mix_simul_test(normMix, eps, c(4, 1)) }) test_that("Normal mixture with very weak component quantile function is correct", { mix_simul_test(normMixWeak, eps, c(4, 1)) }) test_that("Gamma quantile function is correct", { mix_simul_test(gamma, eps, c(2, 7)) }) test_that("Gamma mixture quantile function is correct", { mix_simul_test(gammaMix, eps, c(2, 7), ptest = seq(0.2, 0.8, by=0.1)) }) ## problematic gamma (triggers internally a fallback to root finding) gammaMix2 <- mixgamma(c(8.949227e-01, 7.051570e-01, 6.125121e-02), c(1.049106e-01, 3.009986e-01, 5.169626e-04), c(1.666667e-04, 1.836051e+04, 1.044005e-02)) test_that("Singular gamma mixture quantile function is correct", { mix_simul_test(gammaMix2, 10*eps, c(1, 1E3), ptest = seq(0.2, 0.8, by=0.1)) }) consistent_cdf <- function(mix, values) { dens <- dmix(mix, values) cdf <- pmix(mix, values) lcdf <- pmix(mix, values, log.p=TRUE) expect_true(all(diff(cdf) >= 0)) expect_numeric(dens, lower=0, finite=TRUE, any.missing=FALSE) expect_numeric(cdf, lower=0, upper=1, finite=TRUE, any.missing=FALSE) expect_numeric(lcdf, upper=0, finite=FALSE, any.missing=FALSE) } consistent_ccdf <- function(mix, values) { dens <- dmix(mix, values) ccdf <- pmix(mix, values, FALSE) lccdf <- pmix(mix, values, FALSE, TRUE) expect_true(all(diff(ccdf) <= 0)) expect_numeric(dens, lower=0, finite=TRUE, any.missing=FALSE) expect_numeric(ccdf, lower=0, upper=1, finite=TRUE, any.missing=FALSE) expect_numeric(lccdf, upper=0, finite=FALSE, any.missing=FALSE) } test_that("Beta CDF function is consistent", { consistent_cdf(beta, seq(0.1, 0.9, by=0.1)) }) test_that("Beta mixture CDF function is consistent", { consistent_cdf(betaMix, seq(0.1, 0.9, by=0.1)) }) test_that("Normal CDF is consistent", { consistent_cdf(norm, seq(-2, 2, by=0.1)) }) test_that("Normal mixture CDF is consistent", { consistent_cdf(norm, seq(-2, 2, by=0.1)) }) test_that("Gamma CDF function is consistent", { consistent_cdf(gamma, seq(2, 7, by=0.1)) }) test_that("Gamma mixture CDF function is consistent", { consistent_cdf(gammaMix, seq(2, 7, by=0.1)) }) ## problematic beta which triggers that the cumulative of the ## predictive is not monotone (probably fixed with Stan 2.18, check ## again once 2.18 is out) ## problematic Beta density bm1 <- mixbeta(c(1.0, 298.30333970, 146.75306521)) test_that("Problematic (1) BetaBinomial CDF function is consistent", { consistent_cdf(preddist(bm1, n=50), 0:50) }) test_that("Problematic (1) BetaBinomial CCDF function is consistent", { consistent_ccdf(preddist(bm1, n=50), 0:50) }) bm2 <- mixbeta(c(1.0, 3 + 1/3, 47 + 1/3)) test_that("Problematic (2) BetaBinomial CDF function is consistent", { consistent_cdf(preddist(bm2, n=50), 0:50) }) test_that("Problematic (2) BetaBinomial CCDF function is consistent", { consistent_ccdf(preddist(bm2, n=50), 0:50) }) ## tests for the multivariate normal mixture density p <- 4 Rho <- diag(p) Rho[lower.tri(Rho)] <- c(0.3, 0.8, -0.2, 0.1, 0.5, -0.4) Rho[upper.tri(Rho)] <- t(Rho)[upper.tri(Rho)] s <- c(1, 2, 3, 4) S <- diag(s, p) %*% Rho %*% diag(s, p) rownames(S) <- colnames(S) <- 1:p mvn_consistent_dimension <- function(mix, p) { s <- summary(mix) expect_numeric(s$mean, any.missing=FALSE, len=p) expect_matrix(s$cov, any.missing=FALSE, nrows=p, ncols=p) } test_that("Multivariate normal mixture has consistent dimensionality", { for(i in 1:(nrow(S)-1)) { p_sub <- 4-i S_sub <- S[-c(1:i), -c(1:i), drop=FALSE] mvn_consistent_dimension(mixmvnorm(c(1, rep(0, p_sub), S_sub), sigma=S_sub), p_sub) } }) test_that("Multivariate normal mixture has consistent dimension naming", { for(i in 1:(nrow(S)-1)) { p_sub <- 4-i S_sub <- S[-c(1:i), -c(1:i), drop=FALSE] m_sub <- rep(0, p_sub) dim_labels <- letters[1:p_sub] names(m_sub) <- dim_labels test_mix <- mixmvnorm(c(1, m_sub, S_sub), sigma=S_sub) ## now test that names are used consistently expect_equal(rownames(sigma(test_mix)), dim_labels) expect_equal(colnames(sigma(test_mix)), dim_labels) expect_equal(names(summary(test_mix)$mean), dim_labels) expect_equal(rownames(summary(test_mix)$cov), dim_labels) expect_equal(colnames(summary(test_mix)$cov), dim_labels) expect_equal(colnames(rmix(test_mix, 1)), dim_labels) } }) test_that("Multivariate normal mixture has consistent initialization", { p <- nrow(S) mv1 <- mixmvnorm(c(1, rep(0, p), S), sigma=S, param="ms") mv2 <- mixmvnorm(c(1, rep(0, p), 1), sigma=S, param="mn") mv3 <- mixmvnorm(c(1, rep(0, p), 2), sigma=S, param="mn") expect_equal(summary(mv1)$cov, S, tolerance=eps_lower) expect_equal(summary(mv2)$cov, S, tolerance=eps_lower) expect_equal(summary(mv3)$cov, S/2, tolerance=eps_lower) }) mvn_consistent_summaries <- function(mix, S=Nsamp_equant) { samp <- rmix(mix, S) m <- colMeans(samp) expect_equal(colMeans(samp), summary(mix)$mean, tolerance=eps) expect_equal(cov(samp), summary(mix)$cov, tolerance=eps) } test_that("Multivariate normal mixture has consistent summaries", { p <- nrow(S) mv1 <- mixmvnorm(c(1, rep(0, p), S), sigma=S, param="ms") mv2 <- mixmvnorm(c(1, rep(0, p), 1), sigma=S, param="mn") mv3 <- mixmvnorm(c(0.2, rep(0, p), 2), c(0.8, rep(1, p), 6), sigma=S, param="mn") mvn_consistent_summaries(mv1) mvn_consistent_summaries(mv2) mvn_consistent_summaries(mv3) })