Skip to main content

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}