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}