Skip to main content

runmat_runtime/builtins/common/
random.rs

1use std::collections::{HashMap, VecDeque};
2use std::f64::consts::PI;
3use std::sync::{Mutex, OnceLock};
4
5use crate::{build_runtime_error, BuiltinResult, RuntimeError};
6
7pub(crate) const DEFAULT_RNG_SEED: u64 = 0x9e3779b97f4a7c15;
8pub(crate) const DEFAULT_USER_SEED: u64 = 0;
9const RNG_MULTIPLIER: u64 = 6364136223846793005;
10const RNG_INCREMENT: u64 = 1;
11const RNG_SHIFT: u32 = 11;
12const RNG_SCALE: f64 = 1.0 / ((1u64 << 53) as f64);
13const MIN_UNIFORM: f64 = f64::MIN_POSITIVE;
14
15fn random_error(label: &str, message: impl Into<String>) -> RuntimeError {
16    build_runtime_error(message).with_builtin(label).build()
17}
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub(crate) enum RngAlgorithm {
21    RunMatLcg,
22}
23
24impl RngAlgorithm {
25    pub(crate) fn as_str(&self) -> &'static str {
26        match self {
27            RngAlgorithm::RunMatLcg => "twister",
28        }
29    }
30}
31
32#[derive(Clone, Copy, Debug)]
33pub(crate) struct RngSnapshot {
34    pub state: u64,
35    pub seed: Option<u64>,
36    pub algorithm: RngAlgorithm,
37}
38
39impl RngSnapshot {
40    pub(crate) fn new(state: u64, seed: Option<u64>, algorithm: RngAlgorithm) -> Self {
41        Self {
42            state,
43            seed,
44            algorithm,
45        }
46    }
47}
48
49#[derive(Clone, Copy)]
50struct GlobalRng {
51    state: u64,
52    seed: Option<u64>,
53    algorithm: RngAlgorithm,
54}
55
56impl GlobalRng {
57    fn new() -> Self {
58        Self {
59            state: DEFAULT_RNG_SEED,
60            seed: Some(DEFAULT_USER_SEED),
61            algorithm: RngAlgorithm::RunMatLcg,
62        }
63    }
64
65    fn snapshot(&self) -> RngSnapshot {
66        RngSnapshot {
67            state: self.state,
68            seed: self.seed,
69            algorithm: self.algorithm,
70        }
71    }
72}
73
74impl From<RngSnapshot> for GlobalRng {
75    fn from(snapshot: RngSnapshot) -> Self {
76        Self {
77            state: snapshot.state,
78            seed: snapshot.seed,
79            algorithm: snapshot.algorithm,
80        }
81    }
82}
83
84static RNG_STATE: OnceLock<Mutex<GlobalRng>> = OnceLock::new();
85const LEGACY_TOKEN_BASE: u64 = (1_u64 << 52) + 0x5eed;
86const LEGACY_TOKEN_CAPACITY: usize = 1024;
87
88struct LegacyTokenState {
89    next: u64,
90    entries: HashMap<u64, RngSnapshot>,
91    order: VecDeque<u64>,
92}
93
94impl LegacyTokenState {
95    fn new() -> Self {
96        Self {
97            next: 0,
98            entries: HashMap::new(),
99            order: VecDeque::new(),
100        }
101    }
102
103    fn insert(&mut self, snapshot: RngSnapshot) -> u64 {
104        let token = LEGACY_TOKEN_BASE + self.next;
105        self.next = self.next.wrapping_add(1);
106        if self.entries.insert(token, snapshot).is_none() {
107            self.order.push_back(token);
108        }
109        while self.order.len() > LEGACY_TOKEN_CAPACITY {
110            if let Some(oldest) = self.order.pop_front() {
111                self.entries.remove(&oldest);
112            }
113        }
114        token
115    }
116}
117
118static LEGACY_TOKENS: OnceLock<Mutex<LegacyTokenState>> = OnceLock::new();
119
120fn rng_state() -> &'static Mutex<GlobalRng> {
121    RNG_STATE.get_or_init(|| Mutex::new(GlobalRng::new()))
122}
123
124fn legacy_tokens() -> &'static Mutex<LegacyTokenState> {
125    LEGACY_TOKENS.get_or_init(|| Mutex::new(LegacyTokenState::new()))
126}
127
128fn mix_seed(seed: u64) -> u64 {
129    if seed == 0 {
130        return DEFAULT_RNG_SEED;
131    }
132    let mut z = seed.wrapping_add(0x9e3779b97f4a7c15);
133    z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
134    z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
135    let mixed = z ^ (z >> 31);
136    if mixed == 0 {
137        DEFAULT_RNG_SEED
138    } else {
139        mixed
140    }
141}
142
143pub(crate) fn snapshot() -> BuiltinResult<RngSnapshot> {
144    rng_state()
145        .lock()
146        .map(|guard| guard.snapshot())
147        .map_err(|_| random_error("rng", "rng: failed to acquire RNG lock"))
148}
149
150pub(crate) fn apply_snapshot(snapshot: RngSnapshot) -> BuiltinResult<RngSnapshot> {
151    let mut guard = rng_state()
152        .lock()
153        .map_err(|_| random_error("rng", "rng: failed to acquire RNG lock"))?;
154    let previous = guard.snapshot();
155    guard.state = snapshot.state;
156    guard.seed = snapshot.seed;
157    guard.algorithm = snapshot.algorithm;
158    Ok(previous)
159}
160
161pub(crate) fn set_seed(seed: u64) -> BuiltinResult<RngSnapshot> {
162    let state = mix_seed(seed);
163    apply_snapshot(RngSnapshot::new(state, Some(seed), RngAlgorithm::RunMatLcg))
164}
165
166pub(crate) fn legacy_seed_value() -> BuiltinResult<u64> {
167    let snapshot = snapshot()?;
168    if let Some(seed) = snapshot.seed {
169        if snapshot.state == mix_seed(seed) {
170            return Ok(seed);
171        }
172    }
173    let mut tokens = legacy_tokens()
174        .lock()
175        .map_err(|_| random_error("rand", "rand: failed to acquire legacy RNG token lock"))?;
176    Ok(tokens.insert(snapshot))
177}
178
179pub(crate) fn set_legacy_seed(seed_or_token: u64) -> BuiltinResult<RngSnapshot> {
180    if let Some(snapshot) = legacy_tokens()
181        .lock()
182        .map_err(|_| random_error("rand", "rand: failed to acquire legacy RNG token lock"))?
183        .entries
184        .get(&seed_or_token)
185        .copied()
186    {
187        apply_snapshot(snapshot)
188    } else {
189        set_seed(seed_or_token)
190    }
191}
192
193pub(crate) fn set_default() -> BuiltinResult<RngSnapshot> {
194    apply_snapshot(default_snapshot())
195}
196
197pub(crate) fn default_snapshot() -> RngSnapshot {
198    RngSnapshot::new(
199        DEFAULT_RNG_SEED,
200        Some(DEFAULT_USER_SEED),
201        RngAlgorithm::RunMatLcg,
202    )
203}
204
205pub(crate) fn generate_uniform(len: usize, label: &str) -> BuiltinResult<Vec<f64>> {
206    let mut guard = rng_state()
207        .lock()
208        .map_err(|_| random_error(label, format!("{label}: failed to acquire RNG lock")))?;
209    let mut out = Vec::with_capacity(len);
210    for _ in 0..len {
211        out.push(next_uniform_state(&mut guard.state));
212    }
213    Ok(out)
214}
215
216pub(crate) fn generate_uniform_single(len: usize, label: &str) -> BuiltinResult<Vec<f64>> {
217    generate_uniform(len, label).map(|data| {
218        data.into_iter()
219            .map(|v| {
220                let value = v as f32;
221                value as f64
222            })
223            .collect()
224    })
225}
226
227pub(crate) fn skip_uniform(len: usize, label: &str) -> BuiltinResult<()> {
228    if len == 0 {
229        return Ok(());
230    }
231    let mut guard = rng_state()
232        .lock()
233        .map_err(|_| random_error(label, format!("{label}: failed to acquire RNG lock")))?;
234    guard.state = advance_state(guard.state, len as u64);
235    Ok(())
236}
237
238fn advance_state(state: u64, mut delta: u64) -> u64 {
239    if delta == 0 {
240        return state;
241    }
242    let mut cur_mult = RNG_MULTIPLIER;
243    let mut cur_plus = RNG_INCREMENT;
244    let mut acc_mult = 1u64;
245    let mut acc_plus = 0u64;
246    while delta > 0 {
247        if (delta & 1) != 0 {
248            acc_mult = acc_mult.wrapping_mul(cur_mult);
249            acc_plus = acc_plus.wrapping_mul(cur_mult).wrapping_add(cur_plus);
250        }
251        cur_plus = cur_plus.wrapping_mul(cur_mult.wrapping_add(1));
252        cur_mult = cur_mult.wrapping_mul(cur_mult);
253        delta >>= 1;
254    }
255    acc_mult.wrapping_mul(state).wrapping_add(acc_plus)
256}
257
258pub(crate) fn generate_complex(len: usize, label: &str) -> BuiltinResult<Vec<(f64, f64)>> {
259    let mut guard = rng_state()
260        .lock()
261        .map_err(|_| random_error(label, format!("{label}: failed to acquire RNG lock")))?;
262    let mut out = Vec::with_capacity(len);
263    for _ in 0..len {
264        let re = next_uniform_state(&mut guard.state);
265        let im = next_uniform_state(&mut guard.state);
266        out.push((re, im));
267    }
268    Ok(out)
269}
270
271pub(crate) fn next_uniform_state(state: &mut u64) -> f64 {
272    *state = state
273        .wrapping_mul(RNG_MULTIPLIER)
274        .wrapping_add(RNG_INCREMENT);
275    let bits = *state >> RNG_SHIFT;
276    (bits as f64) * RNG_SCALE
277}
278
279fn next_normal_pair(state: &mut u64) -> (f64, f64) {
280    let mut u1 = next_uniform_state(state);
281    if u1 <= 0.0 {
282        u1 = MIN_UNIFORM;
283    }
284    let u2 = next_uniform_state(state);
285    let radius = (-2.0 * u1.ln()).sqrt();
286    let angle = 2.0 * PI * u2;
287    (radius * angle.cos(), radius * angle.sin())
288}
289
290pub(crate) fn generate_exponential(mu: f64, len: usize, label: &str) -> BuiltinResult<Vec<f64>> {
291    let mut guard = rng_state()
292        .lock()
293        .map_err(|_| random_error(label, format!("{label}: failed to acquire RNG lock")))?;
294    let mut out = Vec::with_capacity(len);
295    for _ in 0..len {
296        let u = next_uniform_state(&mut guard.state).max(MIN_UNIFORM);
297        out.push(-mu * u.ln());
298    }
299    Ok(out)
300}
301
302pub(crate) fn generate_normal_scaled(
303    mu: f64,
304    sigma: f64,
305    len: usize,
306    label: &str,
307) -> BuiltinResult<Vec<f64>> {
308    let mut guard = rng_state()
309        .lock()
310        .map_err(|_| random_error(label, format!("{label}: failed to acquire RNG lock")))?;
311    let mut out = Vec::with_capacity(len);
312    while out.len() < len {
313        let (z0, z1) = next_normal_pair(&mut guard.state);
314        out.push(mu + sigma * z0);
315        if out.len() < len {
316            out.push(mu + sigma * z1);
317        }
318    }
319    Ok(out)
320}
321
322pub(crate) fn generate_student_t(nu: &[f64], len: usize, label: &str) -> BuiltinResult<Vec<f64>> {
323    let mut guard = rng_state()
324        .lock()
325        .map_err(|_| random_error(label, format!("{label}: failed to acquire RNG lock")))?;
326    let mut out = Vec::with_capacity(len);
327    let mut spare_normal = None;
328    for idx in 0..len {
329        let df = if nu.len() == 1 { nu[0] } else { nu[idx] };
330        let z = if let Some(value) = spare_normal.take() {
331            value
332        } else {
333            let (z0, z1) = next_normal_pair(&mut guard.state);
334            spare_normal = Some(z1);
335            z0
336        };
337        if df == f64::INFINITY {
338            out.push(z);
339        } else {
340            let chi_square = sample_gamma_shape_scale(&mut guard.state, df / 2.0, 2.0);
341            out.push(z / (chi_square / df).sqrt());
342        }
343    }
344    Ok(out)
345}
346
347pub(crate) fn generate_gamma_shape_scale(
348    shape: &[f64],
349    scale: &[f64],
350    len: usize,
351    label: &str,
352) -> BuiltinResult<Vec<f64>> {
353    let mut guard = rng_state()
354        .lock()
355        .map_err(|_| random_error(label, format!("{label}: failed to acquire RNG lock")))?;
356    let mut out = Vec::with_capacity(len);
357    for idx in 0..len {
358        let a = if shape.len() == 1 {
359            shape[0]
360        } else {
361            shape[idx]
362        };
363        let b = if scale.len() == 1 {
364            scale[0]
365        } else {
366            scale[idx]
367        };
368        out.push(if a == 0.0 {
369            0.0
370        } else {
371            sample_gamma_shape_scale(&mut guard.state, a, b)
372        });
373    }
374    Ok(out)
375}
376
377pub(crate) fn generate_binomial(
378    trials: &[f64],
379    probability: &[f64],
380    len: usize,
381    label: &str,
382) -> BuiltinResult<Vec<f64>> {
383    let mut guard = rng_state()
384        .lock()
385        .map_err(|_| random_error(label, format!("{label}: failed to acquire RNG lock")))?;
386    let mut out = Vec::with_capacity(len);
387    for idx in 0..len {
388        let n = if trials.len() == 1 {
389            trials[0]
390        } else {
391            trials[idx]
392        };
393        let p = if probability.len() == 1 {
394            probability[0]
395        } else {
396            probability[idx]
397        };
398        out.push(sample_binomial(&mut guard.state, n, p));
399    }
400    Ok(out)
401}
402
403pub(crate) fn generate_weibull(
404    scale: &[f64],
405    shape: &[f64],
406    len: usize,
407    label: &str,
408) -> BuiltinResult<Vec<f64>> {
409    let mut guard = rng_state()
410        .lock()
411        .map_err(|_| random_error(label, format!("{label}: failed to acquire RNG lock")))?;
412    let mut out = Vec::with_capacity(len);
413    for idx in 0..len {
414        let a = if scale.len() == 1 {
415            scale[0]
416        } else {
417            scale[idx]
418        };
419        let b = if shape.len() == 1 {
420            shape[0]
421        } else {
422            shape[idx]
423        };
424        let u = next_uniform_state(&mut guard.state).max(MIN_UNIFORM);
425        out.push(a * (-u.ln()).powf(1.0 / b));
426    }
427    Ok(out)
428}
429
430fn sample_gamma_shape_scale(state: &mut u64, shape: f64, scale: f64) -> f64 {
431    if shape < 1.0 {
432        let u = next_uniform_state(state).max(MIN_UNIFORM);
433        return sample_gamma_shape_scale(state, shape + 1.0, scale) * u.powf(1.0 / shape);
434    }
435
436    let d = shape - 1.0 / 3.0;
437    let c = (1.0 / (9.0 * d)).sqrt();
438    loop {
439        let (x, _) = next_normal_pair(state);
440        let v_base = 1.0 + c * x;
441        if v_base <= 0.0 {
442            continue;
443        }
444        let v = v_base * v_base * v_base;
445        let u = next_uniform_state(state);
446        let x2 = x * x;
447        if u < 1.0 - 0.0331 * x2 * x2 || u.ln() < 0.5 * x2 + d * (1.0 - v + v.ln()) {
448            return scale * d * v;
449        }
450    }
451}
452
453fn sample_binomial(state: &mut u64, trials: f64, probability: f64) -> f64 {
454    if trials == 0.0 || probability == 0.0 {
455        return 0.0;
456    }
457    if probability == 1.0 {
458        return trials;
459    }
460
461    let effective_p = probability.min(1.0 - probability);
462    let mean = trials * effective_p;
463    let sampled = if trials <= 10_000.0 {
464        sample_binomial_direct(state, trials as u64, effective_p)
465    } else if mean < 30.0 {
466        sample_poisson_knuth(state, mean).min(trials)
467    } else {
468        sample_binomial_normal_approx(state, trials, effective_p)
469    };
470
471    if probability <= 0.5 {
472        sampled
473    } else {
474        trials - sampled
475    }
476}
477
478fn sample_binomial_direct(state: &mut u64, trials: u64, probability: f64) -> f64 {
479    let mut successes = 0_u64;
480    for _ in 0..trials {
481        if next_uniform_state(state) < probability {
482            successes += 1;
483        }
484    }
485    successes as f64
486}
487
488fn sample_poisson_knuth(state: &mut u64, lambda: f64) -> f64 {
489    if lambda <= 0.0 {
490        return 0.0;
491    }
492    let threshold = (-lambda).exp();
493    let mut product = 1.0;
494    let mut count = 0_u64;
495    loop {
496        count += 1;
497        product *= next_uniform_state(state).max(MIN_UNIFORM);
498        if product <= threshold {
499            return (count - 1) as f64;
500        }
501    }
502}
503
504fn sample_binomial_normal_approx(state: &mut u64, trials: f64, probability: f64) -> f64 {
505    let mean = trials * probability;
506    let variance = mean * (1.0 - probability);
507    if variance <= 0.0 {
508        return mean.round().clamp(0.0, trials);
509    }
510    let (z, _) = next_normal_pair(state);
511    (mean + variance.sqrt() * z).round().clamp(0.0, trials)
512}
513
514pub(crate) fn generate_uniform_scaled(
515    a: f64,
516    b: f64,
517    len: usize,
518    label: &str,
519) -> BuiltinResult<Vec<f64>> {
520    let mut guard = rng_state()
521        .lock()
522        .map_err(|_| random_error(label, format!("{label}: failed to acquire RNG lock")))?;
523    let mut out = Vec::with_capacity(len);
524    for _ in 0..len {
525        out.push(a + (b - a) * next_uniform_state(&mut guard.state));
526    }
527    Ok(out)
528}
529
530pub(crate) fn generate_normal(len: usize, label: &str) -> BuiltinResult<Vec<f64>> {
531    let mut guard = rng_state()
532        .lock()
533        .map_err(|_| random_error(label, format!("{label}: failed to acquire RNG lock")))?;
534    let mut out = Vec::with_capacity(len);
535    while out.len() < len {
536        let (z0, z1) = next_normal_pair(&mut guard.state);
537        out.push(z0);
538        if out.len() < len {
539            out.push(z1);
540        }
541    }
542    Ok(out)
543}
544
545pub(crate) fn generate_normal_complex(len: usize, label: &str) -> BuiltinResult<Vec<(f64, f64)>> {
546    let mut guard = rng_state()
547        .lock()
548        .map_err(|_| random_error(label, format!("{label}: failed to acquire RNG lock")))?;
549    let mut out = Vec::with_capacity(len);
550    for _ in 0..len {
551        let (re, im) = next_normal_pair(&mut guard.state);
552        out.push((re, im));
553    }
554    Ok(out)
555}
556
557#[cfg(test)]
558pub(crate) fn reset_rng() {
559    if let Some(mutex) = RNG_STATE.get() {
560        if let Ok(mut guard) = mutex.lock() {
561            *guard = GlobalRng::from(default_snapshot());
562        }
563    } else {
564        let _ = RNG_STATE.set(Mutex::new(GlobalRng::new()));
565    }
566    if let Some(mutex) = LEGACY_TOKENS.get() {
567        if let Ok(mut guard) = mutex.lock() {
568            *guard = LegacyTokenState::new();
569        }
570    }
571}
572
573#[cfg(test)]
574pub(crate) fn expected_exponential_sequence(mu: f64, count: usize) -> Vec<f64> {
575    let mut seed = DEFAULT_RNG_SEED;
576    let mut seq = Vec::with_capacity(count);
577    for _ in 0..count {
578        let u = next_uniform_state(&mut seed).max(MIN_UNIFORM);
579        seq.push(-mu * u.ln());
580    }
581    seq
582}
583
584#[cfg(test)]
585pub(crate) fn expected_normal_scaled_sequence(mu: f64, sigma: f64, count: usize) -> Vec<f64> {
586    let mut seed = DEFAULT_RNG_SEED;
587    let mut seq = Vec::with_capacity(count);
588    while seq.len() < count {
589        let (z0, z1) = next_normal_pair(&mut seed);
590        seq.push(mu + sigma * z0);
591        if seq.len() < count {
592            seq.push(mu + sigma * z1);
593        }
594    }
595    seq
596}
597
598#[cfg(test)]
599pub(crate) fn expected_uniform_scaled_sequence(a: f64, b: f64, count: usize) -> Vec<f64> {
600    let mut seed = DEFAULT_RNG_SEED;
601    let mut seq = Vec::with_capacity(count);
602    for _ in 0..count {
603        seq.push(a + (b - a) * next_uniform_state(&mut seed));
604    }
605    seq
606}
607
608#[cfg(test)]
609pub(crate) fn expected_uniform_sequence(count: usize) -> Vec<f64> {
610    let mut seed = DEFAULT_RNG_SEED;
611    let mut seq = Vec::with_capacity(count);
612    for _ in 0..count {
613        seq.push(next_uniform_state(&mut seed));
614    }
615    seq
616}
617
618#[cfg(test)]
619pub(crate) fn expected_complex_sequence(count: usize) -> Vec<(f64, f64)> {
620    let mut seed = DEFAULT_RNG_SEED;
621    let mut seq = Vec::with_capacity(count);
622    for _ in 0..count {
623        let re = next_uniform_state(&mut seed);
624        let im = next_uniform_state(&mut seed);
625        seq.push((re, im));
626    }
627    seq
628}
629
630#[cfg(test)]
631pub(crate) fn expected_normal_sequence(count: usize) -> Vec<f64> {
632    let mut seed = DEFAULT_RNG_SEED;
633    let mut seq = Vec::with_capacity(count);
634    while seq.len() < count {
635        let (z0, z1) = next_normal_pair(&mut seed);
636        seq.push(z0);
637        if seq.len() < count {
638            seq.push(z1);
639        }
640    }
641    seq
642}
643
644#[cfg(test)]
645pub(crate) fn expected_complex_normal_sequence(count: usize) -> Vec<(f64, f64)> {
646    let mut seed = DEFAULT_RNG_SEED;
647    let mut seq = Vec::with_capacity(count);
648    for _ in 0..count {
649        seq.push(next_normal_pair(&mut seed));
650    }
651    seq
652}
653
654#[cfg(test)]
655pub(crate) fn test_guard() -> super::test_support::GlobalStateTestGuard {
656    super::test_support::global_state_test_guard()
657}