rand_pcg/
pcg128cm.rs

1// Copyright 2018-2021 Developers of the Rand project.
2// Copyright 2017 Paul Dicker.
3// Copyright 2014-2017, 2019 Melissa O'Neill and PCG Project contributors
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10
11//! PCG random number generators
12
13// This is the cheap multiplier used by PCG for 128-bit state.
14const MULTIPLIER: u64 = 15750249268501108917;
15
16use core::fmt;
17use rand_core::{impls, le, RngCore, SeedableRng};
18#[cfg(feature = "serde")]
19use serde::{Deserialize, Serialize};
20
21/// A PCG random number generator (CM DXSM 128/64 (LCG) variant).
22///
23/// Permuted Congruential Generator with 128-bit state, internal Linear
24/// Congruential Generator, and 64-bit output via "double xorshift multiply"
25/// output function.
26///
27/// This is a 128-bit LCG with explicitly chosen stream with the PCG-DXSM
28/// output function. This corresponds to `pcg_engines::cm_setseq_dxsm_128_64`
29/// from pcg_cpp and `PCG64DXSM` from NumPy.
30///
31/// Despite the name, this implementation uses 32 bytes (256 bit) space
32/// comprising 128 bits of state and 128 bits stream selector. These are both
33/// set by `SeedableRng`, using a 256-bit seed.
34///
35/// Note that while two generators with different stream parameter may be
36/// closely correlated, this is [mitigated][upgrading-pcg64] by the DXSM output function.
37///
38/// [upgrading-pcg64]: https://numpy.org/doc/stable/reference/random/upgrading-pcg64.html
39#[derive(Clone, PartialEq, Eq)]
40#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
41pub struct Lcg128CmDxsm64 {
42    state: u128,
43    increment: u128,
44}
45
46/// [`Lcg128CmDxsm64`] is also known as `PCG64DXSM`.
47pub type Pcg64Dxsm = Lcg128CmDxsm64;
48
49impl Lcg128CmDxsm64 {
50    /// Multi-step advance functions (jump-ahead, jump-back)
51    ///
52    /// The method used here is based on Brown, "Random Number Generation
53    /// with Arbitrary Stride,", Transactions of the American Nuclear
54    /// Society (Nov. 1994).  The algorithm is very similar to fast
55    /// exponentiation.
56    ///
57    /// Even though delta is an unsigned integer, we can pass a
58    /// signed integer to go backwards, it just goes "the long way round".
59    ///
60    /// Using this function is equivalent to calling `next_64()` `delta`
61    /// number of times.
62    #[inline]
63    pub fn advance(&mut self, delta: u128) {
64        let mut acc_mult: u128 = 1;
65        let mut acc_plus: u128 = 0;
66        let mut cur_mult = MULTIPLIER as u128;
67        let mut cur_plus = self.increment;
68        let mut mdelta = delta;
69
70        while mdelta > 0 {
71            if (mdelta & 1) != 0 {
72                acc_mult = acc_mult.wrapping_mul(cur_mult);
73                acc_plus = acc_plus.wrapping_mul(cur_mult).wrapping_add(cur_plus);
74            }
75            cur_plus = cur_mult.wrapping_add(1).wrapping_mul(cur_plus);
76            cur_mult = cur_mult.wrapping_mul(cur_mult);
77            mdelta /= 2;
78        }
79        self.state = acc_mult.wrapping_mul(self.state).wrapping_add(acc_plus);
80    }
81
82    /// Construct an instance compatible with PCG seed and stream.
83    ///
84    /// Note that the highest bit of the `stream` parameter is discarded
85    /// to simplify upholding internal invariants.
86    ///
87    /// Note that while two generators with different stream parameter may be
88    /// closely correlated, this is [mitigated][upgrading-pcg64] by the DXSM output function.
89    ///
90    /// PCG specifies the following default values for both parameters:
91    ///
92    /// - `state = 0xcafef00dd15ea5e5`
93    /// - `stream = 0xa02bdbf7bb3c0a7ac28fa16a64abf96`
94    ///
95    /// [upgrading-pcg64]: https://numpy.org/doc/stable/reference/random/upgrading-pcg64.html
96    pub fn new(state: u128, stream: u128) -> Self {
97        // The increment must be odd, hence we discard one bit:
98        let increment = (stream << 1) | 1;
99        Self::from_state_incr(state, increment)
100    }
101
102    #[inline]
103    fn from_state_incr(state: u128, increment: u128) -> Self {
104        let mut pcg = Self { state, increment };
105        // Move away from initial value:
106        pcg.state = pcg.state.wrapping_add(pcg.increment);
107        pcg.step();
108        pcg
109    }
110
111    #[inline(always)]
112    fn step(&mut self) {
113        // prepare the LCG for the next round
114        self.state = self
115            .state
116            .wrapping_mul(MULTIPLIER as u128)
117            .wrapping_add(self.increment);
118    }
119}
120
121// Custom Debug implementation that does not expose the internal state
122impl fmt::Debug for Lcg128CmDxsm64 {
123    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
124        write!(f, "Lcg128CmDxsm64 {{}}")
125    }
126}
127
128impl SeedableRng for Lcg128CmDxsm64 {
129    type Seed = [u8; 32];
130
131    /// We use a single 255-bit seed to initialise the state and select a stream.
132    /// One `seed` bit (lowest bit of `seed[8]`) is ignored.
133    fn from_seed(seed: Self::Seed) -> Self {
134        let mut seed_u64 = [0u64; 4];
135        le::read_u64_into(&seed, &mut seed_u64);
136        let state = u128::from(seed_u64[0]) | (u128::from(seed_u64[1]) << 64);
137        let incr = u128::from(seed_u64[2]) | (u128::from(seed_u64[3]) << 64);
138
139        // The increment must be odd, hence we discard one bit:
140        Self::from_state_incr(state, incr | 1)
141    }
142}
143
144impl RngCore for Lcg128CmDxsm64 {
145    #[inline]
146    fn next_u32(&mut self) -> u32 {
147        self.next_u64() as u32
148    }
149
150    #[inline]
151    fn next_u64(&mut self) -> u64 {
152        let res = output_dxsm(self.state);
153        self.step();
154        res
155    }
156
157    #[inline]
158    fn fill_bytes(&mut self, dest: &mut [u8]) {
159        impls::fill_bytes_via_next(self, dest)
160    }
161}
162
163#[inline(always)]
164fn output_dxsm(state: u128) -> u64 {
165    // See https://github.com/imneme/pcg-cpp/blob/ffd522e7188bef30a00c74dc7eb9de5faff90092/include/pcg_random.hpp#L1016
166    // for a short discussion of the construction and its original implementation.
167    let mut hi = (state >> 64) as u64;
168    let mut lo = state as u64;
169
170    lo |= 1;
171    hi ^= hi >> 32;
172    hi = hi.wrapping_mul(MULTIPLIER);
173    hi ^= hi >> 48;
174    hi = hi.wrapping_mul(lo);
175
176    hi
177}