Skip to main content

ferray_random/bitgen/
seed_sequence.rs

1// ferray-random: SeedSequence — entropy mixing and child-spawn API
2//
3// Implements the NumPy `SeedSequence` interface: mix arbitrary entropy
4// (a primary u128 seed plus a spawn-key path) into a deterministic,
5// well-distributed sequence of 32-bit words suitable for seeding any
6// BitGenerator. Spawning produces children whose entropy paths differ
7// from the parent's, so independent streams from a single root seed are
8// reproducible.
9
10#![allow(clippy::unreadable_literal)]
11
12use super::BitGenerator;
13use super::splitmix64;
14
15/// SeedSequence — deterministic entropy expansion and child spawning.
16///
17/// Equivalent to `numpy.random.SeedSequence`. Construct from an
18/// `entropy: u64`; call [`generate_state`](Self::generate_state) for
19/// `n_words` 32-bit seed words, or [`spawn`](Self::spawn) to create
20/// `n` independent child sequences whose state is reproducibly derived
21/// from this one.
22#[derive(Debug, Clone)]
23pub struct SeedSequence {
24    /// User-supplied root entropy.
25    entropy: u64,
26    /// Path of indices identifying this sequence within the parent's
27    /// spawn tree. Empty for the root.
28    spawn_key: Vec<u64>,
29    /// How many children have been spawned from this sequence so far.
30    /// Used to give the next spawn a fresh key.
31    n_children_spawned: u64,
32}
33
34impl SeedSequence {
35    /// Create a SeedSequence from a single entropy value.
36    #[must_use]
37    pub fn new(entropy: u64) -> Self {
38        Self {
39            entropy,
40            spawn_key: Vec::new(),
41            n_children_spawned: 0,
42        }
43    }
44
45    /// Create a SeedSequence with an explicit spawn-key path.
46    ///
47    /// The spawn key disambiguates children of the same parent.
48    /// Normally users construct via [`new`](Self::new) and let
49    /// [`spawn`](Self::spawn) populate the key automatically.
50    #[must_use]
51    pub fn with_spawn_key(entropy: u64, spawn_key: Vec<u64>) -> Self {
52        Self {
53            entropy,
54            spawn_key,
55            n_children_spawned: 0,
56        }
57    }
58
59    /// The entropy this sequence was created with.
60    #[must_use]
61    pub fn entropy(&self) -> u64 {
62        self.entropy
63    }
64
65    /// The spawn-key path of this sequence (empty for the root).
66    #[must_use]
67    pub fn spawn_key(&self) -> &[u64] {
68        &self.spawn_key
69    }
70
71    /// Generate `n_words` 32-bit seed words from this sequence.
72    ///
73    /// The output is a deterministic function of `(entropy, spawn_key)`
74    /// only — calling `generate_state` repeatedly returns the same
75    /// words. Different sequences produce statistically independent
76    /// outputs.
77    #[must_use]
78    pub fn generate_state(&self, n_words: usize) -> Vec<u32> {
79        // Mix entropy + spawn_key into a SplitMix64 state, then emit
80        // `n_words` 32-bit halves.
81        let mut state = self.entropy;
82        for &k in &self.spawn_key {
83            state ^= k.wrapping_mul(0x9e37_79b9_7f4a_7c15);
84            // Round-trip through SplitMix64 to avalanche bits.
85            let _ = splitmix64(&mut state);
86        }
87        let mut out = Vec::with_capacity(n_words);
88        let mut produced = 0usize;
89        while produced < n_words {
90            let v = splitmix64(&mut state);
91            out.push((v & 0xFFFF_FFFF) as u32);
92            produced += 1;
93            if produced < n_words {
94                out.push((v >> 32) as u32);
95                produced += 1;
96            }
97        }
98        out.truncate(n_words);
99        out
100    }
101
102    /// Generate a single u64 derived from this sequence — convenient for
103    /// seeding a [`BitGenerator`] via [`BitGenerator::seed_from_u64`].
104    #[must_use]
105    pub fn generate_u64(&self) -> u64 {
106        let words = self.generate_state(2);
107        (u64::from(words[1]) << 32) | u64::from(words[0])
108    }
109
110    /// Seed a fresh BitGenerator deterministically from this sequence.
111    pub fn seed<B: BitGenerator>(&self) -> B {
112        B::seed_from_u64(self.generate_u64())
113    }
114
115    /// Create `n` independent child sequences.
116    ///
117    /// Each child has the same `entropy` as the parent but a different
118    /// `spawn_key`, so their `generate_state` outputs do not overlap.
119    /// Mutates `self`'s spawn counter so successive calls yield fresh
120    /// children.
121    #[must_use]
122    pub fn spawn(&mut self, n: usize) -> Vec<SeedSequence> {
123        let mut children = Vec::with_capacity(n);
124        for _ in 0..n {
125            let mut key = self.spawn_key.clone();
126            key.push(self.n_children_spawned);
127            self.n_children_spawned += 1;
128            children.push(SeedSequence {
129                entropy: self.entropy,
130                spawn_key: key,
131                n_children_spawned: 0,
132            });
133        }
134        children
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn generate_state_deterministic() {
144        let s1 = SeedSequence::new(42);
145        let s2 = SeedSequence::new(42);
146        assert_eq!(s1.generate_state(8), s2.generate_state(8));
147    }
148
149    #[test]
150    fn different_entropy_differs() {
151        let s1 = SeedSequence::new(42).generate_state(8);
152        let s2 = SeedSequence::new(43).generate_state(8);
153        assert_ne!(s1, s2);
154    }
155
156    #[test]
157    fn spawn_produces_independent_children() {
158        let mut root = SeedSequence::new(1234);
159        let children = root.spawn(4);
160        assert_eq!(children.len(), 4);
161        let mut all_distinct = true;
162        for i in 0..children.len() {
163            for j in (i + 1)..children.len() {
164                if children[i].generate_state(8) == children[j].generate_state(8) {
165                    all_distinct = false;
166                }
167            }
168        }
169        assert!(all_distinct);
170        // Children also differ from root.
171        for c in &children {
172            assert_ne!(c.generate_state(8), root.generate_state(8));
173        }
174    }
175
176    #[test]
177    fn spawn_advances_counter() {
178        let mut a = SeedSequence::new(1);
179        let mut b = SeedSequence::new(1);
180        let _first = a.spawn(2); // a.n_children_spawned = 2
181        let second = a.spawn(1)[0].clone(); // spawn_key uses index 2
182        let zeroth = b.spawn(1)[0].clone(); // spawn_key uses index 0
183        assert_ne!(second.generate_state(4), zeroth.generate_state(4));
184    }
185
186    #[test]
187    fn seed_bitgenerator_roundtrips() {
188        use crate::bitgen::Pcg64;
189        let s = SeedSequence::new(7);
190        let mut a: Pcg64 = s.seed();
191        let mut b: Pcg64 = s.seed();
192        for _ in 0..32 {
193            assert_eq!(a.next_u64(), b.next_u64());
194        }
195    }
196}