igraph/misc/sampling.rs
1//! Sampling random vectors (`igraph_sampling.h`).
2//!
3//! Each sampler exists as a free function drawing from the thread's default
4//! random number generator, and as a method of [`Rng`] drawing from an
5//! explicit generator. Samples are returned as one `Vec<f64>` per point.
6
7use super::columns_of;
8use crate::{
9 error::{Error, Result},
10 ffi::*,
11 igraph_call,
12 matrix::Matrix,
13 rng::Rng,
14 vector::Vector,
15};
16
17fn default_rng() -> *mut igraph_rng_t {
18 crate::error::ensure_init();
19 unsafe { igraph_rng_default() }
20}
21
22fn to_int(x: usize, what: &str) -> Result<igraph_int_t> {
23 igraph_int_t::try_from(x).map_err(|_| Error::invalid(format!("{what} is too large")))
24}
25
26fn check_radius(radius: f64) -> Result<()> {
27 if radius > 0.0 && radius.is_finite() {
28 Ok(())
29 } else {
30 Err(Error::invalid(format!(
31 "the sphere radius must be positive and finite, got {radius}"
32 )))
33 }
34}
35
36fn sphere_surface(
37 rng: *mut igraph_rng_t,
38 dim: usize,
39 n: usize,
40 radius: f64,
41 positive: bool,
42) -> Result<Vec<Vec<f64>>> {
43 check_radius(radius)?;
44 let mut res = Matrix::new();
45 igraph_call!(igraph_rng_sample_sphere_surface(
46 rng,
47 to_int(dim, "dimension")?,
48 to_int(n, "number of samples")?,
49 radius,
50 positive,
51 &mut res
52 ))?;
53 Ok(columns_of(&res))
54}
55
56fn sphere_volume(
57 rng: *mut igraph_rng_t,
58 dim: usize,
59 n: usize,
60 radius: f64,
61 positive: bool,
62) -> Result<Vec<Vec<f64>>> {
63 check_radius(radius)?;
64 let mut res = Matrix::new();
65 igraph_call!(igraph_rng_sample_sphere_volume(
66 rng,
67 to_int(dim, "dimension")?,
68 to_int(n, "number of samples")?,
69 radius,
70 positive,
71 &mut res
72 ))?;
73 Ok(columns_of(&res))
74}
75
76fn dirichlet(rng: *mut igraph_rng_t, n: usize, alpha: &[f64]) -> Result<Vec<Vec<f64>>> {
77 if !alpha.iter().all(|&a| a > 0.0 && a.is_finite()) {
78 return Err(Error::invalid(
79 "Dirichlet concentration parameters must be positive and finite",
80 ));
81 }
82 let alpha = Vector::view(alpha);
83 let mut res = Matrix::new();
84 igraph_call!(igraph_rng_sample_dirichlet(
85 rng,
86 to_int(n, "number of samples")?,
87 alpha.as_ptr(),
88 &mut res
89 ))?;
90 Ok(columns_of(&res))
91}
92
93/// Samples `n` points uniformly from the surface of the `dim`-dimensional
94/// sphere of the given `radius`, centered at the origin, using the thread's
95/// default random number generator.
96///
97/// With `positive` set, the points are restricted to the positive orthant
98/// (all coordinates non-negative). Each returned point has `dim`
99/// coordinates and Euclidean norm `radius`. With `positive` and
100/// `radius <= 1` they are valid latent positions for
101/// [`Graph::dot_product_game`](crate::Graph::dot_product_game) (put them in
102/// the *columns* of its matrix).
103///
104/// Binds [`igraph_rng_sample_sphere_surface`](https://igraph.org/c/html/latest/igraph-Nongraph.html#igraph_rng_sample_sphere_surface).
105/// Time complexity: `O(n · dim)`.
106///
107/// # Errors
108/// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if
109/// `dim < 2`, or `radius` is not positive and finite.
110///
111/// # Examples
112///
113/// ```
114/// use igraph::{misc, prelude::*};
115///
116/// rng::seed(42)?;
117/// for p in misc::sample_sphere_surface(3, 10, 2.0, true)? {
118/// let norm = p.iter().map(|x| x * x).sum::<f64>().sqrt();
119/// assert!((norm - 2.0).abs() < 1e-9);
120/// assert!(p.iter().all(|&x| x >= 0.0));
121/// }
122/// # Ok::<(), igraph::Error>(())
123/// ```
124pub fn sample_sphere_surface(
125 dim: usize,
126 n: usize,
127 radius: f64,
128 positive: bool,
129) -> Result<Vec<Vec<f64>>> {
130 sphere_surface(default_rng(), dim, n, radius, positive)
131}
132
133/// Samples `n` points uniformly from the volume (the ball) of the
134/// `dim`-dimensional sphere of the given `radius`, centered at the origin,
135/// using the thread's default random number generator.
136///
137/// With `positive` set, the points are restricted to the positive orthant.
138/// Each returned point has `dim` coordinates and Euclidean norm at most
139/// `radius`.
140///
141/// See also [`Graph::grg_game`](crate::Graph::grg_game), which samples
142/// points uniformly in the unit square and connects nearby ones, and
143/// [`Graph::nearest_neighbor_graph`](crate::Graph::nearest_neighbor_graph)
144/// to build a graph from sampled points in any dimension.
145///
146/// Binds [`igraph_rng_sample_sphere_volume`](https://igraph.org/c/html/latest/igraph-Nongraph.html#igraph_rng_sample_sphere_volume).
147/// Time complexity: `O(n · dim)`.
148///
149/// # Errors
150/// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if
151/// `dim < 2`, or `radius` is not positive and finite.
152///
153/// # Examples
154///
155/// ```
156/// use igraph::{misc, prelude::*};
157///
158/// rng::seed(42)?;
159/// let pts = misc::sample_sphere_volume(2, 4000, 1.0, false)?;
160/// assert!(pts.iter().all(|p| p[0].hypot(p[1]) <= 1.0));
161/// // Uniform in the disk: about a quarter of the points within radius 1/2.
162/// let inner = pts.iter().filter(|p| p[0].hypot(p[1]) < 0.5).count() as f64 / 4000.0;
163/// assert!((inner - 0.25).abs() < 0.03);
164/// # Ok::<(), igraph::Error>(())
165/// ```
166pub fn sample_sphere_volume(
167 dim: usize,
168 n: usize,
169 radius: f64,
170 positive: bool,
171) -> Result<Vec<Vec<f64>>> {
172 sphere_volume(default_rng(), dim, n, radius, positive)
173}
174
175/// Samples `n` points from the Dirichlet distribution with concentration
176/// parameters `alpha`, using the thread's default random number generator.
177///
178/// Each returned point has `alpha.len()` non-negative coordinates summing to
179/// one (a random probability vector); its expected value is
180/// `alpha / Σ alpha`, and larger concentrations give less spread-out samples.
181/// Extremely small concentrations (e.g. `1e-300`) make igraph's gamma
182/// samples underflow to zero, and then the coordinates are NaN.
183///
184/// Binds [`igraph_rng_sample_dirichlet`](https://igraph.org/c/html/latest/igraph-Nongraph.html#igraph_rng_sample_dirichlet).
185/// Time complexity: `O(n · alpha.len())`.
186///
187/// # Errors
188/// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if `alpha`
189/// has fewer than two entries or an entry that is not positive and finite.
190///
191/// # Examples
192///
193/// ```
194/// use igraph::{misc, prelude::*};
195///
196/// rng::seed(42)?;
197/// for p in misc::sample_dirichlet(100, &[1.0, 2.0, 3.0])? {
198/// assert_eq!(p.len(), 3);
199/// assert!((p.iter().sum::<f64>() - 1.0).abs() < 1e-9);
200/// }
201/// # Ok::<(), igraph::Error>(())
202/// ```
203pub fn sample_dirichlet(n: usize, alpha: &[f64]) -> Result<Vec<Vec<f64>>> {
204 dirichlet(default_rng(), n, alpha)
205}
206
207impl Rng {
208 /// Like [`sample_sphere_surface`],
209 /// drawing from this generator (`igraph_rng_sample_sphere_surface`).
210 ///
211 /// ```
212 /// use igraph::prelude::*;
213 /// let mut a = Rng::new(RngType::Pcg64, 5)?;
214 /// let mut b = Rng::new(RngType::Pcg64, 5)?;
215 /// assert_eq!(a.sample_sphere_surface(4, 3, 1.0, false)?, b.sample_sphere_surface(4, 3, 1.0, false)?);
216 /// # Ok::<(), igraph::Error>(())
217 /// ```
218 pub fn sample_sphere_surface(
219 &mut self,
220 dim: usize,
221 n: usize,
222 radius: f64,
223 positive: bool,
224 ) -> Result<Vec<Vec<f64>>> {
225 sphere_surface(self, dim, n, radius, positive)
226 }
227
228 /// Like [`sample_sphere_volume`],
229 /// drawing from this generator (`igraph_rng_sample_sphere_volume`).
230 pub fn sample_sphere_volume(
231 &mut self,
232 dim: usize,
233 n: usize,
234 radius: f64,
235 positive: bool,
236 ) -> Result<Vec<Vec<f64>>> {
237 sphere_volume(self, dim, n, radius, positive)
238 }
239
240 /// Like [`sample_dirichlet`], drawing from
241 /// this generator (`igraph_rng_sample_dirichlet`).
242 pub fn sample_dirichlet(&mut self, n: usize, alpha: &[f64]) -> Result<Vec<Vec<f64>>> {
243 dirichlet(self, n, alpha)
244 }
245}