Skip to main content

ferray_random/
generator.rs

1// ferray-random: Generator struct — the main user-facing RNG API
2//
3// Wraps a BitGenerator and provides distribution sampling methods.
4// Takes &mut self — stateful, NOT Sync.
5
6use ferray_core::{Array, FerrayError, Ix1};
7
8use crate::bitgen::{BitGenerator, Xoshiro256StarStar};
9
10/// The main random number generator, wrapping a pluggable [`BitGenerator`].
11///
12/// `Generator` takes `&mut self` for all sampling methods — it is stateful
13/// and NOT `Sync`. Thread-safety is handled by spawning independent generators
14/// via [`spawn`](Generator::spawn) or using the parallel generation API.
15///
16/// # Example
17/// ```
18/// use ferray_random::{default_rng_seeded, Generator};
19///
20/// let mut rng = default_rng_seeded(42);
21/// let values = rng.random(10).unwrap();
22/// assert_eq!(values.shape(), &[10]);
23/// ```
24pub struct Generator<B: BitGenerator = Xoshiro256StarStar> {
25    /// The underlying bit generator.
26    pub(crate) bg: B,
27    /// The seed used to create this generator (for spawn).
28    pub(crate) seed: u64,
29}
30
31impl<B: BitGenerator> Generator<B> {
32    /// Create a new `Generator` wrapping the given `BitGenerator`.
33    pub fn new(bg: B) -> Self {
34        Self { bg, seed: 0 }
35    }
36
37    /// Create a new `Generator` with a known seed (stored for spawn).
38    pub(crate) fn new_with_seed(bg: B, seed: u64) -> Self {
39        Self { bg, seed }
40    }
41
42    /// Access the underlying BitGenerator mutably.
43    #[inline]
44    pub fn bit_generator(&mut self) -> &mut B {
45        &mut self.bg
46    }
47
48    /// Generate the next random `u64`.
49    #[inline]
50    pub fn next_u64(&mut self) -> u64 {
51        self.bg.next_u64()
52    }
53
54    /// Generate the next random `f64` in [0, 1).
55    #[inline]
56    pub fn next_f64(&mut self) -> f64 {
57        self.bg.next_f64()
58    }
59
60    /// Generate a `u64` in [0, bound).
61    #[inline]
62    pub fn next_u64_bounded(&mut self, bound: u64) -> u64 {
63        self.bg.next_u64_bounded(bound)
64    }
65}
66
67/// Create a `Generator` with the default BitGenerator (Xoshiro256**)
68/// seeded from a non-deterministic source (using the system time as a
69/// simple entropy source).
70///
71/// # Example
72/// ```
73/// let mut rng = ferray_random::default_rng();
74/// let val = rng.next_f64();
75/// assert!((0.0..1.0).contains(&val));
76/// ```
77pub fn default_rng() -> Generator<Xoshiro256StarStar> {
78    // Use OS entropy via getrandom for proper seeding.
79    // Falls back to time-based entropy if getrandom fails.
80    let seed = {
81        let mut buf = [0u8; 8];
82        if getrandom::getrandom(&mut buf).is_ok() {
83            u64::from_ne_bytes(buf)
84        } else {
85            // Fallback: time + stack address
86            use std::time::SystemTime;
87            let dur = SystemTime::now()
88                .duration_since(SystemTime::UNIX_EPOCH)
89                .unwrap_or_default();
90            let nanos = dur.as_nanos();
91            let mut s = nanos as u64;
92            s ^= (nanos >> 64) as u64;
93            let stack_var: u8 = 0;
94            s ^= &stack_var as *const u8 as u64;
95            s
96        }
97    };
98    default_rng_seeded(seed)
99}
100
101/// Create a `Generator` with the default BitGenerator (Xoshiro256**)
102/// from a specific seed, ensuring deterministic output.
103///
104/// # Example
105/// ```
106/// let mut rng1 = ferray_random::default_rng_seeded(42);
107/// let mut rng2 = ferray_random::default_rng_seeded(42);
108/// assert_eq!(rng1.next_u64(), rng2.next_u64());
109/// ```
110pub fn default_rng_seeded(seed: u64) -> Generator<Xoshiro256StarStar> {
111    let bg = Xoshiro256StarStar::seed_from_u64(seed);
112    Generator::new_with_seed(bg, seed)
113}
114
115/// Spawn `n` independent child generators from this generator.
116///
117/// Uses `jump()` if available (Xoshiro256**), otherwise uses
118/// `stream()` (Philox), otherwise falls back to seeding from
119/// the parent generator's output.
120///
121/// # Errors
122/// Returns `FerrayError::InvalidValue` if `n` is zero.
123pub fn spawn_generators<B: BitGenerator + Clone>(
124    parent: &mut Generator<B>,
125    n: usize,
126) -> Result<Vec<Generator<B>>, FerrayError> {
127    if n == 0 {
128        return Err(FerrayError::invalid_value("spawn count must be > 0"));
129    }
130
131    let mut children = Vec::with_capacity(n);
132
133    // Try jump-based spawning first
134    let mut test_bg = parent.bg.clone();
135    if test_bg.jump().is_some() {
136        // Jump-based: each child starts at a 2^128 offset
137        let mut current = parent.bg.clone();
138        for _ in 0..n {
139            children.push(Generator::new(current.clone()));
140            current.jump();
141        }
142        // Advance parent past all children
143        parent.bg = current;
144        return Ok(children);
145    }
146
147    // Try stream-based spawning
148    if let Some(first) = B::stream(parent.seed, 0) {
149        drop(first);
150        for i in 0..n {
151            if let Some(bg) = B::stream(parent.seed, i as u64) {
152                children.push(Generator::new(bg));
153            }
154        }
155        if children.len() == n {
156            return Ok(children);
157        }
158        children.clear();
159    }
160
161    // Fallback: seed from parent output (less ideal but works for PCG64)
162    for _ in 0..n {
163        let child_seed = parent.bg.next_u64();
164        let bg = B::seed_from_u64(child_seed);
165        children.push(Generator::new(bg));
166    }
167    Ok(children)
168}
169
170// Helper: generate a Vec<f64> of given size using a closure
171pub(crate) fn generate_vec<B: BitGenerator>(
172    rng: &mut Generator<B>,
173    size: usize,
174    mut f: impl FnMut(&mut B) -> f64,
175) -> Vec<f64> {
176    let mut data = Vec::with_capacity(size);
177    for _ in 0..size {
178        data.push(f(&mut rng.bg));
179    }
180    data
181}
182
183// Helper: generate a Vec<i64> of given size using a closure
184pub(crate) fn generate_vec_i64<B: BitGenerator>(
185    rng: &mut Generator<B>,
186    size: usize,
187    mut f: impl FnMut(&mut B) -> i64,
188) -> Vec<i64> {
189    let mut data = Vec::with_capacity(size);
190    for _ in 0..size {
191        data.push(f(&mut rng.bg));
192    }
193    data
194}
195
196// Helper: wrap a Vec<f64> into an Array1<f64>
197pub(crate) fn vec_to_array1(data: Vec<f64>) -> Result<Array<f64, Ix1>, FerrayError> {
198    let n = data.len();
199    Array::<f64, Ix1>::from_vec(Ix1::new([n]), data)
200}
201
202// Helper: wrap a Vec<i64> into an Array1<i64>
203pub(crate) fn vec_to_array1_i64(data: Vec<i64>) -> Result<Array<i64, Ix1>, FerrayError> {
204    let n = data.len();
205    Array::<i64, Ix1>::from_vec(Ix1::new([n]), data)
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211
212    #[test]
213    fn default_rng_seeded_deterministic() {
214        let mut rng1 = default_rng_seeded(42);
215        let mut rng2 = default_rng_seeded(42);
216        for _ in 0..100 {
217            assert_eq!(rng1.next_u64(), rng2.next_u64());
218        }
219    }
220
221    #[test]
222    fn default_rng_works() {
223        let mut rng = default_rng();
224        let v = rng.next_f64();
225        assert!((0.0..1.0).contains(&v));
226    }
227
228    #[test]
229    fn spawn_xoshiro() {
230        let mut parent = default_rng_seeded(42);
231        let children = spawn_generators(&mut parent, 4).unwrap();
232        assert_eq!(children.len(), 4);
233    }
234
235    #[test]
236    fn spawn_zero_is_error() {
237        let mut parent = default_rng_seeded(42);
238        assert!(spawn_generators(&mut parent, 0).is_err());
239    }
240}