Skip to main content

limma/
rng.rs

1//! Bit-exact port of R's default random number generator: the Mersenne-Twister
2//! uniform generator with R's `set.seed` initialisation, plus the `Inversion`
3//! normal generator. This reproduces `set.seed`, `runif` and `rnorm` exactly,
4//! which the rotation gene-set tests (`roast`, `romer`) depend on for
5//! reproducible p-values.
6//!
7//! R does **not** seed the Mersenne-Twister with the reference `sgenrand` /
8//! `init_by_array`. Instead `set.seed(k)` scrambles `k` through the linear
9//! congruential generator `s = 69069 s + 1` (50 warm-up steps, then one value
10//! per state word). `unif_rand` is `fixup(MT_genrand() * 2^-32)` and `norm_rand`
11//! uses the high-precision inversion `qnorm5((floor(2^27 u1) + u2) / 2^27)`,
12//! consuming two uniforms per normal deviate.
13
14const N: usize = 624;
15const M: usize = 397;
16const MATRIX_A: u32 = 0x9908_b0df;
17const UPPER_MASK: u32 = 0x8000_0000;
18const LOWER_MASK: u32 = 0x7fff_ffff;
19/// `2^-32`, R's `MT_genrand` scaling constant.
20const TWO_POW_M32: f64 = 2.328_306_436_538_696_3e-10;
21/// `1/(2^32 - 1)`, used by R's `fixup` to nudge values off the open interval.
22const I2_32M1: f64 = 2.328_306_437_080_797e-10;
23/// `2^27`, the inversion-method precision multiplier (`BIG` in R's `snorm.c`).
24const BIG: f64 = 134_217_728.0;
25
26/// R's default RNG state: a Mersenne-Twister (`MT19937`) seeded the R way.
27pub struct RRng {
28    mt: [u32; N],
29    mti: usize,
30}
31
32impl RRng {
33    /// `set.seed(seed)` with the default `kind = "Mersenne-Twister"`.
34    ///
35    /// Mirrors R's `RNG_Init`: 50 LCG warm-up steps, then 625 LCG outputs filling
36    /// `i_seed[0..624]` (the first, `mti`, is overwritten to `624` by
37    /// `FixupSeeds`, leaving the 624-word state in `i_seed[1..624]`).
38    pub fn new(seed: i32) -> Self {
39        let mut s = seed as u32;
40        for _ in 0..50 {
41            s = s.wrapping_mul(69069).wrapping_add(1);
42        }
43        // i_seed[0] (the mti slot) — discarded, FixupSeeds forces it to 624.
44        s = s.wrapping_mul(69069).wrapping_add(1);
45        let mut mt = [0u32; N];
46        for word in mt.iter_mut() {
47            s = s.wrapping_mul(69069).wrapping_add(1);
48            *word = s;
49        }
50        RRng { mt, mti: N }
51    }
52
53    /// One tempered 32-bit Mersenne-Twister output (`MT_genrand`).
54    fn genrand(&mut self) -> u32 {
55        const MAG01: [u32; 2] = [0, MATRIX_A];
56        if self.mti >= N {
57            let mt = &mut self.mt;
58            for kk in 0..(N - M) {
59                let y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK);
60                mt[kk] = mt[kk + M] ^ (y >> 1) ^ MAG01[(y & 1) as usize];
61            }
62            for kk in (N - M)..(N - 1) {
63                let y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK);
64                mt[kk] = mt[kk + M - N] ^ (y >> 1) ^ MAG01[(y & 1) as usize];
65            }
66            let y = (mt[N - 1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
67            mt[N - 1] = mt[M - 1] ^ (y >> 1) ^ MAG01[(y & 1) as usize];
68            self.mti = 0;
69        }
70        let mut y = self.mt[self.mti];
71        self.mti += 1;
72        y ^= y >> 11;
73        y ^= (y << 7) & 0x9d2c_5680;
74        y ^= (y << 15) & 0xefc6_0000;
75        y ^= y >> 18;
76        y
77    }
78
79    /// `unif_rand()`: a uniform deviate on the open interval `(0, 1)`.
80    pub fn unif_rand(&mut self) -> f64 {
81        fixup(self.genrand() as f64 * TWO_POW_M32)
82    }
83
84    /// `norm_rand()` with the default `normal.kind = "Inversion"`. Consumes two
85    /// uniforms for full double precision.
86    pub fn norm_rand(&mut self) -> f64 {
87        let u1 = self.unif_rand();
88        let big_u = (BIG * u1).trunc() + self.unif_rand();
89        qnorm(big_u / BIG)
90    }
91
92    /// `runif(n)`: `n` uniform deviates.
93    pub fn runif(&mut self, n: usize) -> Vec<f64> {
94        (0..n).map(|_| self.unif_rand()).collect()
95    }
96
97    /// `rnorm(n)`: `n` standard normal deviates.
98    pub fn rnorm(&mut self, n: usize) -> Vec<f64> {
99        (0..n).map(|_| self.norm_rand()).collect()
100    }
101}
102
103/// R's `fixup`: keep uniforms strictly inside `(0, 1)`.
104fn fixup(x: f64) -> f64 {
105    if x <= 0.0 {
106        0.5 * I2_32M1
107    } else if 1.0 - x <= 0.0 {
108        1.0 - 0.5 * I2_32M1
109    } else {
110        x
111    }
112}
113
114/// R's `qnorm5(p, 0, 1, lower_tail = TRUE, log_p = FALSE)` — the standard-normal
115/// quantile via Wichura's AS 241 algorithm. `p` is assumed in the open interval
116/// `(0, 1)` (the only range the inversion generator produces).
117#[allow(clippy::excessive_precision)]
118pub fn qnorm(p: f64) -> f64 {
119    let q = p - 0.5;
120    if q.abs() <= 0.425 {
121        let r = 0.180625 - q * q;
122        return q
123            * (((((((r * 2509.0809287301226727 + 33430.575583588128105) * r
124                + 67265.770927008700853)
125                * r
126                + 45921.953931549871457)
127                * r
128                + 13731.693765509461125)
129                * r
130                + 1971.5909503065514427)
131                * r
132                + 133.14166789178437745)
133                * r
134                + 3.387132872796366608)
135            / (((((((r * 5226.495278852854561 + 28729.085735721942674) * r
136                + 39307.89580009271061)
137                * r
138                + 21213.794301586595867)
139                * r
140                + 5394.1960214247511077)
141                * r
142                + 687.1870074920579083)
143                * r
144                + 42.313330701600911252)
145                * r
146                + 1.0);
147    }
148    let mut r = if q < 0.0 { p } else { 1.0 - p };
149    r = (-r.ln()).sqrt();
150    let val = if r <= 5.0 {
151        r -= 1.6;
152        (((((((r * 7.7454501427834140764e-4 + 0.0227238449892691845833) * r
153            + 0.24178072517745061177)
154            * r
155            + 1.27045825245236838258)
156            * r
157            + 3.64784832476320460504)
158            * r
159            + 5.7694972214606914055)
160            * r
161            + 4.6303378461565452959)
162            * r
163            + 1.42343711074968357734)
164            / (((((((r * 1.05075007164441684324e-9 + 5.475938084995344946e-4) * r
165                + 0.0151986665636164571966)
166                * r
167                + 0.14810397642748007459)
168                * r
169                + 0.68976733498510000455)
170                * r
171                + 1.6763848301838038494)
172                * r
173                + 2.05319162663775882187)
174                * r
175                + 1.0)
176    } else {
177        r -= 5.0;
178        (((((((r * 2.01033439929228813265e-7 + 2.71155556874348757815e-5) * r
179            + 0.0012426609473880784386)
180            * r
181            + 0.026532189526576123093)
182            * r
183            + 0.29656057182850489123)
184            * r
185            + 1.7848265399172913358)
186            * r
187            + 5.4637849111641143699)
188            * r
189            + 6.6579046435011037772)
190            / (((((((r * 2.04426310338993978564e-15 + 1.4215117583164458887e-7) * r
191                + 1.8463183175100546818e-5)
192                * r
193                + 7.868691311456132591e-4)
194                * r
195                + 0.0148753612908506148525)
196                * r
197                + 0.13692988092273580531)
198                * r
199                + 0.59983220655588793769)
200                * r
201                + 1.0)
202    };
203    if q < 0.0 {
204        -val
205    } else {
206        val
207    }
208}
209
210#[cfg(test)]
211#[allow(clippy::excessive_precision)]
212mod tests {
213    use super::*;
214
215    #[test]
216    fn seed_state_matches_r() {
217        let rng = RRng::new(1);
218        // .Random.seed[3..12] (state[0..9]) and state[623] after set.seed(1L).
219        let want_head: [u32; 10] = [
220            4125696813, 3852956682, 3691408899, 4072619880, 1489374793, 865871222, 1734802815,
221            98005428, 268448037, 63650722,
222        ];
223        for (k, &w) in want_head.iter().enumerate() {
224            assert_eq!(rng.mt[k], w, "state[{k}]");
225        }
226        assert_eq!(rng.mt[623], 3605718188, "state[623]");
227        assert_eq!(rng.mti, 624);
228    }
229
230    #[test]
231    fn runif_matches_r() {
232        let mut rng = RRng::new(1);
233        let got = rng.runif(10);
234        let want = [
235            0.26550866314209998,
236            0.37212389963679016,
237            0.57285336335189641,
238            0.90820778999477625,
239            0.2016819310374558,
240            0.89838968496769667,
241            0.94467526860535145,
242            0.66079779248684645,
243            0.62911404389888048,
244            0.061786270467564464,
245        ];
246        for (g, w) in got.iter().zip(want.iter()) {
247            assert_eq!(g, w, "runif seed 1");
248        }
249
250        let mut rng = RRng::new(42);
251        let got = rng.runif(8);
252        let want = [
253            0.91480604349635541,
254            0.93707541329786181,
255            0.28613953478634357,
256            0.83044762606732547,
257            0.64174551889300346,
258            0.51909594913013279,
259            0.73658831464126706,
260            0.13466659723781049,
261        ];
262        for (g, w) in got.iter().zip(want.iter()) {
263            assert_eq!(g, w, "runif seed 42");
264        }
265    }
266
267    #[test]
268    fn rnorm_matches_r() {
269        let mut rng = RRng::new(1);
270        let got = rng.rnorm(10);
271        let want = [
272            -0.62645381074233242,
273            0.18364332422208224,
274            -0.83562861241004716,
275            1.5952808021377916,
276            0.32950777181536051,
277            -0.82046838411801526,
278            0.48742905242848528,
279            0.73832470512921733,
280            0.57578135165349231,
281            -0.30538838715635602,
282        ];
283        for (g, w) in got.iter().zip(want.iter()) {
284            assert_eq!(g, w, "rnorm seed 1");
285        }
286
287        let mut rng = RRng::new(42);
288        let got = rng.rnorm(8);
289        let want = [
290            1.3709584471466685,
291            -0.56469817139608869,
292            0.3631284113373392,
293            0.63286260496104041,
294            0.40426832314099903,
295            -0.10612451609148403,
296            1.5115219974389389,
297            -0.094659038413097557,
298        ];
299        for (g, w) in got.iter().zip(want.iter()) {
300            assert_eq!(g, w, "rnorm seed 42");
301        }
302    }
303
304    #[test]
305    fn qnorm_matches_r() {
306        let probs = [1e-10, 0.0123, 0.25, 0.5, 0.75, 0.9999, 1.0 - 1e-12];
307        let want = [
308            -6.3613409024040557,
309            -2.2476267557951379,
310            -0.67448975019608171,
311            0.0,
312            0.67448975019608171,
313            3.7190164854557084,
314            7.0344869100478356,
315        ];
316        for (&p, &w) in probs.iter().zip(want.iter()) {
317            assert_eq!(qnorm(p), w, "qnorm({p})");
318        }
319    }
320}