├── include ├── .github ├── .gitignore └── workflows │ ├── recheck.yaml │ ├── touchstone-comment.yaml │ ├── pkgdown.yaml │ ├── test-coverage.yaml │ ├── R-CMD-check.yaml │ ├── revdepcheck.yaml │ ├── touchstone-receive.yaml │ └── rhub.yaml ├── cleanup.win ├── vignettes ├── .gitignore ├── data │ ├── replacement.RDS │ ├── no-replacement-low.RDS │ ├── no-replacement-high.RDS │ ├── no-replacement-long.RDS │ └── no-replacement-medium.RDS ├── cpp-api.Rmd ├── sample.Rmd └── dqrng.Rmd ├── cleanup ├── src ├── Makevars ├── generateSeedVectors.cpp └── dqrng.cpp ├── .aspell ├── dqrng.rds └── defaults.R ├── tests ├── testthat.R └── testthat │ ├── helper.R │ ├── test-rademacher.R │ ├── cpp │ ├── external-generator.cpp │ ├── convert.cpp │ ├── default.cpp │ └── xoshiro-jump.cpp │ ├── test-rmv.R │ ├── test-seedvec.R │ ├── test-cpp.R │ ├── test-default.R │ ├── test-external-generator.R │ ├── test-convert.R │ ├── test-sample.R │ └── test-generators.R ├── touchstone ├── .gitignore ├── config.json ├── footer.R ├── header.R └── script.R ├── R ├── dqrng-package.R ├── zzz.R ├── dqrmv.R ├── dqsample.R ├── register_methods.R ├── RcppExports.R └── dqset.seed.R ├── debug └── debug.R ├── .gitmodules ├── codecov.yml ├── .Rbuildignore ├── NAMESPACE ├── dqrng.Rproj ├── cran-comments.md ├── external ├── patch │ ├── 01_static_arbitrary_seed.diff │ └── 02_solaris_extended.diff └── xorshift-cpp │ ├── readme.txt │ ├── main.cpp │ ├── reference_xorshift.h │ └── xorshift.hpp ├── LICENSE.note ├── .vscode └── launch.json ├── man ├── dqrmv.Rd ├── dqsample.Rd ├── generateSeedVectors.Rd ├── dqrng-package.Rd ├── user-supplied-rng.Rd └── dqrng-functions.Rd ├── inst └── include │ ├── dqrng.h │ ├── R_randgen.h │ ├── mystdint.h │ ├── dqrng_threefry.h │ ├── dqrng_distribution.h │ ├── dqrng_extra │ └── parallel_generate.h │ ├── dqrng_sample.h │ ├── minimal_int_set.h │ ├── convert_seed.h │ ├── dqrng_generator.h │ └── dqrng_types.h ├── DESCRIPTION ├── README.Rmd ├── .gitignore ├── README.md └── NEWS.md /include: -------------------------------------------------------------------------------- 1 | inst/include -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /cleanup.win: -------------------------------------------------------------------------------- 1 | rm -f src/*.o src/*.dll 2 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | *_files 4 | -------------------------------------------------------------------------------- /cleanup: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | rm -f src/*.o src/*.so 4 | -------------------------------------------------------------------------------- /src/Makevars: -------------------------------------------------------------------------------- 1 | PKG_CPPFLAGS = -I../inst/include -DSTRICT_R_HEADERS 2 | -------------------------------------------------------------------------------- /.aspell/dqrng.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daqana/dqrng/HEAD/.aspell/dqrng.rds -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(dqrng) 3 | 4 | test_check("dqrng") 5 | -------------------------------------------------------------------------------- /touchstone/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !script.R 3 | !config.json 4 | !.gitignore 5 | !header.R 6 | !footer.R 7 | -------------------------------------------------------------------------------- /vignettes/data/replacement.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daqana/dqrng/HEAD/vignettes/data/replacement.RDS -------------------------------------------------------------------------------- /R/dqrng-package.R: -------------------------------------------------------------------------------- 1 | #' @useDynLib dqrng, .registration = TRUE 2 | #' @importFrom Rcpp evalCpp 3 | "_PACKAGE" 4 | -------------------------------------------------------------------------------- /vignettes/data/no-replacement-low.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daqana/dqrng/HEAD/vignettes/data/no-replacement-low.RDS -------------------------------------------------------------------------------- /vignettes/data/no-replacement-high.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daqana/dqrng/HEAD/vignettes/data/no-replacement-high.RDS -------------------------------------------------------------------------------- /vignettes/data/no-replacement-long.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daqana/dqrng/HEAD/vignettes/data/no-replacement-long.RDS -------------------------------------------------------------------------------- /vignettes/data/no-replacement-medium.RDS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daqana/dqrng/HEAD/vignettes/data/no-replacement-medium.RDS -------------------------------------------------------------------------------- /debug/debug.R: -------------------------------------------------------------------------------- 1 | devtools::clean_dll() 2 | devtools::load_all() 3 | Sys.getpid() 4 | 5 | dqrng::dqrunif(10) 6 | 7 | dqrng::dqRNGkind("pcg64") 8 | -------------------------------------------------------------------------------- /touchstone/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "os": "ubuntu-latest", 3 | "r": "4.3", 4 | "rspm": "https://packagemanager.rstudio.com/all/__linux__/jammy/latest" 5 | } 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/xoroshiro128plus-cpp"] 2 | path = external/xoroshiro128plus-cpp 3 | url = https://github.com/alexcoplan/xoroshiro128plus-cpp.git 4 | [submodule "external/pcg-cpp"] 5 | path = external/pcg-cpp 6 | url = https://github.com/imneme/pcg-cpp.git 7 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | patch: 10 | default: 11 | target: auto 12 | threshold: 1% 13 | ignore: 14 | - "R/zzz.R" 15 | - "inst/include/pcg_*" 16 | -------------------------------------------------------------------------------- /tests/testthat/helper.R: -------------------------------------------------------------------------------- 1 | powerpc_apple <- grepl("powerpc-apple-darwin", R.version$platform) 2 | 3 | safe_expect_error <- function(..., msg) { 4 | os.type <- Sys.info()["sysname"] 5 | if (os.type == "Darwin") { 6 | expect_error(...) 7 | } else { 8 | expect_error(..., msg) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.aspell/defaults.R: -------------------------------------------------------------------------------- 1 | # saveRDS(c("al", "arXiv", "Blackman", "et", "Marsaglia", "Matsumoto", "Mersenne", "Nishimura", "PCG", "Tsang", "Threefry", "Vigna", "Xoroshiro", "Xoshiro", "Ziggurat"), file="dqrng.rds") 2 | Rd_files <- vignettes <- R_files <- description <- 3 | list(encoding = "UTF-8", 4 | language = "en", 5 | dictionaries = c("en_stats", "dqrng")) 6 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^CRAN-RELEASE$ 2 | ^.*\.Rproj$ 3 | ^\.Rproj\.user$ 4 | ^external$ 5 | ^README\.Rmd$ 6 | ^README-.*\.png$ 7 | ^docs$ 8 | ^\.travis\.yml$ 9 | ^.travis$ 10 | ^codecov\.yml$ 11 | ^appveyor\.yml$ 12 | ^include$ 13 | ^cran-comments\.md$ 14 | ^revdep$ 15 | ^\.github$ 16 | ^CRAN-SUBMISSION$ 17 | ^include$ 18 | ^.vscode$ 19 | ^Meta$ 20 | ^doc$ 21 | ^\.cache$ 22 | ^compile_commands\.json$ 23 | ^touchstone$ 24 | ^\.vscode$ 25 | ^debug$ 26 | -------------------------------------------------------------------------------- /tests/testthat/test-rademacher.R: -------------------------------------------------------------------------------- 1 | context("rademacher") 2 | 3 | test_that("rademacher produces -1, 1", { 4 | out <- dqrrademacher(10) 5 | unique_out <- unique(out) 6 | expect_identical(unique_out[order(unique_out)], c(-1L, 1L)) 7 | }) 8 | 9 | test_that("rademacher produces many -1, 1", { 10 | out <- dqrrademacher(1e6) 11 | unique_out <- unique(out) 12 | expect_identical(unique_out[order(unique_out)], c(-1L, 1L)) 13 | }) 14 | 15 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(dqRNGkind) 4 | export(dqrexp) 5 | export(dqrmvnorm) 6 | export(dqrng_get_state) 7 | export(dqrng_set_state) 8 | export(dqrnorm) 9 | export(dqrrademacher) 10 | export(dqrunif) 11 | export(dqsample) 12 | export(dqsample.int) 13 | export(dqset.seed) 14 | export(generateSeedVectors) 15 | export(register_methods) 16 | export(restore_methods) 17 | importFrom(Rcpp,evalCpp) 18 | useDynLib(dqrng, .registration = TRUE) 19 | -------------------------------------------------------------------------------- /.github/workflows/recheck.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | inputs: 4 | which: 5 | type: choice 6 | description: Which dependents to check 7 | options: 8 | - strong 9 | - most 10 | 11 | name: Reverse dependency check 12 | 13 | jobs: 14 | revdep_check: 15 | name: Reverse check ${{ inputs.which }} dependents 16 | uses: r-devel/recheck/.github/workflows/recheck.yml@v1 17 | with: 18 | which: ${{ inputs.which }} 19 | -------------------------------------------------------------------------------- /touchstone/footer.R: -------------------------------------------------------------------------------- 1 | # You can modify the PR comment footer here. You can use github markdown e.g. 2 | # emojis like :tada:. 3 | # This file will be parsed and evaluate within the context of 4 | # `benchmark_analyze` and should return the comment text as the last value. 5 | # See `?touchstone::pr_comment` 6 | link <- "https://lorenzwalthert.github.io/touchstone/articles/inference.html" 7 | glue::glue( 8 | "\nFurther explanation regarding interpretation and", 9 | " methodology can be found in the [documentation]({link})." 10 | ) 11 | -------------------------------------------------------------------------------- /dqrng.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | ProjectId: 774b0980-7de4-47b7-8e8d-a271932b8f8d 3 | 4 | RestoreWorkspace: Default 5 | SaveWorkspace: Default 6 | AlwaysSaveHistory: Default 7 | 8 | EnableCodeIndexing: Yes 9 | UseSpacesForTab: Yes 10 | NumSpacesForTab: 2 11 | Encoding: UTF-8 12 | 13 | RnwWeave: Sweave 14 | LaTeX: pdfLaTeX 15 | 16 | AutoAppendNewline: Yes 17 | StripTrailingWhitespace: Yes 18 | 19 | BuildType: Package 20 | PackageUseDevtools: Yes 21 | PackageInstallArgs: --no-multiarch --with-keep.source 22 | PackageRoxygenize: rd,namespace,vignette 23 | -------------------------------------------------------------------------------- /R/zzz.R: -------------------------------------------------------------------------------- 1 | .onLoad <- function(libname, pkgname) { 2 | if (getOption("dqrng.register_methods", FALSE)) 3 | register_methods() 4 | else { 5 | if (!exists(".Random.seed", mode="numeric", envir=globalenv())) 6 | set.seed(NULL) 7 | original_seed <- get(".Random.seed", mode="numeric", envir=globalenv()) 8 | on.exit(assign(".Random.seed", original_seed, envir=globalenv())) 9 | dqset_seed(seed = NULL, stream = NULL) 10 | } 11 | } 12 | 13 | .onUnload <- function(libpath) { 14 | if (getOption("dqrng.register_methods", FALSE)) 15 | restore_methods() 16 | } 17 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | This release fixes an UBSAN error found by CRAN and a compilation error when the 2 | compiler does not provide a 128bit integer type. 3 | 4 | ## Test environments 5 | 6 | * local: Ubuntu with R 4.4 7 | * Github Actions: 8 | * Ubuntu, R-release and R-devel 9 | * MacOS, R-release 10 | * Windows, R-release 11 | * winbuilder: Windows, R-devel 12 | 13 | ## R CMD check results 14 | 15 | 0 errors | 0 warnings | 0 note 16 | 17 | ## Reverse dependencies 18 | 19 | We checked 12 reverse dependencies from CRAN, comparing R CMD check results 20 | across CRAN and dev versions of this package. 21 | 22 | * We saw 0 new problems 23 | -------------------------------------------------------------------------------- /.github/workflows/touchstone-comment.yaml: -------------------------------------------------------------------------------- 1 | name: Continuous Benchmarks (Comment) 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.head_ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | workflow_run: 9 | workflows: ["Continuous Benchmarks (Receive)"] 10 | types: 11 | - completed 12 | 13 | jobs: 14 | upload: 15 | runs-on: ubuntu-latest 16 | if: > 17 | ${{ github.event.workflow_run.event == 'pull_request' && 18 | github.event.workflow_run.conclusion == 'success' }} 19 | steps: 20 | - uses: lorenzwalthert/touchstone/actions/comment@main 21 | with: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /external/patch/01_static_arbitrary_seed.diff: -------------------------------------------------------------------------------- 1 | --- a/inst/include/pcg_extras.hpp 2 | +++ b/inst/include/pcg_extras.hpp 3 | @@ -607,6 +607,8 @@ public: 4 | } 5 | }; 6 | 7 | +#ifdef STATIC_ARBITRARY_SEED 8 | + 9 | /* 10 | * Sometimes you might want a distinct seed based on when the program 11 | * was compiled. That way, a particular instance of the program will 12 | @@ -628,6 +630,8 @@ public: 13 | __DATE__ __TIME__ __FILE__); 14 | }; 15 | 16 | +#endif // STATIC_ARBITRARY_SEED 17 | + 18 | // Sometimes, when debugging or testing, it's handy to be able print the name 19 | // of a (in human-readable form). This code allows the idiom: 20 | // 21 | -------------------------------------------------------------------------------- /touchstone/header.R: -------------------------------------------------------------------------------- 1 | # You can modify the PR comment header here. You can use github markdown e.g. 2 | # emojis like :tada:. 3 | # This file will be parsed and evaluate within the context of 4 | # `benchmark_analyze` and should return the comment text as the last value. 5 | # Available variables for glue substitution: 6 | # * ci: confidence interval 7 | # * branches: BASE and HEAD branches benchmarked against each other. 8 | # See `?touchstone::pr_comment` 9 | glue::glue( 10 | "This is how benchmark results would change (along with a", 11 | " {100 * ci}% confidence interval in relative change) if ", 12 | "{system2('git', c('rev-parse', 'HEAD'), stdout = TRUE)} is merged into {branches[1]}:\n" 13 | ) 14 | -------------------------------------------------------------------------------- /LICENSE.note: -------------------------------------------------------------------------------- 1 | Files: inst/include/pcg_* 2 | Copyright: 2014-2017 Melissa O'Neill and the PCG Project contributors. 3 | License: Apache-2.0 or MIT 4 | URL: https://github.com/imneme/pcg-cpp 5 | 6 | File: inst/include/xoshiro.h 7 | Copyright: 2016, 2018 David Blackman and Sebastiano Vigna 8 | 2018 Ralf Stubner (daqana GmbH) 9 | 2023 Ralf Stubner 10 | License: CC0 11 | URL: http://xoshiro.di.unimi.it/. 12 | 13 | Files: inst/include/convert_seed.h, inst/include/R_randgen.h, 14 | src/generateSeedVector.cpp 15 | Copyright: 2019 Aaron Lun 16 | License: AGPL-3 17 | 18 | Files: * 19 | Copyright: 2018-2019 Ralf Stubner (daqana GmbH) 20 | 2022-2024 Ralf Stubner 21 | License: AGPL-3 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "(lldb) Launch R", 6 | "type": "lldb", 7 | "request": "launch", 8 | "program": "/Library/Frameworks/R.framework/Resources/bin/exec/R", 9 | "args": [ 10 | "--vanilla", 11 | "-e", 12 | "source('debug/debug.R')" 13 | ], 14 | "env": { 15 | "R_HOME" : "/Library/Frameworks/R.framework/Resources" 16 | }, 17 | "terminal": "console", 18 | "stopOnEntry": false 19 | }, 20 | { 21 | "name": "(lldb) Attach to R", 22 | "type": "lldb", 23 | "request": "attach", 24 | "pid": "${command:pickMyProcess}", 25 | "stopOnEntry": false 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /R/dqrmv.R: -------------------------------------------------------------------------------- 1 | #' @title Multivariate Distributions 2 | #' 3 | #' @param n number of observations 4 | #' @param ... forwarded to \code{\link[mvtnorm]{rmvnorm}} 5 | #' 6 | #' @return numeric matrix of multivariate normal distributed variables 7 | #' @seealso \code{\link[mvtnorm]{rmvnorm}} 8 | #' 9 | #' @export 10 | #' 11 | #' @rdname dqrmv 12 | #' @examplesIf requireNamespace("mvtnorm", quietly = TRUE) 13 | #' sigma <- matrix(c(4,2,2,3), ncol=2) 14 | #' x <- dqrmvnorm(n=500, mean=c(1,2), sigma=sigma) 15 | #' colMeans(x) 16 | #' var(x) 17 | #' plot(x) 18 | dqrmvnorm <- function(n, ...) { 19 | if (!requireNamespace("mvtnorm", quietly = TRUE)) { 20 | stop("Package \"mvtnorm\" must be installed to use this function.", call. = FALSE) 21 | } 22 | mvtnorm::rmvnorm(n, ..., rnorm = dqrnorm) 23 | } 24 | -------------------------------------------------------------------------------- /man/dqrmv.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/dqrmv.R 3 | \name{dqrmvnorm} 4 | \alias{dqrmvnorm} 5 | \title{Multivariate Distributions} 6 | \usage{ 7 | dqrmvnorm(n, ...) 8 | } 9 | \arguments{ 10 | \item{n}{number of observations} 11 | 12 | \item{...}{forwarded to \code{\link[mvtnorm]{rmvnorm}}} 13 | } 14 | \value{ 15 | numeric matrix of multivariate normal distributed variables 16 | } 17 | \description{ 18 | Multivariate Distributions 19 | } 20 | \examples{ 21 | \dontshow{if (requireNamespace("mvtnorm", quietly = TRUE)) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} 22 | sigma <- matrix(c(4,2,2,3), ncol=2) 23 | x <- dqrmvnorm(n=500, mean=c(1,2), sigma=sigma) 24 | colMeans(x) 25 | var(x) 26 | plot(x) 27 | \dontshow{\}) # examplesIf} 28 | } 29 | \seealso{ 30 | \code{\link[mvtnorm]{rmvnorm}} 31 | } 32 | -------------------------------------------------------------------------------- /tests/testthat/cpp/external-generator.cpp: -------------------------------------------------------------------------------- 1 | // [[Rcpp::plugins(cpp11)]] 2 | #include 3 | // [[Rcpp::depends(dqrng,BH)]] 4 | #include 5 | #include 6 | 7 | // [[Rcpp::export(rng = false)]] 8 | Rcpp::NumericVector dqrexp_extrng(const std::size_t n, const double rate = 1.0) { 9 | auto out = Rcpp::NumericVector(Rcpp::no_init(n)); 10 | dqrng::random_64bit_accessor engine{}; 11 | engine.generate(out, rate); 12 | return out; 13 | } 14 | 15 | // [[Rcpp::export]] 16 | bool cloned_calls(int stream) { 17 | dqrng::random_64bit_accessor engine{}; 18 | auto cloned_engine = engine.clone(stream); 19 | 20 | Rcpp::NumericVector u1(10); 21 | Rcpp::NumericVector u2(10); 22 | 23 | engine.generate(u1); 24 | cloned_engine->generate(u2); 25 | 26 | return Rcpp::is_true(Rcpp::all(u1 == u2)); 27 | } 28 | -------------------------------------------------------------------------------- /man/dqsample.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/dqsample.R 3 | \name{dqsample} 4 | \alias{dqsample} 5 | \alias{dqsample.int} 6 | \title{Unbiased Random Samples and Permutations} 7 | \usage{ 8 | dqsample(x, size, replace = FALSE, prob = NULL) 9 | 10 | dqsample.int(n, size = n, replace = FALSE, prob = NULL) 11 | } 12 | \arguments{ 13 | \item{x}{either a vector of one or more elements from which to choose, or a positive integer.} 14 | 15 | \item{size}{a non-negative integer giving the number of items to choose.} 16 | 17 | \item{replace}{should sampling be with replacement?} 18 | 19 | \item{prob}{a vector of probability weights for obtaining the elements of the vector being sampled.} 20 | 21 | \item{n}{a positive number, the number of items to choose from.} 22 | } 23 | \description{ 24 | Unbiased Random Samples and Permutations 25 | } 26 | \seealso{ 27 | \code{vignette("sample", package = "dqrng")}, \code{\link{sample}} and \code{\link{sample.int}} 28 | } 29 | -------------------------------------------------------------------------------- /tests/testthat/test-rmv.R: -------------------------------------------------------------------------------- 1 | context("multivariate generators") 2 | 3 | seed <- 1234567890 4 | sigma <- matrix(c(4,2,2,3), ncol=2) 5 | mean <- c(1,2) 6 | 7 | test_that("consecutive calls yield different random numbers (normal)", { 8 | skip_if_not_installed("mvtnorm") 9 | dqset.seed(seed) 10 | n1 <- dqrmvnorm(10, mean = mean, sigma=sigma) 11 | n2 <- dqrmvnorm(10, mean = mean, sigma=sigma) 12 | expect_false(all(n1 == n2)) 13 | }) 14 | 15 | test_that("setting seed produces identical normaly distributed numbers", { 16 | skip_if_not_installed("mvtnorm") 17 | dqset.seed(seed) 18 | n1 <- dqrmvnorm(10, mean = mean, sigma=sigma) 19 | dqset.seed(seed) 20 | n2 <- dqrmvnorm(10, mean = mean, sigma=sigma) 21 | expect_equal(n1, n2) 22 | }) 23 | 24 | test_that("Means and variance as expected",{ 25 | skip_if_not_installed("mvtnorm") 26 | dqset.seed(seed) 27 | n1 <- dqrmvnorm(1e5, mean = mean, sigma=sigma) 28 | expect_equal(mean, colMeans(n1), tolerance = 0.01) 29 | expect_equal(sigma, var(n1), tolerance = 0.01) 30 | }) 31 | # 32 | -------------------------------------------------------------------------------- /inst/include/dqrng.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Ralf Stubner 2 | // Copyright 2023 Henrik Sloot 3 | // 4 | // This file is part of dqrng. 5 | // 6 | // dqrng is free software: you can redistribute it and/or modify it 7 | // under the terms of the GNU Affero General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // dqrng is distributed in the hope that it will be useful, but 12 | // WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU Affero General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU Affero General Public License 17 | // along with dqrng. If not, see . 18 | 19 | #ifndef dqrng_H 20 | #define dqrng_H 21 | 22 | #include "dqrng_RcppExports.h" 23 | 24 | namespace dqrng { 25 | inline random_64bit_accessor::random_64bit_accessor() : gen(dqrng::get_rng()) {} 26 | } // namespace dqrng 27 | #endif // dqrng_H 28 | -------------------------------------------------------------------------------- /tests/testthat/test-seedvec.R: -------------------------------------------------------------------------------- 1 | context("seed vector generator") 2 | 3 | test_that("seed vector generator gives the right number of words", { 4 | out <- generateSeedVectors(10, nwords=2) 5 | expect_identical(length(out), 10L) 6 | expect_true(all(lengths(out)==2L)) 7 | expect_true(all(sapply(out, typeof)=="integer")) 8 | 9 | out <- generateSeedVectors(50, nwords=1) 10 | expect_identical(length(out), 50L) 11 | expect_true(all(lengths(out)==1L)) 12 | expect_true(all(sapply(out, typeof)=="integer")) 13 | 14 | out <- generateSeedVectors(20, nwords=4) 15 | expect_identical(length(out), 20L) 16 | expect_true(all(lengths(out)==4L)) 17 | expect_true(all(sapply(out, typeof)=="integer")) 18 | }) 19 | 20 | test_that("seed vector generator responds to the R seed state", { 21 | first <- generateSeedVectors(10, nwords=2) 22 | second <- generateSeedVectors(10, nwords=2) 23 | expect_false(identical(first, second)) 24 | 25 | set.seed(100) 26 | first <- generateSeedVectors(10, nwords=2) 27 | set.seed(100) 28 | second <- generateSeedVectors(10, nwords=2) 29 | expect_identical(first, second) 30 | }) 31 | -------------------------------------------------------------------------------- /tests/testthat/cpp/convert.cpp: -------------------------------------------------------------------------------- 1 | // [[Rcpp::plugins(cpp11)]] 2 | 3 | #include 4 | // [[Rcpp::depends(dqrng)]] 5 | #include "convert_seed.h" 6 | 7 | #include 8 | #include 9 | 10 | template 11 | std::string convert_base(Rcpp::IntegerVector seed) { 12 | U out=dqrng::convert_seed(seed); 13 | std::stringstream ss; 14 | ss << out; 15 | return ss.str(); 16 | } 17 | 18 | // [[Rcpp::export]] 19 | std::string convert_16(Rcpp::IntegerVector seed) { 20 | return convert_base(seed); 21 | } 22 | 23 | // [[Rcpp::export]] 24 | std::string convert_32(Rcpp::IntegerVector seed) { 25 | return convert_base(seed); 26 | } 27 | 28 | // [[Rcpp::export]] 29 | std::string convert_64(Rcpp::IntegerVector seed) { 30 | return convert_base(seed); 31 | } 32 | 33 | // [[Rcpp::export]] 34 | bool is_signed_consistent(Rcpp::IntegerVector seed) { 35 | uint64_t val=dqrng::convert_seed(seed); 36 | 37 | std::vector casted(seed.begin(), seed.end()); 38 | uint64_t comp=dqrng::convert_seed(casted.data(), casted.size()); 39 | 40 | return (val==comp); 41 | } 42 | -------------------------------------------------------------------------------- /inst/include/R_randgen.h: -------------------------------------------------------------------------------- 1 | #ifndef DQRNG_R_RANDGEN_H_INCLUDED 2 | #define DQRNG_R_RANDGEN_H_INCLUDED 3 | 4 | #include 5 | 6 | namespace dqrng { 7 | 8 | /* Create a uint32_t or int seed using R's PRNGs. 9 | * The uint32_t seed is more convenient from use within C++, 10 | * the int seed is more convenient if it needs to be passed back to R. 11 | * Since R's PRNG is used, calling one of these functions has to be shielded 12 | * with calls to GetRNGState and PutRNGState, e.g. by using Rcpp::RNGScope. 13 | * This is done automatically when using the Rcpp::export attribute. 14 | */ 15 | 16 | inline uint32_t R_random_u32 () { 17 | constexpr double upper_limit=4294967296; 18 | double val = R_unif_index(upper_limit); 19 | if (val >= upper_limit) { val=0; } // Absolutely avoid overflow. 20 | return static_cast(val); 21 | } 22 | 23 | inline int R_random_int () { 24 | const uint32_t sampled=R_random_u32(); 25 | constexpr uint32_t max_int=2147483647; // See .Machine$integer.max. 26 | if (sampled <= max_int) { 27 | return static_cast(sampled); 28 | } 29 | 30 | // Effectively reverse of the int->uint32_t cast. 31 | constexpr uint32_t max_uint=-1; 32 | return -static_cast(max_uint - sampled) - 1; 33 | } 34 | 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /R/dqsample.R: -------------------------------------------------------------------------------- 1 | ##' @title Unbiased Random Samples and Permutations 2 | ##' @param x either a vector of one or more elements from which to choose, or a positive integer. 3 | ##' @param n a positive number, the number of items to choose from. 4 | ##' @param size a non-negative integer giving the number of items to choose. 5 | ##' @param replace should sampling be with replacement? 6 | ##' @param prob a vector of probability weights for obtaining the elements of the vector being sampled. 7 | ##' @seealso \code{vignette("sample", package = "dqrng")}, \code{\link{sample}} and \code{\link{sample.int}} 8 | ##' @export 9 | dqsample <- function(x, size, replace = FALSE, prob = NULL) { 10 | if(length(x) == 1L && is.numeric(x) && is.finite(x) && x >= 1) { 11 | if(missing(size)) size <- x 12 | dqsample.int(x, size, replace, prob) 13 | } else { 14 | if(missing(size)) size <- length(x) 15 | x[dqsample.int(length(x), size, replace, prob)] 16 | } 17 | } 18 | 19 | ##' @rdname dqsample 20 | ##' @export 21 | dqsample.int <- function(n, size = n, replace = FALSE, prob = NULL) { 22 | if (!is.null(prob)) { 23 | warning("Using 'prob' is not supported yet. Using default 'sample.int'.") 24 | sample.int(n, size, replace, prob) 25 | } else if (n <= .Machine$integer.max) 26 | dqsample_int(n, size, replace, prob, 1L) 27 | else 28 | dqsample_num(n, size, replace, prob, 1L) 29 | } 30 | -------------------------------------------------------------------------------- /inst/include/mystdint.h: -------------------------------------------------------------------------------- 1 | #ifndef MYSTDINT_H 2 | #define MYSTDINT_H 1 3 | 4 | /* C99 defined macros like UINT64_MAX and UINT64_C but made them unavailable 5 | * to C+x unless __STDC_LIMIT_MACROS and __STDC_CONSTANT_MACROS are defined. 6 | * This was removed with C++11 and C11, but some old libc implementations still 7 | * have that (e.g. the one used in RHEL/CentOS/... 6). C++ compilers from that 8 | * time worked around this issue in the cstdint header, but newer compilers 9 | * don't do that any more. This file reintroduces these tricks to support old 10 | * libc versions together with newer compilers. 11 | * 12 | * Limitation: This header has no effect if cstdint is included before it. 13 | */ 14 | 15 | #ifndef __STDC_LIMIT_MACROS 16 | # define _UNDEF__STDC_LIMIT_MACROS 17 | # define __STDC_LIMIT_MACROS 18 | #endif 19 | #ifndef __STDC_CONSTANT_MACROS 20 | # define _UNDEF__STDC_CONSTANT_MACROS 21 | # define __STDC_CONSTANT_MACROS 22 | #endif 23 | #include 24 | #ifdef _UNDEF__STDC_LIMIT_MACROS 25 | # undef __STDC_LIMIT_MACROS 26 | # undef _UNDEF__STDC_LIMIT_MACROS 27 | #endif 28 | #ifdef _UNDEF__STDC_CONSTANT_MACROS 29 | # undef __STDC_CONSTANT_MACROS 30 | # undef _UNDEF__STDC_CONSTANT_MACROS 31 | #endif 32 | 33 | // define our own UINT64_MAX in case it is still missing 34 | #ifndef UINT64_MAX 35 | #include 36 | #define UINT64_MAX std::numeric_limits::max() 37 | #endif 38 | 39 | #endif // MYSTDINT_H 40 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | release: 9 | types: [published] 10 | workflow_dispatch: 11 | 12 | name: pkgdown 13 | 14 | jobs: 15 | pkgdown: 16 | runs-on: ubuntu-latest 17 | # Only restrict concurrency for non-PR jobs 18 | concurrency: 19 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 20 | env: 21 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - uses: r-lib/actions/setup-pandoc@v2 26 | 27 | - uses: r-lib/actions/setup-r@v2 28 | with: 29 | use-public-rspm: true 30 | 31 | - uses: r-lib/actions/setup-r-dependencies@v2 32 | with: 33 | extra-packages: any::pkgdown, local::. 34 | needs: website 35 | 36 | - name: Build site 37 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 38 | shell: Rscript {0} 39 | 40 | - name: Deploy to GitHub pages 🚀 41 | if: github.event_name != 'pull_request' 42 | uses: JamesIves/github-pages-deploy-action@v4.4.1 43 | with: 44 | clean: false 45 | branch: gh-pages 46 | folder: docs 47 | -------------------------------------------------------------------------------- /inst/include/dqrng_threefry.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2019 Ralf Stubner (daqana GmbH) 2 | // Copyright 2023-2024 Ralf Stubner 3 | // 4 | // This file is part of dqrng. 5 | // 6 | // dqrng is free software: you can redistribute it and/or modify it 7 | // under the terms of the GNU Affero General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // dqrng is distributed in the hope that it will be useful, but 12 | // WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU Affero General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU Affero General Public License 17 | // along with dqrng. If not, see . 18 | 19 | #ifndef DQRNG_THREEFRY_H 20 | #define DQRNG_THREEFRY_H 1 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | namespace dqrng { 28 | 29 | template<> 30 | inline void random_64bit_wrapper::set_stream(result_type stream) { 31 | uint64_t number; 32 | std::vector state; 33 | std::stringstream iss; 34 | iss << gen; 35 | while (iss >> number) 36 | state.push_back(number); 37 | // state[4:7] is the current counter, the highest part is incremented by stream 38 | gen.set_counter(state[4], state[5], state[6], state[7] + stream); 39 | } 40 | } // namespace dqrng 41 | 42 | #endif // DQRNG_THREEFRY_H 43 | -------------------------------------------------------------------------------- /touchstone/script.R: -------------------------------------------------------------------------------- 1 | # see `help(run_script, package = 'touchstone')` on how to run this 2 | # interactively 3 | 4 | # TODO OPTIONAL Add directories you want to be available in this file or during the 5 | # benchmarks. 6 | # touchstone::pin_assets("some/dir") 7 | 8 | # installs branches to benchmark 9 | touchstone::branch_install() 10 | 11 | # benchmark a function call from your package (two calls per branch) 12 | touchstone::benchmark_run( 13 | # expr_before_benchmark = source("dir/data.R"), #<-- TODO OTPIONAL setup before benchmark 14 | runif_test = dqrng::dqrunif(1e7), #<- TODO put the call you want to benchmark here 15 | n = 10 16 | ) 17 | 18 | # benchmark a function call from your package (two calls per branch) 19 | touchstone::benchmark_run( 20 | # expr_before_benchmark = source("dir/data.R"), #<-- TODO OTPIONAL setup before benchmark 21 | rnorm_test = dqrng::dqrnorm(1e7), #<- TODO put the call you want to benchmark here 22 | n = 10 23 | ) 24 | 25 | # benchmark a function call from your package (two calls per branch) 26 | touchstone::benchmark_run( 27 | # expr_before_benchmark = source("dir/data.R"), #<-- TODO OTPIONAL setup before benchmark 28 | rexp_test = dqrng::dqrexp(1e7), #<- TODO put the call you want to benchmark here 29 | n = 10 30 | ) 31 | 32 | # TODO OPTIONAL benchmark any R expression (six calls per branch) 33 | # touchstone::benchmark_run( 34 | # more = { 35 | # if (TRUE) { 36 | # y <- yourpkg::f2(x = 3) 37 | # } 38 | # }, #<- TODO put the call you want to benchmark here 39 | # n = 6 40 | # ) 41 | 42 | 43 | # create artifacts used downstream in the GitHub Action 44 | touchstone::benchmark_analyze() 45 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: test-coverage 10 | 11 | jobs: 12 | test-coverage: 13 | runs-on: ubuntu-latest 14 | env: 15 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 16 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - uses: r-lib/actions/setup-r@v2 22 | with: 23 | use-public-rspm: true 24 | 25 | - uses: r-lib/actions/setup-r-dependencies@v2 26 | with: 27 | extra-packages: any::covr 28 | needs: coverage 29 | 30 | - name: Test coverage 31 | run: | 32 | covr::codecov( 33 | quiet = FALSE, 34 | clean = FALSE, 35 | install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") 36 | ) 37 | shell: Rscript {0} 38 | 39 | - name: Show testthat output 40 | if: always() 41 | run: | 42 | ## -------------------------------------------------------------------- 43 | find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true 44 | shell: bash 45 | 46 | - name: Upload test results 47 | if: failure() 48 | uses: actions/upload-artifact@v4 49 | with: 50 | name: coverage-test-failures 51 | path: ${{ runner.temp }}/package 52 | -------------------------------------------------------------------------------- /external/xorshift-cpp/readme.txt: -------------------------------------------------------------------------------- 1 | This project implements xorshift128+ and xorshift1024* algorithms 2 | presented here: http://xorshift.di.unimi.it/ 3 | 4 | File "reference_xorshift.h" contains the reference implementations 5 | of the two algorithms wrapped in classes. 6 | 7 | File "xorshift.hpp" implements these algorithms as typedefs of a 8 | generalised class, implementation of which conforms with the 9 | standard C++11 PRN engine classes in (i.e., it can be 10 | passed as an argument to C++11 distribution objects, used in 11 | standard engine adaptors, etc.). 12 | 13 | File "main.cpp" can be used to verify that the provided 14 | implementation produces the same outputs as the reference 15 | implementation. It also demonstrates using xorshift1024* with a 16 | binomial_distribution from . 17 | 18 | Currently, the implementation requires the typedef uint64_t to be 19 | available on the target system. 20 | 21 | 22 | UPDATES: 23 | 24 | 2016-01-25 25 | - Library assembled into a single .hpp file. 26 | 27 | 2016-01-23: 28 | ! Reseeding the engine now properly resets the state counter. 29 | ! Relational and stream I/O operators (==, !=, <<, >>) are now 30 | state-counter-invariant. 31 | - xorshift64star and xorshift4096star typedefs removed. 32 | - xorshift128plus parameters updated to conform with the new 33 | reference implementation. 34 | - Now using splitmix instead of murmurhash avalanching to 35 | initialize the state from user-provided seeds (as currently 36 | recommended by Vigna). 37 | - Stream output now writes the state values in hexadecimal. 38 | - Other minor tweaks. 39 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | # 4 | # NOTE: This workflow is overkill for most R packages and 5 | # check-standard.yaml is likely a better choice. 6 | # usethis::use_github_action("check-standard") will install it. 7 | on: 8 | push: 9 | branches: [main, master] 10 | pull_request: 11 | branches: [main, master] 12 | workflow_dispatch: 13 | 14 | name: R-CMD-check 15 | 16 | jobs: 17 | R-CMD-check: 18 | runs-on: ${{ matrix.config.os }} 19 | 20 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | config: 26 | - {os: macos-latest, r: 'release'} 27 | 28 | - {os: windows-latest, r: 'release'} 29 | 30 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 31 | - {os: ubuntu-latest, r: 'release'} 32 | - {os: ubuntu-latest, r: 'oldrel'} 33 | 34 | env: 35 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 36 | R_KEEP_PKG_SOURCE: yes 37 | 38 | steps: 39 | - uses: actions/checkout@v4 40 | 41 | - uses: r-lib/actions/setup-pandoc@v2 42 | 43 | - uses: r-lib/actions/setup-r@v2 44 | with: 45 | r-version: ${{ matrix.config.r }} 46 | http-user-agent: ${{ matrix.config.http-user-agent }} 47 | use-public-rspm: true 48 | 49 | - uses: r-lib/actions/setup-r-dependencies@v2 50 | with: 51 | extra-packages: any::rcmdcheck 52 | needs: check 53 | 54 | - uses: r-lib/actions/check-r-package@v2 55 | with: 56 | upload-snapshots: true 57 | -------------------------------------------------------------------------------- /man/generateSeedVectors.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/RcppExports.R 3 | \name{generateSeedVectors} 4 | \alias{generateSeedVectors} 5 | \title{Generate seed as a integer vector} 6 | \usage{ 7 | generateSeedVectors(nseeds, nwords = 2L) 8 | } 9 | \arguments{ 10 | \item{nseeds}{Integer scalar, number of seeds to generate.} 11 | 12 | \item{nwords}{Integer scalar, number of words to generate per seed.} 13 | } 14 | \value{ 15 | A list of length \code{n}, where each element is an integer vector that 16 | contains \code{nwords} words (i.e., \code{32*nwords} bits) of randomness. 17 | } 18 | \description{ 19 | Generate seed as a integer vector 20 | } 21 | \details{ 22 | Each seed is encoded as an integer vector with the most significant bits 23 | at the start of the vector. Each integer vector is converted into an 24 | unsigned integer (in C++ or otherwise) by the following procedure: 25 | \enumerate{ 26 | \item Start with a sum of zero. 27 | \item Add the first value of the vector. 28 | \item Left-shift the sum by 32. 29 | \item Add the next value of the vector, and repeat. 30 | } 31 | 32 | The aim is to facilitate R-level generation of seeds with sufficient 33 | randomness to cover the entire state space of pseudo-random number 34 | generators that require more than the ~32 bits available in an 35 | \code{int}. It also preserves the integer nature of the seed, thus 36 | avoiding problems with casting double-precision numbers to integers. 37 | 38 | It is possible for the seed vector to contain \code{NA_integer_} 39 | values. This should not be cause for alarm, as R uses \code{-INT_MAX} 40 | to encode missing values in integer vectors. 41 | } 42 | \examples{ 43 | generateSeedVectors(10, 2) 44 | 45 | generateSeedVectors(5, 4) 46 | 47 | } 48 | \author{ 49 | Aaron Lun 50 | } 51 | -------------------------------------------------------------------------------- /external/patch/02_solaris_extended.diff: -------------------------------------------------------------------------------- 1 | --- a/inst/include/pcg_random.hpp 2 | +++ b/inst/include/pcg_random.hpp 3 | @@ -1636,23 +1636,23 @@ typedef setseq_base 4 | 5 | template 7 | -using ext_std8 = extended; 9 | +using ext_std8 = ::pcg_detail::extended; 11 | 12 | template 14 | -using ext_std16 = extended; 16 | +using ext_std16 = ::pcg_detail::extended; 18 | 19 | template 21 | -using ext_std32 = extended; 23 | +using ext_std32 = ::pcg_detail::extended; 25 | 26 | template 28 | -using ext_std64 = extended; 30 | +using ext_std64 = ::pcg_detail::extended; 32 | 33 | 34 | template 35 | -------------------------------------------------------------------------------- /.github/workflows/revdepcheck.yaml: -------------------------------------------------------------------------------- 1 | # https://github.com/dieghernan/tidyterra/blob/main/.github/workflows/revdepcheck.yaml 2 | on: 3 | workflow_dispatch: 4 | 5 | name: revdepcheck 6 | 7 | jobs: 8 | revdepcheck: 9 | runs-on: ${{ matrix.config.os }} 10 | 11 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 12 | 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | config: 17 | - {os: ubuntu-latest, r: 'release'} 18 | 19 | env: 20 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 21 | steps: 22 | - uses: actions/checkout@v4 23 | 24 | - uses: r-lib/actions/setup-pandoc@v2 25 | 26 | - uses: r-lib/actions/setup-r@v2 27 | with: 28 | use-public-rspm: true 29 | 30 | - uses: r-lib/actions/setup-r-dependencies@v2 31 | with: 32 | cache-version: revdep 33 | extra-packages: | 34 | local::. 35 | any::usethis 36 | any::BiocManager 37 | any::BiocVersion 38 | any::devtools 39 | any::remotes 40 | needs: check 41 | 42 | - name: revdepcheck 43 | run: | 44 | 45 | dir.create("revdep/Library", recursive = TRUE) 46 | remotes::install_local(lib = "revdep/Library", force = TRUE) 47 | utils::download.packages(devtools::revdep(), 48 | "revdep", 49 | repos = "https://cloud.r-project.org/") 50 | tools::check_packages_in_dir("revdep", 51 | check_args = "--no-manual", 52 | Ncpus = 4) 53 | tools::summarize_check_packages_in_dir_results("revdep") 54 | 55 | 56 | shell: Rscript {0} 57 | 58 | # Commit all changed files back to the repository 59 | - uses: actions/upload-artifact@v4 60 | with: 61 | name: revdep-outputs 62 | path: revdep/Outputs 63 | -------------------------------------------------------------------------------- /.github/workflows/touchstone-receive.yaml: -------------------------------------------------------------------------------- 1 | name: Continuous Benchmarks (Receive) 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.head_ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | pull_request: 9 | paths: 10 | # Directories with source code and benchmarking code 11 | - "inst/**" 12 | - "R/**" 13 | - "src/**" 14 | - "touchstone/**" 15 | # Benchmarking config file 16 | - ".github/workflows/touchstone-*.yaml" 17 | # Package metadata 18 | - DESCRIPTION 19 | 20 | jobs: 21 | prepare: 22 | runs-on: ubuntu-latest 23 | outputs: 24 | config: ${{ steps.read_touchstone_config.outputs.config }} 25 | steps: 26 | - name: Checkout repo 27 | uses: actions/checkout@v4 28 | with: 29 | fetch-depth: 0 30 | 31 | - id: read_touchstone_config 32 | run: | 33 | content=`cat ./touchstone/config.json` 34 | # the following lines are only required for multi line json 35 | content="${content//'%'/'%25'}" 36 | content="${content//$'\n'/'%0A'}" 37 | content="${content//$'\r'/'%0D'}" 38 | # end of optional handling for multi line json 39 | echo "::set-output name=config::$content" 40 | build: 41 | needs: prepare 42 | runs-on: ${{ matrix.config.os }} 43 | strategy: 44 | fail-fast: false 45 | matrix: 46 | config: 47 | - ${{ fromJson(needs.prepare.outputs.config) }} 48 | env: 49 | R_REMOTES_NO_ERRORS_FROM_WARNINGS: true 50 | RSPM: ${{ matrix.config.rspm }} 51 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 52 | steps: 53 | - uses: lorenzwalthert/touchstone/actions/receive@main 54 | with: 55 | cache-version: 1 56 | benchmarking_repo: ${{ matrix.config.benchmarking_repo }} 57 | benchmarking_ref: ${{ matrix.config.benchmarking_ref }} 58 | benchmarking_path: ${{ matrix.config.benchmarking_path }} 59 | -------------------------------------------------------------------------------- /external/xorshift-cpp/main.cpp: -------------------------------------------------------------------------------- 1 | // Written in 2014 by Ivo Doko (ivo.doko@gmail.com) 2 | 3 | // To the extent possible under law, the author has dedicated 4 | // all copyright and related and neighboring rights to this 5 | // software to the public domain worldwide. This software is 6 | // distributed without any warranty. 7 | 8 | // See . 9 | 10 | #include 11 | #include "reference_xorshift.h" 12 | #include "xorshift.hpp" 13 | #include 14 | #include 15 | #include 16 | 17 | using namespace std; 18 | 19 | uint64_t seedval{0}, trynum{0}, differences{0}; 20 | 21 | template 22 | void compare() 23 | { 24 | A en1{seedval}; 25 | B en2{seedval}; 26 | for(size_t i = 0; i < trynum; ++i) 27 | { 28 | if(en1() != en2.next()) ++differences; 29 | } 30 | } 31 | 32 | int main() 33 | { 34 | while(seedval == 0) 35 | { 36 | cout << "Enter a seed value (positive integer): "; 37 | cin >> seedval; 38 | } 39 | 40 | while(trynum == 0) 41 | { 42 | cout << "Enter the number of values to be tested: "; 43 | cin >> trynum; 44 | } 45 | 46 | compare(); 47 | compare(); 48 | 49 | cout << endl << "Number of different outputs: " << differences << endl << endl; 50 | 51 | cout << "Binomial distribution using xorshift1024* with the provided seed:" << endl << endl; 52 | 53 | xorshift1024star engine{seedval}; 54 | binomial_distribution distribution(9, 0.5); 55 | auto rnd = bind(distribution, engine); 56 | 57 | array res; 58 | res.fill(0); 59 | 60 | for(size_t i = 0; i < 200; ++i) ++res[rnd()]; 61 | 62 | for(size_t i = 0; i < 10; ++i) 63 | cout << i << ": " << std::string(res[i], '*') << endl; 64 | 65 | return 0; 66 | } 67 | -------------------------------------------------------------------------------- /external/xorshift-cpp/reference_xorshift.h: -------------------------------------------------------------------------------- 1 | // Written in 2014 by Ivo Doko (ivo.doko@gmail.com) 2 | // based on code written by Sebastiano Vigna (vigna@acm.org) 3 | 4 | // To the extent possible under law, the author has dedicated 5 | // all copyright and related and neighboring rights to this 6 | // software to the public domain worldwide. This software is 7 | // distributed without any warranty. 8 | 9 | // See . 10 | 11 | #include 12 | 13 | namespace 14 | { 15 | uint64_t splitmix_state; 16 | 17 | uint64_t splitmix_next() 18 | { 19 | uint64_t z = (splitmix_state += 0x9e3779b97f4a7c15ULL); 20 | z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL; 21 | z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL; 22 | return z ^ (z >> 31); 23 | } 24 | } 25 | 26 | class ref_xorshift128plus 27 | { 28 | uint64_t s[2]; 29 | 30 | public: 31 | explicit ref_xorshift128plus (const uint64_t& val) 32 | { 33 | splitmix_state = val; 34 | s[0] = splitmix_next(); 35 | s[1] = splitmix_next(); 36 | } 37 | 38 | uint64_t next() 39 | { 40 | uint64_t s1 = s[0]; 41 | const uint64_t s0 = s[1]; 42 | s[0] = s0; 43 | s1 ^= s1 << 23; // a 44 | s[1] = s1 ^ s0 ^ ( s1 >> 18 ) ^ ( s0 >> 5 ); // b, c 45 | return s[1] + s0; 46 | } 47 | }; 48 | 49 | class ref_xorshift1024star 50 | { 51 | uint64_t s[16]; 52 | int p = 0; 53 | 54 | public: 55 | explicit ref_xorshift1024star (const uint64_t& val) 56 | { 57 | splitmix_state = val; 58 | for(size_t i = 0; i < 16; ++i) 59 | s[i] = splitmix_next(); 60 | } 61 | 62 | uint64_t next(void) 63 | { 64 | const uint64_t s0 = s[p]; 65 | uint64_t s1 = s[p = (p + 1) & 15]; 66 | s1 ^= s1 << 31; // a 67 | s[p] = s1 ^ s0 ^ (s1 >> 11) ^ (s0 >> 30); // b,c 68 | return s[p] * 1181783497276652981ULL; 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /src/generateSeedVectors.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "R_randgen.h" 3 | 4 | //' Generate seed as a integer vector 5 | //' 6 | //' @param nseeds Integer scalar, number of seeds to generate. 7 | //' @param nwords Integer scalar, number of words to generate per seed. 8 | //' 9 | //' @return 10 | //' A list of length \code{n}, where each element is an integer vector that 11 | //' contains \code{nwords} words (i.e., \code{32*nwords} bits) of randomness. 12 | //' 13 | //' @details 14 | //' Each seed is encoded as an integer vector with the most significant bits 15 | //' at the start of the vector. Each integer vector is converted into an 16 | //' unsigned integer (in C++ or otherwise) by the following procedure: 17 | //' \enumerate{ 18 | //' \item Start with a sum of zero. 19 | //' \item Add the first value of the vector. 20 | //' \item Left-shift the sum by 32. 21 | //' \item Add the next value of the vector, and repeat. 22 | //' } 23 | //' 24 | //' The aim is to facilitate R-level generation of seeds with sufficient 25 | //' randomness to cover the entire state space of pseudo-random number 26 | //' generators that require more than the ~32 bits available in an 27 | //' \code{int}. It also preserves the integer nature of the seed, thus 28 | //' avoiding problems with casting double-precision numbers to integers. 29 | //' 30 | //' It is possible for the seed vector to contain \code{NA_integer_} 31 | //' values. This should not be cause for alarm, as R uses \code{-INT_MAX} 32 | //' to encode missing values in integer vectors. 33 | //' 34 | //' @author Aaron Lun 35 | //' 36 | //' @examples 37 | //' generateSeedVectors(10, 2) 38 | //' 39 | //' generateSeedVectors(5, 4) 40 | //' 41 | //' @export 42 | // [[Rcpp::export(rng = true)]] 43 | Rcpp::List generateSeedVectors(int nseeds, int nwords=2) { 44 | Rcpp::List output(nseeds); 45 | for (int i=0; i) as well as 19 | the Xoroshiro / Xoshiro family by Blackman and Vigna (2021 20 | ). In addition fast functions for generating random 21 | numbers according to a uniform, normal and exponential distribution 22 | are included. The latter two use the Ziggurat algorithm originally 23 | proposed by Marsaglia and Tsang (2000, ). 24 | The fast sampling methods support unweighted sampling both with and without 25 | replacement. These functions are exported to R and as a C++ interface and are 26 | enabled for use with the default 64 bit generator from the PCG family, 27 | Xoroshiro128+/++/** and Xoshiro256+/++/** as well as the 64 bit version of the 28 | 20 rounds Threefry engine (Salmon et al., 2011, ) 29 | as provided by the package 'sitmo'. 30 | License: AGPL-3 31 | Depends: R (>= 3.5.0) 32 | Imports: Rcpp (>= 0.12.16) 33 | LinkingTo: Rcpp, BH (>= 1.64.0-1), sitmo (>= 2.0.0) 34 | RoxygenNote: 7.3.2 35 | Suggests: 36 | BH, 37 | testthat, 38 | knitr, 39 | rmarkdown, 40 | mvtnorm (>= 1.2-3), 41 | bench, 42 | sitmo 43 | VignetteBuilder: knitr 44 | URL: https://daqana.github.io/dqrng/, https://github.com/daqana/dqrng 45 | BugReports: https://github.com/daqana/dqrng/issues 46 | Encoding: UTF-8 47 | Config/build/compilation-database: true 48 | -------------------------------------------------------------------------------- /tests/testthat/cpp/default.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | // [[Rcpp::depends(dqrng)]] 3 | #include 4 | 5 | // [[Rcpp::export]] 6 | bool consecutive_calls(Rcpp::IntegerVector seed) { 7 | dqrng::dqset_seed(seed); 8 | Rcpp::NumericVector u1 = dqrng::dqrunif(10); 9 | Rcpp::NumericVector u2 = dqrng::dqrunif(10); 10 | return Rcpp::is_true(Rcpp::all(u1 == u2)); 11 | } 12 | 13 | // [[Rcpp::export]] 14 | bool seed_uniform(Rcpp::IntegerVector seed) { 15 | dqrng::dqset_seed(seed); 16 | Rcpp::NumericVector u1 = dqrng::dqrunif(10); 17 | dqrng::dqset_seed(seed); 18 | Rcpp::NumericVector u2 = dqrng::dqrunif(10); 19 | return Rcpp::is_true(Rcpp::all(u1 == u2)); 20 | } 21 | 22 | // [[Rcpp::export]] 23 | bool seed_uniform_scalar(Rcpp::IntegerVector seed) { 24 | dqrng::dqset_seed(seed); 25 | double u1 = dqrng::runif(); 26 | dqrng::dqset_seed(seed); 27 | double u2 = dqrng::runif(); 28 | return u1 == u2; 29 | } 30 | 31 | // [[Rcpp::export]] 32 | bool seed_uniform_scalar_min_eq_max(Rcpp::IntegerVector seed) { 33 | dqrng::dqset_seed(seed); 34 | double m = 47.11; 35 | double u = dqrng::runif(m, m); 36 | return u == m; 37 | } 38 | 39 | // [[Rcpp::export]] 40 | bool seed_uniform_scalar_min_gt_max(Rcpp::IntegerVector seed) { 41 | dqrng::dqset_seed(seed); 42 | double min = 47.11; 43 | double max = 42; 44 | double u = dqrng::runif(min, max); 45 | return u >= min && u <= max; 46 | } 47 | 48 | // [[Rcpp::export]] 49 | bool seed_normal(Rcpp::IntegerVector seed) { 50 | dqrng::dqset_seed(seed); 51 | Rcpp::NumericVector n1 = dqrng::dqrnorm(10); 52 | dqrng::dqset_seed(seed); 53 | Rcpp::NumericVector n2 = dqrng::dqrnorm(10); 54 | return Rcpp::is_true(Rcpp::all(n1 == n2)); 55 | } 56 | 57 | // [[Rcpp::export]] 58 | bool seed_normal_scalar(Rcpp::IntegerVector seed) { 59 | dqrng::dqset_seed(seed); 60 | double n1 = dqrng::rnorm(); 61 | dqrng::dqset_seed(seed); 62 | double n2 = dqrng::rnorm(); 63 | return n1 == n2; 64 | } 65 | 66 | // [[Rcpp::export]] 67 | bool seed_exponential(Rcpp::IntegerVector seed) { 68 | dqrng::dqset_seed(seed); 69 | Rcpp::NumericVector e1 = dqrng::dqrexp(10); 70 | dqrng::dqset_seed(seed); 71 | Rcpp::NumericVector e2 = dqrng::dqrexp(10); 72 | return Rcpp::is_true(Rcpp::all(e1 == e2)); 73 | } 74 | 75 | // [[Rcpp::export]] 76 | bool seed_exponential_scalar(Rcpp::IntegerVector seed) { 77 | dqrng::dqset_seed(seed); 78 | double u1 = dqrng::rexp(); 79 | dqrng::dqset_seed(seed); 80 | double u2 = dqrng::rexp(); 81 | return u1 == u2; 82 | } 83 | -------------------------------------------------------------------------------- /tests/testthat/test-default.R: -------------------------------------------------------------------------------- 1 | context("default generator") 2 | 3 | seed <- 1234567890 4 | 5 | test_that("consecutive calls yield different random numbers", { 6 | dqset.seed(seed) 7 | u1 <- dqrunif(10) 8 | u2 <- dqrunif(10) 9 | expect_false(all(u1 == u2)) 10 | }) 11 | 12 | test_that("setting seed produces identical uniformly distributed numbers", { 13 | dqset.seed(seed) 14 | u1 <- dqrunif(10) 15 | dqset.seed(seed) 16 | u2 <- dqrunif(10) 17 | expect_equal(u1, u2) 18 | }) 19 | 20 | test_that("saving state produces identical uniformly distributed numbers", { 21 | dqset.seed(seed) 22 | state <- dqrng_get_state() 23 | u1 <- dqrunif(10) 24 | dqrng_set_state(state) 25 | u2 <- dqrunif(10) 26 | expect_identical(u1, u2) 27 | }) 28 | 29 | test_that("setting seed produces identical uniformly distributed numbers (user defined RNG)", { 30 | register_methods() 31 | dqset.seed(seed) 32 | u1 <- dqrunif(10) 33 | set.seed(seed) 34 | u2 <- stats::runif(10) 35 | expect_equal(u1, u2) 36 | restore_methods() 37 | }) 38 | 39 | test_that("setting seed produces identical normaly distributed numbers", { 40 | dqset.seed(seed) 41 | n1 <- dqrnorm(10) 42 | dqset.seed(seed) 43 | n2 <- dqrnorm(10) 44 | expect_equal(n1, n2) 45 | }) 46 | 47 | test_that("setting seed produces identical normaly distributed numbers (user defined RNG)", { 48 | register_methods() 49 | dqset.seed(seed) 50 | n1 <- dqrnorm(10) 51 | set.seed(seed) 52 | n2 <- stats::rnorm(10) 53 | expect_equal(n1, n2) 54 | restore_methods() 55 | }) 56 | 57 | test_that("setting seed produces identical exponenetially distributed numbers", { 58 | dqset.seed(seed) 59 | n1 <- dqrexp(10) 60 | dqset.seed(seed) 61 | n2 <- dqrexp(10) 62 | expect_equal(n1, n2) 63 | }) 64 | 65 | test_that("numbers are generated in the correct range", { 66 | dqset.seed(seed) 67 | u <- dqrunif(1e4, min = 10, max = 12) 68 | expect_true(all(u >= 10) && all(u < 12)) 69 | }) 70 | 71 | test_that("numbers can be generated in maximum range", { 72 | dqset.seed(seed) 73 | max <- .Machine$double.xmax 74 | u <- dqrunif(1e4, min = -max, max = max) 75 | expect_true(all(u >= -max) && all(u < max)) 76 | }) 77 | 78 | test_that("min and max can be equal", { 79 | dqset.seed(seed) 80 | u <- dqrunif(5, min = 10, max = 10) 81 | expect_true(all(u == 10)) 82 | }) 83 | 84 | test_that("min must not be larger than max", { 85 | dqset.seed(seed) 86 | expect_error(dqrunif(5, min = 10, max = 9), 87 | "'min' must not be larger than 'max'!") 88 | }) 89 | -------------------------------------------------------------------------------- /tests/testthat/cpp/xoshiro-jump.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | // [[Rcpp::depends(dqrng)]] 3 | #include 4 | // [[Rcpp::plugins(cpp11)]] 5 | 6 | // [[Rcpp::export]] 7 | bool xoroshiro_plus_jump() { 8 | dqrng::xoroshiro128plus rng1; 9 | dqrng::xoroshiro128plus rng2{rng1}; 10 | uint64_t b1 = rng1(); 11 | uint64_t b2 = rng2(); 12 | if (b1 != b2 || b1 != 8565694473480078012ul) return false; 13 | rng2.jump(); 14 | b1 = rng1(); 15 | b2 = rng2(); 16 | return (b1 != b2 && b1 == 15272923827117731108ul && b2 == 17999886805244497548ul); 17 | } 18 | 19 | // [[Rcpp::export]] 20 | bool xoroshiro_plus_plus_jump() { 21 | dqrng::xoroshiro128plusplus rng1; 22 | dqrng::xoroshiro128plusplus rng2{rng1}; 23 | uint64_t b1 = rng1(); 24 | uint64_t b2 = rng2(); 25 | if (b1 != b2 || b1 != 9619973171922872170ul) return false; 26 | rng2.jump(); 27 | b1 = rng1(); 28 | b2 = rng2(); 29 | return (b1 != b2 && b1 == 18140424698325186121ul && b2 == 4303743122785247830ul); 30 | } 31 | 32 | // [[Rcpp::export]] 33 | bool xoroshiro_star_star_jump() { 34 | dqrng::xoroshiro128starstar rng1; 35 | dqrng::xoroshiro128starstar rng2{rng1}; 36 | uint64_t b1 = rng1(); 37 | uint64_t b2 = rng2(); 38 | if (b1 != b2 || b1 != 9411227923535470089ul) return false; 39 | rng2.jump(); 40 | b1 = rng1(); 41 | b2 = rng2(); 42 | return (b1 != b2 && b1 == 13565053679963375829ul && b2 == 16626251538516947336ul); 43 | } 44 | 45 | // [[Rcpp::export]] 46 | bool xoshiro_plus_long_jump() { 47 | dqrng::xoshiro256plus rng1; 48 | dqrng::xoshiro256plus rng2{rng1}; 49 | uint64_t b1 = rng1(); 50 | uint64_t b2 = rng2(); 51 | if (b1 != b2 || b1 != 1636916528417697424ul) return false; 52 | rng2.long_jump(); 53 | b1 = rng1(); 54 | b2 = rng2(); 55 | return (b1 != b2 && b1 == 7650610156038910584ul && b2 == 3829589126310379935ul); 56 | } 57 | 58 | // [[Rcpp::export]] 59 | bool xoshiro_plus_plus_long_jump() { 60 | dqrng::xoshiro256plusplus rng1; 61 | dqrng::xoshiro256plusplus rng2{rng1}; 62 | uint64_t b1 = rng1(); 63 | uint64_t b2 = rng2(); 64 | if (b1 != b2 || b1 != 1043556088660792683ul) return false; 65 | rng2.long_jump(); 66 | b1 = rng1(); 67 | b2 = rng2(); 68 | return (b1 != b2 && b1 == 17143711001946577550ul && b2 == 5651347275477359870ul); 69 | } 70 | 71 | // [[Rcpp::export]] 72 | bool xoshiro_star_star_long_jump() { 73 | dqrng::xoshiro256starstar rng1; 74 | dqrng::xoshiro256starstar rng2{rng1}; 75 | uint64_t b1 = rng1(); 76 | uint64_t b2 = rng2(); 77 | if (b1 != b2 || b1 != 2395286222372858216) return false; 78 | rng2.long_jump(); 79 | b1 = rng1(); 80 | b2 = rng2(); 81 | return (b1 != b2 && b1 == 16934533178094428553ul && b2 == 2234110801786820680ul); 82 | } 83 | -------------------------------------------------------------------------------- /inst/include/dqrng_distribution.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2019 Ralf Stubner (daqana GmbH) 2 | // Copyright 2023-2024 Ralf Stubner 3 | // 4 | // This file is part of dqrng. 5 | // 6 | // dqrng is free software: you can redistribute it and/or modify it 7 | // under the terms of the GNU Affero General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // dqrng is distributed in the hope that it will be useful, but 12 | // WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU Affero General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU Affero General Public License 17 | // along with dqrng. If not, see . 18 | 19 | #ifndef DQRNG_DISTRIBUTION_H 20 | #define DQRNG_DISTRIBUTION_H 1 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | namespace dqrng { 30 | // Boost's implementation allow for performance increasing spezializations 31 | using uniform_distribution = boost::random::uniform_real_distribution; 32 | // Boost's implementation uses the fast Ziggurat method 33 | using normal_distribution = boost::random::normal_distribution; 34 | using exponential_distribution = boost::random::exponential_distribution; 35 | 36 | inline double uniform01(uint64_t x) { 37 | // prefer high bits due to weakness of lowest bits for xoshiro/xoroshiro with the "+" scrambler 38 | return (x >> 11) * 0x1.0p-53; 39 | } 40 | } // namespace dqrng 41 | 42 | namespace boost { 43 | namespace random { 44 | namespace detail { 45 | 46 | template<> 47 | inline std::pair generate_int_float_pair(dqrng::random_64bit_generator& eng) 48 | { 49 | return eng.generate_double_8bit_pair(); 50 | } 51 | 52 | template<> 53 | inline double generate_uniform_real(dqrng::random_64bit_generator& eng, double min, double max) 54 | { 55 | return eng.uniform01() * (max - min) + min; 56 | } 57 | 58 | template<> 59 | inline std::pair generate_int_float_pair(dqrng::random_64bit_accessor& eng) 60 | { 61 | return generate_int_float_pair(eng); 62 | } 63 | 64 | template<> 65 | inline double generate_uniform_real(dqrng::random_64bit_accessor& eng, double min, double max) 66 | { 67 | return generate_uniform_real(eng, min, max); 68 | } 69 | 70 | } // namespace detail 71 | } // namespace random 72 | } // namespace boost 73 | 74 | #endif // DQRNG_DISTRIBUTION_H 75 | -------------------------------------------------------------------------------- /tests/testthat/test-external-generator.R: -------------------------------------------------------------------------------- 1 | context("external-generators") 2 | 3 | Rcpp::sourceCpp("cpp/external-generator.cpp") 4 | 5 | n <- 1e2L 6 | rate <- 0.4 7 | use_seed <- 1623 8 | 9 | test_that("external RNG (normal)", { 10 | dqset.seed(use_seed) 11 | expected <- dqrexp(n, rate) 12 | dqset.seed(use_seed) 13 | actual <- dqrexp_extrng(n, rate) 14 | expect_equal(actual, expected) 15 | 16 | dqset.seed(use_seed) 17 | expected2 <- expected 18 | actual2 <- sapply(1:n, function(x) dqrexp_extrng(1, rate)) 19 | expect_equal(actual2, expected2) 20 | }) 21 | 22 | test_that("external RNG (normal, Xoshiro256++)", { 23 | dqrng::dqRNGkind("Xoshiro256++") 24 | dqset.seed(use_seed) 25 | expected <- dqrexp(n, rate) 26 | dqset.seed(use_seed) 27 | actual <- dqrexp_extrng(n, rate) 28 | expect_equal(actual, expected) 29 | 30 | dqset.seed(use_seed) 31 | expected2 <- expected 32 | actual2 <- sapply(1:n, function(x) dqrexp_extrng(1, rate)) 33 | expect_equal(actual2, expected2) 34 | }) 35 | 36 | test_that("external RNG (parallel, Threefry)", { 37 | cl <- parallel::makeCluster(2) 38 | expected3 <- parallel::clusterApply(cl, 1:8, function(stream, seed, N, rate) { 39 | dqrng::dqRNGkind("Threefry") 40 | dqrng::dqset.seed(seed, stream) 41 | dqrng::dqrexp(N, rate) 42 | }, use_seed, 1e6L, rate) 43 | parallel::stopCluster(cl) 44 | 45 | cl <- parallel::makeCluster(2) 46 | actual3 <- parallel::clusterApply(cl, 1:8, function(stream, seed, N, rate) { 47 | Rcpp::sourceCpp("cpp/external-generator.cpp") ## must be recompiled 48 | dqrng::dqRNGkind("Threefry") 49 | dqrng::dqset.seed(seed, stream) 50 | dqrexp_extrng(N, rate) 51 | }, use_seed, 1e6L, rate) 52 | parallel::stopCluster(cl) 53 | 54 | expect_equal(actual3, expected3) 55 | }) 56 | 57 | test_that("cloned external default RNG gives different result only when a different stream is selected", { 58 | dqrng::dqRNGkind("default") 59 | dqset.seed(use_seed) 60 | expect_true(cloned_calls(stream = 0)) 61 | expect_false(cloned_calls(stream = 1)) 62 | }) 63 | 64 | test_that("cloned external Xoshiro256++ gives different result only when a different stream is selected", { 65 | dqrng::dqRNGkind("Xoshiro256++") 66 | dqset.seed(use_seed) 67 | expect_true(cloned_calls(stream = 0)) 68 | expect_false(cloned_calls(stream = 1)) 69 | dqrng::dqRNGkind("default") 70 | }) 71 | 72 | test_that("cloned external PCG64 gives different result", { 73 | skip_if(powerpc_apple) 74 | dqrng::dqRNGkind("PCG64") 75 | dqset.seed(use_seed) 76 | expect_true(cloned_calls(stream = 0)) 77 | expect_false(cloned_calls(stream = 1)) 78 | dqrng::dqRNGkind("default") 79 | }) 80 | 81 | test_that("cloned external Threefry gives different result only when a different stream is selected", { 82 | dqrng::dqRNGkind("Threefry") 83 | dqset.seed(use_seed) 84 | expect_true(cloned_calls(stream = 0)) 85 | expect_false(cloned_calls(stream = 1)) 86 | dqrng::dqRNGkind("default") 87 | }) 88 | 89 | -------------------------------------------------------------------------------- /inst/include/dqrng_extra/parallel_generate.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Ralf Stubner 2 | // Copyright 2024 Philippe Grosjean 3 | // 4 | // This file is part of dqrng. 5 | // 6 | // dqrng is free software: you can redistribute it and/or modify it 7 | // under the terms of the GNU Affero General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // dqrng is distributed in the hope that it will be useful, but 12 | // WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU Affero General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU Affero General Public License 17 | // along with dqrng. If not, see . 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | namespace dqrng { 25 | namespace extra { 26 | template 27 | Rcpp::NumericVector parallel_generate(std::size_t n, 28 | std::size_t threads, 29 | std::size_t streams, 30 | Params&&... params) { 31 | if (n < streams) 32 | streams = n; 33 | std::size_t stream_size = n / streams; 34 | std::size_t remainder = n % streams; 35 | 36 | // use RcppParallel::RVector as thread safe accessor 37 | Rcpp::NumericVector res(Rcpp::no_init(n)); 38 | RcppParallel::RVector work(res); 39 | 40 | // use global RNG from dqrng 41 | dqrng::random_64bit_accessor rng{}; 42 | std::stringstream buffer; 43 | 44 | #ifdef _OPENMP 45 | std::size_t maxthreads = omp_get_num_procs(); 46 | if (threads > maxthreads) 47 | threads = maxthreads; 48 | // No need for more threads than there are streams 49 | if (threads > streams) 50 | threads = streams; 51 | #endif 52 | 53 | #pragma omp parallel num_threads(threads) 54 | { 55 | std::size_t start,end; 56 | 57 | #pragma omp for schedule(static,1) 58 | for (std::size_t i = 0; i < streams; ++i) { 59 | if (i < remainder) { 60 | start = i * stream_size + i; 61 | end = start + stream_size + 1; 62 | } else { 63 | start = i * stream_size + remainder; 64 | end = start + stream_size; 65 | } 66 | // private RNG in each stream; RNG with i == 0 is identical to global RNG 67 | auto prng = rng.clone(i); 68 | prng->generate(std::begin(work) + start, std::begin(work) + end, 69 | std::forward(params)...); 70 | if (i == 0) {// Save the state of the global RNG's clone 71 | buffer << *prng; 72 | } 73 | } 74 | } 75 | // Make sure that the global RNG advances as well by applying the state 76 | // of the global RNG's clone to the global RNG 77 | buffer >> rng; 78 | return res; 79 | } 80 | } // namespace extra 81 | } // namespace dqrng 82 | -------------------------------------------------------------------------------- /tests/testthat/test-convert.R: -------------------------------------------------------------------------------- 1 | context("conversion") 2 | 3 | Rcpp::sourceCpp("cpp/convert.cpp") # Warnings about shifts can be ignored. 4 | 5 | test_that("conversion to 16-bit integers works correctly", { 6 | expect_identical(convert_16(0), "0") 7 | expect_identical(convert_16(65535), "65535") 8 | 9 | # Handles vectors. 10 | expect_identical(convert_16(c(0, 0, 0)), "0") 11 | expect_identical(convert_16(c(0, 65535)), "65535") 12 | expect_identical(convert_16(c(0, 0, 65535)), "65535") 13 | 14 | # Reports errors. 15 | skip_on_os("solaris") 16 | safe_expect_error(convert_16(-1), msg = "seed element out of range") 17 | safe_expect_error(convert_16(NA_integer_), msg = "seed element out of range") 18 | safe_expect_error(convert_16(65536), msg = "seed element out of range") 19 | safe_expect_error(convert_16(c(1, 0)), msg = "vector implies an out-of-range seed") 20 | }) 21 | 22 | test_that("conversion to 32-bit integers works correctly", { 23 | expect_identical(convert_32(0), "0") 24 | expect_identical(convert_32(.Machine$integer.max), "2147483647") 25 | expect_identical(convert_32(NA_integer_), "2147483648") 26 | expect_identical(convert_32(-1), "4294967295") 27 | 28 | # Handles vectors. 29 | expect_identical(convert_32(c(0, 0, 0)), "0") 30 | expect_identical(convert_32(c(0, .Machine$integer.max)), "2147483647") 31 | expect_identical(convert_32(c(0, 0, -1)), "4294967295") 32 | 33 | # Reports errors. 34 | skip_on_os("solaris") 35 | safe_expect_error(convert_32(c(1, 0)), msg = "vector implies an out-of-range seed") 36 | }) 37 | 38 | test_that("conversion to 64-bit integers works correctly", { 39 | expect_identical(convert_64(0), "0") 40 | expect_identical(convert_64(c(0, 0)), "0") 41 | expect_identical(convert_64(c(0, 0, 0)), "0") 42 | 43 | expect_identical(convert_64(c(0, .Machine$integer.max)), "2147483647") 44 | expect_identical(convert_64(c(0, 0, NA_integer_)), "2147483648") 45 | expect_identical(convert_64(c(0, -1)), "4294967295") 46 | expect_identical(convert_64(c(0, 0, NA_integer_)), "2147483648") 47 | 48 | expect_identical(convert_64(c(1, 0)), "4294967296") 49 | expect_identical(convert_64(c(1, .Machine$integer.max)), "6442450943") 50 | expect_identical(convert_64(c(1, NA_integer_)), "6442450944") 51 | expect_identical(convert_64(c(1, -1)), "8589934591") 52 | 53 | expect_identical(convert_64(c(-1, 0)), "18446744069414584320") 54 | expect_identical(convert_64(c(-1, -1)), "18446744073709551615") 55 | 56 | # Reports errors. 57 | skip_on_os("solaris") 58 | safe_expect_error(convert_64(c(1, 1, 0)), msg = "vector implies an out-of-range seed") 59 | }) 60 | 61 | test_that("unsigned/signed methods are consistent", { 62 | extremes <- c(0L, 1L, .Machine$integer.max, NA_integer_, -1L) 63 | for (x in extremes) { 64 | for (y in extremes) { 65 | expect_true(is_signed_consistent(c(x, y))) 66 | } 67 | } 68 | }) 69 | -------------------------------------------------------------------------------- /inst/include/dqrng_sample.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2019 Ralf Stubner (daqana GmbH) 2 | // Copyright 2022-2023 Ralf Stubner 3 | // 4 | // This file is part of dqrng. 5 | // 6 | // dqrng is free software: you can redistribute it and/or modify it 7 | // under the terms of the GNU Affero General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // dqrng is distributed in the hope that it will be useful, but 12 | // WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU Affero General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU Affero General Public License 17 | // along with dqrng. If not, see . 18 | 19 | #ifndef DQRNG_SAMPLE_H 20 | #define DQRNG_SAMPLE_H 1 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | namespace dqrng { 28 | namespace sample { 29 | template 30 | inline VEC replacement(dqrng::random_64bit_generator &rng, INT n, INT size, int offset) { 31 | VEC result(size); 32 | std::generate(result.begin(), result.end(), 33 | [n, offset, &rng] () {return (offset + rng(n));}); 34 | return result; 35 | } 36 | 37 | template 38 | inline VEC no_replacement_shuffle(dqrng::random_64bit_generator &rng, INT n, INT size, int offset) { 39 | VEC tmp(n); 40 | std::iota(tmp.begin(), tmp.end(), (offset)); 41 | for (INT i = 0; i < size; ++i) { 42 | std::swap(tmp[i], tmp[i + rng(n - i)]); 43 | } 44 | if (n == size) 45 | return tmp; 46 | else 47 | return VEC(tmp.begin(), tmp.begin() + size); 48 | } 49 | 50 | template 51 | inline VEC no_replacement_set(dqrng::random_64bit_generator &rng, INT n, INT size, int offset) { 52 | VEC result(size); 53 | SET elems(n, size); 54 | for (INT i = 0; i < size; ++i) { 55 | INT v; 56 | do { 57 | v = rng(n); 58 | } while (!elems.insert(v)); 59 | result[i] = (offset + v); 60 | } 61 | return result; 62 | } 63 | 64 | template 65 | inline VEC sample(dqrng::random_64bit_generator &rng, INT n, INT size, bool replace, int offset = 0) { 66 | if (replace || size <= 1) { 67 | return dqrng::sample::replacement(rng, n, size, offset); 68 | } else { 69 | if (!(n >= size)) 70 | Rcpp::stop("Argument requirements not fulfilled: n >= size"); 71 | if (n < 2 * size) { 72 | return dqrng::sample::no_replacement_shuffle(rng, n, size, offset); 73 | } else if (n < 1000 * size) { 74 | return dqrng::sample::no_replacement_set(rng, n, size, offset); 75 | } else { 76 | return dqrng::sample::no_replacement_set>(rng, n, size, offset); 77 | } 78 | } 79 | } 80 | } // sample 81 | } // dqrng 82 | 83 | #endif // DQRNG_SAMPLE_H 84 | -------------------------------------------------------------------------------- /inst/include/minimal_int_set.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Ralf Stubner (daqana GmbH) 2 | // 3 | // This file is part of dqrng. 4 | // 5 | // dqrng is free software: you can redistribute it and/or modify it 6 | // under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // dqrng is distributed in the hope that it will be useful, but 11 | // WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with dqrng. If not, see . 17 | 18 | #ifndef MINIMAL_INT_SET_H 19 | #define MINIMAL_INT_SET_H 1 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | // Two set implementations meant to store n integers out of m possible values. 26 | // Common interface: 27 | // * ctor (m, n) 28 | // * bool insert(entry, check = true) 29 | // returns true if insert successful and check == true 30 | 31 | namespace dqrng { 32 | template 33 | class minimal_hash_set { 34 | private: 35 | // open addressing 36 | T* entries; 37 | T empty = -1; 38 | std::size_t total; 39 | std::size_t mask; 40 | std::size_t size{0}; 41 | 42 | // identity hash 43 | std::size_t hash(T entry) {return entry & mask;} 44 | // quadratic probing 45 | std::size_t probe(T entry, std::size_t step) {return (step * step + step)/2;} 46 | 47 | minimal_hash_set(std::size_t n) { 48 | // total will be between 1.5 * n and 3 * n 49 | total = 1 << static_cast(std::ceil(std::log2(1.5 * n))); 50 | mask = total - 1; 51 | entries = new T[total]; 52 | for (std::size_t i = 0; i < total; ++i) 53 | entries[i] = empty; 54 | }; 55 | 56 | public: 57 | minimal_hash_set(std::size_t m, std::size_t n) : minimal_hash_set(n) {}; 58 | 59 | ~minimal_hash_set() {delete[] entries;} 60 | 61 | bool insert(T entry, bool check = true) { 62 | if (size > 0.8 * total) 63 | throw std::runtime_error("Hash set is (almost) full!"); 64 | 65 | std::size_t home; 66 | std::size_t pos = home = hash(entry); 67 | for (int i = 1; empty != entries[pos]; ++i) { 68 | if (check && entry == entries[pos]) { 69 | return false; 70 | } 71 | pos = (home + probe(entry, i)) & mask; 72 | } 73 | entries[pos] = entry; 74 | ++size; 75 | return true; 76 | } 77 | }; 78 | 79 | class minimal_bit_set { 80 | private: 81 | boost::dynamic_bitset<> entries; 82 | 83 | minimal_bit_set(std::size_t m) { 84 | entries.resize(m); 85 | }; 86 | 87 | public: 88 | minimal_bit_set(std::size_t m, std::size_t n) : minimal_bit_set(m) {}; 89 | 90 | ~minimal_bit_set() {}; 91 | 92 | bool insert(std::size_t entry, bool check = true) { 93 | if (check) 94 | return !entries.test_set(entry); 95 | 96 | entries.set(entry); 97 | return true; 98 | }; 99 | }; 100 | } 101 | #endif // MINIMAL_INT_SET_H 102 | -------------------------------------------------------------------------------- /.github/workflows/rhub.yaml: -------------------------------------------------------------------------------- 1 | # R-hub's generic GitHub Actions workflow file. It's canonical location is at 2 | # https://github.com/r-hub/actions/blob/v1/workflows/rhub.yaml 3 | # You can update this file to a newer version using the rhub2 package: 4 | # 5 | # rhub::rhub_setup() 6 | # 7 | # It is unlikely that you need to modify this file manually. 8 | 9 | name: R-hub 10 | run-name: "${{ github.event.inputs.id }}: ${{ github.event.inputs.name || format('Manually run by {0}', github.triggering_actor) }}" 11 | 12 | on: 13 | workflow_dispatch: 14 | inputs: 15 | config: 16 | description: 'A comma separated list of R-hub platforms to use.' 17 | type: string 18 | default: 'linux,windows,macos' 19 | name: 20 | description: 'Run name. You can leave this empty now.' 21 | type: string 22 | id: 23 | description: 'Unique ID. You can leave this empty now.' 24 | type: string 25 | 26 | jobs: 27 | 28 | setup: 29 | runs-on: ubuntu-latest 30 | outputs: 31 | containers: ${{ steps.rhub-setup.outputs.containers }} 32 | platforms: ${{ steps.rhub-setup.outputs.platforms }} 33 | 34 | steps: 35 | # NO NEED TO CHECKOUT HERE 36 | - uses: r-hub/actions/setup@v1 37 | with: 38 | config: ${{ github.event.inputs.config }} 39 | id: rhub-setup 40 | 41 | linux-containers: 42 | needs: setup 43 | if: ${{ needs.setup.outputs.containers != '[]' }} 44 | runs-on: ubuntu-latest 45 | name: ${{ matrix.config.label }} 46 | strategy: 47 | fail-fast: false 48 | matrix: 49 | config: ${{ fromJson(needs.setup.outputs.containers) }} 50 | container: 51 | image: ${{ matrix.config.container }} 52 | 53 | steps: 54 | - uses: r-hub/actions/checkout@v1 55 | - uses: r-hub/actions/platform-info@v1 56 | with: 57 | token: ${{ secrets.RHUB_TOKEN }} 58 | job-config: ${{ matrix.config.job-config }} 59 | - uses: r-hub/actions/setup-deps@v1 60 | with: 61 | token: ${{ secrets.RHUB_TOKEN }} 62 | job-config: ${{ matrix.config.job-config }} 63 | - uses: r-hub/actions/run-check@v1 64 | with: 65 | token: ${{ secrets.RHUB_TOKEN }} 66 | job-config: ${{ matrix.config.job-config }} 67 | 68 | other-platforms: 69 | needs: setup 70 | if: ${{ needs.setup.outputs.platforms != '[]' }} 71 | runs-on: ${{ matrix.config.os }} 72 | name: ${{ matrix.config.label }} 73 | strategy: 74 | fail-fast: false 75 | matrix: 76 | config: ${{ fromJson(needs.setup.outputs.platforms) }} 77 | 78 | steps: 79 | - uses: r-hub/actions/checkout@v1 80 | - uses: r-hub/actions/setup-r@v1 81 | with: 82 | job-config: ${{ matrix.config.job-config }} 83 | token: ${{ secrets.RHUB_TOKEN }} 84 | - uses: r-hub/actions/platform-info@v1 85 | with: 86 | token: ${{ secrets.RHUB_TOKEN }} 87 | job-config: ${{ matrix.config.job-config }} 88 | - uses: r-hub/actions/setup-deps@v1 89 | with: 90 | job-config: ${{ matrix.config.job-config }} 91 | token: ${{ secrets.RHUB_TOKEN }} 92 | - uses: r-hub/actions/run-check@v1 93 | with: 94 | job-config: ${{ matrix.config.job-config }} 95 | token: ${{ secrets.RHUB_TOKEN }} 96 | -------------------------------------------------------------------------------- /man/user-supplied-rng.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/register_methods.R 3 | \name{register_methods} 4 | \alias{register_methods} 5 | \alias{restore_methods} 6 | \title{Registering as user-supplied RNG} 7 | \usage{ 8 | register_methods(kind = c("both", "rng")) 9 | 10 | restore_methods() 11 | } 12 | \arguments{ 13 | \item{kind}{Which methods should be registered? Either \code{"both"} or \code{"rng"}.} 14 | } 15 | \value{ 16 | Invisibly returns a three-element character vector of the RNG, normal 17 | and sample kinds \emph{before} the call. 18 | } 19 | \description{ 20 | The random-number generators (RNG) from this package can be 21 | registered as user-supplied RNG. This way all \code{r} functions make 22 | use of the provided fast RNGs. 23 | } 24 | \details{ 25 | Caveats: 26 | \itemize{ 27 | \item While \code{runif} and \code{dqrunif} as well as \code{rnorm} and 28 | \code{dqrnorm} will produce the same results, this is not the case for 29 | \code{rexp} and \code{dqrexp}. 30 | \item The \code{dqr} functions are still faster than \code{r} 31 | when many random numbers are generated. 32 | \item You can use only the RNG from this package using 33 | \code{register_method("rng")} or both the RNG and the Ziggurat method 34 | for normal draws with \code{register_method("both")}. The latter 35 | approach is used by default. Using only the Ziggurat method will give 36 | \emph{undefined} behavior and is not supported! 37 | \item Calling \code{dqset.seed(NULL)} re-initializes the RNG from R's RNG. 38 | This no longer makes sense when the RNG has been registered as user-supplied 39 | RNG. In that case \code{set.seed{NULL}} needs to be used. 40 | \item With R's in-build RNGs one can get access to the internal state using 41 | \code{.Random.seed}. This is not possible here, since the internal state 42 | is a private member of the used C++ classes. 43 | } 44 | 45 | You can automatically register these methods when loading this package by 46 | setting the option \code{dqrng.register_methods} to \code{TRUE}, e.g. 47 | with \code{options(dqrng.register_methods=TRUE)}. 48 | 49 | Notes on seeding: 50 | \itemize{ 51 | \item When a user-supplied RNG is registered, it is also seeded from the 52 | previously used RNG. You will therefore get reproducible (but different) 53 | whether you call \code{set.seed()} before or after \code{register_methods()}. 54 | \item When called with a single integer as argument, both \code{set.seed()} 55 | and \code{dqset.seed()} have the same effect. However, \code{dqset.seed()} 56 | allows you to call it with two integers thereby supplying 64 bits of 57 | initial state instead of just 32 bits. 58 | } 59 | } 60 | \examples{ 61 | register_methods() 62 | # set.seed and dqset.seed influence both (dq)runif and (dq)rnorm 63 | set.seed(4711); runif(5) 64 | set.seed(4711); dqrunif(5) 65 | dqset.seed(4711); rnorm(5) 66 | dqset.seed(4711); dqrnorm(5) 67 | # similarly for other r functions 68 | set.seed(4711); rt(5, 10) 69 | dqset.seed(4711); rt(5, 10) 70 | # but (dq)rexp give different results 71 | set.seed(4711); rexp(5, 10) 72 | set.seed(4711); dqrexp(5, 10) 73 | restore_methods() 74 | } 75 | \seealso{ 76 | \code{\link{RNGkind}} and \code{\link{Random.user}} 77 | } 78 | -------------------------------------------------------------------------------- /R/register_methods.R: -------------------------------------------------------------------------------- 1 | state <- new.env(parent = emptyenv()) 2 | state$RNGkind <- rep.int("default", 3) 3 | 4 | #' @title Registering as user-supplied RNG 5 | #' 6 | #' @description The random-number generators (RNG) from this package can be 7 | #' registered as user-supplied RNG. This way all \code{r} functions make 8 | #' use of the provided fast RNGs. 9 | #' 10 | #' @param kind Which methods should be registered? Either \code{"both"} or \code{"rng"}. 11 | #' 12 | #' @return Invisibly returns a three-element character vector of the RNG, normal 13 | #' and sample kinds \emph{before} the call. 14 | #' 15 | #' @details Caveats: 16 | #' \itemize{ 17 | #' \item While \code{runif} and \code{dqrunif} as well as \code{rnorm} and 18 | #' \code{dqrnorm} will produce the same results, this is not the case for 19 | #' \code{rexp} and \code{dqrexp}. 20 | #' \item The \code{dqr} functions are still faster than \code{r} 21 | #' when many random numbers are generated. 22 | #' \item You can use only the RNG from this package using 23 | #' \code{register_method("rng")} or both the RNG and the Ziggurat method 24 | #' for normal draws with \code{register_method("both")}. The latter 25 | #' approach is used by default. Using only the Ziggurat method will give 26 | #' \emph{undefined} behavior and is not supported! 27 | #' \item Calling \code{dqset.seed(NULL)} re-initializes the RNG from R's RNG. 28 | #' This no longer makes sense when the RNG has been registered as user-supplied 29 | #' RNG. In that case \code{set.seed{NULL}} needs to be used. 30 | #' \item With R's in-build RNGs one can get access to the internal state using 31 | #' \code{.Random.seed}. This is not possible here, since the internal state 32 | #' is a private member of the used C++ classes. 33 | #' } 34 | #' 35 | #' You can automatically register these methods when loading this package by 36 | #' setting the option \code{dqrng.register_methods} to \code{TRUE}, e.g. 37 | #' with \code{options(dqrng.register_methods=TRUE)}. 38 | #' 39 | #' Notes on seeding: 40 | #' \itemize{ 41 | #' \item When a user-supplied RNG is registered, it is also seeded from the 42 | #' previously used RNG. You will therefore get reproducible (but different) 43 | #' whether you call \code{set.seed()} before or after \code{register_methods()}. 44 | #' \item When called with a single integer as argument, both \code{set.seed()} 45 | #' and \code{dqset.seed()} have the same effect. However, \code{dqset.seed()} 46 | #' allows you to call it with two integers thereby supplying 64 bits of 47 | #' initial state instead of just 32 bits. 48 | #' } 49 | #' @seealso \code{\link{RNGkind}} and \code{\link{Random.user}} 50 | #' 51 | #' @examples 52 | #' register_methods() 53 | #' # set.seed and dqset.seed influence both (dq)runif and (dq)rnorm 54 | #' set.seed(4711); runif(5) 55 | #' set.seed(4711); dqrunif(5) 56 | #' dqset.seed(4711); rnorm(5) 57 | #' dqset.seed(4711); dqrnorm(5) 58 | #' # similarly for other r functions 59 | #' set.seed(4711); rt(5, 10) 60 | #' dqset.seed(4711); rt(5, 10) 61 | #' # but (dq)rexp give different results 62 | #' set.seed(4711); rexp(5, 10) 63 | #' set.seed(4711); dqrexp(5, 10) 64 | #' restore_methods() 65 | 66 | #' @rdname user-supplied-rng 67 | #' @export 68 | register_methods <- function(kind = c("both", "rng")) { 69 | kind <- match.arg(kind) 70 | switch(kind, 71 | both = state$RNGkind <- RNGkind("user", "user"), 72 | rng = state$RNGkind <- RNGkind("user")) 73 | } 74 | 75 | #' @rdname user-supplied-rng 76 | #' @export 77 | restore_methods <- function() { 78 | RNGkind(state$RNGkind[1], state$RNGkind[2], state$RNGkind[3]) 79 | } 80 | -------------------------------------------------------------------------------- /R/RcppExports.R: -------------------------------------------------------------------------------- 1 | # Generated by using Rcpp::compileAttributes() -> do not edit by hand 2 | # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 3 | 4 | dqset_seed <- function(seed, stream = NULL) { 5 | invisible(.Call(`_dqrng_dqset_seed`, seed, stream)) 6 | } 7 | 8 | #' @rdname dqrng-functions 9 | #' @export 10 | dqRNGkind <- function(kind, normal_kind = "ignored") { 11 | invisible(.Call(`_dqrng_dqRNGkind`, kind, normal_kind)) 12 | } 13 | 14 | #' @rdname dqrng-functions 15 | #' @export 16 | dqrng_get_state <- function() { 17 | .Call(`_dqrng_dqrng_get_state`) 18 | } 19 | 20 | #' @rdname dqrng-functions 21 | #' @export 22 | dqrng_set_state <- function(state) { 23 | invisible(.Call(`_dqrng_dqrng_set_state`, state)) 24 | } 25 | 26 | #' @rdname dqrng-functions 27 | #' @export 28 | dqrunif <- function(n, min = 0.0, max = 1.0) { 29 | .Call(`_dqrng_dqrunif`, n, min, max) 30 | } 31 | 32 | runif <- function(min = 0.0, max = 1.0) { 33 | .Call(`_dqrng_runif`, min, max) 34 | } 35 | 36 | #' @rdname dqrng-functions 37 | #' @export 38 | dqrnorm <- function(n, mean = 0.0, sd = 1.0) { 39 | .Call(`_dqrng_dqrnorm`, n, mean, sd) 40 | } 41 | 42 | rnorm <- function(mean = 0.0, sd = 1.0) { 43 | .Call(`_dqrng_rnorm`, mean, sd) 44 | } 45 | 46 | #' @rdname dqrng-functions 47 | #' @export 48 | dqrexp <- function(n, rate = 1.0) { 49 | .Call(`_dqrng_dqrexp`, n, rate) 50 | } 51 | 52 | rexp <- function(rate = 1.0) { 53 | .Call(`_dqrng_rexp`, rate) 54 | } 55 | 56 | #' @keywords internal 57 | get_rng <- function() { 58 | .Call(`_dqrng_get_rng`) 59 | } 60 | 61 | #' @rdname dqrng-functions 62 | #' @export 63 | dqrrademacher <- function(n) { 64 | .Call(`_dqrng_dqrrademacher`, n) 65 | } 66 | 67 | dqsample_int <- function(n, size, replace = FALSE, probs = NULL, offset = 0L) { 68 | .Call(`_dqrng_dqsample_int`, n, size, replace, probs, offset) 69 | } 70 | 71 | dqsample_num <- function(n, size, replace = FALSE, probs = NULL, offset = 0L) { 72 | .Call(`_dqrng_dqsample_num`, n, size, replace, probs, offset) 73 | } 74 | 75 | #' Generate seed as a integer vector 76 | #' 77 | #' @param nseeds Integer scalar, number of seeds to generate. 78 | #' @param nwords Integer scalar, number of words to generate per seed. 79 | #' 80 | #' @return 81 | #' A list of length \code{n}, where each element is an integer vector that 82 | #' contains \code{nwords} words (i.e., \code{32*nwords} bits) of randomness. 83 | #' 84 | #' @details 85 | #' Each seed is encoded as an integer vector with the most significant bits 86 | #' at the start of the vector. Each integer vector is converted into an 87 | #' unsigned integer (in C++ or otherwise) by the following procedure: 88 | #' \enumerate{ 89 | #' \item Start with a sum of zero. 90 | #' \item Add the first value of the vector. 91 | #' \item Left-shift the sum by 32. 92 | #' \item Add the next value of the vector, and repeat. 93 | #' } 94 | #' 95 | #' The aim is to facilitate R-level generation of seeds with sufficient 96 | #' randomness to cover the entire state space of pseudo-random number 97 | #' generators that require more than the ~32 bits available in an 98 | #' \code{int}. It also preserves the integer nature of the seed, thus 99 | #' avoiding problems with casting double-precision numbers to integers. 100 | #' 101 | #' It is possible for the seed vector to contain \code{NA_integer_} 102 | #' values. This should not be cause for alarm, as R uses \code{-INT_MAX} 103 | #' to encode missing values in integer vectors. 104 | #' 105 | #' @author Aaron Lun 106 | #' 107 | #' @examples 108 | #' generateSeedVectors(10, 2) 109 | #' 110 | #' generateSeedVectors(5, 4) 111 | #' 112 | #' @export 113 | generateSeedVectors <- function(nseeds, nwords = 2L) { 114 | .Call(`_dqrng_generateSeedVectors`, nseeds, nwords) 115 | } 116 | 117 | # Register entry points for exported C++ functions 118 | methods::setLoadAction(function(ns) { 119 | .Call(`_dqrng_RcppExport_registerCCallable`) 120 | }) 121 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: 3 | github_document 4 | --- 5 | 6 | 7 | 8 | ```{r, echo = FALSE} 9 | knitr::opts_chunk$set( 10 | collapse = TRUE, 11 | comment = "#>", 12 | fig.path = "README-" 13 | ) 14 | ``` 15 | 16 | [![R build status](https://github.com/daqana/dqrng/workflows/R-CMD-check/badge.svg)](https://github.com/daqana/dqrng/actions) 17 | [![CRAN status](https://www.r-pkg.org/badges/version/dqrng)](https://cran.r-project.org/package=dqrng) 18 | [![dqrng status badge](https://rstub.r-universe.dev/badges/dqrng)](https://rstub.r-universe.dev/dqrng) 19 | [![Coverage status](https://codecov.io/gh/daqana/dqrng/branch/main/graph/badge.svg)](https://app.codecov.io/github/daqana/dqrng?branch=main) 20 | [![Downloads](https://cranlogs.r-pkg.org/badges/dqrng?color=brightgreen)](https://www.r-pkg.org/pkg/dqrng) 21 | [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/2157/badge)](https://bestpractices.coreinfrastructure.org/projects/2157) 22 | [![Dependencies](https://tinyverse.netlify.app/badge/dqrng)](https://cran.r-project.org/package=dqrng) 23 | 24 | # dqrng 25 | 26 | The dqrng package provides fast random number generators (RNG) with good statistical properties for usage with R. 27 | It combines these RNGs with fast distribution functions to sample from uniform, normal or exponential distributions. 28 | Both the RNGs and the distribution functions are distributed as C++ header-only library. 29 | 30 | ## Installation 31 | 32 | The currently released version is available from CRAN via 33 | 34 | ```r 35 | install.packages("dqrng") 36 | ``` 37 | 38 | Intermediate releases can also be obtained via 39 | [r-universe](https://rstub.r-universe.dev/dqrng): 40 | 41 | ```r 42 | install.packages('dqrng', repos = c( 43 | rstub = 'https://rstub.r-universe.dev', 44 | CRAN = 'https://cloud.r-project.org')) 45 | ``` 46 | 47 | ## Example 48 | 49 | Using the provided RNGs from R is deliberately similar to using R's build-in RNGs: 50 | 51 | ```{r example} 52 | library(dqrng) 53 | dqset.seed(42) 54 | dqrunif(5, min = 2, max = 10) 55 | dqrexp(5, rate = 4) 56 | ``` 57 | 58 | They are quite a bit faster, though: 59 | 60 | ```{r performance} 61 | N <- 1e4 62 | bm <- bench::mark(rnorm(N), dqrnorm(N), check = FALSE) 63 | bm[, 1:4] 64 | ``` 65 | 66 | This is also true for the provided sampling functions with replacement: 67 | 68 | ```{r sampling1} 69 | m <- 1e7 70 | n <- 1e5 71 | bm <- bench::mark(sample.int(m, n, replace = TRUE), 72 | sample.int(1e3*m, n, replace = TRUE), 73 | dqsample.int(m, n, replace = TRUE), 74 | dqsample.int(1e3*m, n, replace = TRUE), 75 | check = FALSE) 76 | bm[, 1:4] 77 | ``` 78 | 79 | And without replacement: 80 | 81 | ```{r sampling2} 82 | bm <- bench::mark(sample.int(m, n), 83 | sample.int(1e3*m, n), 84 | sample.int(m, n, useHash = TRUE), 85 | dqsample.int(m, n), 86 | dqsample.int(1e3*m, n), 87 | check = FALSE) 88 | bm[, 1:4] 89 | ``` 90 | 91 | Note that sampling from `10^10` elements triggers "long-vector support" in R. 92 | 93 | In addition the RNGs provide support for multiple independent streams for parallel usage: 94 | 95 | ```{r parallel} 96 | N <- 1e7 97 | dqset.seed(42, 1) 98 | u1 <- dqrunif(N) 99 | dqset.seed(42, 2) 100 | u2 <- dqrunif(N) 101 | cor(u1, u2) 102 | ``` 103 | 104 | It is also possible to register the supplied generators as user-supplied RNGs. This way `set.seed()` and `dqset.seed()` influence both `(dq)runif` and `(dq)rnorm` in the same way. This is also true for other `r` functions, but note that `rexp` and `dqrexp` still give different results: 105 | 106 | ```{r register} 107 | register_methods() 108 | set.seed(4711); runif(5) 109 | set.seed(4711); dqrunif(5) 110 | dqset.seed(4711); rnorm(5) 111 | dqset.seed(4711); dqrnorm(5) 112 | set.seed(4711); rt(5, 10) 113 | dqset.seed(4711); rt(5, 10) 114 | set.seed(4711); rexp(5, 10) 115 | set.seed(4711); dqrexp(5, 10) 116 | restore_methods() 117 | ``` 118 | 119 | ## Feedback 120 | 121 | All feedback (bug reports, security issues, feature requests, ...) should be provided as [issues](https://github.com/daqana/dqrng/issues). 122 | -------------------------------------------------------------------------------- /R/dqset.seed.R: -------------------------------------------------------------------------------- 1 | #' @title R interface 2 | #' 3 | #' @description The \code{dqrng} package provides several fast random number 4 | #' generators together with fast functions for generating random numbers 5 | #' according to a uniform, normal and exponential distribution. These 6 | #' functions are modeled after the \code{base} functions 7 | #' \code{\link{set.seed}}, \code{\link{RNGkind}}, \code{\link{runif}}, 8 | #' \code{\link{rnorm}}, and \code{\link{rexp}}. However, note that the functions 9 | #' provided here do not accept vector arguments for the number of observations 10 | #' as well as the parameters describing the distribution functions. Please see 11 | #' \code{\link{register_methods}} if you need this functionality. 12 | #' 13 | #' \code{dqrrademacher} uses a fast algorithm to generate random 14 | #' Rademacher variables (-1 and 1 with equal probability). To do so, it 15 | #' generates a random 64 bit integer and then uses each bit to generate 16 | #' a 0/1 variable. This generates 64 integers per random number generation. 17 | #' 18 | #' \code{dqrng_get_state} and \code{dqrng_set_state} can be used to get and set 19 | #' the RNG's internal state. The character vector should not be manipulated directly. 20 | #' 21 | #' @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. 22 | #' @param stream integer used for selecting the RNG stream; either a scalar or a vector of length 2 23 | #' @param kind string specifying the RNG (see details) 24 | #' @param normal_kind ignored; included for compatibility with \code{\link{RNGkind}} 25 | #' @param n number of observations 26 | #' @param min lower limit of the uniform distribution 27 | #' @param max upper limit of the uniform distribution 28 | #' @param mean mean value of the normal distribution 29 | #' @param sd standard deviation of the normal distribution 30 | #' @param rate rate of the exponential distribution 31 | #' @param state character vector representation of the RNG's internal state 32 | #' 33 | #' @return \code{dqrunif}, \code{dqrnorm}, and \code{dqrexp} return a numeric vector 34 | #' of length \code{n}. \code{dqrrademacher} returns an integer vector of length \code{n}. 35 | #' \code{dqrng_get_state} returns a character vector representation of the RNG's internal state. 36 | #' 37 | #' @details Supported RNG kinds: 38 | #' \describe{ 39 | #' \item{pcg64}{The default 64 bit variant from the PCG family developed by 40 | #' Melissa O'Neill. See \url{https://www.pcg-random.org/} for more details.} 41 | #' \item{Xoroshiro128++ and Xoshiro256++}{RNGs developed by David Blackman and 42 | #' Sebastiano Vigna. See \url{https://prng.di.unimi.it/} for more details. 43 | #' The older generators Xoroshiro128+ and Xoshiro256+ should be used only for 44 | #' backwards compatibility.} 45 | #' \item{Threefry}{The 64 bit version of the 20 rounds Threefry engine as 46 | #' provided by \code{\link[sitmo]{sitmo-package}}} 47 | #' } 48 | #' 49 | #' Xoroshiro128++ is the default since it is fast, small and has good statistical 50 | #' properties. 51 | #' 52 | #' The functions \code{dqrnorm} and \code{dqrexp} use the Ziggurat algorithm as 53 | #' provided by \code{boost.random}. 54 | #' 55 | #' See \code{\link{generateSeedVectors}} for rapid generation of integer-vector 56 | #' seeds that provide 64 bits of entropy. These allow full exploration of 57 | #' the state space of the 64-bit RNGs provided in this package. 58 | #' 59 | #' If the provided \code{seed} is \code{NULL}, a seed is generated from R's RNG 60 | #' without state alteration. 61 | #' 62 | #' @seealso \code{\link{set.seed}}, \code{\link{RNGkind}}, \code{\link{runif}}, 63 | #' \code{\link{rnorm}}, and \code{\link{rexp}} 64 | #' 65 | #' @examples 66 | #' library(dqrng) 67 | #' 68 | #' # Set custom RNG. 69 | #' dqRNGkind("Xoshiro256++") 70 | #' 71 | #' # Use an integer scalar to set a seed. 72 | #' dqset.seed(42) 73 | #' 74 | #' # Use integer scalars to set a seed and the stream. 75 | #' dqset.seed(42, 123) 76 | #' 77 | #' # Use an integer vector to set a seed. 78 | #' dqset.seed(c(31311L, 24123423L)) 79 | #' 80 | #' # Use an integer vector to set a seed and a scalar to select the stream. 81 | #' dqset.seed(c(31311L, 24123423L), 123) 82 | #' 83 | #' # Random sampling from distributions. 84 | #' dqrunif(5, min = 2, max = 10) 85 | #' dqrexp(5, rate = 4) 86 | #' dqrnorm(5, mean = 5, sd = 3) 87 | #' 88 | #' # get and restore the state 89 | #' (state <- dqrng_get_state()) 90 | #' dqrunif(5) 91 | #' dqrng_set_state(state) 92 | #' dqrunif(5) 93 | #' 94 | #' @rdname dqrng-functions 95 | #' @export 96 | dqset.seed <- function(seed, stream = NULL) { 97 | dqset_seed(seed, stream) 98 | } 99 | -------------------------------------------------------------------------------- /inst/include/convert_seed.h: -------------------------------------------------------------------------------- 1 | #ifndef DQRNG_CONVERT_SEED_H_INCLUDED 2 | #define DQRNG_CONVERT_SEED_H_INCLUDED 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace dqrng { 10 | 11 | /* Internal functions, not to be called by users. 12 | * These define a set of template specializations for the 13 | * bitshifts, which avoids compilation warnings when the 14 | * shift is greater than or equal to the width of the 15 | * unsigned integer type (for 16 or 32-bit unsigned ints). 16 | */ 17 | 18 | template 19 | UIN right_shift(UIN val) { 20 | return val >> SHIFT; 21 | } 22 | 23 | template<> 24 | inline uint16_t right_shift<32, uint16_t>(uint16_t val) { return 0; } // # nocov 25 | 26 | template<> 27 | inline uint32_t right_shift<32, uint32_t>(uint32_t val) { return 0; } // # nocov 28 | 29 | template 30 | UIN left_shift(UIN val) { 31 | return val << SHIFT; 32 | } 33 | 34 | template<> 35 | inline uint16_t left_shift<32, uint16_t>(uint16_t val) { return 0; } // # nocov 36 | 37 | template<> 38 | inline uint32_t left_shift<32, uint32_t>(uint32_t val) { return 0; } // # nocov 39 | 40 | /* This converts a seed vector ('seeds') into a single unsigned 41 | * integer of specified type 'OUT' with all bits set according 42 | * to the combined bit pattern of the individual seed elements. 43 | * This is achieved by bit shifting, with the first element 44 | * of 'seeds' contributing the most significant bits in the output 45 | * and the last element contributing the least significant bits. 46 | * 47 | * Each element of 'seeds' is cast from 'IN' to the corresponding 48 | * unsigned type. 'IN' should have no more than last 'SHIFT' bits 49 | * set. Compile- or run-time errors will be raised for seeds 50 | * that exceed the size of the maximum integer storable in 'OUT'. 51 | */ 52 | 53 | template::type>::digits> 54 | OUT convert_seed_internal(const IN* seeds, size_t N) { 55 | static_assert(std::is_unsigned::value, "output integer type should be unsigned"); 56 | constexpr OUT upper=-1; 57 | 58 | // Check to avoid UB from right-shifting by the length of OUT in bits. 59 | constexpr int OUT_size=std::numeric_limits::digits; 60 | constexpr bool shiftable=OUT_size > SHIFT; 61 | constexpr OUT left_upper=(shiftable ? upper >> SHIFT : 0); // evaluated at compile time; should not generate shift warnings. 62 | 63 | typedef typename std::make_unsigned::type UIN; 64 | constexpr int UIN_size=std::numeric_limits::digits; 65 | 66 | OUT sum=0; 67 | for (size_t i=0; i OUT_size) { // Checking that the UIN value fits inside OUT. 70 | if (unsigned_seed > upper) { 71 | throw std::out_of_range("seed element out of range for possible integers"); 72 | } 73 | } 74 | 75 | OUT current=unsigned_seed; 76 | if (UIN_size > SHIFT && shiftable) { // Checking that the seed value contains no more than SHIFT set bits. 77 | if (right_shift(current) != 0) { 78 | throw std::runtime_error("seed element out of range for possible integers"); 79 | } 80 | } 81 | 82 | if (left_upper < sum) { // Avoid overflow upon left shift. 83 | throw std::out_of_range("vector implies an out-of-range seed"); 84 | } 85 | if (shiftable) { // Avoid UB from left-shifting by the length of OUT in bits. 86 | sum = left_shift(sum); 87 | } 88 | 89 | if (upper - current < sum) { // Subtract first, to avoid overflow during check. 90 | throw std::out_of_range("vector implies an out-of-range seed"); 91 | } 92 | sum |= current; 93 | } 94 | 95 | return sum; 96 | } 97 | 98 | /* Re-start of user-visible functions here. 'convert_seed' 99 | * is overloaded for direct use of input uint32_t's generated 100 | * by, e.g., std::random_device; to use int's from some other 101 | * source; or to use int's passed in from R. 102 | */ 103 | 104 | template 105 | T convert_seed(const uint32_t* seeds, size_t N) { 106 | return convert_seed_internal(seeds, N); 107 | } 108 | 109 | template 110 | T convert_seed(const int* seeds, size_t N) { 111 | return convert_seed_internal(seeds, N); // Enforce 32-bit shift, just in case. 112 | } 113 | 114 | template 115 | T convert_seed(Rcpp::IntegerVector seeds) { 116 | return convert_seed(seeds.begin(), seeds.size()); 117 | } 118 | 119 | } 120 | 121 | #endif 122 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/r,c++,vim,emacs,linux,macos,windows,visualstudiocode 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=r,c++,vim,emacs,linux,macos,windows,visualstudiocode 3 | 4 | ### C++ ### 5 | # Prerequisites 6 | *.d 7 | 8 | # Compiled Object files 9 | *.slo 10 | *.lo 11 | *.o 12 | *.obj 13 | 14 | # Precompiled Headers 15 | *.gch 16 | *.pch 17 | 18 | # Compiled Dynamic libraries 19 | *.so 20 | *.dylib 21 | *.dll 22 | 23 | # Fortran module files 24 | *.mod 25 | *.smod 26 | 27 | # Compiled Static libraries 28 | *.lai 29 | *.la 30 | *.a 31 | *.lib 32 | 33 | # Executables 34 | *.exe 35 | *.out 36 | *.app 37 | 38 | ### Emacs ### 39 | # -*- mode: gitignore; -*- 40 | *~ 41 | \#*\# 42 | /.emacs.desktop 43 | /.emacs.desktop.lock 44 | *.elc 45 | auto-save-list 46 | tramp 47 | .\#* 48 | 49 | # Org-mode 50 | .org-id-locations 51 | *_archive 52 | 53 | # flymake-mode 54 | *_flymake.* 55 | 56 | # eshell files 57 | /eshell/history 58 | /eshell/lastdir 59 | 60 | # elpa packages 61 | /elpa/ 62 | 63 | # reftex files 64 | *.rel 65 | 66 | # AUCTeX auto folder 67 | /auto/ 68 | 69 | # cask packages 70 | .cask/ 71 | dist/ 72 | 73 | # Flycheck 74 | flycheck_*.el 75 | 76 | # server auth directory 77 | /server/ 78 | 79 | # projectiles files 80 | .projectile 81 | 82 | # directory configuration 83 | .dir-locals.el 84 | 85 | # network security 86 | /network-security.data 87 | 88 | 89 | ### Linux ### 90 | 91 | # temporary files which can be created if a process still has a handle open of a deleted file 92 | .fuse_hidden* 93 | 94 | # KDE directory preferences 95 | .directory 96 | 97 | # Linux trash folder which might appear on any partition or disk 98 | .Trash-* 99 | 100 | # .nfs files are created when an open file is removed but is still being accessed 101 | .nfs* 102 | 103 | ### macOS ### 104 | # General 105 | .DS_Store 106 | .AppleDouble 107 | .LSOverride 108 | 109 | # Icon must end with two \r 110 | Icon 111 | 112 | 113 | # Thumbnails 114 | ._* 115 | 116 | # Files that might appear in the root of a volume 117 | .DocumentRevisions-V100 118 | .fseventsd 119 | .Spotlight-V100 120 | .TemporaryItems 121 | .Trashes 122 | .VolumeIcon.icns 123 | .com.apple.timemachine.donotpresent 124 | 125 | # Directories potentially created on remote AFP share 126 | .AppleDB 127 | .AppleDesktop 128 | Network Trash Folder 129 | Temporary Items 130 | .apdisk 131 | 132 | ### macOS Patch ### 133 | # iCloud generated files 134 | *.icloud 135 | 136 | ### R ### 137 | # History files 138 | .Rhistory 139 | .Rapp.history 140 | 141 | # Session Data files 142 | .RData 143 | .RDataTmp 144 | 145 | # User-specific files 146 | .Ruserdata 147 | 148 | # Example code in package build process 149 | *-Ex.R 150 | 151 | # Output files from R CMD build 152 | /*.tar.gz 153 | 154 | # Output files from R CMD check 155 | /*.Rcheck/ 156 | 157 | # RStudio files 158 | .Rproj.user/ 159 | 160 | # produced vignettes 161 | vignettes/*.html 162 | vignettes/*.pdf 163 | 164 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 165 | .httr-oauth 166 | 167 | # knitr and R markdown default cache directories 168 | *_cache/ 169 | /cache/ 170 | 171 | # Temporary files created by R markdown 172 | *.utf8.md 173 | *.knit.md 174 | 175 | # R Environment Variables 176 | .Renviron 177 | 178 | # pkgdown site 179 | docs/ 180 | 181 | # translation temp files 182 | po/*~ 183 | 184 | # RStudio Connect folder 185 | rsconnect/ 186 | 187 | ### R.Bookdown Stack ### 188 | # R package: bookdown caching files 189 | /*_files/ 190 | 191 | ### Vim ### 192 | # Swap 193 | [._]*.s[a-v][a-z] 194 | !*.svg # comment out if you don't need vector files 195 | [._]*.sw[a-p] 196 | [._]s[a-rt-v][a-z] 197 | [._]ss[a-gi-z] 198 | [._]sw[a-p] 199 | 200 | # Session 201 | Session.vim 202 | Sessionx.vim 203 | 204 | # Temporary 205 | .netrwhist 206 | # Auto-generated tag files 207 | tags 208 | # Persistent undo 209 | [._]*.un~ 210 | 211 | ### VisualStudioCode ### 212 | .vscode/* 213 | !.vscode/settings.json 214 | !.vscode/tasks.json 215 | !.vscode/launch.json 216 | !.vscode/extensions.json 217 | !.vscode/*.code-snippets 218 | 219 | # Local History for Visual Studio Code 220 | .history/ 221 | 222 | # Built Visual Studio Code Extensions 223 | *.vsix 224 | 225 | ### VisualStudioCode Patch ### 226 | # Ignore all local history of files 227 | .history 228 | .ionide 229 | 230 | ### Windows ### 231 | # Windows thumbnail cache files 232 | Thumbs.db 233 | Thumbs.db:encryptable 234 | ehthumbs.db 235 | ehthumbs_vista.db 236 | 237 | # Dump file 238 | *.stackdump 239 | 240 | # Folder config file 241 | [Dd]esktop.ini 242 | 243 | # Recycle Bin used on file shares 244 | $RECYCLE.BIN/ 245 | 246 | # Windows Installer files 247 | *.cab 248 | *.msi 249 | *.msix 250 | *.msm 251 | *.msp 252 | 253 | # Windows shortcuts 254 | *.lnk 255 | 256 | # End of https://www.toptal.com/developers/gitignore/api/r,c++,vim,emacs,linux,macos,windows,visualstudiocode 257 | 258 | inst/doc 259 | vignettes/*.R 260 | /external/tmp 261 | .cache 262 | compile_commands.json 263 | revdep -------------------------------------------------------------------------------- /man/dqrng-functions.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/RcppExports.R, R/dqset.seed.R 3 | \name{dqRNGkind} 4 | \alias{dqRNGkind} 5 | \alias{dqrng_get_state} 6 | \alias{dqrng_set_state} 7 | \alias{dqrunif} 8 | \alias{dqrnorm} 9 | \alias{dqrexp} 10 | \alias{dqrrademacher} 11 | \alias{dqset.seed} 12 | \title{R interface} 13 | \usage{ 14 | dqRNGkind(kind, normal_kind = "ignored") 15 | 16 | dqrng_get_state() 17 | 18 | dqrng_set_state(state) 19 | 20 | dqrunif(n, min = 0, max = 1) 21 | 22 | dqrnorm(n, mean = 0, sd = 1) 23 | 24 | dqrexp(n, rate = 1) 25 | 26 | dqrrademacher(n) 27 | 28 | dqset.seed(seed, stream = NULL) 29 | } 30 | \arguments{ 31 | \item{kind}{string specifying the RNG (see details)} 32 | 33 | \item{normal_kind}{ignored; included for compatibility with \code{\link{RNGkind}}} 34 | 35 | \item{state}{character vector representation of the RNG's internal state} 36 | 37 | \item{n}{number of observations} 38 | 39 | \item{min}{lower limit of the uniform distribution} 40 | 41 | \item{max}{upper limit of the uniform distribution} 42 | 43 | \item{mean}{mean value of the normal distribution} 44 | 45 | \item{sd}{standard deviation of the normal distribution} 46 | 47 | \item{rate}{rate of the exponential distribution} 48 | 49 | \item{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.} 50 | 51 | \item{stream}{integer used for selecting the RNG stream; either a scalar or a vector of length 2} 52 | } 53 | \value{ 54 | \code{dqrunif}, \code{dqrnorm}, and \code{dqrexp} return a numeric vector 55 | of length \code{n}. \code{dqrrademacher} returns an integer vector of length \code{n}. 56 | \code{dqrng_get_state} returns a character vector representation of the RNG's internal state. 57 | } 58 | \description{ 59 | The \code{dqrng} package provides several fast random number 60 | generators together with fast functions for generating random numbers 61 | according to a uniform, normal and exponential distribution. These 62 | functions are modeled after the \code{base} functions 63 | \code{\link{set.seed}}, \code{\link{RNGkind}}, \code{\link{runif}}, 64 | \code{\link{rnorm}}, and \code{\link{rexp}}. However, note that the functions 65 | provided here do not accept vector arguments for the number of observations 66 | as well as the parameters describing the distribution functions. Please see 67 | \code{\link{register_methods}} if you need this functionality. 68 | 69 | \code{dqrrademacher} uses a fast algorithm to generate random 70 | Rademacher variables (-1 and 1 with equal probability). To do so, it 71 | generates a random 64 bit integer and then uses each bit to generate 72 | a 0/1 variable. This generates 64 integers per random number generation. 73 | 74 | \code{dqrng_get_state} and \code{dqrng_set_state} can be used to get and set 75 | the RNG's internal state. The character vector should not be manipulated directly. 76 | } 77 | \details{ 78 | Supported RNG kinds: 79 | \describe{ 80 | \item{pcg64}{The default 64 bit variant from the PCG family developed by 81 | Melissa O'Neill. See \url{https://www.pcg-random.org/} for more details.} 82 | \item{Xoroshiro128++ and Xoshiro256++}{RNGs developed by David Blackman and 83 | Sebastiano Vigna. See \url{https://prng.di.unimi.it/} for more details. 84 | The older generators Xoroshiro128+ and Xoshiro256+ should be used only for 85 | backwards compatibility.} 86 | \item{Threefry}{The 64 bit version of the 20 rounds Threefry engine as 87 | provided by \code{\link[sitmo]{sitmo-package}}} 88 | } 89 | 90 | Xoroshiro128++ is the default since it is fast, small and has good statistical 91 | properties. 92 | 93 | The functions \code{dqrnorm} and \code{dqrexp} use the Ziggurat algorithm as 94 | provided by \code{boost.random}. 95 | 96 | See \code{\link{generateSeedVectors}} for rapid generation of integer-vector 97 | seeds that provide 64 bits of entropy. These allow full exploration of 98 | the state space of the 64-bit RNGs provided in this package. 99 | 100 | If the provided \code{seed} is \code{NULL}, a seed is generated from R's RNG 101 | without state alteration. 102 | } 103 | \examples{ 104 | library(dqrng) 105 | 106 | # Set custom RNG. 107 | dqRNGkind("Xoshiro256++") 108 | 109 | # Use an integer scalar to set a seed. 110 | dqset.seed(42) 111 | 112 | # Use integer scalars to set a seed and the stream. 113 | dqset.seed(42, 123) 114 | 115 | # Use an integer vector to set a seed. 116 | dqset.seed(c(31311L, 24123423L)) 117 | 118 | # Use an integer vector to set a seed and a scalar to select the stream. 119 | dqset.seed(c(31311L, 24123423L), 123) 120 | 121 | # Random sampling from distributions. 122 | dqrunif(5, min = 2, max = 10) 123 | dqrexp(5, rate = 4) 124 | dqrnorm(5, mean = 5, sd = 3) 125 | 126 | # get and restore the state 127 | (state <- dqrng_get_state()) 128 | dqrunif(5) 129 | dqrng_set_state(state) 130 | dqrunif(5) 131 | 132 | } 133 | \seealso{ 134 | \code{\link{set.seed}}, \code{\link{RNGkind}}, \code{\link{runif}}, 135 | \code{\link{rnorm}}, and \code{\link{rexp}} 136 | } 137 | -------------------------------------------------------------------------------- /tests/testthat/test-sample.R: -------------------------------------------------------------------------------- 1 | context("sample") 2 | 3 | seed <- 1234567890 4 | 5 | test_that("dqsample_int w/ replacement works for vector", { 6 | dqset.seed(seed) 7 | n <- 1e5 8 | k <- 1e3 9 | result <- dqsample(seq_len(n), k, replace = TRUE) 10 | expect_equal(length(result), k) 11 | expect_lte(length(unique(result)), k) 12 | expect_true(all(result >= 1) && all(result <= n)) 13 | }) 14 | 15 | test_that("dqsample_int w/o replacement works for vector", { 16 | dqset.seed(seed) 17 | n <- 1e5 18 | k <- 1e3 19 | result <- dqsample(seq_len(n), k, replace = FALSE) 20 | expect_equal(length(result), k) 21 | expect_equal(length(unique(result)), k) 22 | expect_true(all(result >= 1) && all(result <= n)) 23 | }) 24 | 25 | test_that("dqsample_int w/o replacement works for shuffling a vector", { 26 | dqset.seed(seed) 27 | n <- 1e5 28 | result <- dqsample(seq_len(n), replace = FALSE) 29 | expect_equal(length(result), n) 30 | expect_equal(length(unique(result)), n) 31 | expect_true(all(result >= 1) && all(result <= n)) 32 | }) 33 | 34 | test_that("dqsample_int w/o replacement works for shuffling numbers", { 35 | dqset.seed(seed) 36 | n <- 1e5 37 | result <- dqsample(n, replace = FALSE) 38 | expect_equal(length(result), n) 39 | expect_equal(length(unique(result)), n) 40 | expect_true(all(result >= 1) && all(result <= n)) 41 | }) 42 | 43 | test_that("dqsample_int w/ replacement works", { 44 | dqset.seed(seed) 45 | n <- 1e5 46 | k <- 1e3 47 | result <- dqsample(n, k, replace = TRUE) 48 | expect_equal(length(result), k) 49 | expect_lte(length(unique(result)), k) 50 | expect_true(all(result >= 1) && all(result <= n)) 51 | }) 52 | 53 | test_that("dqsample_int w/o replacement works with medium rate", { 54 | dqset.seed(seed) 55 | n <- 1e5 56 | k <- 1e3 57 | result <- dqsample(n, k, replace = FALSE) 58 | expect_equal(length(result), k) 59 | expect_equal(length(unique(result)), k) 60 | expect_true(all(result >= 1) && all(result <= n)) 61 | }) 62 | 63 | test_that("dqsample_int w/o replacement works with low rate", { 64 | dqset.seed(seed) 65 | n <- 1e5 66 | k <- 1e1 67 | result <- dqsample(n, k, replace = FALSE) 68 | expect_equal(length(result), k) 69 | expect_equal(length(unique(result)), k) 70 | expect_true(all(result >= 1) && all(result <= n)) 71 | }) 72 | 73 | test_that("dqsample_num w/ replacement works", { 74 | skip_if(.Machine$sizeof.pointer <= 4, "No long-vector support") 75 | dqset.seed(seed) 76 | # use a shorter vector and internal function for performance reasons 77 | n <- 1e5 78 | k <- 1e2 79 | result <- dqrng:::dqsample_num(n, k, replace = TRUE, offset = 1L) 80 | expect_equal(length(result), k) 81 | expect_lte(length(unique(result)), k) 82 | expect_true(all(result >= 1) && all(result <= n)) 83 | }) 84 | 85 | test_that("dqsample_num w/o replacement works with high rate", { 86 | skip_if(.Machine$sizeof.pointer <= 4, "No long-vector support") 87 | dqset.seed(seed) 88 | # use a shorter vector and internal function for performance reasons 89 | n <- 1e5 90 | k <- 6e4 91 | result <- dqrng:::dqsample_num(n, k, replace = FALSE, offset = 1L) 92 | expect_equal(length(result), k) 93 | expect_equal(length(unique(result)), k) 94 | expect_true(all(result >= 1) && all(result <= n)) 95 | }) 96 | 97 | test_that("dqsample_num w/o replacement works with low rate", { 98 | skip_if(.Machine$sizeof.pointer <= 4, "No long-vector support") 99 | dqset.seed(seed) 100 | # use a shorter vector and internal function for performance reasons 101 | n <- 1e5 102 | k <- 1e2 103 | result <- dqrng:::dqsample_num(n, k, replace = FALSE, offset = 1L) 104 | expect_equal(length(result), k) 105 | expect_equal(length(unique(result)), k) 106 | expect_true(all(result >= 1) && all(result <= n)) 107 | }) 108 | 109 | test_that("dqsample_num w/o replacement works with medium rate", { 110 | skip_if(.Machine$sizeof.pointer <= 4, "No long-vector support") 111 | dqset.seed(seed) 112 | # use a shorter vector and internal function for performance reasons 113 | n <- 1e5 114 | k <- 1e4 115 | result <- dqrng:::dqsample_num(n, k, replace = FALSE, offset = 1) 116 | expect_equal(length(result), k) 117 | expect_equal(length(unique(result)), k) 118 | expect_true(all(result >= 1) && all(result <= n)) 119 | }) 120 | 121 | test_that("sampling with weights gives warning", { 122 | dqset.seed(seed) 123 | n <- 1e5 124 | k <- 1e3 125 | expect_warning(dqsample(n, k, replace = TRUE, prob = dqrunif(n)), 126 | "Using 'prob' is not supported yet. Using default 'sample.int'.") 127 | }) 128 | 129 | test_that("error cases", { 130 | dqset.seed(seed) 131 | expect_error(dqsample(10, 20), "Argument requirements not fulfilled: n >= size") 132 | expect_silent(dqsample(10, 20, replace = TRUE)) 133 | expect_error(dqsample(10, -20), "Argument requirements not fulfilled: n > 0 && size >= 0") 134 | expect_error(dqsample(-10, -20), "Argument requirements not fulfilled: n > 0 && size >= 0") 135 | # -10 is treated as a one-element vector by dqsample but not by dqsample.int 136 | expect_error(dqsample(-10, 20), "Argument requirements not fulfilled: n >= size") 137 | expect_error(dqsample.int(-10, 20), "Argument requirements not fulfilled: n > 0 && size >= 0") 138 | 139 | skip_if(.Machine$sizeof.pointer <= 4, "No long-vector support") 140 | expect_error(dqsample(1e10, -20), "Argument requirements not fulfilled: n > 0 && size >= 0") 141 | }) 142 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | [![R build 5 | status](https://github.com/daqana/dqrng/workflows/R-CMD-check/badge.svg)](https://github.com/daqana/dqrng/actions) 6 | [![CRAN 7 | status](https://www.r-pkg.org/badges/version/dqrng)](https://cran.r-project.org/package=dqrng) 8 | [![dqrng status 9 | badge](https://rstub.r-universe.dev/badges/dqrng)](https://rstub.r-universe.dev/dqrng) 10 | [![Coverage 11 | status](https://codecov.io/gh/daqana/dqrng/branch/main/graph/badge.svg)](https://app.codecov.io/github/daqana/dqrng?branch=main) 12 | [![Downloads](https://cranlogs.r-pkg.org/badges/dqrng?color=brightgreen)](https://www.r-pkg.org/pkg/dqrng) 13 | [![CII Best 14 | Practices](https://bestpractices.coreinfrastructure.org/projects/2157/badge)](https://bestpractices.coreinfrastructure.org/projects/2157) 15 | [![Dependencies](https://tinyverse.netlify.app/badge/dqrng)](https://cran.r-project.org/package=dqrng) 16 | 17 | # dqrng 18 | 19 | The dqrng package provides fast random number generators (RNG) with good 20 | statistical properties for usage with R. It combines these RNGs with 21 | fast distribution functions to sample from uniform, normal or 22 | exponential distributions. Both the RNGs and the distribution functions 23 | are distributed as C++ header-only library. 24 | 25 | ## Installation 26 | 27 | The currently released version is available from CRAN via 28 | 29 | ``` r 30 | install.packages("dqrng") 31 | ``` 32 | 33 | Intermediate releases can also be obtained via 34 | [r-universe](https://rstub.r-universe.dev/dqrng): 35 | 36 | ``` r 37 | install.packages('dqrng', repos = c( 38 | rstub = 'https://rstub.r-universe.dev', 39 | CRAN = 'https://cloud.r-project.org')) 40 | ``` 41 | 42 | ## Example 43 | 44 | Using the provided RNGs from R is deliberately similar to using R’s 45 | build-in RNGs: 46 | 47 | ``` r 48 | library(dqrng) 49 | dqset.seed(42) 50 | dqrunif(5, min = 2, max = 10) 51 | #> [1] 9.266963 4.644899 9.607483 3.635770 4.742639 52 | dqrexp(5, rate = 4) 53 | #> [1] 0.111103883 0.084289794 0.003414377 0.042012033 0.143914583 54 | ``` 55 | 56 | They are quite a bit faster, though: 57 | 58 | ``` r 59 | N <- 1e4 60 | bm <- bench::mark(rnorm(N), dqrnorm(N), check = FALSE) 61 | bm[, 1:4] 62 | #> # A tibble: 2 × 4 63 | #> expression min median `itr/sec` 64 | #> 65 | #> 1 rnorm(N) 606.2µs 654.7µs 1456. 66 | #> 2 dqrnorm(N) 82.8µs 85.9µs 10984. 67 | ``` 68 | 69 | This is also true for the provided sampling functions with replacement: 70 | 71 | ``` r 72 | m <- 1e7 73 | n <- 1e5 74 | bm <- bench::mark(sample.int(m, n, replace = TRUE), 75 | sample.int(1e3*m, n, replace = TRUE), 76 | dqsample.int(m, n, replace = TRUE), 77 | dqsample.int(1e3*m, n, replace = TRUE), 78 | check = FALSE) 79 | bm[, 1:4] 80 | #> # A tibble: 4 × 4 81 | #> expression min median `itr/sec` 82 | #> 83 | #> 1 sample.int(m, n, replace = TRUE) 6.88ms 7.07ms 140. 84 | #> 2 sample.int(1000 * m, n, replace = TRUE) 8.57ms 8.81ms 112. 85 | #> 3 dqsample.int(m, n, replace = TRUE) 289.69µs 296.86µs 2834. 86 | #> 4 dqsample.int(1000 * m, n, replace = TRUE) 407.45µs 489.33µs 1645. 87 | ``` 88 | 89 | And without replacement: 90 | 91 | ``` r 92 | bm <- bench::mark(sample.int(m, n), 93 | sample.int(1e3*m, n), 94 | sample.int(m, n, useHash = TRUE), 95 | dqsample.int(m, n), 96 | dqsample.int(1e3*m, n), 97 | check = FALSE) 98 | #> Warning: Some expressions had a GC in every iteration; so filtering is 99 | #> disabled. 100 | bm[, 1:4] 101 | #> # A tibble: 5 × 4 102 | #> expression min median `itr/sec` 103 | #> 104 | #> 1 sample.int(m, n) 40.98ms 42.8ms 23.1 105 | #> 2 sample.int(1000 * m, n) 12.01ms 13.3ms 66.9 106 | #> 3 sample.int(m, n, useHash = TRUE) 9.35ms 10.4ms 92.4 107 | #> 4 dqsample.int(m, n) 616.34µs 679.1µs 1265. 108 | #> 5 dqsample.int(1000 * m, n) 1.42ms 1.7ms 501. 109 | ``` 110 | 111 | Note that sampling from `10^10` elements triggers “long-vector support” 112 | in R. 113 | 114 | In addition the RNGs provide support for multiple independent streams 115 | for parallel usage: 116 | 117 | ``` r 118 | N <- 1e7 119 | dqset.seed(42, 1) 120 | u1 <- dqrunif(N) 121 | dqset.seed(42, 2) 122 | u2 <- dqrunif(N) 123 | cor(u1, u2) 124 | #> [1] 0.0009574617 125 | ``` 126 | 127 | It is also possible to register the supplied generators as user-supplied 128 | RNGs. This way `set.seed()` and `dqset.seed()` influence both 129 | `(dq)runif` and `(dq)rnorm` in the same way. This is also true for other 130 | `r` functions, but note that `rexp` and `dqrexp` still give 131 | different results: 132 | 133 | ``` r 134 | register_methods() 135 | set.seed(4711); runif(5) 136 | #> [1] 0.3143534 0.7835753 0.1443660 0.1109871 0.6433407 137 | set.seed(4711); dqrunif(5) 138 | #> [1] 0.3143534 0.7835753 0.1443660 0.1109871 0.6433407 139 | dqset.seed(4711); rnorm(5) 140 | #> [1] -0.3618122 0.8199887 -0.4075635 0.2073972 -0.8038326 141 | dqset.seed(4711); dqrnorm(5) 142 | #> [1] -0.3618122 0.8199887 -0.4075635 0.2073972 -0.8038326 143 | set.seed(4711); rt(5, 10) 144 | #> [1] -0.3196113 -0.4095873 -1.2928241 0.2399470 -0.1068945 145 | dqset.seed(4711); rt(5, 10) 146 | #> [1] -0.3196113 -0.4095873 -1.2928241 0.2399470 -0.1068945 147 | set.seed(4711); rexp(5, 10) 148 | #> [1] 0.0950560698 0.0567150561 0.1541222748 0.2512966671 0.0002175758 149 | set.seed(4711); dqrexp(5, 10) 150 | #> [1] 0.03254731 0.06855303 0.06977124 0.02579004 0.07629535 151 | restore_methods() 152 | ``` 153 | 154 | ## Feedback 155 | 156 | All feedback (bug reports, security issues, feature requests, …) should 157 | be provided as [issues](https://github.com/daqana/dqrng/issues). 158 | -------------------------------------------------------------------------------- /inst/include/dqrng_generator.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2019 Ralf Stubner (daqana GmbH) 2 | // Copyright 2023-2024 Ralf Stubner 3 | // 4 | // This file is part of dqrng. 5 | // 6 | // dqrng is free software: you can redistribute it and/or modify it 7 | // under the terms of the GNU Affero General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // dqrng is distributed in the hope that it will be useful, but 12 | // WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU Affero General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU Affero General Public License 17 | // along with dqrng. If not, see . 18 | 19 | #ifndef DQRNG_GENERATOR_H 20 | #define DQRNG_GENERATOR_H 1 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #if defined(__cpp_lib_make_unique) && (__cpp_lib_make_unique >= 201304) 32 | using std::make_unique; 33 | #else 34 | template 35 | std::unique_ptr make_unique(Args&&... args) { 36 | return std::unique_ptr(new T(std::forward(args)...)); 37 | } 38 | #endif 39 | 40 | namespace dqrng { 41 | using rng64_t = Rcpp::XPtr; 42 | using default_64bit_generator = ::dqrng::xoroshiro128plusplus; 43 | 44 | template 45 | class random_64bit_wrapper : public random_64bit_generator { 46 | static_assert(std::is_same::value, 47 | "Provided RNG has wrong result_type"); 48 | static_assert(RNG::max() == UINT64_MAX, "Provided RNG has wrong maximum."); 49 | static_assert(RNG::min() == 0, "Provided RNG has wrong minimum."); 50 | private: 51 | RNG gen; 52 | 53 | protected: 54 | virtual void output(std::ostream& ost) const override {ost << gen;} 55 | virtual void input(std::istream& ist) override {ist >> gen;} 56 | void set_stream(result_type stream) {throw std::runtime_error("Stream handling not supported for this RNG!");} 57 | 58 | public: 59 | random_64bit_wrapper() : gen() {}; 60 | random_64bit_wrapper(RNG _gen) : gen(_gen) {}; 61 | random_64bit_wrapper(result_type seed) : gen(seed) {}; 62 | random_64bit_wrapper(result_type seed, result_type stream) : gen() {this->seed(seed, stream);}; 63 | virtual result_type operator() () override {return gen();} 64 | virtual void seed(result_type seed) override {cache = false; gen.seed(seed);} 65 | virtual void seed(result_type seed, result_type stream) override {cache = false; gen.seed(seed); this->set_stream(stream);} 66 | virtual std::unique_ptr clone(result_type stream) override { 67 | auto rng = make_unique>(gen); 68 | rng->set_stream(stream); 69 | return rng; 70 | } 71 | }; 72 | 73 | template<> 74 | inline void random_64bit_wrapper<::dqrng::xoroshiro128plus>::set_stream(result_type stream) { 75 | gen.jump(stream); 76 | } 77 | 78 | template<> 79 | inline void random_64bit_wrapper<::dqrng::xoroshiro128plusplus>::set_stream(result_type stream) { 80 | gen.jump(stream); 81 | } 82 | 83 | template<> 84 | inline void random_64bit_wrapper<::dqrng::xoroshiro128starstar>::set_stream(result_type stream) { 85 | gen.jump(stream); 86 | } 87 | 88 | template<> 89 | inline void random_64bit_wrapper<::dqrng::xoshiro256plus>::set_stream(result_type stream) { 90 | gen.long_jump(stream); 91 | } 92 | 93 | template<> 94 | inline void random_64bit_wrapper<::dqrng::xoshiro256plusplus>::set_stream(result_type stream) { 95 | gen.long_jump(stream); 96 | } 97 | 98 | template<> 99 | inline void random_64bit_wrapper<::dqrng::xoshiro256starstar>::set_stream(result_type stream) { 100 | gen.long_jump(stream); 101 | } 102 | 103 | #if !(defined(__APPLE__) && defined(__POWERPC__)) 104 | template<> 105 | inline void random_64bit_wrapper::set_stream(result_type stream) { 106 | // set the stream relative to the current stream, i.e. stream = 0 does not change the RNG 107 | pcg_extras::pcg128_t number; 108 | std::vector state; 109 | std::stringstream iss; 110 | iss << gen; 111 | using pcg_extras::operator>>; 112 | while (iss >> number) 113 | state.push_back(number); 114 | // state[1] is the current stream 115 | // PCG will do 2*stream + 1 to make sure stream is odd; need to revert that here 116 | gen.set_stream(state[1]/pcg_extras::pcg128_t(2) + pcg_extras::pcg128_t(stream)); 117 | } 118 | 119 | // keep using the two argument ctor for PCG for backwards compatibility 120 | template<> 121 | inline void random_64bit_wrapper::seed(result_type seed, result_type stream) { 122 | gen.seed(seed, stream); 123 | cache = false; 124 | } 125 | #endif 126 | 127 | inline uint64_t get_seed_from_r() { 128 | Rcpp::RNGScope rngScope; 129 | Rcpp::IntegerVector seed(2, dqrng::R_random_int); 130 | return dqrng::convert_seed(seed); 131 | } 132 | 133 | 134 | template 135 | typename std::enable_if::value, rng64_t>::type 136 | generator () { 137 | return rng64_t(new random_64bit_wrapper(get_seed_from_r())); 138 | } 139 | 140 | template 141 | typename std::enable_if::value, rng64_t>::type 142 | generator () { 143 | return rng64_t(new RNG(get_seed_from_r())); 144 | } 145 | 146 | template 147 | typename std::enable_if::value, rng64_t>::type 148 | generator (uint64_t seed) { 149 | return rng64_t(new random_64bit_wrapper(seed)); 150 | } 151 | 152 | template 153 | typename std::enable_if::value, rng64_t>::type 154 | generator (uint64_t seed) { 155 | return rng64_t(new RNG(seed)); 156 | } 157 | 158 | template 159 | typename std::enable_if::value, rng64_t>::type 160 | generator (uint64_t seed, uint64_t stream) { 161 | return rng64_t(new random_64bit_wrapper(seed, stream)); 162 | } 163 | 164 | template 165 | typename std::enable_if::value, rng64_t>::type 166 | generator (uint64_t seed, uint64_t stream) { 167 | return rng64_t(new RNG(seed, stream)); 168 | } 169 | } // namespace dqrng 170 | 171 | #endif // DQRNG_GENERATOR_H 172 | -------------------------------------------------------------------------------- /vignettes/cpp-api.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "C++ API" 3 | author: "Ralf Stubner" 4 | date: "`r Sys.Date()`" 5 | output: rmarkdown::html_vignette 6 | vignette: > 7 | %\VignetteIndexEntry{C++ API} 8 | %\VignetteEngine{knitr::rmarkdown} 9 | %\VignetteEncoding{UTF-8} 10 | --- 11 | 12 | The RNGs and distributions functions can also be used from C++ at various levels of abstraction. 13 | Technically there are three ways to make use of dqrng at the C++ level: 14 | 15 | * use `// [[Rcpp::depends(dqrng)]]` together with `Rcpp::sourceCpp()` 16 | * use `Rcpp::cppFunction(depends = "dqrng", ...)` 17 | * use an R package with `LinkingTo: dqrng` 18 | 19 | The following functions are also available if you include `dqrng.h`. Note that the scalar function `dqrng::runif`, `dqrng::rnorm`, and `dqrng::rexp` have been deprecated and will be removed in a future release. Please use the more flexible and faster `dqrng::random_64bit_accessor` together with `variate()` instead. 20 | 21 | ## Setting seed and RNG type 22 | 23 | ```cpp 24 | void dqrng::dqset_seed(Rcpp::IntegerVector seed, 25 | Rcpp::Nullable stream = R_NilValue) 26 | void dqrng::dqRNGkind(std::string kind, const std::string& normal_kind = "ignored") 27 | ``` 28 | 29 | `seed` 30 | : seed for the RNG; length 1 or 2 31 | 32 | `stream` 33 | : RNG stream to use; length 1 or 2 34 | 35 | `kind` 36 | : string specifying the RNG, One of "pcg64", "Xoroshiro128+", "Xoroshiro128++", "Xoshiro256+", "Xoshiro256++" or "Threefry" 37 | 38 | `normal-kind` 39 | : ignored; included for compatibility with `RNGkind` 40 | 41 | ## Random variates with uniform distribution 42 | 43 | ```cpp 44 | Rcpp::NumericVector dqrng::dqrunif(size_t n, double min = 0.0, double max = 1.0) 45 | DEPRECATED double dqrng::runif(double min = 0.0, double max = 1.0) 46 | ``` 47 | `n` 48 | : number of observations 49 | 50 | `min` 51 | : lower limit of the uniform distribution 52 | 53 | `max` 54 | : upper limit of the uniform distribution 55 | 56 | ## Random variates with normal distribution 57 | 58 | ```cpp 59 | Rcpp::NumericVector dqrng::dqrnorm(size_t n, double mean = 0.0, double sd = 1.0) 60 | DEPRECATED double dqrng::rnorm(double mean = 0.0, double sd = 1.0) 61 | ``` 62 | 63 | `n` 64 | : number of observations 65 | 66 | `mean` 67 | : mean value of the normal distribution 68 | 69 | `sd` 70 | : standard deviation of the normal distribution 71 | 72 | ## Random variates with exponential distribution 73 | 74 | ```cpp 75 | Rcpp::NumericVector dqrng::dqrexp(size_t n, double rate = 1.0) 76 | DEPRECATED double dqrng::rexp(double rate = 1.0) 77 | ``` 78 | 79 | `n` 80 | : number of observations 81 | 82 | `rate` 83 | : rate of the exponential distribution 84 | 85 | ## Random variates with Rademacher distribution 86 | 87 | ```cpp 88 | Rcpp::IntegerVector dqrng::dqrrademacher(size_t n) 89 | ``` 90 | 91 | `n` 92 | : number of observations 93 | 94 | ## Random sampling 95 | 96 | ```cpp 97 | Rcpp::IntegerVector dqrng::dqsample_int(int n, int size, bool replace = false, 98 | Rcpp::Nullable probs = R_NilValue, 99 | int offset = 0) 100 | Rcpp::NumericVector dqrng::dqsample_num(double n, double size, bool replace = false, 101 | Rcpp::Nullable probs = R_NilValue, 102 | int offset = 0) 103 | ``` 104 | 105 | `n` 106 | : a positive number, the number of items to choose from 107 | 108 | `size` 109 | : a non-negative number giving the number of items to choose 110 | 111 | `replace` 112 | : should sampling be with replacement? 113 | 114 | `prob` 115 | : a vector of probability weights for obtaining the elements of the vector being sampled (currently ignored) 116 | 117 | `offset` 118 | : sample from range `[offset, offset + m)` 119 | 120 | The two functions are used for "normal" and "long-vector" support in R. 121 | 122 | ## Getting and setting the RNG state 123 | 124 | ```cpp 125 | std::vector dqrng::dqrng_get_state() 126 | void dqrng::dqrng_set_state(std::vector state) 127 | ``` 128 | 129 | `state` 130 | : a `std::vector` as produced by `dqrng_get_state()` 131 | 132 | ## Accessing the global RNG 133 | 134 | ```cpp 135 | Rcpp::XPtr dqrng::get_rng() 136 | ``` 137 | 138 | Direct usage of this method is **discouraged**. The preferred way of accessing the global RNG is to instantiate `dqrng::random_64bit_accessor` within your function. Note that you MUST NOT delete the global RNG. Using `dqrng::random_64bit_accessor` makes this impossible. In addition, you SHOULD NOT store a reference to the RNG permanently, because it can be invalidated by calls to `dqRNGkind`. Therefore, instances of `dqrng::random_64bit_accessor` SHOULD be stored as (non-static) variables in functions. 139 | 140 | Note that `dqrng::random_64bit_accessor` supports [UniformRandomBitGenerator](https://en.cppreference.com/w/cpp/named_req/UniformRandomBitGenerator) and can therefore be used together with any C++11 distribution function. In addition, the following functions are supported, since they are inherited from the abstract parent class `random_64bit_generator`: 141 | 142 | ```cpp 143 | // clone RNG and select a different stream 144 | std::unique_ptr clone(uint64_t stream) 145 | // uniform doubles in [0,1) and double-int-pairs 146 | double uniform01() 147 | std::pair generate_double_8bit_pair() 148 | // uniform integers in a range 149 | uint32_t operator() (uint32_t range) 150 | uint64_t operator() (uint64_t range) 151 | ResultType variate(param1, ... paramN) 152 | generate(container, param1, ... paramN) 153 | generate(start, end, param1, ... paramN) 154 | Dist::result_type variate(param1, ... paramN) 155 | generate(container, param1, ... paramN) 156 | generate(start, end, param1, ... paramN) 157 | ``` 158 | 159 | `stream` 160 | : RNG stream to use for the cloned RNG 161 | 162 | `range` 163 | : Integers are generated in closed interval `[0, range]` 164 | 165 | `ResultType` 166 | : Expected result from the distribution template `DistTmpl` 167 | 168 | `DistTmpl` 169 | : Distribution template like `std::uniform_distribution`. `DistTmpl` defines the full distribution. 170 | 171 | `Dist` 172 | : Full distribution like `std::uniform_distribution` or `dqrng::normal_distriubtion`. 173 | 174 | `param1, ... paramN` 175 | : Necessary parameters to initialize the distribution. 176 | 177 | `container` 178 | : A container that is to be filled with variates from the distribution function. Needs to support `std::begin` and `std::end`. 179 | 180 | `start, end` 181 | : Forward iterators pointing to start and end of a range to be filled with variates from the distribution function. 182 | -------------------------------------------------------------------------------- /vignettes/sample.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Fast sampling methods" 3 | author: "Ralf Stubner" 4 | date: "`r Sys.Date()`" 5 | output: rmarkdown::html_vignette 6 | vignette: > 7 | %\VignetteIndexEntry{Fast sampling methods} 8 | %\VignetteEngine{knitr::rmarkdown} 9 | %\VignetteEncoding{UTF-8} 10 | --- 11 | 12 | ```{r, include = FALSE} 13 | knitr::opts_chunk$set( 14 | collapse = TRUE, 15 | comment = "#>" 16 | ) 17 | evaluate <- FALSE 18 | require(bench) 19 | ``` 20 | 21 | Random sampling from a fixed set is used in many areas of statistical computing. 22 | The performance of this operation can be critical, especially when the sampled set is large. 23 | The fast RNGs provided in this package make very fast sampling possible when combined with suitably fast algorithms. 24 | 25 | ## Benchmarks 26 | 27 | By combining fast RNGs with a fast methods for creating [integers in a range](https://www.pcg-random.org/posts/bounded-rands.html) one gets good performance for sampling with replacement: 28 | 29 | ```{r replacement, eval=evaluate} 30 | library(dqrng) 31 | m <- 1e6 32 | n <- 1e4 33 | bm <- bench::mark(sample.int(m, n, replace = TRUE), 34 | sample.int(1e4*m, n, replace = TRUE), 35 | dqsample.int(m, n, replace = TRUE), 36 | dqsample.int(1e4*m, n, replace = TRUE), 37 | check = FALSE) 38 | ``` 39 | ```{r, echo=FALSE} 40 | if (evaluate) { 41 | saveRDS(bm, "data/replacement.RDS") 42 | } else { 43 | bm <- readRDS("data/replacement.RDS") 44 | } 45 | knitr::kable(bm[, 1:5]) 46 | ``` 47 | 48 | 49 | 50 | 51 | Note that sampling from `10^10` integers triggers "[long-vector support](https://stat.ethz.ch/R-manual/R-devel/library/base/html/LongVectors.html)" in R. 52 | 53 | When sampling _without_ replacement one has to consider an appropriate algorithm for making sure that no entry is repeated. When more than 50% of the population are sampled, dqrng shuffles an appropriate part of the full list and returns that. The algorithm used in R is similar but dqrng has the edge with respect to performance: 54 | 55 | ```{r no-replacement-high, eval=evaluate} 56 | library(dqrng) 57 | m <- 1e6 58 | n <- 6e5 59 | bm <- bench::mark(sample.int(m, n), 60 | dqsample.int(m, n), 61 | check = FALSE, min_iterations = 50) 62 | ``` 63 | ```{r, echo=FALSE} 64 | if (evaluate) { 65 | saveRDS(bm, "data/no-replacement-high.RDS") 66 | } else { 67 | bm <- readRDS("data/no-replacement-high.RDS") 68 | } 69 | knitr::kable(bm[, 1:5]) 70 | ``` 71 | 72 | For lower sampling ratios a set based rejection sampling algorithm is used by dqrng. In principle, R can make use of a similar algorithm based on a hashset. However, it is only used for larger input vectors even though it is faster than the default method. The algorithm in dqrng, which is based on a [bitset](https://lemire.me/blog/2012/11/13/fast-sets-of-integers/), is even faster, though: 73 | 74 | ```{r no-replacement-medium, eval=evaluate} 75 | library(dqrng) 76 | m <- 1e6 77 | n <- 1e4 78 | bm <- bench::mark(sample.int(m, n), 79 | sample.int(m, n, useHash = TRUE), 80 | dqsample.int(m, n), 81 | check = FALSE) 82 | ``` 83 | ```{r, echo=FALSE} 84 | if (evaluate) { 85 | saveRDS(bm, "data/no-replacement-medium.RDS") 86 | } else { 87 | bm <- readRDS("data/no-replacement-medium.RDS") 88 | } 89 | knitr::kable(bm[, 1:5]) 90 | ``` 91 | 92 | As one decreases the sampling rate even more, dqrng switches to a hashset based rejection sampling. Both hashset based methods have similar performance and are much faster than R's default method. 93 | 94 | ```{r no-replacement-low, eval=evaluate} 95 | library(dqrng) 96 | m <- 1e6 97 | n <- 1e2 98 | bm <- bench::mark(sample.int(m, n), 99 | sample.int(m, n, useHash = TRUE), 100 | dqsample.int(m, n), 101 | check = FALSE) 102 | ``` 103 | ```{r, echo=FALSE} 104 | if (evaluate) { 105 | saveRDS(bm, "data/no-replacement-low.RDS") 106 | } else { 107 | bm <- readRDS("data/no-replacement-low.RDS") 108 | } 109 | knitr::kable(bm[, 1:5]) 110 | ``` 111 | 112 | For larger sampling ranges R uses the hashset by default, though `dqsample.int` is still faster: 113 | 114 | ```{r no-replacement-long, eval=evaluate} 115 | library(dqrng) 116 | m <- 1e10 117 | n <- 1e5 118 | bm <- bench::mark(sample.int(m, n), 119 | dqsample.int(m, n), 120 | check = FALSE) 121 | ``` 122 | ```{r, echo=FALSE} 123 | if (evaluate) { 124 | saveRDS(bm, "data/no-replacement-long.RDS") 125 | } else { 126 | bm <- readRDS("data/no-replacement-long.RDS") 127 | } 128 | knitr::kable(bm[, 1:5]) 129 | ``` 130 | 131 | 132 | ## Technicalities 133 | 134 | The following methods are used for sampling without replacement. The algorithms are presented in R-like pseudo code, even though the real implementation is in C++. For sampling rates above 50%, a partial [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle) is used: 135 | 136 | ```{r, eval=FALSE} 137 | no_replace_shuffle <- function(m, n) { 138 | tmp <- seq_len(m) 139 | for (i in seq_len(n)) 140 | swap(tmp[i], tmp[i + random_int(m-i)]) 141 | tmp[1:n] 142 | } 143 | ``` 144 | 145 | where `random_int(m-i)` returns a random integer in `[0, m-i]`. Since the full population is kept in memory, this method is only suitable for high selection rates. One could expect that [reservoir sampling](https://en.wikipedia.org/wiki/Reservoir_sampling) should work well for lower selection rates. However, in my tests set based algorithms were faster: 146 | 147 | ```{r, eval=FALSE} 148 | no_replace_set <- function(m, n) { 149 | result <- vector(mode = "...", length = n) # integer or numeric 150 | elems <- new(set, m, n) # set object for storing n objects out of m possible values 151 | for (i in seq_len(n)) 152 | while (TRUE) { 153 | v = random_int(m) 154 | if (elems.insert(v)) { 155 | result[i] = v 156 | break 157 | } 158 | } 159 | result 160 | } 161 | ``` 162 | 163 | Here `elems.insert(v)` returns `TRUE` if the insert was successful, i.e. `v` was not in `elems` before, and `FALSE` otherwise. There are different strategies for implementing such a set. For intermediate sampling rates (currently between 0.1% and 50%) dqrng uses a bitset, i.e. a vector of `m` bits each representing one of the possible values. For lower sampling rates the memory usage of this algorithm is to expensive, which is why a hashset^[For the specialists: Open addressing with a power-of-two size between 1.5 and 3 times `n`, identity hash function for the stored integers and quadratic probing.] is used, since there the used memory scales with `n` and not with `m`. One could expect that [Robert Floyd's sampling algorithm](https://stackoverflow.com/a/2394292/8416610) would be superior, but this was not the case in my tests, probably because it requires a final shuffling of the result to get a random _permutation_ instead of a random _combination_. 164 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # dqrng 0.4.1 2 | 3 | * Fix an UBSAN error found by CRAN ([#90](https://github.com/daqana/dqrng/pull/90) fixing [#89](https://github.com/daqana/dqrng/issues/89)) 4 | * Fix a compilation error when the compiler does not provide a 128bit integer type ([#90](https://github.com/daqana/dqrng/pull/90) fixing [#88](https://github.com/daqana/dqrng/issues/88)) 5 | * Disable PCG64 on MacOS PowerPC ([#91](https://github.com/daqana/dqrng/pull/91) fixing [#88](https://github.com/daqana/dqrng/issues/88)) 6 | 7 | # dqrng 0.4.0 8 | 9 | ## Breaking changes 10 | 11 | * The default RNG has changed from Xoroshiro128+ to Xoroshiro128++. The older generators Xoroshiro128+ and Xoshiro256+ are still available but should only be used for backward compatibility or for generating floating point numbers, i.e. not sampling etc. ([#57](https://github.com/daqana/dqrng/pull/57) fixing [#56](https://github.com/daqana/dqrng/issues/56)) 12 | * The `dqrng::rng64_t` type has been changed to use `Rcpp::XPtr` instead of `std::shared_ptr` and the functions from `dqrng_sample.h` now expect a reference to `dqrng::random_64bit_generator` instead of `dqrng::rng64_t` ([#70](https://github.com/daqana/dqrng/pull/70) fixing [#63](https://github.com/daqana/dqrng/issues/63)) 13 | 14 | ## Other changes 15 | 16 | * Decoupled the 'sitmo' package. It is now possible to use, e.g., the distribution functions from the header-only library without having an explicit `LinkingTo: sitmo`. 17 | * Make the internal RNG accessible from the outside (Henrik Sloot fixing [#41](https://github.com/daqana/dqrng/issues/41) in [#58](https://github.com/daqana/dqrng/pull/58)) 18 | * Add Xoroshiro128\*\*/++ and Xoshiro256\*\*/++ to `xoshiro.h` 19 | * Allow uniform and normal distributions to be registered as user-supplied RNG within R. This happens automatically if the option `dqrng.register_methods` is set to `TRUE`. 20 | * Add missing inline attributes and limit the included Rcpp headers in `dqrng_types.h` ([#75](https://github.com/daqana/dqrng/pull/75) together with Paul Liétar) 21 | * Add I/O methods for the RNG's internal state (fixing [#66](https://github.com/daqana/dqrng/issues/66) in [#78](https://github.com/daqana/dqrng/pull/78)) 22 | * Extend `random_64bit_generator` with additional convenience methods (fixing [#64](https://github.com/daqana/dqrng/issues/64) in [#79](https://github.com/daqana/dqrng/pull/79)) 23 | * A `clone(stream)` method to allow using the global RNG state for parallel computation. Note that for consistency with the other provided RNGs, `stream` is counted relative to the current stream for PCG64. 24 | * New methods `variate(param)`, `generate(container, param)` etc. using and inspired by [`randutils`](https://www.pcg-random.org/posts/ease-of-use-without-loss-of-power.html). 25 | * The scalar functions `dqrng::runif`, `dqrng::rnorm` and `dqrng::rexp` available from `dqrng.h` have been deprecated and will be removed in a future release. Please use the more flexible and faster `dqrng::random_64bit_accessor` together with `variate()` instead. The same applies to `dqrng::uniform01` from `dqrng_distribution.h`, which can be replaced by the member function `dqrng::random_64bit_generator::uniform01`. 26 | * New template function `dqrng::extra::parallel_generate` in `dqrng_extra/parallel_generate.h` as an example for using the global RNG in a parallel context (fixing [#77](https://github.com/daqana/dqrng/issues/77) in [#82](https://github.com/daqana/dqrng/issues/82) together with Philippe Grosjean) 27 | 28 | 29 | # dqrng 0.3.2 30 | 31 | * Recreate RcppExports.cpp with current development version of Rcpp to fix WARN on CRAN 32 | 33 | # dqrng 0.3.1 34 | 35 | * new method `dqrrademacher` for drawing Rademacher weights (Kyle Butts in [#50](https://github.com/daqana/dqrng/pull/50) fixing [#49](https://github.com/daqana/dqrng/pull/49)) 36 | * Move sampling methods to separate header file, allowing for parallel usage. 37 | * New method `dqrmvnorm` sampling from a multivariate normal distribution. 38 | This uses the methods implemented in the `mvtnorm` package and uses `dqrnorm`. 39 | 40 | # dqrng 0.3.0 41 | 42 | ## Breaking changes 43 | 44 | * The initial state of `dqrng`'s RNG is based on R's RNG, which used to advance R's RNG state. 45 | The implementation has been changed to preserve R's RNG state, which is less surprising but 46 | can change the outcome of current scripts. 47 | ([#44](https://github.com/daqana/dqrng/pull/34) fixing [#43](https://github.com/daqana/dqrng/issues/33)) 48 | 49 | ## Other changes 50 | 51 | * For uniform random numbers take short-cut for min == max and throw error for min > max 52 | ([#34](https://github.com/daqana/dqrng/pull/34) fixing [#33](https://github.com/daqana/dqrng/issues/33)) 53 | 54 | # dqrng 0.2.1 55 | 56 | * Make template specialisations `inline` and include required standard headers (Aaron Lun in [#29](https://github.com/daqana/dqrng/pull/29) fixing [#28](https://github.com/daqana/dqrng/issues/28)) 57 | * Add workaround for new C++ compiler with old libc ([#30](https://github.com/daqana/dqrng/pull/30) fixing [#27](https://github.com/daqana/dqrng/issues/27)) 58 | * update maintainer's email address 59 | 60 | # dqrng 0.2.0 61 | 62 | * Add R side support for selecting multiple streams for parallel usage. 63 | * Implement `long_jump()` for Xo(ro)shiro as alternative to `jump()` 64 | providing fewer streams with longer period. 65 | * Handle R's RNG scope properly during initialisation. 66 | * New functions `dqsample` and `dqsample.int` using an unbiased sampling 67 | algorithm. 68 | * Use `R_unif_index()` instead of `unif_rand()` to retrieve random data 69 | from R's RNG in `generateSeedVectors()`. 70 | * Scalar RNGs in the C++ API: dqrng::runif, dqrng::rnorm and dqrng::rexp 71 | 72 | # dqrng 0.1.1 73 | 74 | * Use template specializations to avoid compiler warnings during tests (Aaron Lun in [#16](https://github.com/daqana/dqrng/pull/16)) 75 | * Do not expect a particular error message on MacOS and skip exception throwing tests on Solaris. 76 | 77 | # dqrng 0.1.0 78 | 79 | ## Breaking changes 80 | 81 | * An integer vector instead of a single `int` is used for seeding (Aaron Lun in [#10](https://github.com/daqana/dqrng/pull/10)) 82 | * Single integer seeds lead to a different RNG state than before. 83 | * `dqrng::dqset_seed()` expects a `Rcpp::IntegerVector` instead of an `int` 84 | * Support for Mersenne-Twister has been removed, Xoroshiro128+ is now the default. 85 | 86 | ## Other changes 87 | 88 | * New method `generateSeedVectors()` for generating a list of random `int` 89 | vectors from R's RNG. These vectors can be used as seed (Aaron Lun in #10). 90 | * The initial state of the default RNG is now based on R's RNG. 91 | 92 | # dqrng 0.0.5 93 | 94 | * New RNG: Threefry from package 'sitmo' 95 | * Update PCG Headers (c.f. #8) 96 | * Unit-Tests for the C++ interface 97 | * Define STRICT_R_HEADERS to prepare for future Rcpp (c.f. #6) 98 | 99 | # dqrng 0.0.4 100 | 101 | * Fix critical bug w.r.t. setting seeds 102 | * Use time in addition to `std::random_device` as source of the default seed, since 103 | `std::random_device` is deterministic with MinGW (c.f. #2) 104 | * Add jump() method to Xoshiro256+ and Xoroshiro128+ 105 | * New vignette on parallel usage 106 | 107 | # dqrng 0.0.3 108 | 109 | * PCG has been patched to compile on Solaris. 110 | 111 | # dqrng 0.0.2 112 | 113 | * `dqrng_distribution.h` can now be used independently of Rcpp 114 | * Replace `xorshift.hpp` and `xoroshiro.hpp` with `xoshiro.h`. 115 | This implementation is directly derived from the original C implementations. 116 | It provides v1.0 of Xoroshiro128+ and Xoshiro256+. 117 | 118 | # dqrng 0.0.1 119 | 120 | * First public release. 121 | -------------------------------------------------------------------------------- /tests/testthat/test-generators.R: -------------------------------------------------------------------------------- 1 | context("generators") 2 | 3 | seed <- 1234567890 4 | 5 | test_that("Default generator: setting seed produces identical uniformly distributed numbers", { 6 | dqRNGkind("default") 7 | dqset.seed(seed) 8 | u1 <- dqrunif(10) 9 | dqset.seed(seed) 10 | u2 <- dqrunif(10) 11 | expect_equal(u1, u2) 12 | }) 13 | 14 | test_that("Default generator: setting seed and stream produces identical uniformly distributed numbers", { 15 | dqRNGkind("default") 16 | dqset.seed(seed, 1) 17 | u1 <- dqrunif(10) 18 | dqset.seed(seed, 1) 19 | u2 <- dqrunif(10) 20 | expect_equal(u1, u2) 21 | }) 22 | 23 | test_that("Default generator: saving state produces identical uniformly distributed numbers", { 24 | dqRNGkind("default") 25 | dqset.seed(seed, 1) 26 | state <- dqrng_get_state() 27 | u1 <- dqrunif(10) 28 | dqrng_set_state(state) 29 | u2 <- dqrunif(10) 30 | expect_equal(u1, u2) 31 | }) 32 | 33 | test_that("Default generator: setting same seed but different stream produces different uniformly distributed numbers", { 34 | dqRNGkind("default") 35 | dqset.seed(seed, 1) 36 | u1 <- dqrunif(10) 37 | dqset.seed(seed, 2) 38 | u2 <- dqrunif(10) 39 | expect_false(identical(u1, u2)) 40 | }) 41 | 42 | test_that("PCG64: setting seed produces identical uniformly distributed numbers", { 43 | skip_if(powerpc_apple) 44 | dqRNGkind("pcg64") 45 | dqset.seed(seed) 46 | u1 <- dqrunif(10) 47 | dqset.seed(seed) 48 | u2 <- dqrunif(10) 49 | expect_equal(u1, u2) 50 | }) 51 | 52 | test_that("PCG64: setting seed and stream produces identical uniformly distributed numbers", { 53 | skip_if(powerpc_apple) 54 | dqRNGkind("pcg64") 55 | dqset.seed(seed, 1) 56 | u1 <- dqrunif(10) 57 | dqset.seed(seed, 1) 58 | u2 <- dqrunif(10) 59 | expect_equal(u1, u2) 60 | }) 61 | 62 | test_that("PCG64: saving state produces identical uniformly distributed numbers", { 63 | skip_if(powerpc_apple) 64 | dqRNGkind("pcg64") 65 | dqset.seed(seed, 1) 66 | state <- dqrng_get_state() 67 | u1 <- dqrunif(10) 68 | dqrng_set_state(state) 69 | u2 <- dqrunif(10) 70 | expect_equal(u1, u2) 71 | }) 72 | 73 | test_that("PCG64: setting same seed but different stream produces different uniformly distributed numbers", { 74 | skip_if(powerpc_apple) 75 | dqRNGkind("pcg64") 76 | dqset.seed(seed, 1) 77 | u1 <- dqrunif(10) 78 | dqset.seed(seed, 2) 79 | u2 <- dqrunif(10) 80 | expect_false(identical(u1, u2)) 81 | }) 82 | 83 | test_that("Xoroshiro128+: setting seed produces identical uniformly distributed numbers", { 84 | dqRNGkind("Xoroshiro128+") 85 | dqset.seed(seed) 86 | u1 <- dqrunif(10) 87 | dqset.seed(seed) 88 | u2 <- dqrunif(10) 89 | expect_equal(u1, u2) 90 | }) 91 | 92 | test_that("Xoroshiro128+: setting seed and stream produces identical uniformly distributed numbers", { 93 | dqRNGkind("Xoroshiro128+") 94 | dqset.seed(seed, 1) 95 | u1 <- dqrunif(10) 96 | dqset.seed(seed, 1) 97 | u2 <- dqrunif(10) 98 | expect_equal(u1, u2) 99 | }) 100 | 101 | test_that("Xoroshiro128+: saving state produces identical uniformly distributed numbers", { 102 | dqRNGkind("Xoroshiro128+") 103 | dqset.seed(seed, 1) 104 | state <- dqrng_get_state() 105 | u1 <- dqrunif(10) 106 | dqrng_set_state(state) 107 | u2 <- dqrunif(10) 108 | expect_equal(u1, u2) 109 | }) 110 | 111 | test_that("Xoroshiro128+: setting same seed but different stream produces different uniformly distributed numbers", { 112 | dqRNGkind("Xoroshiro128+") 113 | dqset.seed(seed, 1) 114 | u1 <- dqrunif(10) 115 | dqset.seed(seed, 2) 116 | u2 <- dqrunif(10) 117 | expect_false(identical(u1, u2)) 118 | }) 119 | 120 | test_that("Xoroshiro128++: setting seed produces identical uniformly distributed numbers", { 121 | dqRNGkind("Xoroshiro128++") 122 | dqset.seed(seed) 123 | u1 <- dqrunif(10) 124 | dqset.seed(seed) 125 | u2 <- dqrunif(10) 126 | expect_equal(u1, u2) 127 | }) 128 | 129 | test_that("Xoroshiro128++: setting seed and stream produces identical uniformly distributed numbers", { 130 | dqRNGkind("Xoroshiro128++") 131 | dqset.seed(seed, 1) 132 | u1 <- dqrunif(10) 133 | dqset.seed(seed, 1) 134 | u2 <- dqrunif(10) 135 | expect_equal(u1, u2) 136 | }) 137 | 138 | test_that("Xoroshiro128++: saving state produces identical uniformly distributed numbers", { 139 | dqRNGkind("Xoroshiro128++") 140 | dqset.seed(seed, 1) 141 | state <- dqrng_get_state() 142 | u1 <- dqrunif(10) 143 | dqrng_set_state(state) 144 | u2 <- dqrunif(10) 145 | expect_equal(u1, u2) 146 | }) 147 | 148 | test_that("Xoroshiro128++: setting same seed but different stream produces different uniformly distributed numbers", { 149 | dqRNGkind("Xoroshiro128++") 150 | dqset.seed(seed, 1) 151 | u1 <- dqrunif(10) 152 | dqset.seed(seed, 2) 153 | u2 <- dqrunif(10) 154 | expect_false(identical(u1, u2)) 155 | }) 156 | 157 | test_that("Xoshiro256+: setting seed produces identical uniformly distributed numbers", { 158 | dqRNGkind("Xoshiro256+") 159 | dqset.seed(seed) 160 | u1 <- dqrunif(10) 161 | dqset.seed(seed) 162 | u2 <- dqrunif(10) 163 | expect_equal(u1, u2) 164 | }) 165 | 166 | test_that("Xoshiro256+: setting seed and stream produces identical uniformly distributed numbers", { 167 | dqRNGkind("Xoshiro256+") 168 | dqset.seed(seed, 1) 169 | u1 <- dqrunif(10) 170 | dqset.seed(seed, 1) 171 | u2 <- dqrunif(10) 172 | expect_equal(u1, u2) 173 | }) 174 | 175 | test_that("Xoshiro256+: saving state produces identical uniformly distributed numbers", { 176 | dqRNGkind("Xoshiro256+") 177 | dqset.seed(seed, 1) 178 | state <- dqrng_get_state() 179 | u1 <- dqrunif(10) 180 | dqrng_set_state(state) 181 | u2 <- dqrunif(10) 182 | expect_equal(u1, u2) 183 | }) 184 | 185 | test_that("Xoshiro256+: setting same seed but different stream produces different uniformly distributed numbers", { 186 | dqRNGkind("Xoshiro256+") 187 | dqset.seed(seed, 1) 188 | u1 <- dqrunif(10) 189 | dqset.seed(seed, 2) 190 | u2 <- dqrunif(10) 191 | expect_false(identical(u1, u2)) 192 | }) 193 | 194 | test_that("Xoshiro256++: setting seed produces identical uniformly distributed numbers", { 195 | dqRNGkind("Xoshiro256++") 196 | dqset.seed(seed) 197 | u1 <- dqrunif(10) 198 | dqset.seed(seed) 199 | u2 <- dqrunif(10) 200 | expect_equal(u1, u2) 201 | }) 202 | 203 | test_that("Xoshiro256++: setting seed and stream produces identical uniformly distributed numbers", { 204 | dqRNGkind("Xoshiro256++") 205 | dqset.seed(seed, 1) 206 | u1 <- dqrunif(10) 207 | dqset.seed(seed, 1) 208 | u2 <- dqrunif(10) 209 | expect_equal(u1, u2) 210 | }) 211 | 212 | test_that("Xoshiro256++: saving state produces identical uniformly distributed numbers", { 213 | dqRNGkind("Xoshiro256++") 214 | dqset.seed(seed, 1) 215 | state <- dqrng_get_state() 216 | u1 <- dqrunif(10) 217 | dqrng_set_state(state) 218 | u2 <- dqrunif(10) 219 | expect_equal(u1, u2) 220 | }) 221 | 222 | test_that("Xoshiro256++: setting same seed but different stream produces different uniformly distributed numbers", { 223 | dqRNGkind("Xoshiro256++") 224 | dqset.seed(seed, 1) 225 | u1 <- dqrunif(10) 226 | dqset.seed(seed, 2) 227 | u2 <- dqrunif(10) 228 | expect_false(identical(u1, u2)) 229 | }) 230 | 231 | test_that("Threefry: setting seed produces identical uniformly distributed numbers", { 232 | dqRNGkind("Threefry") 233 | dqset.seed(seed) 234 | u1 <- dqrunif(10) 235 | dqset.seed(seed) 236 | u2 <- dqrunif(10) 237 | expect_equal(u1, u2) 238 | }) 239 | 240 | test_that("Threefry: setting seed and stream produces identical uniformly distributed numbers", { 241 | dqRNGkind("Threefry") 242 | dqset.seed(seed, 1) 243 | u1 <- dqrunif(10) 244 | dqset.seed(seed, 1) 245 | u2 <- dqrunif(10) 246 | expect_equal(u1, u2) 247 | }) 248 | 249 | test_that("Threefry: saving state produces identical uniformly distributed numbers", { 250 | dqRNGkind("Threefry") 251 | dqset.seed(seed, 1) 252 | state <- dqrng_get_state() 253 | u1 <- dqrunif(10) 254 | dqrng_set_state(state) 255 | u2 <- dqrunif(10) 256 | expect_equal(u1, u2) 257 | }) 258 | 259 | test_that("Threefry: setting same seed but different stream produces different uniformly distributed numbers", { 260 | dqRNGkind("Threefry") 261 | dqset.seed(seed, 1) 262 | u1 <- dqrunif(10) 263 | dqset.seed(seed, 2) 264 | u2 <- dqrunif(10) 265 | expect_false(identical(u1, u2)) 266 | }) 267 | 268 | test_that("non-existant RNG produces error", { 269 | expect_error(dqRNGkind("does_not_exist")) 270 | }) 271 | -------------------------------------------------------------------------------- /src/dqrng.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2019 Ralf Stubner (daqana GmbH) 2 | // Copyright 2022-2024 Ralf Stubner 3 | // 4 | // This file is part of dqrng. 5 | // 6 | // dqrng is free software: you can redistribute it and/or modify it 7 | // under the terms of the GNU Affero General Public License as published by 8 | // the Free Software Foundation, either version 3 of the License, or 9 | // (at your option) any later version. 10 | // 11 | // dqrng is distributed in the hope that it will be useful, but 12 | // WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU Affero General Public License for more details. 15 | // 16 | // You should have received a copy of the GNU Affero General Public License 17 | // along with dqrng. If not, see . 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | namespace { 29 | dqrng::rng64_t rng = dqrng::generator(56478348); 30 | std::string rng_kind = "default"; 31 | } 32 | 33 | // [[Rcpp::interfaces(r, cpp)]] 34 | 35 | // [[Rcpp::export(rng = false)]] 36 | void dqset_seed(Rcpp::Nullable seed, 37 | Rcpp::Nullable stream = R_NilValue) { 38 | if (seed.isNull()) { 39 | rng = dqrng::generator(); 40 | } else { 41 | uint64_t _seed = dqrng::convert_seed(seed.as()); 42 | if (stream.isNotNull()) { 43 | uint64_t _stream = dqrng::convert_seed(stream.as()); 44 | rng->seed(_seed, _stream); 45 | } else { 46 | rng->seed(_seed); 47 | } 48 | } 49 | } 50 | 51 | //' @rdname dqrng-functions 52 | //' @export 53 | // [[Rcpp::export(rng = false)]] 54 | void dqRNGkind(std::string kind, const std::string& normal_kind = "ignored") { 55 | for (auto & c: kind) 56 | c = std::tolower(c); 57 | uint64_t seed = rng->operator()(); 58 | rng_kind = kind; 59 | if (kind == "default") { 60 | rng = dqrng::generator(seed); 61 | } else if (kind == "xoroshiro128+") { 62 | rng = dqrng::generator(seed); 63 | } else if (kind == "xoroshiro128++") { 64 | rng = dqrng::generator(seed); 65 | } else if (kind == "xoshiro256+") { 66 | rng = dqrng::generator(seed); 67 | } else if (kind == "xoshiro256++") { 68 | rng = dqrng::generator(seed); 69 | #if !(defined(__APPLE__) && defined(__POWERPC__)) 70 | } else if (kind == "pcg64") { 71 | rng = dqrng::generator(seed); 72 | #endif 73 | } else if (kind == "threefry") { 74 | rng = dqrng::generator(seed); 75 | } else { 76 | Rcpp::stop("Unknown random generator kind: %s", kind); 77 | } 78 | } 79 | 80 | //' @rdname dqrng-functions 81 | //' @export 82 | // [[Rcpp::export(rng = false)]] 83 | std::vector dqrng_get_state() { 84 | std::stringstream buffer; 85 | buffer << rng_kind << " " << *rng; 86 | std::vector state{std::istream_iterator{buffer}, 87 | std::istream_iterator{}}; 88 | return state; 89 | } 90 | 91 | //' @rdname dqrng-functions 92 | //' @export 93 | // [[Rcpp::export(rng = false)]] 94 | void dqrng_set_state(std::vector state) { 95 | std::stringstream buffer; 96 | std::copy(state.begin() + 1, 97 | state.end(), 98 | std::ostream_iterator(buffer, " ")); 99 | dqRNGkind(state[0]); 100 | buffer >> *rng; 101 | } 102 | 103 | //' @rdname dqrng-functions 104 | //' @export 105 | // [[Rcpp::export(rng = false)]] 106 | Rcpp::NumericVector dqrunif(size_t n, double min = 0.0, double max = 1.0) { 107 | if (min > max) 108 | Rcpp::stop("Error: 'min' must not be larger than 'max'!"); 109 | if (min == max) 110 | return Rcpp::NumericVector(n, min); 111 | if(max / 2. - min / 2. > (std::numeric_limits::max)() / 2.) 112 | return 2. * dqrunif(n, min/2., max/2.); 113 | 114 | auto out = Rcpp::NumericVector(Rcpp::no_init(n)); 115 | rng->generate(out, min, max); 116 | return out; 117 | } 118 | 119 | // [[Rcpp::export(rng = false)]] 120 | double runif(double min = 0.0, double max = 1.0) { 121 | if (min > max) 122 | Rcpp::stop("'min' must not be larger than 'max'!"); 123 | if (min == max) 124 | return min; 125 | if (max / 2. - min / 2. > (std::numeric_limits::max)() / 2.) 126 | return 2. * runif(min/2., max/2.); 127 | 128 | return rng->variate(min, max);; 129 | } 130 | 131 | //' @rdname dqrng-functions 132 | //' @export 133 | // [[Rcpp::export(rng = false)]] 134 | Rcpp::NumericVector dqrnorm(size_t n, double mean = 0.0, double sd = 1.0) { 135 | auto out = Rcpp::NumericVector(Rcpp::no_init(n)); 136 | rng->generate(out, mean, sd); 137 | return out; 138 | } 139 | 140 | // [[Rcpp::export(rng = false)]] 141 | double rnorm(double mean = 0.0, double sd = 1.0) { 142 | return rng->variate(mean, sd);; 143 | } 144 | 145 | //' @rdname dqrng-functions 146 | //' @export 147 | // [[Rcpp::export(rng = false)]] 148 | Rcpp::NumericVector dqrexp(size_t n, double rate = 1.0) { 149 | auto out = Rcpp::NumericVector(Rcpp::no_init(n)); 150 | rng->generate(out, rate); 151 | return out; 152 | } 153 | 154 | // [[Rcpp::export(rng = false)]] 155 | double rexp(double rate = 1.0) { 156 | return rng->variate(rate);; 157 | } 158 | 159 | //' @keywords internal 160 | // [[Rcpp::export(rng = false)]] 161 | Rcpp::XPtr get_rng() { 162 | return Rcpp::XPtr(rng); 163 | } 164 | 165 | //' @rdname dqrng-functions 166 | //' @export 167 | // [[Rcpp::export(rng = false)]] 168 | Rcpp::IntegerVector dqrrademacher(size_t n) { 169 | Rcpp::IntegerVector res = Rcpp::no_init(n); 170 | size_t k = 0; 171 | for (size_t i = 0; i < ceil(n / 64.0) - 1; ++i) { 172 | uint64_t bits = (*rng)(); 173 | 174 | for (int j = 0; j <= 63; ++j, ++k) { 175 | res[k] = ((bits >> j) & 1) * 2 - 1; 176 | } 177 | } 178 | 179 | uint64_t bits = (*rng)(); 180 | for (int j = 0; k < n; ++j, ++k) { 181 | res[k] = ((bits >> j) & 1) * 2 - 1; 182 | } 183 | 184 | return res; 185 | } 186 | 187 | // [[Rcpp::export(rng = false)]] 188 | Rcpp::IntegerVector dqsample_int(int n, 189 | int size, 190 | bool replace = false, 191 | Rcpp::Nullable probs = R_NilValue, 192 | int offset = 0) { 193 | if (!(n > 0 && size >= 0)) 194 | Rcpp::stop("Argument requirements not fulfilled: n > 0 && size >= 0"); 195 | return dqrng::sample::sample(*rng, uint32_t(n), uint32_t(size), replace, offset); 196 | } 197 | 198 | // [[Rcpp::export(rng = false)]] 199 | Rcpp::NumericVector dqsample_num(double n, 200 | double size, 201 | bool replace = false, 202 | Rcpp::Nullable probs = R_NilValue, 203 | int offset = 0) { 204 | if (!(n > 0 && size >= 0)) 205 | Rcpp::stop("Argument requirements not fulfilled: n > 0 && size >= 0"); 206 | #ifndef LONG_VECTOR_SUPPORT 207 | Rcpp::stop("Long vectors are not supported"); 208 | #else 209 | return dqrng::sample::sample(*rng, uint64_t(n), uint64_t(size), replace, offset); 210 | #endif 211 | } 212 | 213 | extern "C" { 214 | // allow registering as user-supplied RNG 215 | double * user_unif_rand(void) { 216 | static double res; 217 | res = rng->uniform01(); 218 | return &res; 219 | } 220 | 221 | // https://stackoverflow.com/a/47839021/8416610 222 | Int32 unscramble(Int32 u) { 223 | for (int j = 0; j < 50; ++j) { 224 | u = ((u - 1) * 2783094533UL); 225 | } 226 | return u; 227 | } 228 | 229 | void user_unif_init(Int32 seed_in) { 230 | rng->seed(uint64_t(unscramble(seed_in))); 231 | } 232 | 233 | double * user_norm_rand(void) { 234 | static double res; 235 | res = rng->variate(0.0, 1.0); 236 | return &res; 237 | } 238 | } // extern "C" 239 | 240 | static const R_CMethodDef cMethods[] = { 241 | {"user_unif_rand", (DL_FUNC) &user_unif_rand, 0, NULL}, 242 | {"user_unif_init", (DL_FUNC) &user_unif_init, 0, NULL}, 243 | {"user_norm_rand", (DL_FUNC) &user_norm_rand, 0, NULL}, 244 | {NULL, NULL, 0, NULL} 245 | }; 246 | 247 | // [[Rcpp::init]] 248 | void dqrng_init(DllInfo *dll) { 249 | R_registerRoutines(dll, cMethods, NULL, NULL, NULL); 250 | } 251 | -------------------------------------------------------------------------------- /inst/include/dqrng_types.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2019 Ralf Stubner (daqana GmbH) 2 | // Copyright 2023-2024 Ralf Stubner 3 | // Copyright 2023 Henrik Sloot 4 | // 5 | // This file is part of dqrng. 6 | // 7 | // dqrng is free software: you can redistribute it and/or modify it 8 | // under the terms of the GNU Affero General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // dqrng is distributed in the hope that it will be useful, but 13 | // WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with dqrng. If not, see . 19 | 20 | #ifndef DQRNG_TYPES_H 21 | #define DQRNG_TYPES_H 1 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #if defined(__GNUC__) || defined(__clang__) 30 | # define DEPRECATED(func) func __attribute__ ((deprecated)) 31 | #elif defined(_MSC_VER) 32 | # define DEPRECATED(func) __declspec(deprecated) func 33 | #else 34 | # define DEPRECATED(func) func 35 | #endif 36 | 37 | namespace dqrng { 38 | 39 | DEPRECATED(double runif(double min, double max)); 40 | DEPRECATED(double rnorm(double mean, double sd)); 41 | DEPRECATED(double rexp(double rate)); 42 | DEPRECATED(double uniform01(uint64_t x)); 43 | 44 | class random_64bit_generator { 45 | private: 46 | uint64_t bit64() {return this->operator()();} 47 | uint32_t bit32() { 48 | if (has_cache) { 49 | has_cache = false; 50 | return cache; 51 | } 52 | uint64_t random = this->bit64(); 53 | cache = uint32_t(random); 54 | has_cache = true; 55 | return random >> 32; 56 | } 57 | double uniform01(uint64_t x) { 58 | // prefer high bits due to weakness of lowest bits for xoshiro/xoroshiro with the "+" scrambler 59 | return (x >> 11) * 0x1.0p-53; 60 | } 61 | 62 | protected: 63 | bool has_cache{false}; 64 | uint32_t cache; 65 | 66 | virtual void output(std::ostream& ost) const = 0; 67 | virtual void input(std::istream& ist) = 0; 68 | 69 | public: 70 | using result_type = uint64_t; 71 | 72 | virtual ~random_64bit_generator() {}; 73 | virtual result_type operator() () = 0; 74 | virtual void seed(result_type seed) = 0; 75 | virtual void seed(result_type seed, result_type stream) = 0; 76 | virtual std::unique_ptr clone(result_type stream) = 0; 77 | static constexpr result_type min() {return 0;}; 78 | static constexpr result_type max() {return UINT64_MAX;}; 79 | 80 | double uniform01() { 81 | return uniform01(this->operator()()); 82 | } 83 | std::pair generate_double_8bit_pair() { 84 | result_type x = this->operator()(); 85 | double r = uniform01(x); 86 | // shift x due to weakness of lowest bits for xoshiro/xoroshiro with used "+" scrambler 87 | int bucket = (x >> 3) & 0xFF; 88 | return std::make_pair(r, bucket); 89 | } 90 | /* 91 | * https://raw.githubusercontent.com/imneme/bounded-rands/3d71f53c975b1e5b29f2f3b05a74e26dab9c3d84/bounded32.cpp 92 | * https://raw.githubusercontent.com/imneme/bounded-rands/3d71f53c975b1e5b29f2f3b05a74e26dab9c3d84/bounded64.cpp 93 | * A C++ implementation methods and benchmarks for random numbers in a range 94 | * (64 and 32-bit version) 95 | * 96 | * The MIT License (MIT) 97 | * 98 | * Copyright (c) 2018 Melissa E. O'Neill 99 | * 100 | * Permission is hereby granted, free of charge, to any person obtaining a 101 | * copy of this software and associated documentation files (the "Software"), 102 | * to deal in the Software without restriction, including without limitation 103 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 104 | * and/or sell copies of the Software, and to permit persons to whom the 105 | * Software is furnished to do so, subject to the following conditions: 106 | * 107 | * The above copyright notice and this permission notice shall be included in 108 | * all copies or substantial portions of the Software. 109 | * 110 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 111 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 112 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 113 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 114 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 115 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 116 | * DEALINGS IN THE SOFTWARE. 117 | */ 118 | 119 | virtual uint32_t operator() (uint32_t range) { 120 | uint32_t x = this->bit32(); 121 | uint64_t m = uint64_t(x) * uint64_t(range); 122 | uint32_t l = uint32_t(m); 123 | if (l < range) { 124 | uint32_t t = -range; 125 | if (t >= range) { 126 | t -= range; 127 | if (t >= range) 128 | t %= range; 129 | } 130 | while (l < t) { 131 | x = this->bit32(); 132 | m = uint64_t(x) * uint64_t(range); 133 | l = uint32_t(m); 134 | } 135 | } 136 | return m >> 32; 137 | } 138 | 139 | #ifdef LONG_VECTOR_SUPPORT 140 | virtual uint64_t operator() (uint64_t range) { 141 | using pcg_extras::pcg128_t; 142 | uint64_t x = this->bit64(); 143 | pcg128_t m = pcg128_t(x) * pcg128_t(range); 144 | uint64_t l = uint64_t(m); 145 | if (l < range) { // # nocov start 146 | uint64_t t = -range; 147 | if (t >= range) { 148 | t -= range; 149 | if (t >= range) 150 | t %= range; 151 | } 152 | while (l < t) { 153 | x = this->bit64(); 154 | m = pcg128_t(x) * pcg128_t(range); 155 | l = uint64_t(m); 156 | } 157 | } // # nocov end 158 | return m >> 64; 159 | } 160 | #endif 161 | 162 | /* 163 | * https://gist.github.com/imneme/540829265469e673d045 164 | * Random-Number Utilities (randutil) 165 | * Addresses common issues with C++11 random number generation. 166 | * Makes good seeding easier, and makes using RNGs easy while retaining 167 | * all the power. 168 | * 169 | * The MIT License (MIT) 170 | * 171 | * Copyright (c) 2015-2022 Melissa E. O'Neill 172 | * 173 | * Permission is hereby granted, free of charge, to any person obtaining a copy 174 | * of this software and associated documentation files (the "Software"), to deal 175 | * in the Software without restriction, including without limitation the rights 176 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 177 | * copies of the Software, and to permit persons to whom the Software is 178 | * furnished to do so, subject to the following conditions: 179 | * 180 | * The above copyright notice and this permission notice shall be included in 181 | * all copies or substantial portions of the Software. 182 | * 183 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 184 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 185 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 186 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 187 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 188 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 189 | * SOFTWARE. 190 | */ 191 | 192 | template class DistTmpl, 194 | typename... Params> 195 | ResultType variate(Params&&... params) 196 | { 197 | DistTmpl dist(std::forward(params)...); 198 | 199 | return dist(*this); 200 | } 201 | 202 | template