Skip to content

Commit 3841dc8

Browse files
committed
Implement sampling from the unit sphere
This uses a method by Marsaglia (1972), which was found to be the fastest of the methods given by [MathWorld]. [MathWorld]: http://mathworld.wolfram.com/SpherePointPicking.html
1 parent ba3d273 commit 3841dc8

File tree

2 files changed

+97
-1
lines changed

2 files changed

+97
-1
lines changed

src/distributions/mod.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
//! - [`FisherF`] distribution
101101
//! - Multivariate probability distributions
102102
//! - [`Dirichlet`] distribution
103+
//! - [`UnitSphereSurface`] distribution
103104
//!
104105
//! # Examples
105106
//!
@@ -169,6 +170,7 @@
169170
//! [`Uniform`]: struct.Uniform.html
170171
//! [`Uniform::new`]: struct.Uniform.html#method.new
171172
//! [`Uniform::new_inclusive`]: struct.Uniform.html#method.new_inclusive
173+
//! [`UnitSphereSurface`]: struct.UnitSphereSurface.html
172174
//! [`WeightedIndex`]: struct.WeightedIndex.html
173175
174176
use Rng;
@@ -178,6 +180,7 @@ pub use self::other::Alphanumeric;
178180
pub use self::float::{OpenClosed01, Open01};
179181
pub use self::bernoulli::Bernoulli;
180182
#[cfg(feature="alloc")] pub use self::weighted::{WeightedIndex, WeightedError};
183+
#[cfg(feature="std")] pub use self::unit_sphere::UnitSphereSurface;
181184
#[cfg(feature="std")] pub use self::gamma::{Gamma, ChiSquared, FisherF, StudentT};
182185
#[cfg(feature="std")] pub use self::normal::{Normal, LogNormal, StandardNormal};
183186
#[cfg(feature="std")] pub use self::exponential::{Exp, Exp1};
@@ -190,6 +193,7 @@ pub use self::bernoulli::Bernoulli;
190193
pub mod uniform;
191194
mod bernoulli;
192195
#[cfg(feature="alloc")] mod weighted;
196+
#[cfg(feature="std")] mod unit_sphere;
193197
#[cfg(feature="std")] mod gamma;
194198
#[cfg(feature="std")] mod normal;
195199
#[cfg(feature="std")] mod exponential;
@@ -578,7 +582,7 @@ mod tests {
578582
Weighted { weight: x, item: 2 },
579583
Weighted { weight: 1, item: 3 }]);
580584
}
581-
585+
582586
#[cfg(feature="std")]
583587
#[test]
584588
fn test_distributions_iter() {

src/distributions/unit_sphere.rs

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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

Comments
 (0)