igraph/misc/mod.rs
1//! Epidemics, spatial graphs, non-graph utilities and library-level hooks.
2//!
3//! This module gathers the parts of igraph that are not about one family of
4//! graph algorithms, but are still very useful when working with networks:
5//!
6//! - **Epidemics** (`igraph_epidemics.h`): stochastic SIR simulations on a
7//! graph, see [`Graph::sir`] and [`SirRun`].
8//! - **Spatial graphs** (`igraph_spatial.h`): graphs built from point clouds
9//! (Delaunay graph, Gabriel graph, relative neighborhood graph,
10//! β-skeletons, *k* nearest neighbor graphs), edge lengths from
11//! coordinates, and 2D convex hulls.
12//! - **Non-graph utilities** (`igraph_nongraph.h`): power-law fitting
13//! ([`power_law_fit`], [`PowerLawFit`]), running means, sequential random
14//! sampling, tolerant floating-point comparisons.
15//! - **Linear sum assignment** (`igraph_lsap.h`): the Hungarian method,
16//! [`solve_lsap`].
17//! - **Random sampling of vectors** (`igraph_sampling.h`): uniform points on
18//! or inside a sphere, Dirichlet samples, both from the thread's default
19//! generator and from an explicit [`Rng`](crate::rng::Rng).
20//! - **Partial prefix-sum trees** (`igraph_psumtree.h`): [`PsumTree`], a
21//! data structure to sample from a changing discrete distribution in
22//! `O(log n)`.
23//! - **Library hooks** (`igraph_version.h`, `igraph_progress.h`,
24//! `igraph_statusbar.h`, `igraph_interrupt.h`): [`version`], and Rust
25//! closures installed as *progress*, *status* and *interruption*
26//! handlers of the calling thread, with RAII guards that restore the
27//! previous handler.
28//!
29//! # Example
30//!
31//! Build the Delaunay triangulation of the corners and the center of a
32//! square, weight its edges by their Euclidean length, and solve a small
33//! assignment problem:
34//!
35//! ```
36//! use igraph::misc::{self, Metric};
37//! use igraph::prelude::*;
38//!
39//! let points = Matrix::from_rows(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.5, 0.5]])?;
40//! let delaunay = Graph::delaunay_graph(&points)?;
41//! // 4 sides of the square + 4 spokes towards the center.
42//! assert_eq!(delaunay.ecount(), 8);
43//! let lengths = delaunay.spatial_edge_lengths(&points, Metric::Euclidean)?;
44//! assert!(lengths.iter().all(|&l| l == 1.0 || (l - 0.5f64.sqrt()).abs() < 1e-12));
45//!
46//! // Three workers, three jobs: who does what at minimum total cost?
47//! let cost = Matrix::from_rows(&[[4.0, 1.0, 3.0], [2.0, 0.0, 5.0], [3.0, 2.0, 2.0]])?;
48//! assert_eq!(misc::solve_lsap(&cost)?, vec![1, 0, 2]);
49//!
50//! let v = misc::version();
51//! assert_eq!(v.major, 1);
52//! # Ok::<(), igraph::Error>(())
53//! ```
54//!
55//! # Randomness
56//!
57//! [`Graph::sir`], [`random_sample`], [`PowerLawFit::p_value`], the
58//! samplers and [`PsumTree::sample`] draw from the calling thread's default
59//! generator: every thread has its own, so [`rng::seed`](crate::rng::seed)
60//! makes a thread's results reproducible without affecting other threads.
61//!
62//! # Workarounds for igraph bugs
63//!
64//! Some inputs make igraph 1.0.0 and 1.0.1 misbehave (the relevant C
65//! sources did not change in 1.0.1); the wrappers handle them on the Rust
66//! side:
67//!
68//! - [`Graph::lune_beta_skeleton`] with `beta > 2` or `beta < 0.5`, and
69//! [`Graph::circle_beta_skeleton`] with `beta < 0.5`, where igraph returns
70//! spurious edges;
71//! - [`solve_lsap`] with NaN or infinite costs, where igraph loops forever
72//! (rejected);
73//! - [`random_sample`] with `length == 0` and `low == high`, where igraph
74//! returns `[low]`, and with `low == i64::MIN`, where igraph negates
75//! `low` (undefined behavior in C; the wrapper samples from a shifted
76//! interval);
77//! - [`PowerLawFit::p_value`] on invalid models (empty sample, `alpha <= 1`
78//! or not finite, invalid `xmin`) or with a tiny precision, where igraph
79//! crashes, loops forever or overflows a C `long` (rejected).
80//!
81//! # See also
82//!
83//! - [`crate::games`] for random graph models, e.g.
84//! [`Graph::grg_game`](crate::Graph::grg_game) (random geometric graphs)
85//! and [`Graph::dot_product_game`](crate::Graph::dot_product_game) (with
86//! latent positions from [`sample_sphere_surface`]);
87//! - [`crate::layout`] for point sets to feed to the spatial functions;
88//! - [`crate::paths`] and [`crate::structural`] for weighted computations
89//! using [`Graph::spatial_edge_lengths`] as weights;
90//! - [`crate::rng`] for the random number generators.
91//!
92//! # Contents
93//!
94//! | Rust | C function(s) | Chapter |
95//! |------|---------------|---------|
96//! | [`Graph::sir`], [`SirRun`] | `igraph_sir`, `igraph_sir_t` (`igraph_sir_init` / `igraph_sir_destroy` are memory plumbing, not wrapped: [`SirRun`] owns the vectors and frees them on drop) | Processes |
97//! | [`Graph::delaunay_graph`] | `igraph_delaunay_graph` | Spatial |
98//! | [`Graph::gabriel_graph`] | `igraph_gabriel_graph` | Spatial |
99//! | [`Graph::relative_neighborhood_graph`] | `igraph_relative_neighborhood_graph` | Spatial |
100//! | [`Graph::lune_beta_skeleton`] | `igraph_lune_beta_skeleton` | Spatial |
101//! | [`Graph::circle_beta_skeleton`] | `igraph_circle_beta_skeleton` | Spatial |
102//! | [`Graph::beta_weighted_gabriel_graph`] | `igraph_beta_weighted_gabriel_graph` | Spatial |
103//! | [`Graph::nearest_neighbor_graph`] | `igraph_nearest_neighbor_graph` | Spatial |
104//! | [`Graph::spatial_edge_lengths`] | `igraph_spatial_edge_lengths` | Spatial |
105//! | [`convex_hull_2d`] | `igraph_convex_hull_2d` | Spatial |
106//! | [`power_law_fit`], [`PowerLawFit::p_value`] | `igraph_power_law_fit`, `igraph_plfit_result_calculate_p_value` | Nongraph |
107//! | [`running_mean`] | `igraph_running_mean` | Nongraph |
108//! | [`random_sample`] | `igraph_random_sample` | Nongraph |
109//! | [`almost_equals`], [`cmp_epsilon`] | `igraph_almost_equals`, `igraph_cmp_epsilon` | Nongraph |
110//! | [`solve_lsap`] | `igraph_solve_lsap` | — |
111//! | [`sample_sphere_surface`], [`sample_sphere_volume`], [`sample_dirichlet`] (and the [`Rng`](crate::rng::Rng) methods) | `igraph_rng_sample_*` | Nongraph |
112//! | [`PsumTree`] | `igraph_psumtree_*` | Data structures |
113//! | [`version`] | `igraph_version` | Nongraph |
114//! | [`set_progress_handler`], [`with_progress_handler`], [`set_progress_handler_stderr`], [`progress`] | `igraph_set_progress_handler`, `igraph_progress_handler_stderr`, `igraph_progress` | Advanced |
115//! | [`set_status_handler`], [`with_status_handler`], [`set_status_handler_stderr`], [`status`] | `igraph_set_status_handler`, `igraph_status_handler_stderr`, `igraph_status` | Advanced |
116//! | [`set_interruption_handler`], [`with_interruption_handler`], [`allow_interruption`] | `igraph_set_interruption_handler`, `igraph_allow_interruption` | — |
117//!
118//! [`Graph::sir`]: crate::Graph::sir
119//! [`Graph::delaunay_graph`]: crate::Graph::delaunay_graph
120//! [`Graph::gabriel_graph`]: crate::Graph::gabriel_graph
121//! [`Graph::relative_neighborhood_graph`]: crate::Graph::relative_neighborhood_graph
122//! [`Graph::lune_beta_skeleton`]: crate::Graph::lune_beta_skeleton
123//! [`Graph::circle_beta_skeleton`]: crate::Graph::circle_beta_skeleton
124//! [`Graph::beta_weighted_gabriel_graph`]: crate::Graph::beta_weighted_gabriel_graph
125//! [`Graph::nearest_neighbor_graph`]: crate::Graph::nearest_neighbor_graph
126//! [`Graph::spatial_edge_lengths`]: crate::Graph::spatial_edge_lengths
127//! [`PsumTree::sample`]: crate::misc::PsumTree::sample
128
129mod epidemics;
130mod handlers;
131mod lsap;
132mod nongraph;
133mod psumtree;
134mod sampling;
135mod spatial;
136
137pub use epidemics::SirRun;
138pub use handlers::{
139 InterruptionHandlerGuard, ProgressHandlerGuard, StatusHandlerGuard, allow_interruption,
140 progress, set_interruption_handler, set_progress_handler, set_progress_handler_stderr,
141 set_status_handler, set_status_handler_stderr, status, with_interruption_handler,
142 with_progress_handler, with_status_handler,
143};
144pub use lsap::solve_lsap;
145pub use nongraph::{
146 PowerLawFit, Version, almost_equals, cmp_epsilon, power_law_fit, random_sample, running_mean,
147 version,
148};
149pub use psumtree::PsumTree;
150pub use sampling::{sample_dirichlet, sample_sphere_surface, sample_sphere_volume};
151pub use spatial::{ConvexHull, Metric, convex_hull_2d};
152
153/// Converts a nullable C string into an owned Rust string (lossy UTF-8).
154fn lossy(ptr: *const std::ffi::c_char) -> String {
155 if ptr.is_null() {
156 String::new()
157 } else {
158 unsafe { std::ffi::CStr::from_ptr(ptr) }
159 .to_string_lossy()
160 .into_owned()
161 }
162}
163
164/// Splits a column-major `nrow × ncol` matrix into its columns.
165fn columns_of(m: &crate::matrix::Matrix) -> Vec<Vec<f64>> {
166 m.columns().map(<[f64]>::to_vec).collect()
167}