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, IxDyn};
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 const 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) const 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 const 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 the next random `f32` in [0, 1).
61    #[inline]
62    pub fn next_f32(&mut self) -> f32 {
63        self.bg.next_f32()
64    }
65
66    /// Generate a `u64` in [0, bound).
67    #[inline]
68    pub fn next_u64_bounded(&mut self, bound: u64) -> u64 {
69        self.bg.next_u64_bounded(bound)
70    }
71
72    /// Generate `n` random bytes as a `Vec<u8>`.
73    ///
74    /// Equivalent to `numpy.random.Generator.bytes(n)`. Each byte is
75    /// drawn from the underlying bit generator's `u64` stream and
76    /// little-endian-decomposed; calling `bytes(n)` advances the bit
77    /// generator by `ceil(n / 8)` `u64` draws (#446).
78    pub fn bytes(&mut self, n: usize) -> Vec<u8> {
79        let mut out = Vec::with_capacity(n);
80        let full_words = n / 8;
81        for _ in 0..full_words {
82            out.extend_from_slice(&self.bg.next_u64().to_le_bytes());
83        }
84        let remainder = n % 8;
85        if remainder > 0 {
86            let bytes = self.bg.next_u64().to_le_bytes();
87            out.extend_from_slice(&bytes[..remainder]);
88        }
89        out
90    }
91}
92
93/// Create a `Generator` with the default `BitGenerator` (Xoshiro256**)
94/// seeded from a non-deterministic source (using the system time as a
95/// simple entropy source).
96///
97/// # Example
98/// ```
99/// let mut rng = ferray_random::default_rng();
100/// let val = rng.next_f64();
101/// assert!((0.0..1.0).contains(&val));
102/// ```
103#[must_use]
104pub fn default_rng() -> Generator<Xoshiro256StarStar> {
105    // Use OS entropy via getrandom for proper seeding.
106    // Falls back to time-based entropy if getrandom fails.
107    let seed = {
108        let mut buf = [0u8; 8];
109        if getrandom::fill(&mut buf).is_ok() {
110            u64::from_ne_bytes(buf)
111        } else {
112            // Fallback: time + stack address
113            use std::time::SystemTime;
114            let dur = SystemTime::now()
115                .duration_since(SystemTime::UNIX_EPOCH)
116                .unwrap_or_default();
117            let nanos = dur.as_nanos();
118            let mut s = nanos as u64;
119            s ^= (nanos >> 64) as u64;
120            let stack_var: u8 = 0;
121            s ^= &raw const stack_var as u64;
122            s
123        }
124    };
125    default_rng_seeded(seed)
126}
127
128/// Create a `Generator` with the default `BitGenerator` (Xoshiro256**)
129/// from a specific seed, ensuring deterministic output.
130///
131/// # Example
132/// ```
133/// let mut rng1 = ferray_random::default_rng_seeded(42);
134/// let mut rng2 = ferray_random::default_rng_seeded(42);
135/// assert_eq!(rng1.next_u64(), rng2.next_u64());
136/// ```
137#[must_use]
138pub fn default_rng_seeded(seed: u64) -> Generator<Xoshiro256StarStar> {
139    let bg = Xoshiro256StarStar::seed_from_u64(seed);
140    Generator::new_with_seed(bg, seed)
141}
142
143/// Spawn `n` independent child generators from this generator.
144///
145/// Uses `jump()` if available (Xoshiro256**), otherwise uses
146/// `stream()` (Philox), otherwise falls back to seeding from
147/// the parent generator's output.
148///
149/// # Errors
150/// Returns `FerrayError::InvalidValue` if `n` is zero.
151pub fn spawn_generators<B: BitGenerator + Clone>(
152    parent: &mut Generator<B>,
153    n: usize,
154) -> Result<Vec<Generator<B>>, FerrayError> {
155    if n == 0 {
156        return Err(FerrayError::invalid_value("spawn count must be > 0"));
157    }
158
159    let mut children = Vec::with_capacity(n);
160
161    // Try jump-based spawning first
162    let mut test_bg = parent.bg.clone();
163    if test_bg.jump().is_some() {
164        // Jump-based: each child starts at a 2^128 offset
165        let mut current = parent.bg.clone();
166        for _ in 0..n {
167            children.push(Generator::new(current.clone()));
168            current.jump();
169        }
170        // Advance parent past all children
171        parent.bg = current;
172        return Ok(children);
173    }
174
175    // Try stream-based spawning
176    if let Some(first) = B::stream(parent.seed, 0) {
177        drop(first);
178        for i in 0..n {
179            if let Some(bg) = B::stream(parent.seed, i as u64) {
180                children.push(Generator::new(bg));
181            }
182        }
183        if children.len() == n {
184            return Ok(children);
185        }
186        children.clear();
187    }
188
189    // Fallback: seed from parent output (less ideal but works for PCG64)
190    for _ in 0..n {
191        let child_seed = parent.bg.next_u64();
192        let bg = B::seed_from_u64(child_seed);
193        children.push(Generator::new(bg));
194    }
195    Ok(children)
196}
197
198// Helper: generate a Vec<f64> of given total size using a closure.
199pub(crate) fn generate_vec<B: BitGenerator>(
200    rng: &mut Generator<B>,
201    size: usize,
202    mut f: impl FnMut(&mut B) -> f64,
203) -> Vec<f64> {
204    let mut data = Vec::with_capacity(size);
205    for _ in 0..size {
206        data.push(f(&mut rng.bg));
207    }
208    data
209}
210
211// Helper: generate a Vec<f32> of given total size using a closure.
212pub(crate) fn generate_vec_f32<B: BitGenerator>(
213    rng: &mut Generator<B>,
214    size: usize,
215    mut f: impl FnMut(&mut B) -> f32,
216) -> Vec<f32> {
217    let mut data = Vec::with_capacity(size);
218    for _ in 0..size {
219        data.push(f(&mut rng.bg));
220    }
221    data
222}
223
224// Helper: generate a Vec<i64> of given total size using a closure.
225pub(crate) fn generate_vec_i64<B: BitGenerator>(
226    rng: &mut Generator<B>,
227    size: usize,
228    mut f: impl FnMut(&mut B) -> i64,
229) -> Vec<i64> {
230    let mut data = Vec::with_capacity(size);
231    for _ in 0..size {
232        data.push(f(&mut rng.bg));
233    }
234    data
235}
236
237/// Total element count for a shape, returning 0 for an empty shape.
238#[inline]
239pub(crate) fn shape_size(shape: &[usize]) -> usize {
240    if shape.is_empty() {
241        0
242    } else {
243        shape.iter().product()
244    }
245}
246
247/// Wrap a `Vec<f64>` into an `Array<f64, IxDyn>` with the given shape.
248pub(crate) fn vec_to_array_f64(
249    data: Vec<f64>,
250    shape: &[usize],
251) -> Result<Array<f64, IxDyn>, FerrayError> {
252    Array::<f64, IxDyn>::from_vec(IxDyn::new(shape), data)
253}
254
255/// Wrap a `Vec<f32>` into an `Array<f32, IxDyn>` with the given shape.
256pub(crate) fn vec_to_array_f32(
257    data: Vec<f32>,
258    shape: &[usize],
259) -> Result<Array<f32, IxDyn>, FerrayError> {
260    Array::<f32, IxDyn>::from_vec(IxDyn::new(shape), data)
261}
262
263/// Wrap a `Vec<i64>` into an `Array<i64, IxDyn>` with the given shape.
264pub(crate) fn vec_to_array_i64(
265    data: Vec<i64>,
266    shape: &[usize],
267) -> Result<Array<i64, IxDyn>, FerrayError> {
268    Array::<i64, IxDyn>::from_vec(IxDyn::new(shape), data)
269}
270
271#[cfg(test)]
272mod tests {
273    use super::*;
274
275    #[test]
276    fn default_rng_seeded_deterministic() {
277        let mut rng1 = default_rng_seeded(42);
278        let mut rng2 = default_rng_seeded(42);
279        for _ in 0..100 {
280            assert_eq!(rng1.next_u64(), rng2.next_u64());
281        }
282    }
283
284    #[test]
285    fn default_rng_works() {
286        let mut rng = default_rng();
287        let v = rng.next_f64();
288        assert!((0.0..1.0).contains(&v));
289    }
290
291    #[test]
292    fn spawn_xoshiro() {
293        let mut parent = default_rng_seeded(42);
294        let children = spawn_generators(&mut parent, 4).unwrap();
295        assert_eq!(children.len(), 4);
296    }
297
298    #[test]
299    fn spawn_zero_is_error() {
300        let mut parent = default_rng_seeded(42);
301        assert!(spawn_generators(&mut parent, 0).is_err());
302    }
303
304    // ----- bytes() coverage (#446) -----
305
306    #[test]
307    fn bytes_length_zero() {
308        let mut rng = default_rng_seeded(42);
309        assert!(rng.bytes(0).is_empty());
310    }
311
312    #[test]
313    fn bytes_length_full_word() {
314        let mut rng = default_rng_seeded(42);
315        let b = rng.bytes(8);
316        assert_eq!(b.len(), 8);
317    }
318
319    #[test]
320    fn bytes_length_partial_word() {
321        let mut rng = default_rng_seeded(42);
322        let b = rng.bytes(13);
323        assert_eq!(b.len(), 13);
324    }
325
326    #[test]
327    fn bytes_deterministic_for_same_seed() {
328        let mut rng1 = default_rng_seeded(42);
329        let mut rng2 = default_rng_seeded(42);
330        assert_eq!(rng1.bytes(64), rng2.bytes(64));
331    }
332}