igraph/misc/spatial.rs
1//! Spatial graphs and computational geometry (`igraph_spatial.h`).
2//!
3//! Point sets are given as a [`Matrix`] with one point per row; the number
4//! of columns is the dimension of the space. All the graph builders except
5//! [`convex_hull_2d`] are marked *experimental* in igraph 1.0.x.
6//!
7//! The lune- and circle-based β-skeletons of igraph 1.0.0 and 1.0.1 return
8//! spurious edges for some ranges of β; the wrappers of this module detect
9//! these ranges and compute the correct skeletons instead (see
10//! [`Graph::lune_beta_skeleton`]).
11//!
12//! See also [`Graph::grg_game`] (random geometric graphs, which are
13//! nearest neighbor graphs with a distance cutoff), the layouts of
14//! [`crate::layout`] (which produce point sets for these functions), and
15//! the weighted path functions of [`crate::paths`], which take the output of
16//! [`Graph::spatial_edge_lengths`] as weights.
17
18use crate::{
19 error::{Error, Result},
20 ffi::*,
21 graph::Graph,
22 igraph_call,
23 matrix::Matrix,
24 vector::{Vector, VectorInt},
25};
26
27crate::ffi_enum! {
28 /// The distance metric used by spatial functions (`igraph_metric_t`).
29 pub enum Metric: igraph_metric_t {
30 /// The Euclidean (L2) distance, `sqrt(Σ (x_i - y_i)²)`.
31 Euclidean = igraph_metric_t_IGRAPH_METRIC_EUCLIDEAN,
32 /// The Manhattan (L1, taxicab) distance, `Σ |x_i - y_i|`.
33 Manhattan = igraph_metric_t_IGRAPH_METRIC_MANHATTAN,
34 }
35}
36
37impl Metric {
38 /// Alias of [`Metric::Euclidean`] (`IGRAPH_METRIC_L2`).
39 pub const L2: Metric = Metric::Euclidean;
40 /// Alias of [`Metric::Manhattan`] (`IGRAPH_METRIC_L1`).
41 pub const L1: Metric = Metric::Manhattan;
42}
43
44impl igraph_t {
45 /// The Delaunay graph of a point set: two points are adjacent when they
46 /// share an edge of the Delaunay triangulation (tetrahedralization, ...)
47 /// of the points.
48 ///
49 /// `points` has one point per row, in any dimension `d >= 1`; vertex `i`
50 /// of the result is the point in row `i`. The Delaunay graph is a
51 /// supergraph of the [Gabriel graph](Graph::gabriel_graph), itself a
52 /// supergraph of the [relative neighborhood graph](Graph::relative_neighborhood_graph)
53 /// and of the Euclidean minimum spanning tree. The computation relies on
54 /// Qhull.
55 ///
56 /// See also [`convex_hull_2d`]: in 2D, the outer boundary of the
57 /// triangulation is the convex hull of the points.
58 ///
59 /// Binds [`igraph_delaunay_graph`](https://igraph.org/c/html/latest/igraph-Spatial.html#igraph_delaunay_graph)
60 /// (experimental). Time complexity: `O(n log n)` for `d <= 3`, and
61 /// `O(n^⌊d/2⌋ / ⌊d/2⌋!)` in general.
62 ///
63 /// # Errors
64 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for
65 /// duplicate points, non-finite coordinates, zero-dimensional points,
66 /// and (currently) for degenerate sets that do not span the space, such
67 /// as `d + 1` or more points all lying on a hyperplane.
68 ///
69 /// # Examples
70 ///
71 /// ```
72 /// use igraph::prelude::*;
73 /// // A 3x3 square lattice (igraph's own test case).
74 /// let pts: Vec<[f64; 2]> =
75 /// (0..9).map(|i| [(i / 3) as f64, (i % 3) as f64]).collect();
76 /// let g = Graph::delaunay_graph(&Matrix::from_rows(&pts)?)?;
77 /// assert_eq!(g.vcount(), 9);
78 /// // 12 lattice sides plus one diagonal in each of the 4 cells.
79 /// assert_eq!(g.ecount(), 16);
80 /// # Ok::<(), igraph::Error>(())
81 /// ```
82 pub fn delaunay_graph(points: &Matrix) -> Result<Graph> {
83 Graph::init_with(|g| unsafe { igraph_delaunay_graph(g, points) })
84 }
85
86 /// The lune-based β-skeleton of a point set.
87 ///
88 /// Two points `A` and `B` are adjacent when no other point lies in their
89 /// (closed) *lune*, a region whose size grows with `beta`: larger values
90 /// of `beta` give sparser graphs. For `beta >= 1` the lune is the
91 /// intersection of the two balls of radius `beta·|AB|/2` centered on the
92 /// line `AB` and passing through `A` and `B` respectively; for
93 /// `beta < 1` it is the intersection of the two disks of radius
94 /// `|AB|/(2·beta)` whose boundaries pass through both `A` and `B`.
95 /// `beta = 1` gives the [Gabriel graph](Graph::gabriel_graph);
96 /// `beta = 2` is (almost, see
97 /// [`relative_neighborhood_graph`](Graph::relative_neighborhood_graph))
98 /// the relative neighborhood graph. Values of `beta < 1` are only
99 /// supported in 2D, and are considerably slower.
100 ///
101 /// # Correctness workarounds
102 ///
103 /// igraph 1.0.0 and 1.0.1 under-estimate the search radius of the lune for
104 /// `beta > 2` and `beta < 0.5`, and then returns spurious edges (for
105 /// `beta < 0.5`, even the complete graph). This wrapper returns the
106 /// correct skeleton in these ranges: for `beta > 2` it keeps the edges of
107 /// [`beta_weighted_gabriel_graph`](Graph::beta_weighted_gabriel_graph)
108 /// whose threshold exceeds `beta` (same complexity; point sets with no
109 /// more points than dimensions are tested pair by pair, as igraph does),
110 /// for `beta < 0.5` it tests every pair of points against every other
111 /// point (`O(n³)`).
112 ///
113 /// Binds [`igraph_lune_beta_skeleton`](https://igraph.org/c/html/latest/igraph-Spatial.html#igraph_lune_beta_skeleton)
114 /// (experimental). Time complexity: about `O(n^⌊d/2⌋ log n)`.
115 ///
116 /// # Errors
117 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) unless
118 /// `beta` is positive and finite, or for NaN or infinite coordinates;
119 /// [`ErrorKind::Unimplemented`](crate::ErrorKind::Unimplemented)
120 /// for `beta < 1` outside of 2D; and, for `beta >= 1` with more points
121 /// than dimensions (where the candidate edges come from the Delaunay
122 /// graph), the errors of [`delaunay_graph`](Graph::delaunay_graph), e.g.
123 /// for duplicate points.
124 ///
125 /// # Examples
126 ///
127 /// ```
128 /// use igraph::prelude::*;
129 /// // Two points and a third one slightly off their midpoint.
130 /// let pts = Matrix::from_rows(&[[0.0, 0.0], [2.0, 0.0], [1.0, 1.2]])?;
131 /// // For beta = 1 the third point is outside the disk with diameter 0-1...
132 /// assert!(Graph::lune_beta_skeleton(&pts, 1.0)?.get_eid(0, 1, false)?.is_some());
133 /// // ...but it is inside the fatter lune of beta = 2.
134 /// assert!(Graph::lune_beta_skeleton(&pts, 2.0)?.get_eid(0, 1, false)?.is_none());
135 /// # Ok::<(), igraph::Error>(())
136 /// ```
137 pub fn lune_beta_skeleton(points: &Matrix, beta: f64) -> Result<Graph> {
138 check_beta(beta)?;
139 check_finite(points)?;
140 let (n, dim) = (points.nrow(), points.ncol());
141 if beta > 2.0 {
142 if n <= dim {
143 // Too few points for a Delaunay triangulation: igraph tests
144 // every pair, so do we (there are at most `dim` points).
145 return brute_force_skeleton(points, |a, b, p| in_lune(a, b, p, beta));
146 }
147 // Thresholds up to (just above) beta are enough to decide.
148 let (g, thresholds) = Graph::beta_weighted_gabriel_graph(points, beta * (1.0 + 1e-9))?;
149 let kept: Vec<(igraph_int_t, igraph_int_t)> = g
150 .edge_list()
151 .into_iter()
152 .zip(thresholds)
153 .filter(|&(_, t)| t > beta)
154 .map(|(e, _)| e)
155 .collect();
156 return Graph::from_edges(&kept, n, false);
157 }
158 if beta < 0.5 && dim == 2 {
159 return brute_force_skeleton(points, |a, b, p| in_lens(a, b, p, beta));
160 }
161 Graph::init_with(|g| unsafe { igraph_lune_beta_skeleton(g, points, beta) })
162 }
163
164 /// The circle-based β-skeleton of a 2D point set.
165 ///
166 /// For `beta >= 1`, `A` and `B` are adjacent when no other point lies in
167 /// the *union* of the two disks of radius `beta·|AB|/2` whose boundaries
168 /// pass through both `A` and `B`; for `beta < 1` the forbidden region is
169 /// the intersection of the disks of radius `|AB|/(2·beta)`, as for the
170 /// [lune-based skeleton](Graph::lune_beta_skeleton). `beta` must be
171 /// positive; larger values give sparser graphs, and values below 1 are
172 /// considerably slower. For `beta = 1` it coincides with the Gabriel
173 /// graph.
174 ///
175 /// For `beta < 0.5` igraph 1.0.0 and 1.0.1 return spurious edges (see the
176 /// [lune-based skeleton](Graph::lune_beta_skeleton)): this wrapper then
177 /// computes the correct skeleton by brute force, in `O(n³)`.
178 ///
179 /// Binds [`igraph_circle_beta_skeleton`](https://igraph.org/c/html/latest/igraph-Spatial.html#igraph_circle_beta_skeleton)
180 /// (experimental).
181 ///
182 /// # Errors
183 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) unless
184 /// `beta` is positive and finite, or for NaN or infinite coordinates;
185 /// [`ErrorKind::Unimplemented`](crate::ErrorKind::Unimplemented)
186 /// if the points are not two-dimensional.
187 pub fn circle_beta_skeleton(points: &Matrix, beta: f64) -> Result<Graph> {
188 check_beta(beta)?;
189 check_finite(points)?;
190 if beta < 0.5 && points.ncol() == 2 {
191 return brute_force_skeleton(points, |a, b, p| in_lens(a, b, p, beta));
192 }
193 Graph::init_with(|g| unsafe { igraph_circle_beta_skeleton(g, points, beta) })
194 }
195
196 /// The Gabriel graph together with, for each edge, the threshold β at
197 /// which the edge disappears from the lune-based β-skeleton.
198 ///
199 /// The edge `e` belongs to the [lune β-skeleton](Graph::lune_beta_skeleton)
200 /// exactly for `1 <= β < weights[e]`, so this single call summarizes all
201 /// the skeletons with `β >= 1`. Edges that persist for arbitrarily large
202 /// β, or beyond the `max_beta` cutoff, get the weight
203 /// [`f64::INFINITY`]. A smaller `max_beta` makes the computation faster;
204 /// pass [`f64::INFINITY`] for no cutoff.
205 ///
206 /// Returns the graph and the weights, indexed by edge id.
207 ///
208 /// Binds [`igraph_beta_weighted_gabriel_graph`](https://igraph.org/c/html/latest/igraph-Spatial.html#igraph_beta_weighted_gabriel_graph)
209 /// (experimental).
210 ///
211 /// # Errors
212 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if
213 /// `max_beta` is NaN, and the errors of
214 /// [`delaunay_graph`](Graph::delaunay_graph) (which it uses even for
215 /// tiny point sets: it needs more points than dimensions, and there must
216 /// be no duplicate points).
217 ///
218 /// # Examples
219 ///
220 /// ```
221 /// use igraph::prelude::*;
222 /// let pts = Matrix::from_rows(&[[0.0, 0.0], [2.0, 0.0], [1.0, 1.2]])?;
223 /// let (g, beta) = Graph::beta_weighted_gabriel_graph(&pts, f64::INFINITY)?;
224 /// assert_eq!(g.ecount(), 3);
225 /// let long = g.get_eid(0, 1, false)?.unwrap() as usize;
226 /// // Edge 0-1 leaves the lune skeleton at beta = 1.22 (the third point
227 /// // enters its lune), the other two sides at a much larger beta.
228 /// assert!((beta[long] - 1.22).abs() < 1e-9);
229 /// assert!(beta.iter().all(|&b| b >= beta[long]));
230 /// # Ok::<(), igraph::Error>(())
231 /// ```
232 pub fn beta_weighted_gabriel_graph(
233 points: &Matrix,
234 max_beta: f64,
235 ) -> Result<(Graph, Vec<f64>)> {
236 if max_beta.is_nan() {
237 return Err(Error::invalid("max_beta must not be NaN"));
238 }
239 let mut weights = Vector::new();
240 let g = Graph::init_with(|g| unsafe {
241 igraph_beta_weighted_gabriel_graph(g, &mut weights, points, max_beta)
242 })?;
243 Ok((g, weights.into()))
244 }
245
246 /// The Gabriel graph of a point set: `A` and `B` are adjacent when no
247 /// other point lies in the closed ball having the segment `AB` as a
248 /// diameter.
249 ///
250 /// The Gabriel graph is connected, planar in 2D, and it is the β-skeleton
251 /// (lune- or circle-based) with `β = 1`. Any dimension is supported.
252 ///
253 /// Binds [`igraph_gabriel_graph`](https://igraph.org/c/html/latest/igraph-Spatial.html#igraph_gabriel_graph)
254 /// (experimental). Time complexity: about `O(n^⌊d/2⌋ log n)`.
255 ///
256 /// # Examples
257 ///
258 /// ```
259 /// use igraph::prelude::*;
260 /// // An obtuse triangle: the long side has the third point inside its
261 /// // diametral circle, so it is not a Gabriel edge.
262 /// let pts = Matrix::from_rows(&[[0.0, 0.0], [4.0, 0.0], [2.0, 0.5]])?;
263 /// let g = Graph::gabriel_graph(&pts)?;
264 /// assert_eq!(g.ecount(), 2);
265 /// assert_eq!(g.get_eid(0, 1, false)?, None);
266 /// # Ok::<(), igraph::Error>(())
267 /// ```
268 pub fn gabriel_graph(points: &Matrix) -> Result<Graph> {
269 Graph::init_with(|g| unsafe { igraph_gabriel_graph(g, points) })
270 }
271
272 /// The relative neighborhood graph of a point set: `A` and `B` are
273 /// adjacent unless some other point `C` is strictly closer to both of
274 /// them than they are to each other (`AC < AB` and `BC < AB`).
275 ///
276 /// It is always connected, and it is a supergraph of the Euclidean
277 /// minimum spanning tree. Unlike the `β = 2` lune skeleton (which uses
278 /// non-strict inequalities and is triangle-free), it connects the three
279 /// corners of an equilateral triangle.
280 ///
281 /// Binds [`igraph_relative_neighborhood_graph`](https://igraph.org/c/html/latest/igraph-Spatial.html#igraph_relative_neighborhood_graph)
282 /// (experimental). Time complexity: about `O(n^⌊d/2⌋ log n)`.
283 ///
284 /// See also [`Graph::minimum_spanning_tree`]: with the
285 /// [edge lengths](Graph::spatial_edge_lengths) as weights, the minimum
286 /// spanning tree of this graph is the Euclidean minimum spanning tree of
287 /// the points.
288 ///
289 /// # Examples
290 ///
291 /// ```
292 /// use igraph::prelude::*;
293 /// // A 1x2 rectangle with its center: the center is closer to every
294 /// // corner than the corners to each other along the long sides.
295 /// let pts = Matrix::from_rows(&[[0.0, 0.0], [2.0, 0.0], [2.0, 1.0], [0.0, 1.0], [1.0, 0.5]])?;
296 /// let g = Graph::relative_neighborhood_graph(&pts)?;
297 /// let mut edges = g.edge_list();
298 /// edges.sort();
299 /// // The two short sides and the four spokes to the center.
300 /// assert_eq!(edges, vec![(0, 3), (0, 4), (1, 2), (1, 4), (2, 4), (3, 4)]);
301 /// # Ok::<(), igraph::Error>(())
302 /// ```
303 pub fn relative_neighborhood_graph(points: &Matrix) -> Result<Graph> {
304 Graph::init_with(|g| unsafe { igraph_relative_neighborhood_graph(g, points) })
305 }
306
307 /// The *k* nearest neighbor graph of a point set.
308 ///
309 /// Each point is connected to (at most) its `k` nearest other points
310 /// according to `metric`, considering only points closer than `cutoff`.
311 /// `k = None` means no limit on the number of neighbors, and
312 /// `cutoff = None` no limit on the distance (with both `None` the result
313 /// is complete). With `directed`, the edge `i → j` means that `j` is
314 /// among the neighbors of `i`; otherwise `i` and `j` are connected when
315 /// *either* chose the other (mutual choices give a single edge). Ties
316 /// between equidistant neighbors are broken arbitrarily.
317 ///
318 /// Binds [`igraph_nearest_neighbor_graph`](https://igraph.org/c/html/latest/igraph-Spatial.html#igraph_nearest_neighbor_graph)
319 /// (experimental). Time complexity: `O(n log n)` (k-d tree).
320 ///
321 /// See also [`Graph::grg_game`], which samples random points in the unit
322 /// square and connects those closer than a radius: the undirected graph
323 /// built here with `k = None` and that radius as `cutoff`.
324 ///
325 /// # Errors
326 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for
327 /// zero-dimensional points, non-finite coordinates, or a negative or NaN
328 /// `cutoff`.
329 ///
330 /// # Examples
331 ///
332 /// ```
333 /// use igraph::{misc::Metric, prelude::*};
334 /// // Points on a line: 0, 1, 3, 7.
335 /// let pts = Matrix::from_rows(&[[0.0], [1.0], [3.0], [7.0]])?;
336 /// let g = Graph::nearest_neighbor_graph(&pts, Metric::Euclidean, Some(1), None, true)?;
337 /// let mut edges = g.edge_list();
338 /// edges.sort();
339 /// assert_eq!(edges, vec![(0, 1), (1, 0), (2, 1), (3, 2)]);
340 /// # Ok::<(), igraph::Error>(())
341 /// ```
342 pub fn nearest_neighbor_graph(
343 points: &Matrix,
344 metric: Metric,
345 k: Option<usize>,
346 cutoff: Option<f64>,
347 directed: bool,
348 ) -> Result<Graph> {
349 // More neighbors than points means no limit at all.
350 let k = k.map_or(-1, |k| {
351 igraph_int_t::try_from(k).unwrap_or(igraph_int_t::MAX)
352 });
353 let cutoff = match cutoff {
354 None => f64::INFINITY,
355 Some(c) if c >= 0.0 => c,
356 Some(c) => {
357 return Err(Error::invalid(format!(
358 "the cutoff distance must be non-negative, got {c}"
359 )));
360 }
361 };
362 Graph::init_with(|g| unsafe {
363 igraph_nearest_neighbor_graph(g, points, metric.into(), k, cutoff, directed)
364 })
365 }
366
367 /// The length of each edge, computed from the coordinates of its
368 /// endpoints with the given `metric`, indexed by edge id.
369 ///
370 /// Row `i` of `points` holds the coordinates of vertex `i`, in any
371 /// dimension. The lengths can be used as weights by path-length based
372 /// functions, e.g. [`Graph::distances_dijkstra`],
373 /// [`Graph::minimum_spanning_tree`], [`Graph::betweenness`],
374 /// [`Graph::closeness`] or [`Graph::voronoi`].
375 ///
376 /// Binds [`igraph_spatial_edge_lengths`](https://igraph.org/c/html/latest/igraph-Spatial.html#igraph_spatial_edge_lengths)
377 /// (experimental). Time complexity: `O(|E| d)`.
378 ///
379 /// # Errors
380 /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if the
381 /// number of rows differs from the number of vertices, or the points are
382 /// zero-dimensional (a `0 × 0` matrix is accepted for the null graph).
383 ///
384 /// # Examples
385 ///
386 /// ```
387 /// use igraph::{misc::Metric, prelude::*};
388 /// let g = Graph::from_edges(&[(0, 1), (0, 2)], 3, false)?;
389 /// let pts = Matrix::from_rows(&[[0.0, 0.0], [3.0, 4.0], [1.0, 1.0]])?;
390 /// assert_eq!(g.spatial_edge_lengths(&pts, Metric::Euclidean)?[0], 5.0);
391 /// assert_eq!(g.spatial_edge_lengths(&pts, Metric::Manhattan)?, vec![7.0, 2.0]);
392 /// # Ok::<(), igraph::Error>(())
393 /// ```
394 pub fn spatial_edge_lengths(&self, points: &Matrix, metric: Metric) -> Result<Vec<f64>> {
395 let mut res = Vector::new();
396 igraph_call!(igraph_spatial_edge_lengths(
397 self,
398 &mut res,
399 points,
400 metric.into()
401 ))?;
402 Ok(res.into())
403 }
404}
405
406fn check_beta(beta: f64) -> Result<()> {
407 if beta > 0.0 && beta.is_finite() {
408 Ok(())
409 } else {
410 Err(Error::invalid(format!(
411 "beta must be positive and finite, got {beta}"
412 )))
413 }
414}
415
416fn check_finite(points: &Matrix) -> Result<()> {
417 if points.as_slice().iter().all(|x| x.is_finite()) {
418 Ok(())
419 } else {
420 Err(Error::invalid("coordinates must not be NaN or infinite"))
421 }
422}
423
424/// igraph's relative tolerance: the forbidden regions are closed.
425const TOL: f64 = 1.0 + 128.0 * f64::EPSILON;
426
427fn sqr_dist(p: &[f64], q: &[f64]) -> f64 {
428 p.iter().zip(q).map(|(x, y)| (x - y) * (x - y)).sum()
429}
430
431/// Whether `p` lies in the closed lune of the segment `ab` for `beta >= 1`:
432/// the intersection of the balls of radius `beta·|ab|/2` centered at
433/// `a + (beta/2 - 1)(a - b)` and `b + (beta/2 - 1)(b - a)` (any dimension).
434fn in_lune(a: &[f64], b: &[f64], p: &[f64], beta: f64) -> bool {
435 let r = beta / 2.0;
436 let radius2 = r * r * sqr_dist(a, b) * TOL * TOL;
437 let ca: Vec<f64> = a
438 .iter()
439 .zip(b)
440 .map(|(x, y)| x + (r - 1.0) * (x - y))
441 .collect();
442 let cb: Vec<f64> = a
443 .iter()
444 .zip(b)
445 .map(|(x, y)| y + (r - 1.0) * (y - x))
446 .collect();
447 sqr_dist(p, &ca) <= radius2 && sqr_dist(p, &cb) <= radius2
448}
449
450/// Whether the 2D point `p` lies in the closed lens of the segment `ab` for
451/// `beta < 1`: the intersection of the two disks of radius `|ab|/(2 beta)`
452/// whose boundaries pass through both `a` and `b`.
453fn in_lens(a: &[f64], b: &[f64], p: &[f64], beta: f64) -> bool {
454 let d2 = sqr_dist(a, b);
455 let mid = [(a[0] + b[0]) / 2.0, (a[1] + b[1]) / 2.0];
456 // The lens lies within the disk having `ab` as a diameter.
457 if sqr_dist(p, &mid) > d2 / 4.0 * TOL * TOL {
458 return false;
459 }
460 let r = 0.5 / beta;
461 let shift = (r * r - 0.25).sqrt();
462 let perp = [-(a[1] - b[1]) * shift, (a[0] - b[0]) * shift];
463 let c1 = [mid[0] + perp[0], mid[1] + perp[1]];
464 let c2 = [mid[0] - perp[0], mid[1] - perp[1]];
465 let radius2 = r * r * d2 * TOL * TOL;
466 sqr_dist(p, &c1) <= radius2 && sqr_dist(p, &c2) <= radius2
467}
468
469/// A β-skeleton by brute force, in `O(n³ d)`: `a` and `b` are adjacent
470/// unless `blocked(a, b, p)` holds for some other point `p`. Used where
471/// igraph 1.0.0 and 1.0.1 are wrong.
472fn brute_force_skeleton(
473 points: &Matrix,
474 blocked: impl Fn(&[f64], &[f64], &[f64]) -> bool,
475) -> Result<Graph> {
476 let n = points.nrow();
477 let pts = points.to_rows();
478 let mut edges = Vec::new();
479 for a in 0..n {
480 for b in a + 1..n {
481 let hit = (0..n).any(|k| k != a && k != b && blocked(&pts[a], &pts[b], &pts[k]));
482 if !hit {
483 edges.push((a as igraph_int_t, b as igraph_int_t));
484 }
485 }
486 }
487 Graph::from_edges(&edges, n, false)
488}
489
490/// The convex hull of a 2D point set, as returned by [`convex_hull_2d`].
491#[derive(Debug, Clone, PartialEq)]
492pub struct ConvexHull {
493 /// The indices (rows of the input matrix) of the corners of the hull,
494 /// in order along the boundary. Points in the interior of a side are
495 /// not corners.
496 pub vertices: Vec<i64>,
497 /// The coordinates of the corners, in the same order as `vertices`.
498 pub points: Vec<[f64; 2]>,
499}
500
501impl ConvexHull {
502 /// The (non-negative) area enclosed by the hull, by the shoelace formula.
503 pub fn area(&self) -> f64 {
504 let n = self.points.len();
505 let twice: f64 = (0..n)
506 .map(|i| {
507 let [x1, y1] = self.points[i];
508 let [x2, y2] = self.points[(i + 1) % n];
509 x1 * y2 - x2 * y1
510 })
511 .sum();
512 twice.abs() / 2.0
513 }
514
515 /// The perimeter of the hull.
516 pub fn perimeter(&self) -> f64 {
517 let n = self.points.len();
518 if n < 2 {
519 return 0.0;
520 }
521 (0..n)
522 .map(|i| {
523 let [x1, y1] = self.points[i];
524 let [x2, y2] = self.points[(i + 1) % n];
525 (x2 - x1).hypot(y2 - y1)
526 })
527 .sum()
528 }
529}
530
531/// The convex hull of a set of points in the plane, by the Graham scan.
532///
533/// `points` must have two columns (x and y) and one point per row. The
534/// corners are reported in order along the boundary; collinear points on
535/// the sides are left out. Degenerate inputs are fine: one point gives a
536/// one-corner hull, collinear points give the two extremes, no points an
537/// empty hull.
538///
539/// Binds [`igraph_convex_hull_2d`](https://igraph.org/c/html/latest/igraph-Spatial.html#igraph_convex_hull_2d).
540/// Time complexity: `O(n log n)`.
541///
542/// See also [`Graph::layout_circle`](crate::Graph::layout_circle) and the
543/// other layouts of [`crate::layout`], whose coordinate matrices can be
544/// passed directly.
545///
546/// # Errors
547/// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if `points`
548/// does not have exactly two columns, or has NaN or infinite coordinates.
549///
550/// # Examples
551///
552/// ```
553/// use igraph::{misc, prelude::*};
554/// let pts = Matrix::from_rows(&[[0.0, 0.0], [2.0, 0.0], [1.0, 1.0], [2.0, 2.0], [0.0, 2.0]])?;
555/// let hull = misc::convex_hull_2d(&pts)?;
556/// let mut corners = hull.vertices.clone();
557/// corners.sort();
558/// assert_eq!(corners, vec![0, 1, 3, 4]); // the center point (row 2) is inside
559/// assert_eq!(hull.area(), 4.0);
560/// assert_eq!(hull.perimeter(), 8.0);
561/// # Ok::<(), igraph::Error>(())
562/// ```
563pub fn convex_hull_2d(points: &Matrix) -> Result<ConvexHull> {
564 check_finite(points)?;
565 let mut vertices = VectorInt::new();
566 let mut coords = Matrix::new();
567 igraph_call!(igraph_convex_hull_2d(points, &mut vertices, &mut coords))?;
568 let points = (0..coords.nrow())
569 .map(|i| [coords[(i, 0)], coords[(i, 1)]])
570 .collect();
571 Ok(ConvexHull {
572 vertices: vertices.into(),
573 points,
574 })
575}