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