rustic_zen/
sampler.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Wrappers for stochastically sampled variables.
6
7use crate::geom::Point;
8use crate::spectrum::blackbody_wavelength;
9
10use rand::prelude::*;
11use rand_distr::num_traits::{Float, FromPrimitive};
12use rand_distr::uniform::SampleUniform;
13use rand_distr::Distribution;
14use rand_distr::{Normal, StandardNormal};
15
16use std::sync::Arc;
17
18/// Wrapper for a bounded stocastically sampled value
19///
20/// a `Sampler` encloses the distribution function and bounds for a random
21/// variable of type `T`, such that is can be sampled with an external random
22/// number generator of type `R`
23///
24/// Several constructors are provided for common distributions of values, however
25/// if a custom one is needed it can be provided as a closure and set of bounds to
26/// `Sampler::from_fn`
27#[derive(Clone)]
28pub struct Sampler<T: Copy, R: Rng> {
29    sample: Arc<dyn Fn(&mut R) -> T + Send + Sync>,
30    bounds: (T, T),
31}
32
33impl<T, R> Sampler<T, R>
34where
35    R: Rng,
36    T: Copy + Send + Sync + 'static,
37{
38    /// Creates a new `Sampler` with a constant distibution.
39    ///
40    /// When sampled the `Sampler` will always return `c`
41    pub fn new_const<A>(c: A) -> Self
42    where
43        A: Into<T>,
44    {
45        let c = c.into();
46        Self {
47            sample: Arc::new(move |_| c),
48            bounds: (c, c),
49        }
50    }
51}
52
53impl<T, R> Sampler<T, R>
54where
55    R: Rng,
56    T: Copy + Float + SampleUniform + Send + Sync + 'static,
57{
58    /// Creates a new `Sampler` with a uniform distibution.
59    ///
60    /// When sampled the `Sampler` will always return a value between `a` and `b` (inclusive)
61    pub fn new_range<A, B>(a: A, b: B) -> Self
62    where
63        A: Into<T>,
64        B: Into<T>,
65    {
66        let a = a.into();
67        let b = b.into();
68        if a == b {
69            return Self::new_const(a);
70        }
71        let lower = T::min(a, b);
72        let upper = T::max(a, b);
73        Self {
74            sample: Arc::new(move |r| r.gen_range(lower..=upper)),
75            bounds: (lower, upper),
76        }
77    }
78}
79
80impl<T, R> Sampler<T, R>
81where
82    R: Rng,
83    T: Copy + Float + From<f32> + Send + Sync + 'static,
84    StandardNormal: Distribution<T>,
85{
86    /// Creates a new `Sampler` with a normal (gaussian) distibution.
87    ///
88    /// Real gaussian's have infinate bounds, this can cause problems in the raytracing engine, so
89    /// this implementation wraps at 3 standard deviations.
90    ///
91    /// When sampled the `Sampler` will always return a value between `mean - 3 * std_dev` and `mean + 3 * std_dev` (inclusive)
92    pub fn new_gaussian<A, B>(mean: A, std_dev: B) -> Self
93    where
94        A: Copy + Into<T>,
95        B: Copy + Into<T>,
96    {
97        let width: T = std_dev.into() * 3.0.into();
98        let mean = mean.into();
99        let g = Normal::new(0.0.into(), std_dev.into()).unwrap();
100        Self {
101            sample: Arc::new(move |r| (g.sample(r) % width) + mean),
102            bounds: (mean - width, mean + width),
103        }
104    }
105}
106
107impl<T, R> Sampler<T, R>
108where
109    R: Rng,
110    T: Copy + Float + SampleUniform + FromPrimitive + From<f32> + Send + Sync + 'static,
111{
112    /// Creates a new `Sampler` with a blackbody radiation curve distibution.
113    ///
114    /// This is only really useful for creating realistic white lights of a given colour temprature, or stars.
115    ///
116    /// This sampler should be used carefully; it can lead to unexpected results when used on a value other than `Light.wavelength`.
117    /// It is also __substancially__ slower than the other `Sampler` types. When Sampled it will return a value between
118    /// 0 and `T::MAX` (probably larger than you want) This can cause all sorts of weird problems for collision detection, so _please_
119    /// don't use this on a `SamplerPoint`
120    pub fn new_blackbody<A>(temperature: A) -> Self
121    where
122        A: Into<T>,
123    {
124        let t = temperature.into();
125        Self {
126            sample: Arc::new(move |r| blackbody_wavelength(t, r.gen_range(0.0.into()..1.0.into()))),
127            bounds: (0.0.into(), T::max_value()),
128        }
129    }
130}
131
132impl<T, R> Sampler<T, R>
133where
134    T: Copy,
135    R: Rng,
136{
137    /// Creates a new `Sampler` with a distribution dictated by `f`
138    ///
139    /// `f` must always return a value within the bounds specified by `lower` & `upper`
140    ///
141    /// This is very low level access to how the renderer works; you're in control,
142    /// if you break it it's your fault!
143    pub fn from_fn(f: Arc<dyn Fn(&mut R) -> T + Send + Sync>, lower: T, upper: T) -> Self {
144        Self {
145            sample: f,
146            bounds: (lower, upper),
147        }
148    }
149
150    /// Get a sampled value from this `Sampler` using random number generator `rng`
151    #[inline(always)]
152    pub fn sample(&self, rng: &mut R) -> T {
153        (self.sample)(rng)
154    }
155
156    /// Get the bounds of a possible sampled value (useful for spacial partitioning and limit checks etc)
157    #[inline(always)]
158    pub fn bounds(&self) -> (T, T) {
159        self.bounds
160    }
161}
162
163/// A single value of type `T` will be interpreted as a constant distribution if turned into a sampler.
164///
165/// # Example:
166/// ```
167/// extern crate rand;
168///
169/// use rustic_zen::sampler::Sampler;
170/// use rand::prelude::*;
171/// let mut r = rand::thread_rng();
172///
173/// let s: Sampler<f64, ThreadRng> = 5.0.into();
174///
175/// assert_eq!(s.sample(&mut r), 5.0); // will always be true
176/// ```
177impl<T, R> From<T> for Sampler<T, R>
178where
179    T: Copy + From<T> + Send + Sync + 'static,
180    R: Rng,
181{
182    fn from(value: T) -> Self {
183        Self::new_const(value)
184    }
185}
186
187/// A tuple of type `(T,T)` will be interpreted as a uniform distribution if turned into a sampler.
188///
189/// # Example:
190/// ```
191/// extern crate rand;
192///
193/// use rustic_zen::sampler::Sampler;
194/// use rand::prelude::*;
195/// let mut r = rand::thread_rng();
196///
197/// let s: Sampler<f64, ThreadRng> = (5.0,10.0).into();
198///
199/// assert!(s.sample(&mut r) >= 5.0); // will always be true
200/// assert!(s.sample(&mut r) <= 10.0); // will always be true
201/// ```
202impl<T, R> From<(T, T)> for Sampler<T, R>
203where
204    T: Copy + Float + SampleUniform + Send + Sync + From<f32> + 'static,
205    R: Rng,
206{
207    fn from(value: (T, T)) -> Self {
208        Self::new_range(value.0, value.1)
209    }
210}
211
212/// Wrapper around two `Sampler`s to make a Stochasically sampled point.
213#[derive(Clone)]
214pub struct SamplerPoint<R: Rng> {
215    x: Sampler<f64, R>,
216    y: Sampler<f64, R>,
217}
218
219/// A tuple of type `(Sampler::<T>,Sampler<T>)` will be interpreted as a uniform distribution if turned into a sampler.
220///
221/// # Example:
222/// ```
223/// extern crate rand;
224///
225/// use rustic_zen::sampler::SamplerPoint;
226/// use rustic_zen::sampler::Sampler;
227/// use rand::prelude::*;
228/// let mut r = rand::thread_rng();
229///
230/// // results in a rounds soft distribution, useful for area lights.
231/// let _p: SamplerPoint<ThreadRng> = (Sampler::new_gaussian(0.0, 3.0), Sampler::new_gaussian(0.0, 3.0)).into();
232///
233/// ```
234///
235/// This is the only way to construct a `SamplerPoint`. This shorthand can be extended using the `Sampler`'s into traits:
236/// ```
237/// extern crate rand;
238///
239/// use rustic_zen::sampler::SamplerPoint;
240/// use rustic_zen::geom::Point;
241/// use rand::prelude::*;
242/// let mut r = rand::thread_rng();
243///
244/// // results in a fixed point at 0.0, 0.0
245/// let _p: SamplerPoint<ThreadRng> = (0.0, 0.0).into();
246///
247/// // results in a uniformly distributed square from 0.0, 0.0 to 10.0, 10.0
248/// let _p: SamplerPoint<ThreadRng> = ((0.0, 10.0), (0.0, 10.0)).into();
249/// ```
250impl<R> From<Point> for SamplerPoint<R>
251where
252    R: Rng,
253{
254    fn from(value: Point) -> Self {
255        Self {
256            x: value.x.into(),
257            y: value.y.into(),
258        }
259    }
260}
261
262impl<A, B, R> From<(A, B)> for SamplerPoint<R>
263where
264    A: Into<Sampler<f64, R>>,
265    B: Into<Sampler<f64, R>>,
266    R: Rng,
267{
268    fn from(value: (A, B)) -> Self {
269        Self {
270            x: value.0.into(),
271            y: value.1.into(),
272        }
273    }
274}
275
276impl<R> SamplerPoint<R>
277where
278    R: Rng,
279{
280    #[inline(always)]
281    pub(crate) fn get(&self, rng: &mut R) -> Point {
282        Point {
283            x: self.x.sample(rng),
284            y: self.y.sample(rng),
285        }
286    }
287}
288
289#[cfg(test)]
290mod tests {
291    type RandGen = rand_pcg::Pcg64Mcg;
292    use rand::prelude::*;
293
294    use super::Sampler;
295
296    #[test]
297    fn const_bounds() {
298        // test bounds with 1000 random numbers
299        for _ in 0..1000 {
300            let mut stdrng = RandGen::from_entropy();
301            let f: f64 = stdrng.gen();
302            let s = Sampler::<f64, RandGen>::new_const(f);
303            let (a, b) = s.bounds();
304            assert_eq!(a, b);
305            assert_eq!(a, f);
306            assert_eq!(b, f);
307        }
308    }
309
310    #[test]
311    fn range_bounds() {
312        for _ in 0..1000 {
313            let mut stdrng = RandGen::from_entropy();
314            let a: f64 = stdrng.gen();
315            let b: f64 = stdrng.gen();
316            let s = Sampler::<f64, RandGen>::new_range(a, b);
317
318            let (c, d) = s.bounds();
319            assert_eq!(a.min(b), c);
320            assert_eq!(a.max(b), d);
321        }
322    }
323
324    #[test]
325    fn val_const() {
326        let mut rng = RandGen::from_entropy();
327
328        let mut stdrng = RandGen::from_entropy();
329        let f: f64 = stdrng.gen();
330        let s = Sampler::new_const(f);
331
332        for _ in 0..1000 {
333            let y: f64 = s.sample(&mut rng);
334            assert_eq!(y, f);
335        }
336    }
337
338    #[test]
339    fn val_range() {
340        let mut rng = RandGen::from_entropy();
341
342        let mut stdrng = RandGen::from_entropy();
343        let mut f1: f64 = stdrng.gen();
344        let mut f2: f64 = stdrng.gen();
345        if f1 < f2 {
346            let tmp = f1;
347            f1 = f2;
348            f2 = tmp;
349        }
350        let s = Sampler::new_range(f1, f2);
351
352        // This does actually run 100,000 times despite finishing so quickly
353        for _ in 0..100000 {
354            let y: f64 = s.sample(&mut rng);
355            assert!(y <= f1);
356            assert!(y >= f2);
357        }
358    }
359
360    #[test]
361    fn blackbody_works() {
362        let mut rng = RandGen::from_entropy();
363        // Get a sampled value from the range of valid wavelenghts
364        let w: Sampler<f64, rand_pcg::Mcg128Xsl64> = Sampler::new_range(780.0, 360.0);
365
366        // create sample with random wavelenght
367        let s = Sampler::new_blackbody(w.sample(&mut rng));
368
369        // Check s can be sampled without panicing
370        let _: f64 = s.sample(&mut rng);
371    }
372
373    #[test]
374    fn blackbody_white_light_64() {
375        let mut rng = RandGen::from_entropy();
376        let s = Sampler::new_blackbody(0.0);
377        // Check s can be sampled without panicing
378        let _: f64 = s.sample(&mut rng);
379    }
380
381    #[test]
382    fn blackbody_white_light_32() {
383        let mut rng = RandGen::from_entropy();
384        let s = Sampler::new_blackbody(0.0);
385        // Check s can be sampled without panicing
386        let _: f32 = s.sample(&mut rng);
387    }
388
389    #[test]
390    fn gaussian_at_zero() {
391        let mut rng = RandGen::from_entropy();
392        let s = Sampler::new_gaussian(0.0, 10.0);
393        // Check s can be sampled without panicing
394        let _: f64 = s.sample(&mut rng);
395    }
396
397    #[test]
398    fn range_big_first() {
399        let mut rng = RandGen::from_entropy();
400        let s = Sampler::new_range(10.0, 0.0);
401        // Check s can be sampled without panicing
402        let _: f64 = s.sample(&mut rng);
403    }
404
405    #[test]
406    fn range_small_first() {
407        let mut rng = RandGen::from_entropy();
408        let s = Sampler::new_range(0.0, 10.0);
409        // Check s can be sampled without panicing
410        let _: f64 = s.sample(&mut rng);
411    }
412}