From 43b3a48ce7363fc283e17ab553ad79d4a48ccbd1 Mon Sep 17 00:00:00 2001 From: Kyle Butts Date: Sat, 1 Jul 2023 07:54:25 -0600 Subject: [PATCH 01/13] Initial attempt at dqrrademacher --- DESCRIPTION | 2 +- NAMESPACE | 1 + R/RcppExports.R | 17 +++++++++++++ R/dqset.seed.R | 9 +++++-- inst/include/dqrng_RcppExports.h | 20 ++++++++++++++++ man/dqrng-functions.Rd | 9 +++++-- man/dqrng-package.Rd | 14 +---------- man/dqrrademacher.Rd | 21 ++++++++++++++++ man/dqsample.Rd | 2 +- src/RcppExports.cpp | 41 ++++++++++++++++++++++++++++++++ src/dqrng.cpp | 41 ++++++++++++++++++++++++++++++++ 11 files changed, 158 insertions(+), 19 deletions(-) create mode 100644 man/dqrrademacher.Rd diff --git a/DESCRIPTION b/DESCRIPTION index fbc24e6..bc24f3b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -27,7 +27,7 @@ License: AGPL-3 | file LICENSE Depends: R (>= 3.1.0) Imports: Rcpp (>= 0.12.16) LinkingTo: Rcpp, BH (>= 1.64.0-1), sitmo (>= 2.0.0) -RoxygenNote: 7.1.1 +RoxygenNote: 7.2.3 Suggests: testthat, knitr, diff --git a/NAMESPACE b/NAMESPACE index 3743bad..c26a37e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -3,6 +3,7 @@ export(dqRNGkind) export(dqrexp) export(dqrnorm) +export(dqrrademacher) export(dqrunif) export(dqsample) export(dqsample.int) diff --git a/R/RcppExports.R b/R/RcppExports.R index cd8b55b..32284cb 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -41,6 +41,23 @@ rexp <- function(rate = 1.0) { .Call(`_dqrng_rexp`, rate) } +#' Sample Rademacher distribution really fast +#' +#' @description This uses a fancy trick to draw Rademacher weights very +#' quickly. To do so, the function draws from 1:(2^31 - 1), and then +#' uses each bit of the integer to determine 31 values of 0/1. This +#' allows for 31 Rademacher random variables to be drawn per random draw. +#' Taking those bits * 2 - 1 gives the Rademacher random variables. +#' +#' @param n Integer, number of random variables to draw +#' +#' @return integer vector of length n with values -1 or 1 +#' +#' @export +dqrrademacher <- function(n) { + .Call(`_dqrng_dqrrademacher`, n) +} + dqsample_int <- function(m, n, replace = FALSE, probs = NULL, offset = 0L) { .Call(`_dqrng_dqsample_int`, m, n, replace, probs, offset) } diff --git a/R/dqset.seed.R b/R/dqset.seed.R index 2cb48dc..1443d19 100644 --- a/R/dqset.seed.R +++ b/R/dqset.seed.R @@ -5,7 +5,12 @@ #' according to a uniform, normal and exponential distribution. These #' functions are modeled after the \code{base} functions #' \code{\link{set.seed}}, \code{\link{RNGkind}}, \code{\link{runif}}, -#' \code{\link{rnorm}}, and \code{\link{rexp}}. +#' \code{\link{rnorm}}, and \code{\link{rexp}}. +#' +#' \code{dqrrademacher} uses a fast algorithm to generate random +#' Rademacher variables (-1 and 1 with equal probability). To do so, it +#' generates a random 64 bit integer and then uses each bit to generate +#' a 0/1 variable. This generates 64 integers per random number generation. #' #' @param seed integer scalar to seed the random number generator, or an integer vector of length 2 representing a 64-bit seed. Maybe \code{NULL}, see details. #' @param stream integer used for selecting the RNG stream; either a scalar or a vector of length 2 @@ -18,7 +23,7 @@ #' @param sd standard deviation of the normal distribution #' @param rate rate of the exponential distribution #' -#' @return \code{dqrunif}, \code{dqrnorm}, and \code{dqrexp} return a numeric vector of length \code{n}. +#' @return \code{dqrunif}, \code{dqrnorm}, \code{dqrexp}, and \code{dqrrademacher} return a numeric vector of length \code{n}. #' #' @details Supported RNG kinds: #' \describe{ diff --git a/inst/include/dqrng_RcppExports.h b/inst/include/dqrng_RcppExports.h index 2a6e22d..50d21e4 100644 --- a/inst/include/dqrng_RcppExports.h +++ b/inst/include/dqrng_RcppExports.h @@ -182,6 +182,26 @@ namespace dqrng { return Rcpp::as(rcpp_result_gen); } + inline Rcpp::IntegerVector dqrrademacher(size_t n) { + typedef SEXP(*Ptr_dqrrademacher)(SEXP); + static Ptr_dqrrademacher p_dqrrademacher = NULL; + if (p_dqrrademacher == NULL) { + validateSignature("Rcpp::IntegerVector(*dqrrademacher)(size_t)"); + p_dqrrademacher = (Ptr_dqrrademacher)R_GetCCallable("dqrng", "_dqrng_dqrrademacher"); + } + RObject rcpp_result_gen; + { + rcpp_result_gen = p_dqrrademacher(Shield(Rcpp::wrap(n))); + } + if (rcpp_result_gen.inherits("interrupted-error")) + throw Rcpp::internal::InterruptedException(); + if (Rcpp::internal::isLongjumpSentinel(rcpp_result_gen)) + throw Rcpp::LongjumpException(rcpp_result_gen); + if (rcpp_result_gen.inherits("try-error")) + throw Rcpp::exception(Rcpp::as(rcpp_result_gen).c_str()); + return Rcpp::as(rcpp_result_gen); + } + inline Rcpp::IntegerVector dqsample_int(int m, int n, bool replace = false, Rcpp::Nullable probs = R_NilValue, int offset = 0) { typedef SEXP(*Ptr_dqsample_int)(SEXP,SEXP,SEXP,SEXP,SEXP); static Ptr_dqsample_int p_dqsample_int = NULL; diff --git a/man/dqrng-functions.Rd b/man/dqrng-functions.Rd index ca5e266..0bee775 100644 --- a/man/dqrng-functions.Rd +++ b/man/dqrng-functions.Rd @@ -40,7 +40,7 @@ dqset.seed(seed, stream = NULL) \item{stream}{integer used for selecting the RNG stream; either a scalar or a vector of length 2} } \value{ -\code{dqrunif}, \code{dqrnorm}, and \code{dqrexp} return a numeric vector of length \code{n}. +\code{dqrunif}, \code{dqrnorm}, \code{dqrexp}, and \code{dqrrademacher} return a numeric vector of length \code{n}. } \description{ The \code{dqrng} package provides several fast random number @@ -48,7 +48,12 @@ The \code{dqrng} package provides several fast random number according to a uniform, normal and exponential distribution. These functions are modeled after the \code{base} functions \code{\link{set.seed}}, \code{\link{RNGkind}}, \code{\link{runif}}, - \code{\link{rnorm}}, and \code{\link{rexp}}. + \code{\link{rnorm}}, and \code{\link{rexp}}. + + \code{dqrrademacher} uses a fast algorithm to generate random + Rademacher variables (-1 and 1 with equal probability). To do so, it + generates a random 64 bit integer and then uses each bit to generate + a 0/1 variable. This generates 64 integers per random number generation. } \details{ Supported RNG kinds: diff --git a/man/dqrng-package.Rd b/man/dqrng-package.Rd index 89d6000..372c193 100644 --- a/man/dqrng-package.Rd +++ b/man/dqrng-package.Rd @@ -6,19 +6,7 @@ \alias{dqrng-package} \title{dqrng: Fast Pseudo Random Number Generators} \description{ -Several fast random number generators are provided as C++ - header only libraries: The PCG family by O'Neill (2014 - ) as well as - Xoroshiro128+ and Xoshiro256+ by Blackman and Vigna (2018 - ). In addition fast functions for generating random - numbers according to a uniform, normal and exponential distribution - are included. The latter two use the Ziggurat algorithm originally - proposed by Marsaglia and Tsang (2000, ). - These functions are exported to R and as a C++ interface and are - enabled for use with the default 64 bit generator from the PCG family, - Xoroshiro128+ and Xoshiro256+ as well as the 64 bit version of the 20 rounds - Threefry engine (Salmon et al., 2011 ) as - provided by the package 'sitmo'. +Several fast random number generators are provided as C++ header only libraries: The PCG family by O'Neill (2014 \url{https://www.cs.hmc.edu/tr/hmc-cs-2014-0905.pdf}) as well as Xoroshiro128+ and Xoshiro256+ by Blackman and Vigna (2018 \href{https://arxiv.org/abs/1805.01407}{arXiv:1805.01407}). In addition fast functions for generating random numbers according to a uniform, normal and exponential distribution are included. The latter two use the Ziggurat algorithm originally proposed by Marsaglia and Tsang (2000, \doi{10.18637/jss.v005.i08}). These functions are exported to R and as a C++ interface and are enabled for use with the default 64 bit generator from the PCG family, Xoroshiro128+ and Xoshiro256+ as well as the 64 bit version of the 20 rounds Threefry engine (Salmon et al., 2011 \doi{10.1145/2063384.2063405}) as provided by the package 'sitmo'. } \seealso{ Useful links: diff --git a/man/dqrrademacher.Rd b/man/dqrrademacher.Rd new file mode 100644 index 0000000..b8860a5 --- /dev/null +++ b/man/dqrrademacher.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/RcppExports.R +\name{dqrrademacher} +\alias{dqrrademacher} +\title{Sample Rademacher distribution really fast} +\usage{ +dqrrademacher(n) +} +\arguments{ +\item{n}{Integer, number of random variables to draw} +} +\value{ +integer vector of length n with values -1 or 1 +} +\description{ +This uses a fancy trick to draw Rademacher weights very + quickly. To do so, the function draws from 1:(2^31 - 1), and then + uses each bit of the integer to determine 31 values of 0/1. This + allows for 31 Rademacher random variables to be drawn per random draw. + Taking those bits * 2 - 1 gives the Rademacher random variables. +} diff --git a/man/dqsample.Rd b/man/dqsample.Rd index 5e56bbd..4c9a386 100644 --- a/man/dqsample.Rd +++ b/man/dqsample.Rd @@ -24,5 +24,5 @@ dqsample.int(n, size = n, replace = FALSE, prob = NULL) Unbiased Random Samples and Permutations } \seealso{ -\code{link{sample}} and \code{\link{sample.int}} +\code{\link{sample}} and \code{\link{sample.int}} } diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index 63c21c8..b0e3944 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -8,6 +8,11 @@ using namespace Rcpp; +#ifdef RCPP_USE_GLOBAL_ROSTREAM +Rcpp::Rostream& Rcpp::Rcout = Rcpp::Rcpp_cout_get(); +Rcpp::Rostream& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get(); +#endif + // dqset_seed void dqset_seed(Rcpp::Nullable seed, Rcpp::Nullable stream); static SEXP _dqrng_dqset_seed_try(SEXP seedSEXP, SEXP streamSEXP) { @@ -279,6 +284,39 @@ RcppExport SEXP _dqrng_rexp(SEXP rateSEXP) { UNPROTECT(1); return rcpp_result_gen; } +// dqrrademacher +Rcpp::IntegerVector dqrrademacher(size_t n); +static SEXP _dqrng_dqrrademacher_try(SEXP nSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::traits::input_parameter< size_t >::type n(nSEXP); + rcpp_result_gen = Rcpp::wrap(dqrrademacher(n)); + return rcpp_result_gen; +END_RCPP_RETURN_ERROR +} +RcppExport SEXP _dqrng_dqrrademacher(SEXP nSEXP) { + SEXP rcpp_result_gen; + { + rcpp_result_gen = PROTECT(_dqrng_dqrrademacher_try(nSEXP)); + } + Rboolean rcpp_isInterrupt_gen = Rf_inherits(rcpp_result_gen, "interrupted-error"); + if (rcpp_isInterrupt_gen) { + UNPROTECT(1); + Rf_onintr(); + } + bool rcpp_isLongjump_gen = Rcpp::internal::isLongjumpSentinel(rcpp_result_gen); + if (rcpp_isLongjump_gen) { + Rcpp::internal::resumeJump(rcpp_result_gen); + } + Rboolean rcpp_isError_gen = Rf_inherits(rcpp_result_gen, "try-error"); + if (rcpp_isError_gen) { + SEXP rcpp_msgSEXP_gen = Rf_asChar(rcpp_result_gen); + UNPROTECT(1); + Rf_error(CHAR(rcpp_msgSEXP_gen)); + } + UNPROTECT(1); + return rcpp_result_gen; +} // dqsample_int Rcpp::IntegerVector dqsample_int(int m, int n, bool replace, Rcpp::Nullable probs, int offset); static SEXP _dqrng_dqsample_int_try(SEXP mSEXP, SEXP nSEXP, SEXP replaceSEXP, SEXP probsSEXP, SEXP offsetSEXP) { @@ -378,6 +416,7 @@ static int _dqrng_RcppExport_validate(const char* sig) { signatures.insert("double(*rnorm)(double,double)"); signatures.insert("Rcpp::NumericVector(*dqrexp)(size_t,double)"); signatures.insert("double(*rexp)(double)"); + signatures.insert("Rcpp::IntegerVector(*dqrrademacher)(size_t)"); signatures.insert("Rcpp::IntegerVector(*dqsample_int)(int,int,bool,Rcpp::Nullable,int)"); signatures.insert("Rcpp::NumericVector(*dqsample_num)(double,double,bool,Rcpp::Nullable,int)"); } @@ -394,6 +433,7 @@ RcppExport SEXP _dqrng_RcppExport_registerCCallable() { R_RegisterCCallable("dqrng", "_dqrng_rnorm", (DL_FUNC)_dqrng_rnorm_try); R_RegisterCCallable("dqrng", "_dqrng_dqrexp", (DL_FUNC)_dqrng_dqrexp_try); R_RegisterCCallable("dqrng", "_dqrng_rexp", (DL_FUNC)_dqrng_rexp_try); + R_RegisterCCallable("dqrng", "_dqrng_dqrrademacher", (DL_FUNC)_dqrng_dqrrademacher_try); R_RegisterCCallable("dqrng", "_dqrng_dqsample_int", (DL_FUNC)_dqrng_dqsample_int_try); R_RegisterCCallable("dqrng", "_dqrng_dqsample_num", (DL_FUNC)_dqrng_dqsample_num_try); R_RegisterCCallable("dqrng", "_dqrng_RcppExport_validate", (DL_FUNC)_dqrng_RcppExport_validate); @@ -409,6 +449,7 @@ static const R_CallMethodDef CallEntries[] = { {"_dqrng_rnorm", (DL_FUNC) &_dqrng_rnorm, 2}, {"_dqrng_dqrexp", (DL_FUNC) &_dqrng_dqrexp, 2}, {"_dqrng_rexp", (DL_FUNC) &_dqrng_rexp, 1}, + {"_dqrng_dqrrademacher", (DL_FUNC) &_dqrng_dqrrademacher, 1}, {"_dqrng_dqsample_int", (DL_FUNC) &_dqrng_dqsample_int, 5}, {"_dqrng_dqsample_num", (DL_FUNC) &_dqrng_dqsample_num, 5}, {"_dqrng_generateSeedVectors", (DL_FUNC) &_dqrng_generateSeedVectors, 2}, diff --git a/src/dqrng.cpp b/src/dqrng.cpp index 205916b..cbf3130 100644 --- a/src/dqrng.cpp +++ b/src/dqrng.cpp @@ -41,6 +41,7 @@ dqrng::normal_distribution normal{}; generator rnorm_impl = [] () {return normal(*rng);}; dqrng::exponential_distribution exponential{}; generator rexp_impl = [] () {return exponential(*rng);}; +auto ruint64t_impl = [] () {return static_cast((*rng)());}; } // [[Rcpp::interfaces(r, cpp)]] @@ -145,6 +146,46 @@ double rexp(double rate = 1.0) { return rexp_impl(); } +//' Sample Rademacher distribution really fast +//' +//' @description This uses a fancy trick to draw Rademacher weights very +//' quickly. To do so, the function draws from 1:(2^31 - 1), and then +//' uses each bit of the integer to determine 31 values of 0/1. This +//' allows for 31 Rademacher random variables to be drawn per random draw. +//' Taking those bits * 2 - 1 gives the Rademacher random variables. +//' +//' @param n Integer, number of random variables to draw +//' +//' @return integer vector of length n with values -1 or 1 +//' +//' @export +// [[Rcpp::export(rng = false)]] +Rcpp::IntegerVector dqrrademacher(size_t n) { + size_t n_ints = ceil(n / 31.0); + std::vector rand_ints(n_ints); + std::generate(rand_ints.begin(), rand_ints.end(), ruint64t_impl); + + Rcpp::IntegerVector res(n); + + int k = 0; + for (int i = 0; i < n_ints - 1; ++i) { + uint64_t curr = rand_ints[i]; + + for (int j = 31; j >= 0; j--) { + res[k] = ((curr >> j) & 1) * 2 - 1; + k++; + } + } + + uint64_t curr = rand_ints[n_ints]; + for (int j = 31; j >= 31 - (n % 31); j--) { + res[k] = ((curr >> j) & 1) * 2 - 1; + k++; + } + + return res; +} + // code for sampling namespace dqrng { namespace sample { From 31e606b52dfddf955779cc17e9308e2e508443a8 Mon Sep 17 00:00:00 2001 From: Kyle Butts Date: Sat, 1 Jul 2023 08:03:23 -0600 Subject: [PATCH 02/13] Use full 64 bits --- src/dqrng.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dqrng.cpp b/src/dqrng.cpp index cbf3130..209b5d1 100644 --- a/src/dqrng.cpp +++ b/src/dqrng.cpp @@ -161,7 +161,7 @@ double rexp(double rate = 1.0) { //' @export // [[Rcpp::export(rng = false)]] Rcpp::IntegerVector dqrrademacher(size_t n) { - size_t n_ints = ceil(n / 31.0); + size_t n_ints = ceil(n / 63.0); std::vector rand_ints(n_ints); std::generate(rand_ints.begin(), rand_ints.end(), ruint64t_impl); @@ -171,14 +171,14 @@ Rcpp::IntegerVector dqrrademacher(size_t n) { for (int i = 0; i < n_ints - 1; ++i) { uint64_t curr = rand_ints[i]; - for (int j = 31; j >= 0; j--) { + for (int j = 63; j >= 0; j--) { res[k] = ((curr >> j) & 1) * 2 - 1; k++; } } uint64_t curr = rand_ints[n_ints]; - for (int j = 31; j >= 31 - (n % 31); j--) { + for (int j = 63; j >= 63 - (n % 63); j--) { res[k] = ((curr >> j) & 1) * 2 - 1; k++; } From b1c0c9cf3c85dbc367ddbf4b29eb7e96b02d35a6 Mon Sep 17 00:00:00 2001 From: Kyle Butts Date: Sat, 1 Jul 2023 13:43:49 -0600 Subject: [PATCH 03/13] Bug fixed segfaults - I think the problem was bitshifting too far --- R/RcppExports.R | 13 +------------ man/dqrng-functions.Rd | 3 +++ man/dqrrademacher.Rd | 21 --------------------- src/dqrng.cpp | 24 ++++++------------------ 4 files changed, 10 insertions(+), 51 deletions(-) delete mode 100644 man/dqrrademacher.Rd diff --git a/R/RcppExports.R b/R/RcppExports.R index 32284cb..bd5b554 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -41,18 +41,7 @@ rexp <- function(rate = 1.0) { .Call(`_dqrng_rexp`, rate) } -#' Sample Rademacher distribution really fast -#' -#' @description This uses a fancy trick to draw Rademacher weights very -#' quickly. To do so, the function draws from 1:(2^31 - 1), and then -#' uses each bit of the integer to determine 31 values of 0/1. This -#' allows for 31 Rademacher random variables to be drawn per random draw. -#' Taking those bits * 2 - 1 gives the Rademacher random variables. -#' -#' @param n Integer, number of random variables to draw -#' -#' @return integer vector of length n with values -1 or 1 -#' +#' @rdname dqrng-functions #' @export dqrrademacher <- function(n) { .Call(`_dqrng_dqrrademacher`, n) diff --git a/man/dqrng-functions.Rd b/man/dqrng-functions.Rd index 0bee775..e0f3983 100644 --- a/man/dqrng-functions.Rd +++ b/man/dqrng-functions.Rd @@ -5,6 +5,7 @@ \alias{dqrunif} \alias{dqrnorm} \alias{dqrexp} +\alias{dqrrademacher} \alias{dqset.seed} \title{R interface} \usage{ @@ -16,6 +17,8 @@ dqrnorm(n, mean = 0, sd = 1) dqrexp(n, rate = 1) +dqrrademacher(n) + dqset.seed(seed, stream = NULL) } \arguments{ diff --git a/man/dqrrademacher.Rd b/man/dqrrademacher.Rd deleted file mode 100644 index b8860a5..0000000 --- a/man/dqrrademacher.Rd +++ /dev/null @@ -1,21 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/RcppExports.R -\name{dqrrademacher} -\alias{dqrrademacher} -\title{Sample Rademacher distribution really fast} -\usage{ -dqrrademacher(n) -} -\arguments{ -\item{n}{Integer, number of random variables to draw} -} -\value{ -integer vector of length n with values -1 or 1 -} -\description{ -This uses a fancy trick to draw Rademacher weights very - quickly. To do so, the function draws from 1:(2^31 - 1), and then - uses each bit of the integer to determine 31 values of 0/1. This - allows for 31 Rademacher random variables to be drawn per random draw. - Taking those bits * 2 - 1 gives the Rademacher random variables. -} diff --git a/src/dqrng.cpp b/src/dqrng.cpp index 209b5d1..d83fadb 100644 --- a/src/dqrng.cpp +++ b/src/dqrng.cpp @@ -146,27 +146,16 @@ double rexp(double rate = 1.0) { return rexp_impl(); } -//' Sample Rademacher distribution really fast -//' -//' @description This uses a fancy trick to draw Rademacher weights very -//' quickly. To do so, the function draws from 1:(2^31 - 1), and then -//' uses each bit of the integer to determine 31 values of 0/1. This -//' allows for 31 Rademacher random variables to be drawn per random draw. -//' Taking those bits * 2 - 1 gives the Rademacher random variables. -//' -//' @param n Integer, number of random variables to draw -//' -//' @return integer vector of length n with values -1 or 1 -//' +//' @rdname dqrng-functions //' @export // [[Rcpp::export(rng = false)]] Rcpp::IntegerVector dqrrademacher(size_t n) { - size_t n_ints = ceil(n / 63.0); + size_t n_ints = ceil(n / 64.0); std::vector rand_ints(n_ints); std::generate(rand_ints.begin(), rand_ints.end(), ruint64t_impl); + Rcpp::Rcout << rand_ints[0] << std::endl; Rcpp::IntegerVector res(n); - int k = 0; for (int i = 0; i < n_ints - 1; ++i) { uint64_t curr = rand_ints[i]; @@ -177,10 +166,9 @@ Rcpp::IntegerVector dqrrademacher(size_t n) { } } - uint64_t curr = rand_ints[n_ints]; - for (int j = 63; j >= 63 - (n % 63); j--) { - res[k] = ((curr >> j) & 1) * 2 - 1; - k++; + uint64_t curr = rand_ints[n_ints - 1]; + for (int j = 63; k < n; ++k, --j) { + res[k] = ((curr >> j) & 1) * 2 - 1; } return res; From 89adc76441d7893732087ca1250de75313bb18a9 Mon Sep 17 00:00:00 2001 From: Kyle Butts Date: Sat, 1 Jul 2023 13:45:58 -0600 Subject: [PATCH 04/13] Delete Rcout call --- src/dqrng.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dqrng.cpp b/src/dqrng.cpp index d83fadb..f1dd34e 100644 --- a/src/dqrng.cpp +++ b/src/dqrng.cpp @@ -153,7 +153,6 @@ Rcpp::IntegerVector dqrrademacher(size_t n) { size_t n_ints = ceil(n / 64.0); std::vector rand_ints(n_ints); std::generate(rand_ints.begin(), rand_ints.end(), ruint64t_impl); - Rcpp::Rcout << rand_ints[0] << std::endl; Rcpp::IntegerVector res(n); int k = 0; From 94d30415b16971856744d9e78cad14c85bf599c8 Mon Sep 17 00:00:00 2001 From: Kyle Butts Date: Sat, 1 Jul 2023 13:50:06 -0600 Subject: [PATCH 05/13] Add simple test --- tests/testthat/test-rademacher.R | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 tests/testthat/test-rademacher.R diff --git a/tests/testthat/test-rademacher.R b/tests/testthat/test-rademacher.R new file mode 100644 index 0000000..92822dc --- /dev/null +++ b/tests/testthat/test-rademacher.R @@ -0,0 +1,8 @@ +context("rademacher") + +test_that("rademacher produces -1, 1", { + out <- dqrrademacher(10) + unique_out <- unique(out) + expect_identical(unique_out[order(unique_out)], c(-1, 1)) +}) + From 060fe04c23703bf7202f1327dcc15b7098a60bc7 Mon Sep 17 00:00:00 2001 From: Kyle Butts Date: Sun, 2 Jul 2023 09:22:22 -0600 Subject: [PATCH 06/13] Draw `rng` as needed --- R/dqset.seed.R | 2 +- man/dqrng-functions.Rd | 2 +- src/dqrng.cpp | 19 +++++++------------ 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/R/dqset.seed.R b/R/dqset.seed.R index 1443d19..e8805f3 100644 --- a/R/dqset.seed.R +++ b/R/dqset.seed.R @@ -23,7 +23,7 @@ #' @param sd standard deviation of the normal distribution #' @param rate rate of the exponential distribution #' -#' @return \code{dqrunif}, \code{dqrnorm}, \code{dqrexp}, and \code{dqrrademacher} return a numeric vector of length \code{n}. +#' @return \code{dqrunif}, \code{dqrnorm}, and \code{dqrexp} return a numeric vector of length \code{n}. \code{dqrrademacher} returns an integer vector of length \code{n}. #' #' @details Supported RNG kinds: #' \describe{ diff --git a/man/dqrng-functions.Rd b/man/dqrng-functions.Rd index e0f3983..a4bb646 100644 --- a/man/dqrng-functions.Rd +++ b/man/dqrng-functions.Rd @@ -43,7 +43,7 @@ dqset.seed(seed, stream = NULL) \item{stream}{integer used for selecting the RNG stream; either a scalar or a vector of length 2} } \value{ -\code{dqrunif}, \code{dqrnorm}, \code{dqrexp}, and \code{dqrrademacher} return a numeric vector of length \code{n}. +\code{dqrunif}, \code{dqrnorm}, and \code{dqrexp} return a numeric vector of length \code{n}. \code{dqrrademacher} returns an integer vector of length \code{n}. } \description{ The \code{dqrng} package provides several fast random number diff --git a/src/dqrng.cpp b/src/dqrng.cpp index f1dd34e..64a1de7 100644 --- a/src/dqrng.cpp +++ b/src/dqrng.cpp @@ -41,7 +41,6 @@ dqrng::normal_distribution normal{}; generator rnorm_impl = [] () {return normal(*rng);}; dqrng::exponential_distribution exponential{}; generator rexp_impl = [] () {return exponential(*rng);}; -auto ruint64t_impl = [] () {return static_cast((*rng)());}; } // [[Rcpp::interfaces(r, cpp)]] @@ -150,23 +149,19 @@ double rexp(double rate = 1.0) { //' @export // [[Rcpp::export(rng = false)]] Rcpp::IntegerVector dqrrademacher(size_t n) { - size_t n_ints = ceil(n / 64.0); - std::vector rand_ints(n_ints); - std::generate(rand_ints.begin(), rand_ints.end(), ruint64t_impl); - - Rcpp::IntegerVector res(n); - int k = 0; - for (int i = 0; i < n_ints - 1; ++i) { - uint64_t curr = rand_ints[i]; + Rcpp::IntegerVector res = Rcpp::no_init(n); + size_t k = 0; + for (size_t i = 0; i < ceil(n / 64.0) - 1; ++i) { + uint64_t curr = (*rng)(); - for (int j = 63; j >= 0; j--) { + for (int j = 0; j <= 63; ++j) { res[k] = ((curr >> j) & 1) * 2 - 1; k++; } } - uint64_t curr = rand_ints[n_ints - 1]; - for (int j = 63; k < n; ++k, --j) { + uint64_t curr = (*rng)(); + for (int j = 0; k < n; ++k, ++j) { res[k] = ((curr >> j) & 1) * 2 - 1; } From 0940ad8eb6721bb3414f80b363c3c4079023879e Mon Sep 17 00:00:00 2001 From: Kyle Butts Date: Sun, 2 Jul 2023 09:23:21 -0600 Subject: [PATCH 07/13] Rename to `bits` --- src/dqrng.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dqrng.cpp b/src/dqrng.cpp index 64a1de7..e93fa7d 100644 --- a/src/dqrng.cpp +++ b/src/dqrng.cpp @@ -152,17 +152,17 @@ Rcpp::IntegerVector dqrrademacher(size_t n) { Rcpp::IntegerVector res = Rcpp::no_init(n); size_t k = 0; for (size_t i = 0; i < ceil(n / 64.0) - 1; ++i) { - uint64_t curr = (*rng)(); + uint64_t bits = (*rng)(); for (int j = 0; j <= 63; ++j) { - res[k] = ((curr >> j) & 1) * 2 - 1; + res[k] = ((bits >> j) & 1) * 2 - 1; k++; } } - uint64_t curr = (*rng)(); + uint64_t bits = (*rng)(); for (int j = 0; k < n; ++k, ++j) { - res[k] = ((curr >> j) & 1) * 2 - 1; + res[k] = ((bits >> j) & 1) * 2 - 1; } return res; From ceed4ed2f4e16384352c37f24bfaa625e7998890 Mon Sep 17 00:00:00 2001 From: Kyle Butts Date: Sun, 2 Jul 2023 09:24:03 -0600 Subject: [PATCH 08/13] in the for loop --- src/dqrng.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/dqrng.cpp b/src/dqrng.cpp index e93fa7d..10220f0 100644 --- a/src/dqrng.cpp +++ b/src/dqrng.cpp @@ -154,14 +154,13 @@ Rcpp::IntegerVector dqrrademacher(size_t n) { for (size_t i = 0; i < ceil(n / 64.0) - 1; ++i) { uint64_t bits = (*rng)(); - for (int j = 0; j <= 63; ++j) { + for (int j = 0; j <= 63; ++j, ++k) { res[k] = ((bits >> j) & 1) * 2 - 1; - k++; } } uint64_t bits = (*rng)(); - for (int j = 0; k < n; ++k, ++j) { + for (int j = 0; k < n; ++j, ++k) { res[k] = ((bits >> j) & 1) * 2 - 1; } From 8cdc38facce3d8531091eeab3ea8ac4d405569b0 Mon Sep 17 00:00:00 2001 From: Ralf Stubner Date: Sun, 2 Jul 2023 20:59:00 +0200 Subject: [PATCH 09/13] Remove trailing white space --- R/dqset.seed.R | 12 ++++++------ man/dqrng-functions.Rd | 10 +++++----- src/dqrng.cpp | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/R/dqset.seed.R b/R/dqset.seed.R index e8805f3..6b58cf5 100644 --- a/R/dqset.seed.R +++ b/R/dqset.seed.R @@ -5,11 +5,11 @@ #' according to a uniform, normal and exponential distribution. These #' functions are modeled after the \code{base} functions #' \code{\link{set.seed}}, \code{\link{RNGkind}}, \code{\link{runif}}, -#' \code{\link{rnorm}}, and \code{\link{rexp}}. -#' -#' \code{dqrrademacher} uses a fast algorithm to generate random -#' Rademacher variables (-1 and 1 with equal probability). To do so, it -#' generates a random 64 bit integer and then uses each bit to generate +#' \code{\link{rnorm}}, and \code{\link{rexp}}. +#' +#' \code{dqrrademacher} uses a fast algorithm to generate random +#' Rademacher variables (-1 and 1 with equal probability). To do so, it +#' generates a random 64 bit integer and then uses each bit to generate #' a 0/1 variable. This generates 64 integers per random number generation. #' #' @param seed integer scalar to seed the random number generator, or an integer vector of length 2 representing a 64-bit seed. Maybe \code{NULL}, see details. @@ -46,7 +46,7 @@ #' seeds that provide 64 bits of entropy. These allow full exploration of #' the state space of the 64-bit RNGs provided in this package. #' -#' If the provided \code{seed} is \code{NULL}, a seed is genenrated from R's RNG +#' If the provided \code{seed} is \code{NULL}, a seed is generated from R's RNG #' without state alteration. #' #' @seealso \code{\link{set.seed}}, \code{\link{RNGkind}}, \code{\link{runif}}, diff --git a/man/dqrng-functions.Rd b/man/dqrng-functions.Rd index a4bb646..067c742 100644 --- a/man/dqrng-functions.Rd +++ b/man/dqrng-functions.Rd @@ -51,11 +51,11 @@ The \code{dqrng} package provides several fast random number according to a uniform, normal and exponential distribution. These functions are modeled after the \code{base} functions \code{\link{set.seed}}, \code{\link{RNGkind}}, \code{\link{runif}}, - \code{\link{rnorm}}, and \code{\link{rexp}}. + \code{\link{rnorm}}, and \code{\link{rexp}}. - \code{dqrrademacher} uses a fast algorithm to generate random - Rademacher variables (-1 and 1 with equal probability). To do so, it - generates a random 64 bit integer and then uses each bit to generate + \code{dqrrademacher} uses a fast algorithm to generate random + Rademacher variables (-1 and 1 with equal probability). To do so, it + generates a random 64 bit integer and then uses each bit to generate a 0/1 variable. This generates 64 integers per random number generation. } \details{ @@ -80,7 +80,7 @@ See \code{\link{generateSeedVectors}} for rapid generation of integer-vector seeds that provide 64 bits of entropy. These allow full exploration of the state space of the 64-bit RNGs provided in this package. -If the provided \code{seed} is \code{NULL}, a seed is genenrated from R's RNG +If the provided \code{seed} is \code{NULL}, a seed is generated from R's RNG without state alteration. } \examples{ diff --git a/src/dqrng.cpp b/src/dqrng.cpp index 10220f0..4533141 100644 --- a/src/dqrng.cpp +++ b/src/dqrng.cpp @@ -153,7 +153,7 @@ Rcpp::IntegerVector dqrrademacher(size_t n) { size_t k = 0; for (size_t i = 0; i < ceil(n / 64.0) - 1; ++i) { uint64_t bits = (*rng)(); - + for (int j = 0; j <= 63; ++j, ++k) { res[k] = ((bits >> j) & 1) * 2 - 1; } @@ -161,7 +161,7 @@ Rcpp::IntegerVector dqrrademacher(size_t n) { uint64_t bits = (*rng)(); for (int j = 0; k < n; ++j, ++k) { - res[k] = ((bits >> j) & 1) * 2 - 1; + res[k] = ((bits >> j) & 1) * 2 - 1; } return res; From ec401e4a0dae2cce8f1a2cfa6cb00e54c1730cac Mon Sep 17 00:00:00 2001 From: Ralf Stubner Date: Fri, 30 Dec 2022 14:41:15 +0100 Subject: [PATCH 10/13] Update GHA --- .github/workflows/R-CMD-check.yaml | 83 +++++++++------------------- .github/workflows/pkgdown.yaml | 60 ++++++++++---------- .github/workflows/test-coverage.yaml | 60 ++++++++++---------- 3 files changed, 86 insertions(+), 117 deletions(-) diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index fb7b370..782a13c 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -1,14 +1,14 @@ -# For help debugging build failures open an issue on the RStudio community with the 'github-actions' tag. -# https://community.rstudio.com/new-topic?category=Package%20development&tags=github-actions +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +# +# NOTE: This workflow is overkill for most R packages and +# check-standard.yaml is likely a better choice. +# usethis::use_github_action("check-standard") will install it. on: push: - branches: - - main - - master + branches: [main, master] pull_request: - branches: - - main - - master + branches: [main, master] name: R-CMD-check @@ -22,65 +22,34 @@ jobs: fail-fast: false matrix: config: + - {os: macos-latest, r: 'release'} + - {os: windows-latest, r: 'release'} - - {os: macOS-latest, r: 'release'} - - {os: ubuntu-20.04, r: 'release', rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest"} - - {os: ubuntu-20.04, r: 'devel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest"} + + - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} + - {os: ubuntu-latest, r: 'release'} + - {os: ubuntu-latest, r: 'oldrel-1'} env: - R_REMOTES_NO_ERRORS_FROM_WARNINGS: true - RSPM: ${{ matrix.config.rspm }} GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + R_KEEP_PKG_SOURCE: yes steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - uses: r-lib/actions/setup-r@v1 + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 with: r-version: ${{ matrix.config.r }} + http-user-agent: ${{ matrix.config.http-user-agent }} + use-public-rspm: true - - uses: r-lib/actions/setup-pandoc@v1 - - - name: Query dependencies - run: | - install.packages('remotes') - saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) - writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") - shell: Rscript {0} - - - name: Restore R package cache - if: runner.os != 'Windows' - uses: actions/cache@v2 + - uses: r-lib/actions/setup-r-dependencies@v2 with: - path: ${{ env.R_LIBS_USER }} - key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} - restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- - - - name: Install system dependencies - if: runner.os == 'Linux' - run: | - while read -r cmd - do - eval sudo $cmd - done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "20.04"))') - - - name: Install dependencies - run: | - remotes::install_deps(dependencies = TRUE) - remotes::install_cran("rcmdcheck") - shell: Rscript {0} - - - name: Check - env: - _R_CHECK_CRAN_INCOMING_REMOTE_: false - run: | - options(crayon.enabled = TRUE) - rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check") - shell: Rscript {0} + extra-packages: any::rcmdcheck + needs: check - - name: Upload check results - if: failure() - uses: actions/upload-artifact@main + - uses: r-lib/actions/check-r-package@v2 with: - name: ${{ runner.os }}-r${{ matrix.config.r }}-results - path: check + upload-snapshots: true \ No newline at end of file diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index 1abece4..087f0b0 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -1,48 +1,46 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help on: push: - branches: - - main - - master + branches: [main, master] + pull_request: + branches: [main, master] + release: + types: [published] + workflow_dispatch: name: pkgdown jobs: pkgdown: - runs-on: macOS-latest + runs-on: ubuntu-latest + # Only restrict concurrency for non-PR jobs + concurrency: + group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - uses: r-lib/actions/setup-r@v1 + - uses: r-lib/actions/setup-pandoc@v2 - - uses: r-lib/actions/setup-pandoc@v1 - - - name: Query dependencies - run: | - install.packages('remotes') - saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) - writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") - shell: Rscript {0} + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true - - name: Restore R package cache - uses: actions/cache@v2 + - uses: r-lib/actions/setup-r-dependencies@v2 with: - path: ${{ env.R_LIBS_USER }} - key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} - restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- + extra-packages: any::pkgdown, local::. + needs: website - - name: Install dependencies - run: | - remotes::install_deps(dependencies = TRUE) - install.packages("pkgdown", type = "binary") + - name: Build site + run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) shell: Rscript {0} - - name: Install package - run: R CMD INSTALL . - - - name: Deploy package - run: | - git config --local user.email "actions@github.com" - git config --local user.name "GitHub Actions" - Rscript -e 'pkgdown::deploy_to_branch(new_process = FALSE)' + - name: Deploy to GitHub pages 🚀 + if: github.event_name != 'pull_request' + uses: JamesIves/github-pages-deploy-action@v4.4.1 + with: + clean: false + branch: gh-pages + folder: docs diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index ba1f94f..0ceda36 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -1,48 +1,50 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help on: push: - branches: - - main - - master + branches: [main, master] pull_request: - branches: - - main - - master + branches: [main, master] name: test-coverage jobs: test-coverage: - runs-on: macOS-latest + runs-on: ubuntu-latest env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - uses: r-lib/actions/setup-r@v1 + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true - - uses: r-lib/actions/setup-pandoc@v1 + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::covr + needs: coverage - - name: Query dependencies + - name: Test coverage run: | - install.packages('remotes') - saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) - writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") + covr::codecov( + quiet = FALSE, + clean = FALSE, + install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package") + ) shell: Rscript {0} - - name: Restore R package cache - uses: actions/cache@v2 - with: - path: ${{ env.R_LIBS_USER }} - key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} - restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- - - - name: Install dependencies + - name: Show testthat output + if: always() run: | - install.packages(c("remotes")) - remotes::install_deps(dependencies = TRUE) - remotes::install_cran("covr") - shell: Rscript {0} + ## -------------------------------------------------------------------- + find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true + shell: bash - - name: Test coverage - run: covr::codecov() - shell: Rscript {0} + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v3 + with: + name: coverage-test-failures + path: ${{ runner.temp }}/package \ No newline at end of file From d68ea48f2a74d95505f79ccf712c95a82834ec07 Mon Sep 17 00:00:00 2001 From: Ralf Stubner Date: Sun, 2 Jul 2023 21:13:48 +0200 Subject: [PATCH 11/13] Compare output from dqrrademacher with integer vector. --- tests/testthat/test-rademacher.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-rademacher.R b/tests/testthat/test-rademacher.R index 92822dc..b1b7ee2 100644 --- a/tests/testthat/test-rademacher.R +++ b/tests/testthat/test-rademacher.R @@ -3,6 +3,6 @@ context("rademacher") test_that("rademacher produces -1, 1", { out <- dqrrademacher(10) unique_out <- unique(out) - expect_identical(unique_out[order(unique_out)], c(-1, 1)) + expect_identical(unique_out[order(unique_out)], c(-1L, 1L)) }) From 693c973c8866c7cb62aad76a30f8a09c18f2bc09 Mon Sep 17 00:00:00 2001 From: Ralf Stubner Date: Sun, 2 Jul 2023 21:14:42 +0200 Subject: [PATCH 12/13] Remove request for C++11, since this is standard (almost) everywhere these days. --- src/Makevars | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Makevars b/src/Makevars index f1cb658..4cfa3e2 100644 --- a/src/Makevars +++ b/src/Makevars @@ -1,2 +1 @@ -CXX_STD = CXX11 PKG_CPPFLAGS = -I../inst/include -DSTRICT_R_HEADERS From b978f6df1acb4750ddc19269ec6ff5ecf88b79ed Mon Sep 17 00:00:00 2001 From: Kyle Butts Date: Mon, 3 Jul 2023 09:57:07 -0600 Subject: [PATCH 13/13] Add name to contributor list --- DESCRIPTION | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index bc24f3b..a5b5548 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -8,8 +8,9 @@ Authors@R: c( person("David Blackman", role = "ctb"), person("Melissa O'Neill", email = "oneill@pcg-random.org", role = "ctb"), person("Sebastiano Vigna", email = "vigna@acm.org", role = "ctb"), - person("Aaron", "Lun", role="ctb") - ) + person("Aaron", "Lun", role="ctb"), + person("Kyle", "Butts", role = "ctb", email = "kyle.butts@colorado.edu") + ) Description: Several fast random number generators are provided as C++ header only libraries: The PCG family by O'Neill (2014 ) as well as