Consider the Monte Carlo simulation of a matrix of i.i.d. normal random variables. We will show how rTRNG can be used to perform a consistent (fair-playing) simulation of a subset of the variables and simulations.
We rely on the TRNG engines exposed to R as reference classes by rTRNG.
library(rTRNG)
The mcMatR
function below performs the full sequential Monte Carlo simulation of nrow
normal i.i.d. samples of ncol
variables using the yarn2
generator.
<- function(nrow, ncol) {
mcMatR <- yarn2$new(12358)
r <- matrix(rnorm_trng(nrow * ncol, engine = r),
M nrow = nrow, ncol = ncol, byrow = TRUE)
M }
A second function mcSubMatR
relies on jump
and split
operations to perform only a chunk [startRow
, endRow
] of simulations for a subset subCols
of the variables.
<- function(nrow, ncol,
mcSubMatR
startRow, endRow, subCols) {<- yarn2$new(12358)
r $jump((startRow - 1)*ncol)
r<- endRow - startRow + 1
nSubCols <- matrix(0.0, nrow, ncol)
S :endRow, subCols] <-
S[startRowvapply(subCols,
function(j) {
= r$copy()
rj $split(ncol, j)
rjrnorm_trng(nSubCols, engine = rj)
},FUN.VALUE = numeric(nSubCols))
S }
The parallel nature of the yarn2
generator ensures the sub-simulation obtained via mcSubMatR
is consistent with the full sequential simulation.
<- 9
rows <- 5
cols <- mcMatR(rows, cols)
M <- 4
startRow <- 6
endRow <- c(2, 4:5)
subCols <- mcSubMatR(rows, cols,
S
startRow, endRow, subCols)identical(M[startRow:endRow, subCols],
:endRow, subCols])
S[startRow## [1] TRUE
M.1 | M.2 | M.3 | M.4 | M.5 | S.1 | S.2 | S.3 | S.4 | S.5 | |
---|---|---|---|---|---|---|---|---|---|---|
1 | 0.20256 | -0.41401 | -0.76749 | -0.33344 | 0.10718 | 0 | 0.00000 | 0 | 0.00000 | 0.00000 |
2 | -2.76439 | -1.15524 | -0.39394 | -1.16604 | 1.61759 | 0 | 0.00000 | 0 | 0.00000 | 0.00000 |
3 | -0.42199 | -1.13148 | -0.30448 | 0.12741 | -0.16111 | 0 | 0.00000 | 0 | 0.00000 | 0.00000 |
4 | -0.94448 | -1.86384 | -1.03244 | -0.41155 | 1.31009 | 0 | -1.86384 | 0 | -0.41155 | 1.31009 |
5 | -0.09614 | -0.16366 | -0.31964 | 0.87053 | 0.77996 | 0 | -0.16366 | 0 | 0.87053 | 0.77996 |
6 | 1.42049 | -0.73062 | -1.19459 | -1.02146 | 0.07202 | 0 | -0.73062 | 0 | -1.02146 | 0.07202 |
7 | -0.61202 | 0.02906 | -0.29100 | 0.10095 | -0.74647 | 0 | 0.00000 | 0 | 0.00000 | 0.00000 |
8 | 1.10246 | -0.50507 | 0.01286 | 0.63140 | -1.28893 | 0 | 0.00000 | 0 | 0.00000 | 0.00000 |
9 | -0.08732 | -0.32545 | 0.29099 | 0.62003 | -0.94617 | 0 | 0.00000 | 0 | 0.00000 | 0.00000 |
We now use Rcpp to define functions mcMatRcpp
and mcSubMatRcpp
for the full sequential simulation and the sub-simulation, respectively. The Rcpp::depends
attribute makes sure the TRNG library and headers shipped with rTRNG are available to the C++ code. Moreover, Rcpp::plugins(cpp11)
enforces the C++11 standard required by TRNG >= 4.22.
// [[Rcpp::depends(rTRNG)]]
// TRNG >= 4.22 requires C++11
// [[Rcpp::plugins(cpp11)]]
#include <Rcpp.h>
#include <trng/normal_dist.hpp>
#include <trng/yarn2.hpp>
using namespace Rcpp;
// [[Rcpp::export]]
const int nrow, const int ncol) {
NumericMatrix mcMatRcpp(
NumericMatrix M(nrow, ncol);12358);
trng::yarn2 r(0.0, 1.0);
trng::normal_dist<> normal(for (int i = 0; i < nrow; i++) {
for (int j = 0; j < ncol; j++) {
M(i, j) = normal(r);
}
}return M;
}
// [[Rcpp::export]]
const int nrow, const int ncol,
NumericMatrix mcSubMatRcpp(const int startRow,
const int endRow,
const IntegerVector subCols) {
NumericMatrix M(nrow, ncol);12358), rj;
trng::yarn2 r(0.0, 1.0);
trng::normal_dist<> normal(1) * ncol);
r.jump((startRow - for (IntegerVector::const_iterator jSub = subCols.begin();
jSub < subCols.end(); jSub++) {int j = *jSub - 1;
rj = r;
rj.split(ncol, j);for (int i = startRow - 1; i < endRow; i++) {
M(i, j) = normal(rj);
}
}return M;
}
As seen above for the R case, consistency of the simulation obtained via mcSubMatRcpp
with the full sequential simulation is guaranteed.
<- 9
rows <- 5
cols <- 4
startRow <- 6
endRow <- c(2, 4:5)
subCols <- mcMatRcpp(rows, cols)
M <- mcSubMatRcpp(rows, cols, startRow, endRow, subCols)
S identical(M[startRow:endRow, subCols],
:endRow, subCols])
S[startRow## [1] TRUE
M.1 | M.2 | M.3 | M.4 | M.5 | S.1 | S.2 | S.3 | S.4 | S.5 | |
---|---|---|---|---|---|---|---|---|---|---|
1 | 0.20256 | -0.41401 | -0.76749 | -0.33344 | 0.10718 | 0 | 0.00000 | 0 | 0.00000 | 0.00000 |
2 | -2.76439 | -1.15524 | -0.39394 | -1.16604 | 1.61759 | 0 | 0.00000 | 0 | 0.00000 | 0.00000 |
3 | -0.42199 | -1.13148 | -0.30448 | 0.12741 | -0.16111 | 0 | 0.00000 | 0 | 0.00000 | 0.00000 |
4 | -0.94448 | -1.86384 | -1.03244 | -0.41155 | 1.31009 | 0 | -1.86384 | 0 | -0.41155 | 1.31009 |
5 | -0.09614 | -0.16366 | -0.31964 | 0.87053 | 0.77996 | 0 | -0.16366 | 0 | 0.87053 | 0.77996 |
6 | 1.42049 | -0.73062 | -1.19459 | -1.02146 | 0.07202 | 0 | -0.73062 | 0 | -1.02146 | 0.07202 |
7 | -0.61202 | 0.02906 | -0.29100 | 0.10095 | -0.74647 | 0 | 0.00000 | 0 | 0.00000 | 0.00000 |
8 | 1.10246 | -0.50507 | 0.01286 | 0.63140 | -1.28893 | 0 | 0.00000 | 0 | 0.00000 | 0.00000 |
9 | -0.08732 | -0.32545 | 0.29099 | 0.62003 | -0.94617 | 0 | 0.00000 | 0 | 0.00000 | 0.00000 |
The same technique used for generating a sub-set of the simulations can be exploited for performing a parallel simulation in C++. We can embed the body of mcSubMatRcpp
above into an RcppParallel::Worker
for performing chunks of Monte Carlo simulations in parallel, for any subset subCols
of the variables.
struct MCMatWorker : public Worker
{double> M;
RMatrix<const RVector<int> subCols;
// constructor
MCMatWorker(NumericMatrix M,const IntegerVector subCols)
: M(M), subCols(subCols) {}
// operator processing an exclusive range of indices
void operator()(std::size_t begin, std::size_t end) {
12358), rj;
trng::yarn2 r(0.0, 1.0);
trng::normal_dist<> normal(int)begin * M.ncol());
r.jump((for (IntegerVector::const_iterator jSub = subCols.begin();
jSub < subCols.end(); jSub++) {int j = *jSub - 1;
rj = r;
rj.split(M.ncol(), j);for (int i = (int)begin; i < (int)end; i++) {
M(i, j) = normal(rj);
}
}
}
};// [[Rcpp::export]]
const int nrow, const int ncol,
NumericMatrix mcMatRcppParallel(const IntegerVector subCols) {
NumericMatrix M(nrow, ncol);
MCMatWorker w(M, subCols);0, M.nrow(), w);
parallelFor(return M;
}
The parallel nature of the yarn2
generator ensures the parallel simulation is playing fair, i.e. is consistent with the sequential simulation.
<- mcMatRcpp(rows, cols)
M <- mcMatRcppParallel(rows, cols, seq_len(ncol(M)))
Mp identical(M, Mp)
## [1] TRUE
Similarly, we can achieve a consistent parallel simulation of a subset of the variables only.
<- mcMatRcppParallel(rows, cols, subCols)
Sp identical(M[, subCols],
Sp[, subCols])## [1] TRUE
M.1 | M.2 | M.3 | M.4 | M.5 | Sp.1 | Sp.2 | Sp.3 | Sp.4 | Sp.5 | |
---|---|---|---|---|---|---|---|---|---|---|
1 | 0.20256 | -0.41401 | -0.76749 | -0.33344 | 0.10718 | 0 | -0.41401 | 0 | -0.33344 | 0.10718 |
2 | -2.76439 | -1.15524 | -0.39394 | -1.16604 | 1.61759 | 0 | -1.15524 | 0 | -1.16604 | 1.61759 |
3 | -0.42199 | -1.13148 | -0.30448 | 0.12741 | -0.16111 | 0 | -1.13148 | 0 | 0.12741 | -0.16111 |
4 | -0.94448 | -1.86384 | -1.03244 | -0.41155 | 1.31009 | 0 | -1.86384 | 0 | -0.41155 | 1.31009 |
5 | -0.09614 | -0.16366 | -0.31964 | 0.87053 | 0.77996 | 0 | -0.16366 | 0 | 0.87053 | 0.77996 |
6 | 1.42049 | -0.73062 | -1.19459 | -1.02146 | 0.07202 | 0 | -0.73062 | 0 | -1.02146 | 0.07202 |
7 | -0.61202 | 0.02906 | -0.29100 | 0.10095 | -0.74647 | 0 | 0.02906 | 0 | 0.10095 | -0.74647 |
8 | 1.10246 | -0.50507 | 0.01286 | 0.63140 | -1.28893 | 0 | -0.50507 | 0 | 0.63140 | -1.28893 |
9 | -0.08732 | -0.32545 | 0.29099 | 0.62003 | -0.94617 | 0 | -0.32545 | 0 | 0.62003 | -0.94617 |