-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,11 @@ | |
//! [`Standard`]: struct.Standard.html | ||
|
||
use Rng; | ||
#[cfg(feature = "rayon")] | ||
use {RngCore, SeedableRng}; | ||
#[cfg(feature = "rayon")] | ||
use rayon::iter::plumbing::{Consumer, Producer, ProducerCallback, UnindexedConsumer, bridge}; | ||
use rayon::iter::{ParallelIterator, IndexedParallelIterator}; | ||
|
||
pub use self::other::Alphanumeric; | ||
pub use self::range::Range; | ||
|
@@ -169,17 +174,39 @@ pub trait Distribution<T> { | |
/// println!("Not a 6; rolling again!"); | ||
/// } | ||
/// ``` | ||
fn sample_iter<'a, R: Rng>(&'a self, rng: &'a mut R) | ||
-> DistIter<'a, Self, R, T> where Self: Sized | ||
fn sample_iter<'a, R>(&'a self, rng: &'a mut R) -> DistIter<'a, Self, R, T> | ||
where Self: Sized, R: Rng | ||
{ | ||
DistIter { | ||
distr: self, | ||
rng: rng, | ||
phantom: ::core::marker::PhantomData, | ||
} | ||
} | ||
|
||
/// Create a parallel iterator. | ||
#[cfg(feature = "rayon")] | ||
fn sample_par_iter<'a, R>(&'a self, rng: &mut R, amount: usize) | ||
-> ParallelDistIter<'a, Self, R, T> | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
pitdicker
Author
Owner
|
||
where Self: Sized, | ||
R: Rng + SeedableRng, | ||
{ | ||
ParallelDistIter { | ||
distr: self, | ||
rng: R::from_rng(rng).unwrap(), | ||
amount, | ||
phantom: ::core::marker::PhantomData, | ||
} | ||
} | ||
} | ||
|
||
impl<'a, T, D: Distribution<T>> Distribution<T> for &'a D { | ||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> T { | ||
(*self).sample(rng) | ||
} | ||
} | ||
|
||
|
||
/// An iterator that generates random values of `T` with distribution `D`, | ||
/// using `R` as the source of randomness. | ||
/// | ||
|
@@ -189,7 +216,7 @@ pub trait Distribution<T> { | |
/// [`Distribution`]: trait.Distribution.html | ||
/// [`sample_iter`]: trait.Distribution.html#method.sample_iter | ||
#[derive(Debug)] | ||
pub struct DistIter<'a, D, R, T> where D: Distribution<T> + 'a, R: Rng + 'a { | ||
pub struct DistIter<'a, D: 'a, R: 'a, T> { | ||
distr: &'a D, | ||
rng: &'a mut R, | ||
phantom: ::core::marker::PhantomData<T>, | ||
|
@@ -206,12 +233,187 @@ impl<'a, D, R, T> Iterator for DistIter<'a, D, R, T> | |
} | ||
} | ||
|
||
impl<'a, T, D: Distribution<T>> Distribution<T> for &'a D { | ||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> T { | ||
(*self).sample(rng) | ||
|
||
/// An iterator that generates random values of `T` with distribution `D`, | ||
/// using `R` as the source of randomness. | ||
/// | ||
/// This `struct` is created by the [`par_sample_iter`] method on | ||
/// [`Distribution`]. See its documentation for more. | ||
/// | ||
/// [`Distribution`]: trait.Distribution.html | ||
/// [`sample_iter`]: trait.Distribution.html#method.sample_iter | ||
#[cfg(feature = "rayon")] | ||
#[derive(Debug)] | ||
pub struct ParallelDistIter<'a, D: 'a, R, T> { | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong. |
||
distr: &'a D, | ||
rng: R, | ||
amount: usize, | ||
phantom: ::core::marker::PhantomData<T>, | ||
} | ||
|
||
#[cfg(feature = "rayon")] | ||
impl<'a, D, R, T> ParallelIterator for ParallelDistIter<'a, D, R, T> | ||
where D: Distribution<T> + Send + Sync, | ||
R: RngCore + SeedableRng + Send, | ||
T: Send, | ||
{ | ||
type Item = T; | ||
|
||
fn drive_unindexed<C>(self, consumer: C) -> C::Result | ||
where C: UnindexedConsumer<Self::Item> | ||
{ | ||
bridge(self, consumer) | ||
} | ||
|
||
fn opt_len(&self) -> Option<usize> { | ||
Some(self.amount) | ||
} | ||
} | ||
|
||
#[cfg(feature = "rayon")] | ||
impl<'a, D, R, T> IndexedParallelIterator for ParallelDistIter<'a, D, R, T> | ||
where D: Distribution<T> + Send + Sync, | ||
R: RngCore + SeedableRng + Send, | ||
T: Send, | ||
{ | ||
fn drive<C>(self, consumer: C) -> C::Result | ||
where C: Consumer<Self::Item> | ||
{ | ||
bridge(self, consumer) | ||
} | ||
|
||
fn len(&self) -> usize { | ||
self.amount | ||
} | ||
|
||
fn with_producer<CB>(self, callback: CB) -> CB::Output | ||
where CB: ProducerCallback<Self::Item> | ||
{ | ||
callback.callback( | ||
DistProducer { | ||
distr: self.distr.clone(), | ||
amount: self.amount, | ||
rng: self.rng, | ||
phantom: ::core::marker::PhantomData, | ||
} | ||
) | ||
} | ||
} | ||
|
||
/// FIXME | ||
#[cfg(feature = "rayon")] | ||
#[derive(Debug)] | ||
pub struct DistProducer<'a, D: 'a, R, T> { | ||
distr: &'a D, | ||
rng: R, | ||
amount: usize, | ||
phantom: ::core::marker::PhantomData<T>, | ||
} | ||
|
||
/// This method is intented to be used by fast and relatively simple PRNGs used | ||
/// for simulations etc. While it will also work with cryptographic RNGs, that | ||
/// is not optimal. | ||
/// | ||
/// Every time `rayon` splits the work in two to create parallel tasks, one new | ||
/// PRNG is created. The original PRNG is used to seed the new one using | ||
/// `SeedableRng::from_rng`. **Important**: Not all RNG algorithms support this! | ||
/// Notably the low-quality plain Xorshift, the current default for `SmallRng`, | ||
/// will simply clone itself using this method instead of seeding the split off | ||
/// RNG well. Consider using something like PCG or Xoroshiro128+. | ||
/// | ||
/// It is hard to predict what will happen to the statistical quality of PRNGs | ||
/// when they are split off many times, and only very short runs are used. We | ||
/// limit the minimum number of items that should be used of the PRNG to at | ||
/// least 100 to hopefully keep similar statistical properties as one PRNG used | ||
/// continuously. | ||
#[cfg(feature = "rayon")] | ||
impl<'a, D, R, T> Producer for DistProducer<'a, D, R, T> | ||
where D: Distribution<T> + Send + Sync, | ||
R: RngCore + SeedableRng + Send, | ||
T: Send, | ||
{ | ||
type Item = T; | ||
type IntoIter = BoundedDistIter<'a, D, R, T>; | ||
fn into_iter(self) -> Self::IntoIter { | ||
BoundedDistIter { | ||
distr: self.distr, | ||
amount: self.amount, | ||
rng: self.rng, | ||
phantom: ::core::marker::PhantomData, | ||
} | ||
} | ||
|
||
fn split_at(mut self, index: usize) -> (Self, Self) { | ||
assert!(index <= self.amount); | ||
// Create a new PRNG of the same type, by seeding it with this PRNG. | ||
// `from_rng` should never fail. | ||
let new = DistProducer { | ||
distr: self.distr.clone(), | ||
amount: self.amount - index, | ||
rng: R::from_rng(&mut self.rng).unwrap(), | ||
phantom: ::core::marker::PhantomData, | ||
}; | ||
self.amount = index; | ||
(self, new) | ||
} | ||
|
||
fn min_len(&self) -> usize { | ||
100 | ||
} | ||
} | ||
|
||
/// FIXME | ||
#[cfg(feature = "rayon")] | ||
#[derive(Debug)] | ||
pub struct BoundedDistIter<'a, D: 'a, R, T> { | ||
distr: &'a D, | ||
rng: R, | ||
amount: usize, | ||
phantom: ::core::marker::PhantomData<T>, | ||
} | ||
|
||
#[cfg(feature = "rayon")] | ||
impl<'a, D, R, T> Iterator for BoundedDistIter<'a, D, R, T> | ||
where D: Distribution<T>, R: Rng | ||
{ | ||
type Item = T; | ||
|
||
#[inline(always)] | ||
fn next(&mut self) -> Option<T> { | ||
if self.amount > 0 { | ||
self.amount -= 1; | ||
Some(self.distr.sample(&mut self.rng)) | ||
} else { | ||
None | ||
} | ||
} | ||
} | ||
|
||
#[cfg(feature = "rayon")] | ||
impl<'a, D, R, T> DoubleEndedIterator for BoundedDistIter<'a, D, R, T> | ||
where D: Distribution<T>, R: Rng | ||
{ | ||
#[inline(always)] | ||
fn next_back(&mut self) -> Option<T> { | ||
if self.amount > 0 { | ||
self.amount -= 1; | ||
Some(self.distr.sample(&mut self.rng)) | ||
} else { | ||
None | ||
} | ||
} | ||
} | ||
|
||
#[cfg(feature = "rayon")] | ||
impl<'a, D, R, T> ExactSizeIterator for BoundedDistIter<'a, D, R, T> | ||
where D: Distribution<T>, R: Rng | ||
{ | ||
fn len(&self) -> usize { | ||
self.amount | ||
} | ||
} | ||
|
||
|
||
/// A generic random value distribution. Generates values for various types | ||
/// with numerically uniform distribution. | ||
/// | ||
|
@@ -620,4 +822,18 @@ mod tests { | |
let results: Vec<_> = distr.sample_iter(&mut rng).take(100).collect(); | ||
println!("{:?}", results); | ||
} | ||
|
||
#[cfg(all(feature="std", feature="rayon"))] | ||
#[test] | ||
fn test_distributions_par_iter() { | ||
use distributions::Range; | ||
use rayon::iter::ParallelIterator; | ||
use NewRng; | ||
use prng::XorShiftRng; // *EXTREMELY* bad choice! | ||
let mut rng = XorShiftRng::new(); | ||
let range = Range::new(100, 200); | ||
let results: Vec<_> = range.sample_par_iter(&mut rng, 1000).collect(); | ||
println!("{:?}", results); | ||
panic!(); | ||
} | ||
} |
I see little reason to use the same RNG for the inner and outer RNGs here. The supplied source could simply be
thread_rng
but the inner one should be something simple and fast to initialise like Xorshift. Alternatively usingthread_rng()
in each thread will be a good option in some cases. Maybe we could add a dumb RNG type with no state which simply getsthread_rng()
on use so that this can be parameterised across different RNG types.Also, should we do
self.clone()
andSelf: Clone
here? Ifdistr
is the last field I don't think we needSelf: Sized
. And either way a useful trick for non-copy distrs may be(&distr).sample_par_iter(...)
since&distr
also implementsDistribution
; it would be good to document this (and note that output will not be reproducible).Finally why does this function depend on Rayon anyway?Sorry, I guess this won't do anything without Rayon. Pretty neat idea!