Skip to main content

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}