Skip to content

Commit dcb3ead

Browse files
committed
Make large DOF infinite.
Change QMC routne for qrng with scrambling
1 parent b0c8640 commit dcb3ead

17 files changed

+76
-73
lines changed

DESCRIPTION

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Package: TruncatedNormal
22
Type: Package
33
Title: Truncated Multivariate Normal and Student Distributions
4-
Version: 2.2.4.0002
4+
Version: 2.3
55
Authors@R: c(person(given="Zdravko", family="Botev", role = "aut", email = "botev@unsw.edu.au", comment = c(ORCID = "0000-0001-9054-3452")), person(given="Leo", family="Belzile", role = c("aut", "cre"), email = "belzilel@gmail.com", comment = c(ORCID = "0000-0002-9135-014X")))
66
Description: A collection of functions to deal with the truncated univariate and multivariate normal and Student distributions, described in Botev (2017) <doi:10.1111/rssb.12162> and Botev and L'Ecuyer (2015) <doi:10.1109/WSC.2015.7408180>.
77
License: GPL-3
@@ -10,13 +10,14 @@ BugReports:
1010
Depends: R (>= 2.10)
1111
Imports:
1212
nleqslv,
13-
randtoolbox,
13+
qrng,
14+
spacefillr,
1415
alabama,
1516
Rcpp (>= 0.12.16)
1617
LinkingTo:
1718
Rcpp,
1819
RcppArmadillo
19-
RoxygenNote: 7.3.1
20+
RoxygenNote: 7.3.2
2021
VignetteBuilder: knitr
2122
Encoding: UTF-8
2223
Suggests:

NAMESPACE

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ export(tregress)
2929
importFrom(Rcpp,evalCpp)
3030
importFrom(alabama,"auglag")
3131
importFrom(nleqslv,nleqslv)
32-
importFrom(randtoolbox,sobol)
32+
importFrom(qrng,sobol)
33+
importFrom(spacefillr,generate_sobol_owen_set)
3334
importFrom(stats,"dnorm")
3435
importFrom(stats,"pnorm")
3536
importFrom(stats,"pt")

NEWS.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
## Version 2.4
1+
## Version 2.3
22

33
### Bug fixes
44

5+
- `rtnorm` now checks arguments length and throw an error rather than try to recycle mean and standard deviation vectors (issue #6).
56
- `rtnorm` recycles arguments when `ltrunc` or `rtrunc` are length 1.
6-
- `*tmvt` and `*tmvnorm` now check that the scale matrix `sigma` is symmetric, positive-definite and non-degenerate (issue #8)
7+
- `*tmvt` and `*tmvnorm` now check that the scale matrix `sigma` is symmetric, positive-definite and non-degenerate (issue #8) by default.
78
- `rmvnorm` returns a vector rather than an 1 by n matrix for the unidimensional setting.
9+
- Degrees of freedom larger than 350 are not treated as infinite, as the code suffers from overflow and returns missing values otherwise.
810

911
### Changes
1012

1113
- The package uses `tinytest` rather than `testthat` for unit tests.
12-
13-
## Version 2.3
14-
15-
- `rtnorm` now checks arguments length and throw an error rather than try to recycle mean and standard deviation vectors.
14+
- Since scrambling is disabled in `randtoolbox` (throwing warning messages), the package now relies on the `qrng` package and the Owen scrambling method from `spacefillr` package for quasi Monte Carlo.
15+
- Added alias in package documentation, as requested by Kurt Hornik.
1616

1717
## Version 2.2.2
1818

R/RcppExports.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ lnNpr <- function(a, b, check = TRUE) {
5454
.Call(`_TruncatedNormal_lnNpr`, a, b, check)
5555
}
5656

57-
#' Cholesky matrix decomposition with GGE ordering
57+
#' Cholesky matrix decomposition with GB ordering
5858
#'
5959
#' This function computes the Cholesky decomposition of a covariance matrix
6060
#' \code{Sigma} and returns a list containing the permuted bounds for integration.

R/mvNqmc.R

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
#' where \eqn{X} is a zero-mean multivariate normal vector
66
#' with covariance matrix \eqn{\Sigma}, that is, \eqn{X} is drawn from \eqn{N(0,\Sigma)}.
77
#' Infinite values for vectors \eqn{u} and \eqn{l} are accepted.
8-
#' The Monte Carlo method uses sample size \eqn{n}:
8+
#' The Monte Carlo method uses sample size \eqn{n}:
99
#' the larger \eqn{n}, the smaller the relative error of the estimator.
1010
#'
1111
#' @inheritParams mvNcdf
12+
#' @importFrom spacefillr generate_sobol_owen_set
1213
#' @details Suppose you wish to estimate Pr\eqn{(l<AX<u)},
1314
#' where \eqn{A} is a full rank matrix
1415
#' and \eqn{X} is drawn from \eqn{N(\mu,\Sigma)}, then you simply compute
@@ -37,10 +38,10 @@
3738
#' @export
3839
#' @keywords internal
3940
#' @examples
40-
#' d <- 15
41+
#' d <- 15
4142
#' l <- 1:d
4243
#' u <- rep(Inf, d)
43-
#' Sig <- matrix(rnorm(d^2), d, d)*2
44+
#' Sig <- matrix(rnorm(d^2), d, d)*2
4445
#' Sig <- Sig %*% t(Sig)
4546
#' mvNqmc(l, u, Sig, 1e4) # compute the probability
4647
mvNqmc <- function(l, u, Sig, n = 1e5){
@@ -53,10 +54,10 @@ mvNqmc <- function(l, u, Sig, n = 1e5){
5354
return(list(prob = exp(lnNpr(a = l / sqrt(Sig[1]), b = u / sqrt(Sig[1]))), err = NA, relErr = NA, upbnd = NA))
5455
}
5556
# Cholesky decomposition of matrix
56-
out <- cholperm(Sig, l, u)
57-
L <- out$L
58-
l <- out$l
59-
u <- out$u
57+
out <- cholperm(Sig, l, u)
58+
L <- out$L
59+
l <- out$l
60+
u <- out$u
6061
D <- diag(L)
6162
if (any(D < 1e-10)){
6263
warning('Method may fail as covariance matrix is singular!')
@@ -67,11 +68,11 @@ mvNqmc <- function(l, u, Sig, n = 1e5){
6768
diag(L) <- rep(0, d) # remove diagonal
6869
# find optimal tilting parameter via non-linear equation solver
6970
x0 <- rep(0, 2 * length(l) - 2)
70-
solvneq <- nleqslv::nleqslv(x0,
71-
fn = gradpsi,
71+
solvneq <- nleqslv::nleqslv(x0,
72+
fn = gradpsi,
7273
jac = jacpsi,
73-
L = L, l = l, u = u,
74-
global = "pwldog",
74+
L = L, l = l, u = u,
75+
global = "pwldog",
7576
method = "Newton",
7677
control = list(maxit = 500L))
7778
xmu <- solvneq$x
@@ -82,7 +83,7 @@ mvNqmc <- function(l, u, Sig, n = 1e5){
8283
}
8384
# assign saddlepoint x* and mu*
8485
x <- xmu[1:(d-1)]
85-
mu <- xmu[d:(2*d-2)]
86+
mu <- xmu[d:(2*d-2)]
8687
# check the constraints
8788
if(any((out$L %*% c(x,0) - out$u)[-d] > 0, (-out$L %*% c(x,0) + out$l)[-d] > 0)){
8889
warning("Solution to exponential tilting problem using Powell's dogleg method \n does not lie in convex set l < Lx < u.")
@@ -103,9 +104,9 @@ mvNqmc <- function(l, u, Sig, n = 1e5){
103104
control.outer = list(trace = FALSE,method="nlminb"))
104105
if(solvneqc$convergence == 0){
105106
x <- solvneqc$par[1:(d-1)]
106-
mu <- solvneqc$par[d:(2*d-2)]
107+
mu <- solvneqc$par[d:(2*d-2)]
107108
} else{
108-
stop('Did not find a solution to the nonlinear system in `mvNqmc`!')
109+
stop('Did not find a solution to the nonlinear system in `mvNqmc`!')
109110
}
110111
}
111112
p <- rep(0, 12)

R/mvTcdf.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
#' @export
4343
#' @keywords internal
4444
#' @author \code{Matlab} code by Zdravko Botev, \code{R} port by Leo Belzile
45-
#' @importFrom randtoolbox sobol
45+
#' @importFrom qrng sobol
4646
mvTcdf <- function(l, u, Sig, df, n = 1e5){
4747
d <- length(l)
4848
if (length(u) != d | d != sqrt(length(Sig)) | any(l > u)) {

R/mvnprqmc.R

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@ mvnprqmc <- function(n, L, l, u, mu){
99
if(n*(d-1) > 2e7){
1010
warning("High memory requirements for storage of QMC sequence\nConsider reducing n")
1111
}
12-
x <- as.matrix(randtoolbox::sobol(n, dim = d-1, init =TRUE, scrambling = 1, seed=ceiling(1e6*runif(1))))
12+
x <- as.matrix(qrng::sobol(n,
13+
d = d-1,
14+
randomize = "Owen",
15+
seed = ceiling(1e6*runif(1))))
1316
## Similar option in fOptions package. Problem: sobol sequence can overflow (values above 1).
1417
# x <- qrng::sobol(n = n, d = d - 1, randomize = TRUE)
1518
p <- 0
1619
for (k in 1:(d-1)){
1720
# compute matrix multiplication L*Z
1821
if(k > 1){
19-
col <- crossprod(L[k, 1:(k-1)], Z[1:(k-1),])
22+
col <- crossprod(L[k, 1:(k-1)], Z[1:(k-1),])
2023
} else{
2124
col <- rep(0, n)
2225
}

R/mvtprqmc.R

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ mvtprqmc <- function(n, L, l, u, nu, mu){
1010
if(n*(d-1) > 2e7){
1111
warning("High memory requirements for storage of QMC sequence\nConsider reducing n")
1212
}
13-
x <- as.matrix(randtoolbox::sobol(n, dim = d - 1, init = TRUE, scrambling = 1, seed = ceiling(1e6 * runif(1))))
14-
# x <- as.matrix(qrng::sobol(n = n, d = d - 1, randomize = TRUE))
13+
# x <- as.matrix(randtoolbox::sobol(n, dim = d - 1, init = TRUE, scrambling = 1, seed = ceiling(1e6 * runif(1))))
14+
x <- as.matrix(qrng::sobol(n = n,
15+
d = d - 1,
16+
randomize = "digital.shift",
17+
seed = ceiling(1e6 * runif(1))))
1518
#Fixed 21.03.2018 to ensure that if d=2, no error returned
1619
# Monte Carlo uses 'n' samples;
1720
# precompute constants

R/tmvt.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ ptmvt <- function(q, mu, sigma, df, lb, ub, type = c("mc", "qmc"), log = FALSE,
145145
}
146146
type <- match.arg(type)
147147
df <- as.vector(df)[1]
148-
if(isTRUE(all.equal(df, 0)) || isTRUE(all.equal(df, Inf))){
148+
if(isTRUE(all.equal(df, 0)) || df > 350){
149149
return(ptmvnorm(q = q, mu = mu, sigma = sigma, lb = lb, ub = ub, B = B, log = log, type = type))
150150
}
151151
stopifnot(df > 0, length(mu) == ncol(sigma), nrow(sigma) == ncol(sigma), is.logical(log))

inst/tinytest/test_TruncatedNormal.R

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,31 @@ library(tinytest)
33
lb <- c(0, 0)
44
ub <- c(740.0, 76.2)
55
mu <- c(344.31293403, 62.6937066)
6-
sigma <- matrix(c(36407.0005966, -1167.50805662, -1167.50805662, 290.76915744), 2, 2)
6+
sigma <- matrix(c(36407.0005966, -1167.50805662, -1167.50805662, 290.76915744),
7+
nrow = 2, ncol = 2)
78
df1 <- 3
89
df2 <- 300
910
x = c(100, 50)
1011
set.seed(1234)
1112

12-
## "Truncated normal DF (MC versus QMC) give similar answers"
13+
## "Truncated normal DF (MC versus QMC) give similar answers"
1314

14-
expect_equal(ptmvnorm(x, mu=mu, sigma=sigma, lb=lb, ub=ub, log=FALSE, type="qmc"),
15-
ptmvnorm(x, mu=mu, sigma=sigma, lb=lb, ub=ub, log=FALSE, type="mc"),
15+
expect_equal(
16+
ptmvnorm(x, mu=mu, B = 1e5, sigma=sigma, lb=lb, ub=ub, log=FALSE, type="qmc"),
17+
ptmvnorm(x, mu=mu, B = 1e5, sigma=sigma, lb=lb, ub=ub, log=FALSE, type="mc"),
1618
tolerance = 1e-4)
17-
expect_equal(ptmvt(x, B = 1e5, sigma=sigma, df = 2, lb=lb, ub=ub, log=FALSE, type="mc"),
18-
ptmvt(x, B = 1e5, sigma=sigma, lb=lb, ub=ub, df = 2, log=FALSE, type="qmc"),
19+
expect_equal(
20+
ptmvt(x, B = 1e5, sigma=sigma, df = 2, lb=lb, ub=ub, log=FALSE, type="mc"),
21+
ptmvt(x, B = 1e5, sigma=sigma, lb=lb, ub=ub, df = 2, log=FALSE, type="qmc"),
1922
tolerance = 3e-3)
2023

2124

2225
## "Student with large df gives same answer as normal"
23-
24-
expect_equal(ptmvnorm(x, B = 1e6, sigma=sigma, lb=lb, ub=ub, log=FALSE, type="qmc"),
25-
ptmvt(x, B = 1e6, sigma=sigma, lb=lb, ub=ub, df = 300, log=FALSE, type="qmc"),
26+
27+
expect_equal(
28+
ptmvnorm(x, B = 1e6, sigma=sigma, lb=lb, ub=ub, log=FALSE, type="qmc"),
29+
ptmvt(x, B = 1e6, sigma=sigma, lb=lb, ub=ub, df = 300, log=FALSE,
30+
type="mc"),
2631
tolerance = 2e-3)
2732

2833

@@ -35,7 +40,7 @@ prob <- pmvnorm(lb = lower, ub = upper, mu = mean, sigma = corr)
3540

3641

3742
## "Univariate probabilities",{
38-
43+
3944
expect_equivalent(pmvnorm(lb = -Inf, ub = 3, mu = 2, sigma = 1), pnorm(3, mean = 2))
4045
expect_equivalent(pmvt(lb = -Inf, ub = 3, df = 2, mu = 0, sigma = 1), pt(3, 2))
4146

@@ -58,7 +63,7 @@ D <- 10
5863
muV <- 1:D
5964
Smat <- diag(0.5, D) + matrix(0.5, D, D)
6065
## "Expectation of (truncated) elliptical distributions"
61-
66+
6267
expect_equal(colMeans(rtmvnorm(n = B, sigma = Smat)),
6368
rep(0, D), tolerance = 5/sqrt(B))
6469
expect_equal(colMeans(rtmvnorm(n = B, mu = muV, sigma = Smat)),
@@ -69,22 +74,22 @@ Smat <- diag(0.5, D) + matrix(0.5, D, D)
6974
expect_equal(colMeans(rtmvt(n = B, lb = (1:D)/D, df = 3, ub = 2*(1:D)/D, mu = rep(0,D), sigma = diag(1, D))),
7075
mean_tt(lb = (1:D)/D, ub = 2*(1:D)/D, df = 3),
7176
tolerance = 5/sqrt(B))
72-
77+
7378

7479

7580
lb <- rnorm(n = D, mean = 0, sd = 10)
7681
ub <- lb + rgamma(D, shape = 4, rate = 1)
7782
## "Bounds of simulated variables"
78-
83+
7984
expect_true(isTRUE(all(apply(rtmvnorm(n = 1e4, lb = lb, ub = ub, mu = muV, 100*Smat), 2, min) > lb)))
8085
expect_true(isTRUE(all(apply(rtmvnorm(n = 1e4, lb = lb, ub = ub, mu = muV, 100*Smat), 2, min) < ub)))
8186
expect_true(isTRUE(all(apply(rtmvt(n = 1e4, df = 3, lb = lb, ub = ub, mu = muV, 100*Smat), 2, min) > lb)))
8287
expect_true(isTRUE(all(apply(rtmvt(n = 1e4, df = 3, lb = lb, ub = ub, mu = muV, 100*Smat), 2, min) < ub)))
83-
88+
8489

8590

8691
## "Bounds on distribution function beyond truncation points"
87-
92+
8893
expect_equal(ptmvnorm(q = ub + runif(D), lb = lb, ub = ub, mu = muV, Smat), 1)
8994
expect_equal(ptmvt(q = ub + runif(D), df = 2, lb = lb, ub = ub, mu = muV, Smat), 1)
9095
expect_equal(ptmvnorm(q = lb + c(-1, runif(D-1)), lb = lb, ub = ub, mu = muV/D, Smat), 0)
@@ -93,27 +98,29 @@ ub <- lb + rgamma(D, shape = 4, rate = 1)
9398

9499
pt <- rnorm(D)
95100
## "Untruncated density agrees with that in the mvtnorm package"
96-
97-
expect_equal(mvtnorm::dmvnorm(x = pt, mean = muV, sigma = Smat, log = TRUE),
101+
102+
expect_equal(mvtnorm::dmvnorm(x = pt, mean = muV, sigma = Smat, log = TRUE),
98103
TruncatedNormal::dtmvnorm(x = pt, mu = muV, lb = rep(-Inf, D), ub = rep(Inf, D), sigma = Smat, log = TRUE))
99-
expect_equal(mvtnorm::dmvt(x = pt, df = 2, delta = muV, sigma = Smat, log = TRUE),
104+
expect_equal(mvtnorm::dmvt(x = pt, df = 2, delta = muV, sigma = Smat, log = TRUE),
100105
TruncatedNormal::dtmvt(x = pt, df = 2, mu = muV, lb = rep(-Inf, D), ub = rep(Inf, D), sigma = Smat, log = TRUE))
101106

102107

103108

104109
pt <- rnorm(D)
105110
## "Untruncated density agrees with that in the mvtnorm package"
106-
107-
expect_equal(mvtnorm::dmvnorm(x = pt, mean = muV, sigma = Smat, log = TRUE),
111+
112+
expect_equal(mvtnorm::dmvnorm(x = pt, mean = muV, sigma = Smat, log = TRUE),
108113
TruncatedNormal::dtmvnorm(x = pt, mu = muV, lb = rep(-Inf, D), ub = rep(Inf, D), sigma = Smat, log = TRUE))
109-
expect_equal(mvtnorm::dmvt(x = pt, df = 2, delta = muV, sigma = Smat, log = TRUE),
114+
expect_equal(mvtnorm::dmvt(x = pt, df = 2, delta = muV, sigma = Smat, log = TRUE),
110115
TruncatedNormal::dtmvt(x = pt, df = 2, mu = muV, lb = rep(-Inf, D), ub = rep(Inf, D), sigma = Smat, log = TRUE))
111116

112117

113118
d <- 15
114119
sigma <- 0.5 * (diag(d) + matrix(1, d, d))
115120
## "Known probability"
116-
117-
expect_equivalent((d+1)*pmvnorm(sigma = sigma, lb = rep(0, d), type = "qmc", B = B),
118-
1, tolerance = 1/sqrt(B))
119-
121+
est <- pmvnorm(sigma = sigma, lb = rep(0, d), type = "qmc", B = 1e6)
122+
expect_equivalent(
123+
(d+1)*as.numeric(est),
124+
1,
125+
tolerance = attr(est, "relerr")*(d+1)*2)
126+

man/dot-cholpermGB.Rd

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/man.Rproj

Lines changed: 0 additions & 13 deletions
This file was deleted.

man/mvNqmc.Rd

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/RcppExports.o

-544 Bytes
Binary file not shown.

src/TruncatedNormal.so

-648 Bytes
Binary file not shown.

src/densities.o

-472 Bytes
Binary file not shown.

src/lnNpr_cholperm_Phinv.o

-664 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)