|
| 1 | +use Rng; |
| 2 | +use distributions::{Distribution, Uniform}; |
| 3 | + |
| 4 | +/// Samples uniformly from the surface of the unit sphere in three dimensions. |
| 5 | +/// |
| 6 | +/// Implemented via a method by Marsaglia[^1]. |
| 7 | +/// |
| 8 | +/// |
| 9 | +/// # Example |
| 10 | +/// |
| 11 | +/// ``` |
| 12 | +/// use rand::distributions::{UnitSphereSurface, Distribution}; |
| 13 | +/// |
| 14 | +/// let sphere = UnitSphereSurface::new(); |
| 15 | +/// let v = sphere.sample(&mut rand::thread_rng()); |
| 16 | +/// println!("{:?} is from the unit sphere surface.", v) |
| 17 | +/// ``` |
| 18 | +/// |
| 19 | +/// [^1]: Marsaglia, George (1972). [*Choosing a Point from the Surface of a |
| 20 | +/// Sphere.*](https://doi.org/10.1214/aoms/1177692644) |
| 21 | +/// Ann. Math. Statist. 43 (1972), no. 2, 645--646. |
| 22 | +#[derive(Clone, Copy, Debug)] |
| 23 | +pub struct UnitSphereSurface { |
| 24 | + uniform: Uniform<f64>, |
| 25 | +} |
| 26 | + |
| 27 | +impl UnitSphereSurface { |
| 28 | + /// Construct a new `UnitSphereSurface` distribution. |
| 29 | + #[inline] |
| 30 | + pub fn new() -> UnitSphereSurface { |
| 31 | + UnitSphereSurface { uniform: Uniform::new(-1., 1.) } |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +impl Distribution<[f64; 3]> for UnitSphereSurface { |
| 36 | + #[inline] |
| 37 | + fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> [f64; 3] { |
| 38 | + loop { |
| 39 | + let (x1, x2) = (self.uniform.sample(rng), self.uniform.sample(rng)); |
| 40 | + let sum = x1*x1 + x2*x2; |
| 41 | + if sum >= 1. { |
| 42 | + continue; |
| 43 | + } |
| 44 | + let factor = 2. * (1.0_f64 - sum).sqrt(); |
| 45 | + return [x1 * factor, x2 * factor, 1. - 2.*sum]; |
| 46 | + } |
| 47 | + } |
| 48 | +} |
| 49 | + |
| 50 | +#[cfg(test)] |
| 51 | +mod tests { |
| 52 | + use distributions::Distribution; |
| 53 | + use super::UnitSphereSurface; |
| 54 | + |
| 55 | + /// Assert that two numbers are almost equal to each other. |
| 56 | + /// |
| 57 | + /// On panic, this macro will print the values of the expressions with their |
| 58 | + /// debug representations. |
| 59 | + macro_rules! assert_almost_eq { |
| 60 | + ($a:expr, $b:expr, $prec:expr) => ( |
| 61 | + let diff = ($a - $b).abs(); |
| 62 | + if diff > $prec { |
| 63 | + panic!(format!( |
| 64 | + "assertion failed: `abs(left - right) = {:.1e} < {:e}`, \ |
| 65 | + (left: `{}`, right: `{}`)", |
| 66 | + diff, $prec, $a, $b)); |
| 67 | + } |
| 68 | + ); |
| 69 | + } |
| 70 | + |
| 71 | + #[test] |
| 72 | + fn norm() { |
| 73 | + let mut rng = ::test::rng(1); |
| 74 | + let dist = UnitSphereSurface::new(); |
| 75 | + for _ in 0..1000 { |
| 76 | + let x = dist.sample(&mut rng); |
| 77 | + assert_almost_eq!(x[0]*x[0] + x[1]*x[1] + x[2]*x[2], 1., 1e-15); |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + #[test] |
| 82 | + fn value_stability() { |
| 83 | + let mut rng = ::test::rng(2); |
| 84 | + let dist = UnitSphereSurface::new(); |
| 85 | + assert_eq!(dist.sample(&mut rng), |
| 86 | + [0.1660727273690104, 0.5202698793511903, 0.8376986939610902]); |
| 87 | + assert_eq!(dist.sample(&mut rng), |
| 88 | + [0.40052443371799834, 0.4237054996596154, -0.812436968356975]); |
| 89 | + assert_eq!(dist.sample(&mut rng), |
| 90 | + [0.9209910250970449, -0.32692477745072107, 0.21188610520628948]); |
| 91 | + } |
| 92 | +} |
0 commit comments