odem_rs_util/random.rs
1//! Contains facilities to ease the creation, distribution and use of random
2//! number generators.
3//!
4//! The module offers [RngStream] as a utility structure to deal with
5//! pseudo-randomness. It provides a two-level hierarchy for generating
6//! random numbers, ensuring reproducibility in both sequential and parallel
7//! simulations.
8//!
9//! The two levels are:
10//! 1. *Inter-Simulation Streams*: This level is for creating independent
11//! random number "universes" for separate, top-level simulation runs (e.g.,
12//! in a parameter sweep). The main entry points are [`RngStream::iter()`]
13//! for sequential runs and [`RngStream::par_iter()`] for parallel runs.
14//! The latter function requires `feature = "rayon"`.
15//!
16//! 2. *Intra-Simulation RNGs*: This level is for drawing individual,
17//! independent PRNGs from a single `RngStream`. These are used for
18//! different tasks *within* one simulation (e.g., one RNG for arrival
19//! times, another for service times). This is handled by methods on an
20//! `RngStream` instance, like [`rng()`][RngStream::rng] or by iterating
21//! over it.
22
23use core::cell::RefCell;
24
25#[cfg(feature = "rand_pcg")]
26use rand_pcg::*;
27
28#[cfg(feature = "rand_xoshiro")]
29use rand_xoshiro::*;
30
31#[cfg(feature = "rayon")]
32#[cfg_attr(docsrs, doc(cfg(feature = "rayon")))]
33mod rayon_support;
34
35#[cfg(feature = "rayon")]
36pub use rayon_support::RngStreamParIter;
37
38/// Type-alias for the default random-number generator that is used in absense
39/// of another user-selected one.
40///
41/// This defaults to the 64-Bit PCG for `feature="rand_pcg"` with 256-Bit
42/// state-space. If instead of `rand_pcg` feature `rand_xoshiro` is active,
43/// Xoshiro256++ is used as a default PRNG.
44#[cfg(all(feature = "rand_pcg", target_pointer_width = "64"))]
45#[cfg_attr(docsrs, doc(cfg(feature = "rand_pcg")))]
46pub type DefaultRng = Pcg64Dxsm;
47
48/// Type-alias for the default random-number generator that is used in absense
49/// of another user-selected one.
50///
51/// This defaults to the 64-Bit PCG for `feature="rand_pcg"` with 256-Bit
52/// state-space. If instead of `rand_pcg` feature `rand_xoshiro` is active,
53/// Xoshiro256++ is used as a default PRNG.
54#[cfg(all(feature = "rand_pcg", target_pointer_width = "32"))]
55pub type DefaultRng = Pcg32;
56
57/// Type-alias for the default random-number generator that is used in absense
58/// of another user-selected one.
59///
60/// This defaults to the 64-Bit PCG for `feature="rand_pcg"` with 256-Bit
61/// state-space. If instead of `rand_pcg` feature `rand_xoshiro` is active,
62/// Xoshiro256++ is used as a default PRNG.
63#[cfg(all(
64 not(feature = "rand_pcg"),
65 feature = "rand_xoshiro",
66 target_pointer_width = "64"
67))]
68#[cfg_attr(
69 docsrs,
70 doc(cfg(all(not(feature = "rand_pcg"), feature = "rand_xoshiro")))
71)]
72pub type DefaultRng = Xoshiro256PlusPlus;
73
74/// Type-alias for the default random-number generator that is used in absense
75/// of another user-selected one.
76///
77/// This defaults to the 64-Bit PCG for `feature="rand_pcg"` with 256-Bit
78/// state-space. If instead of `rand_pcg` feature `rand_xoshiro` is active,
79/// Xoshiro256++ is used as a default PRNG.
80#[cfg(all(
81 not(feature = "rand_pcg"),
82 feature = "rand_xoshiro",
83 target_pointer_width = "32"
84))]
85pub type DefaultRng = Xoshiro128PlusPlus;
86
87/// A trait that abstracts over the state management of a splittable PRNG.
88///
89/// This trait provides the core logic for the two-level hierarchy of randomness
90/// used by [`RngStream`].
91pub trait RngGen {
92 /// The internal state necessary to generate random-number generators.
93 type State;
94
95 /// Initializes the state using a default value.
96 fn default_state() -> Self::State;
97
98 /// Returns an independent PRNG for use within a single simulation.
99 ///
100 /// This method advances the internal state by a small, fixed amount (a
101 /// "jump") and returns a new PRNG instance. Each call produces an
102 /// independent generator for different components within a simulation run.
103 fn next_rng(state: &mut Self::State) -> Self;
104
105 /// Splits the stream of streams, advancing the current state and returning
106 /// a new state for a sub-stream.
107 ///
108 /// This is used for creating independent simulation runs. It partitions the
109 /// state space, returning the current state for a new `RngStream` and
110 /// advancing the parent state by a large amount corresponding to `count`
111 /// full streams.
112 fn split_at(state: &mut Self::State, count: usize) -> Self::State;
113}
114
115/// Represents a stream of independent random-number generators.
116///
117/// An `RngStream` is the main entry point for randomness in a simulation.
118///
119/// Use static methods like [`RngStream::iter()`] or [`RngStream::par_iter()`]
120/// to create sequences of independent streams for multiple simulation runs.
121///
122/// Use instance methods like [`rng()`][RngStream::rng] or iterate over an
123/// instance to get individual PRNGs for tasks within a single simulation.
124///
125/// # Usage
126///
127/// ### Sequential Simulation with Multiple RNGs
128/// Inside a single simulation, you can create multiple independent PRNGs.
129///
130/// ```
131/// # use odem_rs_util::random::{RngStream, DefaultRng};
132/// # use rand::RngExt;
133/// let rng_stream = RngStream::<DefaultRng>::default();
134///
135/// // Get an RNG for event generation.
136/// let mut event_rng = rng_stream.rng();
137/// // Get another, independent RNG for agent decisions.
138/// let mut agent_rng = rng_stream.rng();
139///
140/// println!("Event: {}", event_rng.random::<u64>());
141/// println!("Agent: {}", agent_rng.random::<u64>());
142/// ```
143///
144/// ### Multiple Sequential Simulation Runs
145/// For sequential runs, use [`RngStream::iter()`] combined with `take()` to
146/// specify the number of runs.
147///
148/// ```
149/// # use odem_rs_util::random::{RngStream, DefaultRng};
150/// // Create two independent simulation streams sequentially.
151/// for rng_stream in RngStream::<DefaultRng>::iter().take(2) {
152/// // ... run a full simulation with `rng_stream` ...
153/// }
154/// ```
155///
156/// ### Multiple Parallel Simulation Runs
157/// For parallel runs, use [`RngStream::par_iter()`] combined with `take()` to
158/// specify the number of runs. This is the idiomatic approach when using
159/// `rayon` and requires feature `"rayon"` to be enabled.
160///
161/// ```no_run
162/// # #[cfg(rayon)]
163/// # mod rayon_specific {
164/// # use odem_rs_util::{random::{RngStream, DefaultRng}, random_variable::RandomVariable};
165/// # use rayon::prelude::*;
166/// # fn sim_main(rng_stream: RngStream) -> f64 { 0.0 }
167/// # fn main() {
168/// let stats: RandomVariable<f64> = RngStream::par_iter()
169/// .take(100) // <- run 100 experiments in parallel
170/// .map(|rng_stream| sim_main(rng_stream))
171/// .fold(RandomVariable::default, |var, duration| {
172/// var.tabulate(duration);
173/// var
174/// })
175/// .reduce(RandomVariable::default, RandomVariable::join);
176/// # }}
177/// ```
178pub struct RngStream<R: RngGen = DefaultRng>(RefCell<R::State>);
179
180impl<R: RngGen> RngStream<R> {
181 /// Returns an infinite iterator over independent `RngStream`s.
182 ///
183 /// This is the entry point for creating multiple sequential simulation
184 /// runs. Use `take(n)` to specify the number of streams you need.
185 pub fn iter() -> RngStreamIter<R> {
186 RngStreamIter(RngStream::default())
187 }
188
189 /// Returns an infinite parallel iterator over independent `RngStream`s.
190 ///
191 /// This is the preferred method for running multiple simulations in
192 /// parallel. It requires the `rayon` feature to be enabled.
193 #[cfg(feature = "rayon")]
194 #[cfg_attr(docsrs, doc(cfg(feature = "rayon")))]
195 pub fn par_iter() -> RngStreamParIter<R> {
196 RngStreamParIter::new(RngStream::default())
197 }
198
199 /// Creates a new RngStream from the inner state.
200 pub const fn new(inner: R::State) -> Self {
201 RngStream(RefCell::new(inner))
202 }
203
204 /// Creates a new `RngStream` from a seed value.
205 ///
206 /// The `seed` is a positional index into the sequence of possible streams.
207 /// This is useful for deterministically recreating a specific simulation
208 /// run.
209 pub fn from_seed(seed: u64) -> Self {
210 let mut default_state = R::default_state();
211 R::split_at(&mut default_state, seed as usize);
212 RngStream(RefCell::new(default_state))
213 }
214
215 /// Creates and returns a new, independent random number generator from this
216 /// stream.
217 ///
218 /// This is the primary method for getting an RNG for a task within a
219 /// simulation.
220 pub fn rng(&self) -> R {
221 R::next_rng(&mut self.0.borrow_mut())
222 }
223
224 /// Creates an infinite iterator yielding independent random number
225 /// generators from this stream.
226 pub fn rng_iter(&self) -> RngIter<'_, R> {
227 RngIter(self)
228 }
229}
230
231// --- Trait Implementations (Default, Clone, IntoIterator) ---
232
233impl<R: RngGen> Default for RngStream<R> {
234 fn default() -> Self {
235 RngStream::new(R::default_state())
236 }
237}
238
239impl<R: RngGen> Clone for RngStream<R>
240where
241 R::State: Clone,
242{
243 fn clone(&self) -> Self {
244 RngStream(self.0.clone())
245 }
246}
247
248impl<R: RngGen> IntoIterator for RngStream<R> {
249 type Item = R;
250 type IntoIter = RngIntoIter<R>;
251
252 fn into_iter(self) -> Self::IntoIter {
253 RngIntoIter(self)
254 }
255}
256
257impl<'r, R: RngGen> IntoIterator for &'r RngStream<R> {
258 type Item = R;
259 type IntoIter = RngIter<'r, R>;
260
261 fn into_iter(self) -> Self::IntoIter {
262 RngIter(self)
263 }
264}
265
266/* ************************************************************ Rng Iterators */
267
268/// An infinite iterator over individual PRNGs drawn from a shared
269/// [`RngStream`].
270///
271/// # Usage
272///
273/// Use it within a simulation to generate a sequence of RNGs:
274/// ```
275/// # use odem_rs_util::random::RngStream;
276/// # fn sim_main(rng_stream: RngStream) {
277/// for rng in rng_stream.rng_iter().take(20) {
278/// // Use the random-number generators,
279/// // e.g., for initializing independent jobs.
280/// }
281/// # }
282/// ```
283pub struct RngIter<'r, R: RngGen>(&'r RngStream<R>);
284
285impl<R: RngGen> Iterator for RngIter<'_, R> {
286 type Item = R;
287
288 fn next(&mut self) -> Option<Self::Item> {
289 Some(self.0.rng())
290 }
291}
292
293impl<R: RngGen> Clone for RngIter<'_, R> {
294 fn clone(&self) -> Self {
295 *self
296 }
297}
298
299impl<R: RngGen> Copy for RngIter<'_, R> {}
300
301/// An infinite owning iterator over individual PRNGs drawn from an
302/// [`RngStream`].
303///
304/// # Usage
305///
306/// Use it within a simulation to generate a sequence of RNGs:
307/// ```
308/// # use odem_rs_util::random::RngStream;
309/// # fn sim_main(rng_stream: RngStream) {
310/// for rng in rng_stream.into_iter().take(20) {
311/// // Use the random-number generators,
312/// // e.g., for initializing independent jobs.
313/// }
314/// # }
315/// ```
316pub struct RngIntoIter<R: RngGen>(RngStream<R>);
317
318impl<R: RngGen> Iterator for RngIntoIter<R> {
319 type Item = R;
320
321 fn next(&mut self) -> Option<Self::Item> {
322 Some(self.0.rng())
323 }
324}
325
326impl<R: RngGen> Clone for RngIntoIter<R>
327where
328 R::State: Clone,
329{
330 fn clone(&self) -> Self {
331 RngIntoIter(self.0.clone())
332 }
333}
334
335/// An infinite iterator over independent [`RngStream`] generators.
336///
337/// This is used to set up multiple, independent simulations sequentially.
338///
339/// # Usage
340///
341/// Use it during the set-up of a simulation run to generate a sequence of
342/// independent `RngStream`s to pass into the simulation to repeat an experiment
343/// with different random events.
344///
345/// ```
346/// # use odem_rs_util::random::{DefaultRng, RngStream};
347/// # fn run_sim(s: RngStream) -> usize { 0 }
348/// let sum: usize = RngStream::iter()
349/// .take(100)
350/// .map(|rng_stream| run_sim(rng_stream))
351/// .sum();
352/// ```
353pub struct RngStreamIter<R: RngGen>(RngStream<R>);
354
355impl<R: RngGen> Iterator for RngStreamIter<R> {
356 type Item = RngStream<R>;
357
358 fn next(&mut self) -> Option<Self::Item> {
359 Some(RngStream::new(R::split_at(self.0.0.get_mut(), 1)))
360 }
361
362 fn nth(&mut self, n: usize) -> Option<Self::Item> {
363 let _left_stream = R::split_at(self.0.0.get_mut(), n);
364 self.next()
365 }
366}
367
368impl<R: RngGen> Clone for RngStreamIter<R>
369where
370 R::State: Clone,
371{
372 fn clone(&self) -> Self {
373 RngStreamIter(self.0.clone())
374 }
375}
376
377/* ************************************************* Built-In Implementations */
378
379// Xoshiro-family of PRNGs
380#[cfg(feature = "rand_xoshiro")]
381macro_rules! impl_stream_xoshiro {
382 ($($UID:ident),* $(,)?) => {$(
383 #[cfg_attr(docsrs, doc(cfg(feature = "rand_xoshiro")))]
384 impl RngGen for $UID {
385 type State = $UID;
386
387 fn default_state() -> $UID {
388 use rand_xoshiro::rand_core::SeedableRng;
389 $UID::seed_from_u64(0)
390 }
391
392 fn next_rng(state: &mut $UID) -> $UID {
393 let res = state.clone();
394 state.jump();
395 res
396 }
397
398 fn split_at(state: &mut $UID, steps: usize) -> $UID {
399 let res = state.clone();
400
401 for _ in 0..steps {
402 state.long_jump();
403 }
404
405 res
406 }
407 }
408 )*};
409}
410
411#[cfg(feature = "rand_xoshiro")]
412impl_stream_xoshiro!(
413 Xoroshiro128Plus,
414 Xoroshiro128PlusPlus,
415 Xoroshiro128StarStar,
416 // Xoshiro128Plus, // doesn't support long_jump
417 Xoshiro128PlusPlus,
418 Xoshiro128StarStar,
419 Xoshiro256Plus,
420 Xoshiro256PlusPlus,
421 Xoshiro256StarStar,
422 Xoshiro512Plus,
423 Xoshiro512PlusPlus,
424 Xoshiro512StarStar,
425);
426
427/// A constant related to the golden ratio, used for incrementing 128-bit PCG
428/// sequence numbers.
429///
430/// This value is `floor((φ - 1) * 2^128)` made odd, where φ is the golden
431/// ratio. Using an increment based on an irrational number ensures that
432/// successive sequence numbers are well-distributed, avoiding simple
433/// arithmetic patterns. This contributes to the statistical independence
434/// of the generated streams. The value must be odd to ensure that it is
435/// coprime to 2^128, which guarantees that repeated additions will visit
436/// every possible sequence number before repeating (a full period).
437#[cfg(feature = "rand_pcg")]
438const PHI_MINUS_ONE_128: u128 = 210306068529402873165736369884012333109;
439
440/// A constant related to the golden ratio, used for incrementing 64-bit PCG
441/// sequence numbers.
442///
443/// This is the 64-bit equivalent of `PHI_MINUS_ONE_128`. See that constant's
444/// documentation for a detailed explanation of the principle.
445#[cfg(feature = "rand_pcg")]
446const PHI_MINUS_ONE_64: u64 = 11400714819323198485;
447
448// PCG-family of PRNGs
449#[cfg(feature = "rand_pcg")]
450#[cfg_attr(docsrs, doc(cfg(feature = "rand_pcg")))]
451impl RngGen for Pcg32 {
452 type State = (u64, u64);
453
454 fn default_state() -> Self::State {
455 (0xcafef00dd15ea5e5, 0xa02bdbf7bb3c0a7)
456 }
457
458 fn next_rng((state, seq): &mut Self::State) -> Self {
459 let mut res = Pcg32::new(*state, *seq);
460
461 // Advance the state significantly. For PCG variants with weaker output
462 // functions, creating streams with very close sequence numbers can lead
463 // to initial correlations. Advancing the state mitigates this.
464 res.advance(1u64 << 32);
465
466 // increase the sequence number
467 *seq = seq.wrapping_add(PHI_MINUS_ONE_64);
468
469 res
470 }
471
472 fn split_at(state: &mut Self::State, steps: usize) -> Self::State {
473 let res = *state;
474 state.1 = state
475 .1
476 .wrapping_add((steps as u64).wrapping_mul(PHI_MINUS_ONE_64));
477 res
478 }
479}
480
481#[cfg(feature = "rand_pcg")]
482#[cfg_attr(docsrs, doc(cfg(feature = "rand_pcg")))]
483impl RngGen for Pcg64 {
484 type State = (u128, u128);
485
486 fn default_state() -> Self::State {
487 (0xcafef00dd15ea5e5, 0xa02bdbf7bb3c0a7ac28fa16a64abf96)
488 }
489
490 fn next_rng((state, seq): &mut Self::State) -> Self {
491 let mut res = Self::new(*state, *seq);
492
493 // Advance the state significantly. For PCG variants with weaker output
494 // functions, creating streams with very close sequence numbers can lead
495 // to initial correlations. Advancing the state mitigates this.
496 res.advance(1u128 << 64);
497
498 // increase the sequence number
499 *seq = seq.wrapping_add(PHI_MINUS_ONE_128);
500
501 res
502 }
503
504 fn split_at(state: &mut Self::State, steps: usize) -> Self::State {
505 let res = *state;
506 state.1 = state
507 .1
508 .wrapping_add((steps as u128).wrapping_mul(PHI_MINUS_ONE_128));
509 res
510 }
511}
512
513#[cfg(feature = "rand_pcg")]
514#[cfg_attr(docsrs, doc(cfg(feature = "rand_pcg")))]
515impl RngGen for Pcg64Mcg {
516 type State = u128;
517
518 fn default_state() -> Self::State {
519 0xcafef00dd15ea5e5
520 }
521
522 fn next_rng(state: &mut Self::State) -> Self {
523 let mut res = Self::new(*state);
524
525 // Advance the state significantly. For PCG variants with weaker output
526 // functions, creating streams with very close sequence numbers can lead
527 // to initial correlations. Advancing the state mitigates this.
528 res.advance(1u128 << 64);
529
530 // increase the sequence number
531 *state = state.wrapping_add(PHI_MINUS_ONE_128);
532
533 res
534 }
535
536 fn split_at(state: &mut Self::State, steps: usize) -> Self::State {
537 let res = *state;
538 *state = state.wrapping_add((steps as u128).wrapping_mul(PHI_MINUS_ONE_128));
539 res
540 }
541}
542
543#[cfg(feature = "rand_pcg")]
544#[cfg_attr(docsrs, doc(cfg(feature = "rand_pcg")))]
545impl RngGen for Pcg64Dxsm {
546 type State = (u128, u128);
547
548 fn default_state() -> Self::State {
549 (0xcafef00dd15ea5e5, 0xa02bdbf7bb3c0a7ac28fa16a64abf96)
550 }
551
552 fn next_rng((state, seq): &mut Self::State) -> Self {
553 let res = Self::new(*state, *seq);
554
555 // This specific PRNG ensures practical statistical independence through
556 // increased complexity of its output function and so does not benefit
557 // from the mitigation strategy employed for the other variants.
558
559 // increase the sequence number
560 *seq = seq.wrapping_add(PHI_MINUS_ONE_128);
561
562 res
563 }
564
565 fn split_at(state: &mut Self::State, steps: usize) -> Self::State {
566 let res = *state;
567 state.1 = state
568 .1
569 .wrapping_add((steps as u128).wrapping_mul(PHI_MINUS_ONE_128));
570 res
571 }
572}