From 86d5b52149bc8690f78b358271353e9002401c37 Mon Sep 17 00:00:00 2001 From: Ralf Stubner Date: Sun, 12 May 2024 21:21:02 +0200 Subject: [PATCH] Restore pcg ctor (#87) * Use two-argument ctor for PCG64 again (R and C++) * Remove usage of boost.multiprecision --- DESCRIPTION | 2 +- NEWS.md | 3 +-- inst/include/dqrng_generator.h | 9 ++++++++- vignettes/cpp-api.Rmd | 4 ++-- vignettes/parallel.Rmd | 24 +++++++++++------------- 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index b82cbd1..a01aa86 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: dqrng Type: Package Title: Fast Pseudo Random Number Generators -Version: 0.3.2.6 +Version: 0.3.2.7 Authors@R: c( person("Ralf", "Stubner", email = "ralf.stubner@gmail.com", role = c("aut", "cre"), comment = c(ORCID = "0009-0009-1908-106X")), person("daqana GmbH", role = "cph"), diff --git a/NEWS.md b/NEWS.md index 253b804..9352dac 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,7 +4,6 @@ * 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)) * 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)) -* The two argument constructor and `seed` function from PCG has [surprising properties](https://github.com/imneme/pcg-cpp/issues/91): it is not identical to the one argument version followed by `set_stream(stream)`. For consistency with the new `clone(stream)` method, the two argument versions are no longer used. This influences code that uses multiple streams with PCG together with the tooling from this package, e.g. the example code in the vignette on parallel RNG usage. In addition, setting the stream on PCG64 via `dqset.seed(seed, stream)` or at the C++ level using the interface provided by dqrng will be relative to the current stream, i.e. setting `stream=0` will not change the RNG. This is for consistency with the other provided RNGs. You still get the standard behaviour if you are using the C++ classes for PCG directly. ## Other changes @@ -15,7 +14,7 @@ * 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) * 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)) * 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)) - * A `clone(stream)` method to allow using the global RNG state for parallel computation + * 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. * 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). * 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`. * 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) diff --git a/inst/include/dqrng_generator.h b/inst/include/dqrng_generator.h index 55cfabf..ee9a5aa 100644 --- a/inst/include/dqrng_generator.h +++ b/inst/include/dqrng_generator.h @@ -59,7 +59,7 @@ class random_64bit_wrapper : public random_64bit_generator { random_64bit_wrapper() : gen() {}; random_64bit_wrapper(RNG _gen) : gen(_gen) {}; random_64bit_wrapper(result_type seed) : gen(seed) {}; - random_64bit_wrapper(result_type seed, result_type stream) : gen(seed) {this->set_stream(stream);}; + random_64bit_wrapper(result_type seed, result_type stream) : gen() {this->seed(seed, stream);}; virtual result_type operator() () override {return gen();} virtual void seed(result_type seed) override {cache = false; gen.seed(seed);} virtual void seed(result_type seed, result_type stream) override {cache = false; gen.seed(seed); this->set_stream(stream);} @@ -115,6 +115,13 @@ inline void random_64bit_wrapper::set_stream(result_type stream) { gen.set_stream(state[1]/2 + stream); } +// keep using the two argument ctor for PCG for backwards compatibility +template<> +inline void random_64bit_wrapper::seed(result_type seed, result_type stream) { + gen.seed(seed, stream); + cache = false; +} + inline uint64_t get_seed_from_r() { Rcpp::RNGScope rngScope; Rcpp::IntegerVector seed(2, dqrng::R_random_int); diff --git a/vignettes/cpp-api.Rmd b/vignettes/cpp-api.Rmd index b593142..5a5f0e0 100644 --- a/vignettes/cpp-api.Rmd +++ b/vignettes/cpp-api.Rmd @@ -135,9 +135,9 @@ void dqrng::dqrng_set_state(std::vector state) Rcpp::XPtr dqrng::get_rng() ``` -Direct usage of this method is **discouraged**. The prefered way of accesing 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. +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. -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 distribtion function. In addition, the following functions are supported, since they are inherited from the abstract parent class `random_64bit_generator`: +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`: ```cpp // clone RNG and select a different stream diff --git a/vignettes/parallel.Rmd b/vignettes/parallel.Rmd index a36228e..249b144 100644 --- a/vignettes/parallel.Rmd +++ b/vignettes/parallel.Rmd @@ -154,8 +154,6 @@ From the PCG family we will look at pcg64, a 64-bit generator with a period of $ It offers the function [`advance(int n)`](https://www.pcg-random.org/using-pcg-cpp.html#void-advance-state-type-delta), which is equivalent to `n` random draws but scales as $O(ln(n))$ instead of $O(n)$. In addition, it offers $2^{127}$ separate streams that can be enabled via the function [`set_stream(int n)`](https://www.pcg-random.org/using-pcg-cpp.html#void-pcg32-set-stream-state-type-stream) or the [two argument constructor](https://www.pcg-random.org/using-pcg-cpp.html#pcg32-pcg32-constructor) with `seed` and `stream`. When used from R or C++ with the two argument `dqset.seed` and `dqset_seed` you get $2^{64}$ streams out of the possible $2^{127}$ separate streams. -Note that **here** in contrast to the default PCG implementation, streams are counted from the default stream, i.e. setting `stream` to `0` or not at all will give the same RNG. -This brings PCG in line with the other RNGs. In the following example a matrix with random numbers is generated in parallel using RcppParallel. Instead of using the more traditional approach of generating the random numbers from a certain distribution, we are using the fast sampling methods from `dqrng_sample.h`. @@ -206,23 +204,23 @@ symnum(x = cor(res), cutpoints = c(0.001, 0.003, 0.999), Head of the random matrix: [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] - [1,] 16583 94445 14240 53296 15021 72402 12677 47777 - [2,] 49983 29144 49983 80361 49983 60477 49983 6255 - [3,] 56598 19559 87036 76208 8804 67365 60990 22777 - [4,] 64117 18406 69814 56954 14447 27027 50950 10545 - [5,] 48709 62087 66064 2944 11887 75422 5823 48069 - [6,] 81061 71093 52958 6324 82242 68439 68331 41016 + [1,] 67984 16279 69262 7126 21441 37720 51107 51045 + [2,] 69310 21713 82885 81157 54051 5261 91165 17833 + [3,] 76742 31232 78953 4626 94939 29416 85652 78296 + [4,] 76349 47427 1770 37957 33888 59134 94591 65793 + [5,] 85008 89224 43493 7925 60866 2464 14080 10763 + [6,] 38017 88509 51195 73086 1883 68193 75259 62216 Correlation matrix: [1,] 1 [2,] 1 [3,] ? 1 - [4,] ? 1 + [4,] ? 1 [5,] 1 - [6,] ? 1 - [7,] ? ? ? ? ? 1 - [8,] ? 1 + [6,] ? ? ? 1 + [7,] ? 1 + [8,] ? 1 attr(,"legend") [1] 0 ‘ ’ 0.001 ‘?’ 0.003 ‘!’ 0.999 ‘1’ 1 @@ -307,7 +305,7 @@ This way, the result becomes independent of the used amount of parallelism. However, one has to consider one important aspect: After the parallel for loop, the global RNG has not advanced at all. Calling the function repeatedly would lead to identical results. -To circumvent this, one of the stream specific RNGs is an exact clone of the global RNG (`clone(stream=0)`) and the state of this RNG after processing its stream is saved and used to overwrite the global RNG's state. +To circumvent this, one of the stream specific RNGs is an exact clone of the global RNG (`clone(stream=0)`) and the state of this RNG after processing its stream is saved and used to overwrite the global RNG's state.^[Note that **here** in contrast to the default PCG implementation, streams are counted from the current stream, i.e. setting `stream` to `0` will give the same RNG. This brings PCG in line with the other RNGs.] This way, the global RNG's state advances as if it had processed one of the streams and successive calls to `parallel_generate()` produce different random numbers as expected. ```{r, eval=evaluate}