Skip to main content

odem_rs_util/random/
rayon_support.rs

1//! Contains the `IndexedParallelIterator` implementation for `RngStream`.
2//!
3//! This module provides [`RngStreamParIter`], which is created by the
4//! [`RngStream::par_iter`] method.
5
6use super::{RngGen, RngStream};
7use rayon::iter::{
8	IndexedParallelIterator, IntoParallelIterator, ParallelIterator,
9	plumbing::{Consumer, Producer, ProducerCallback, UnindexedConsumer, bridge},
10};
11
12/// An infinite parallel iterator that yields independent `RngStream`s.
13///
14/// This iterator is the primary mechanism for running multiple simulations in
15/// parallel. It is an `IndexedParallelIterator`, which allows for more
16/// efficient work splitting by Rayon and enables adapters like `zip` and
17/// `enumerate`.
18///
19/// It works by maintaining a single `RngStream` and dynamically splitting its
20/// state as Rayon requests chunks of work, ensuring perfect independence and
21/// reproducibility with minimal overhead.
22///
23/// It is created by calling [`RngStream::par_iter`] and should be chained with
24/// [`take(n)`](IndexedParallelIterator::take) to limit the number of runs.
25///
26/// # Usage
27///
28/// Use it during the set-up of a simulation run to generate a sequence of
29/// independent `RngStream`s to pass into the simulation to repeat a set of
30/// experiments in parallel.
31///
32/// ```no_run
33/// # use rand::Rng;
34/// # use odem_rs_util::random::{DefaultRng, RngStream};
35/// # fn run_sim(s: RngStream) -> usize { 0 }
36/// use rayon::prelude::*;
37/// let sum: usize = RngStream::<DefaultRng>::par_iter()
38///     .take(100)
39///     .map(|rng_stream| run_sim(rng_stream))
40///     .sum();
41/// ```
42pub struct RngStreamParIter<R: RngGen> {
43	stream: RngStream<R>,
44	/// The number of streams this iterator will produce.
45	/// Starts at `usize::MAX` to be effectively infinite until limited by
46	/// `take()`.
47	reruns: usize,
48}
49
50impl<R: RngGen> RngStreamParIter<R> {
51	/// Creates a new, effectively infinite parallel iterator.
52	pub(super) fn new(stream: RngStream<R>) -> Self {
53		Self {
54			stream,
55			reruns: usize::MAX,
56		}
57	}
58}
59
60impl<R> ParallelIterator for RngStreamParIter<R>
61where
62	R: RngGen + Send,
63	R::State: Send,
64{
65	type Item = RngStream<R>;
66
67	fn drive_unindexed<C>(self, consumer: C) -> C::Result
68	where
69		C: UnindexedConsumer<Self::Item>,
70	{
71		bridge(self, consumer)
72	}
73
74	fn opt_len(&self) -> Option<usize> {
75		Some(self.reruns)
76	}
77}
78
79impl<R> IndexedParallelIterator for RngStreamParIter<R>
80where
81	R: RngGen + Send,
82	R::State: Send,
83{
84	fn len(&self) -> usize {
85		self.reruns
86	}
87
88	fn drive<C: Consumer<Self::Item>>(self, consumer: C) -> C::Result {
89		bridge(self, consumer)
90	}
91
92	fn with_producer<CB>(self, callback: CB) -> CB::Output
93	where
94		CB: ProducerCallback<Self::Item>,
95	{
96		let producer = StreamProducer(self);
97		callback.callback(producer)
98	}
99}
100
101impl<R> IntoParallelIterator for RngStream<R>
102where
103	R: RngGen + Send,
104	R::State: Send,
105{
106	type Iter = RngStreamParIter<R>;
107	type Item = RngStream<R>;
108
109	fn into_par_iter(self) -> Self::Iter {
110		RngStreamParIter::new(self)
111	}
112}
113
114impl<R: RngGen> Clone for RngStreamParIter<R>
115where
116	R::State: Clone,
117{
118	fn clone(&self) -> Self {
119		Self {
120			stream: self.stream.clone(),
121			reruns: self.reruns,
122		}
123	}
124}
125
126/// The stateful producer for our `RngStream` indexed parallel iterator.
127///
128/// This producer implements Rayon's split-on-demand logic. When `split_at`
129/// is called, it uses `RngGen::split_at` to carve off a new, independent
130/// `RngStream` for the left-hand producer, while the right-hand producer
131/// continues with the advanced state.
132struct StreamProducer<R: RngGen>(RngStreamParIter<R>);
133
134impl<R: RngGen + Send> Producer for StreamProducer<R>
135where
136	R::State: Send,
137{
138	type Item = RngStream<R>;
139	type IntoIter = StreamIter<R>;
140
141	fn into_iter(self) -> Self::IntoIter {
142		StreamIter(self.0)
143	}
144
145	fn split_at(mut self, index: usize) -> (Self, Self) {
146		// Carve out a new stream for the left side of the split.
147		let left_stream_state = R::split_at(self.0.stream.0.get_mut(), index);
148
149		let left_iter = RngStreamParIter {
150			stream: RngStream::new(left_stream_state),
151			reruns: index,
152		};
153
154		// The right side (self) keeps the remaining runs.
155		self.0.reruns -= index;
156
157		(Self(left_iter), self)
158	}
159}
160
161/// The concrete `Iterator` created by the `StreamProducer` for sequential
162/// consumption.
163struct StreamIter<R: RngGen>(RngStreamParIter<R>);
164
165impl<R: RngGen> Iterator for StreamIter<R> {
166	type Item = RngStream<R>;
167
168	fn next(&mut self) -> Option<Self::Item> {
169		if self.0.reruns > 0 {
170			self.0.reruns -= 1;
171
172			// Produce the new stream by splitting off from the iterator state.
173			Some(RngStream::new(R::split_at(self.0.stream.0.get_mut(), 1)))
174		} else {
175			None
176		}
177	}
178
179	fn size_hint(&self) -> (usize, Option<usize>) {
180		(self.0.reruns, Some(self.0.reruns))
181	}
182
183	fn nth(&mut self, n: usize) -> Option<Self::Item> {
184		if self.0.reruns > n {
185			self.0.reruns -= n + 1;
186
187			// Produce the new stream by splitting off from the iterator state.
188			let _left_state = R::split_at(self.0.stream.0.get_mut(), n);
189			Some(RngStream::new(R::split_at(self.0.stream.0.get_mut(), 1)))
190		} else {
191			self.0.reruns = 0;
192			None
193		}
194	}
195}
196
197impl<R: RngGen> DoubleEndedIterator for StreamIter<R> {
198	fn next_back(&mut self) -> Option<Self::Item> {
199		use core::mem::replace;
200		if self.0.reruns == 0 {
201			return None;
202		}
203
204		// To get the *last* item, we must split off the first `n-1` items,
205		// leaving the original stream in the desired final state.
206		let new_state = self.0.stream.0.get_mut();
207		let old_state = R::split_at(new_state, self.0.reruns - 1);
208
209		// The state we want is what `new_state` has become. We must swap it
210		// with the `old_state` we just carved off to restore the parent stream.
211		let result_state = replace(new_state, old_state);
212
213		self.0.reruns -= 1;
214		Some(RngStream::new(result_state))
215	}
216}
217
218impl<R: RngGen> ExactSizeIterator for StreamIter<R> {}