use crate::error::{SpatialError, SpatialResult};
use std::collections::HashSet;
#[derive(Debug, Clone)]
pub struct MstEdge {
pub u: usize,
pub v: usize,
pub weight: f64,
}
pub fn euclidean_mst(points: &[[f64; 2]]) -> SpatialResult<Vec<MstEdge>> {
let n = points.len();
if n == 0 {
return Ok(Vec::new());
}
if n == 1 {
return Ok(Vec::new());
}
let mut edges: Vec<(usize, usize, f64)> = Vec::with_capacity(n * (n - 1) / 2);
for i in 0..n {
for j in (i + 1)..n {
let dx = points[i][0] - points[j][0];
let dy = points[i][1] - points[j][1];
let dist = (dx * dx + dy * dy).sqrt();
edges.push((i, j, dist));
}
}
edges.sort_by(|a, b| a.2.partial_cmp(&b.2).unwrap_or(std::cmp::Ordering::Equal));
let mut parent: Vec<usize> = (0..n).collect();
let mut rank: Vec<usize> = vec![0; n];
let mut mst = Vec::with_capacity(n - 1);
fn find(parent: &mut [usize], x: usize) -> usize {
if parent[x] != x {
parent[x] = find(parent, parent[x]);
}
parent[x]
}
fn union(parent: &mut [usize], rank: &mut [usize], x: usize, y: usize) -> bool {
let rx = find(parent, x);
let ry = find(parent, y);
if rx == ry {
return false;
}
if rank[rx] < rank[ry] {
parent[rx] = ry;
} else if rank[rx] > rank[ry] {
parent[ry] = rx;
} else {
parent[ry] = rx;
rank[rx] += 1;
}
true
}
for (u, v, w) in edges {
if mst.len() == n - 1 {
break;
}
if union(&mut parent, &mut rank, u, v) {
mst.push(MstEdge { u, v, weight: w });
}
}
Ok(mst)
}
pub fn gabriel_graph(points: &[[f64; 2]]) -> SpatialResult<Vec<(usize, usize)>> {
let n = points.len();
if n < 2 {
return Ok(Vec::new());
}
let mut edges = Vec::new();
for i in 0..n {
for j in (i + 1)..n {
let dx = points[i][0] - points[j][0];
let dy = points[i][1] - points[j][1];
let dist_sq_ij = dx * dx + dy * dy;
let mut is_gabriel = true;
for k in 0..n {
if k == i || k == j {
continue;
}
let dxi = points[i][0] - points[k][0];
let dyi = points[i][1] - points[k][1];
let dist_sq_ik = dxi * dxi + dyi * dyi;
let dxj = points[j][0] - points[k][0];
let dyj = points[j][1] - points[k][1];
let dist_sq_jk = dxj * dxj + dyj * dyj;
if dist_sq_ij > dist_sq_ik + dist_sq_jk + 1e-10 {
is_gabriel = false;
break;
}
}
if is_gabriel {
edges.push((i, j));
}
}
}
Ok(edges)
}
pub fn relative_neighborhood_graph(points: &[[f64; 2]]) -> SpatialResult<Vec<(usize, usize)>> {
let n = points.len();
if n < 2 {
return Ok(Vec::new());
}
let mut edges = Vec::new();
for i in 0..n {
for j in (i + 1)..n {
let dx = points[i][0] - points[j][0];
let dy = points[i][1] - points[j][1];
let dist_ij = (dx * dx + dy * dy).sqrt();
let mut is_rng = true;
for k in 0..n {
if k == i || k == j {
continue;
}
let dxi = points[i][0] - points[k][0];
let dyi = points[i][1] - points[k][1];
let dist_ik = (dxi * dxi + dyi * dyi).sqrt();
let dxj = points[j][0] - points[k][0];
let dyj = points[j][1] - points[k][1];
let dist_jk = (dxj * dxj + dyj * dyj).sqrt();
if dist_ij > dist_ik.max(dist_jk) + 1e-10 {
is_rng = false;
break;
}
}
if is_rng {
edges.push((i, j));
}
}
}
Ok(edges)
}
pub fn alpha_shape_edges(points: &[[f64; 2]], alpha: f64) -> SpatialResult<Vec<(usize, usize)>> {
let n = points.len();
if n < 2 {
return Ok(Vec::new());
}
if alpha <= 0.0 {
return Err(SpatialError::ValueError(
"Alpha must be positive".to_string(),
));
}
let radius = 1.0 / alpha;
let radius_sq = radius * radius;
let mut edges = Vec::new();
for i in 0..n {
for j in (i + 1)..n {
let dx = points[j][0] - points[i][0];
let dy = points[j][1] - points[i][1];
let dist_sq = dx * dx + dy * dy;
let dist = dist_sq.sqrt();
if dist > 2.0 * radius + 1e-10 {
continue;
}
let mid_x = (points[i][0] + points[j][0]) * 0.5;
let mid_y = (points[i][1] + points[j][1]) * 0.5;
let half_dist_sq = dist_sq * 0.25;
let h_sq = radius_sq - half_dist_sq;
if h_sq < 0.0 {
continue; }
let h = h_sq.sqrt();
let nx = -dy / dist;
let ny = dx / dist;
let c1x = mid_x + h * nx;
let c1y = mid_y + h * ny;
let c2x = mid_x - h * nx;
let c2y = mid_y - h * ny;
let circle1_empty = (0..n).all(|k| {
if k == i || k == j {
return true;
}
let dxk = points[k][0] - c1x;
let dyk = points[k][1] - c1y;
dxk * dxk + dyk * dyk >= radius_sq - 1e-10
});
let circle2_empty = (0..n).all(|k| {
if k == i || k == j {
return true;
}
let dxk = points[k][0] - c2x;
let dyk = points[k][1] - c2y;
dxk * dxk + dyk * dyk >= radius_sq - 1e-10
});
if circle1_empty || circle2_empty {
edges.push((i, j));
}
}
}
Ok(edges)
}
pub fn alpha_shape_boundary(points: &[[f64; 2]], alpha: f64) -> SpatialResult<Vec<(usize, usize)>> {
let all_edges = alpha_shape_edges(points, alpha)?;
let edge_set: HashSet<(usize, usize)> = all_edges.iter().copied().collect();
let mut boundary = Vec::new();
for &(i, j) in &all_edges {
let common_count = (0..points.len())
.filter(|&k| {
k != i
&& k != j
&& (edge_set.contains(&(i.min(k), i.max(k))))
&& (edge_set.contains(&(j.min(k), j.max(k))))
})
.count();
if common_count <= 1 {
boundary.push((i, j));
}
}
Ok(boundary)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mst_triangle() {
let points = vec![[0.0, 0.0], [1.0, 0.0], [0.5, 0.866]]; let mst = euclidean_mst(&points).expect("compute");
assert_eq!(mst.len(), 2);
for e in &mst {
assert!((e.weight - 1.0).abs() < 1e-3);
}
}
#[test]
fn test_mst_square() {
let points = vec![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
let mst = euclidean_mst(&points).expect("compute");
assert_eq!(mst.len(), 3);
let total: f64 = mst.iter().map(|e| e.weight).sum();
assert!((total - 3.0).abs() < 1e-10);
}
#[test]
fn test_mst_single_point() {
let points = vec![[0.0, 0.0]];
let mst = euclidean_mst(&points).expect("compute");
assert!(mst.is_empty());
}
#[test]
fn test_mst_empty() {
let points: Vec<[f64; 2]> = vec![];
let mst = euclidean_mst(&points).expect("compute");
assert!(mst.is_empty());
}
#[test]
fn test_gabriel_graph_square() {
let points = vec![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
let edges = gabriel_graph(&points).expect("compute");
let edge_set: HashSet<(usize, usize)> = edges.into_iter().collect();
assert!(edge_set.contains(&(0, 1))); assert!(edge_set.contains(&(0, 2))); assert!(edge_set.contains(&(1, 3))); assert!(edge_set.contains(&(2, 3))); assert!(edge_set.len() >= 4);
assert!(edge_set.len() <= 6); }
#[test]
fn test_gabriel_is_supergraph_of_rng() {
let points = vec![[0.0, 0.0], [1.0, 0.0], [0.5, 0.5], [0.0, 1.0], [1.0, 1.0]];
let gabriel_edges = gabriel_graph(&points).expect("compute");
let rng_edges = relative_neighborhood_graph(&points).expect("compute");
let gabriel_set: HashSet<(usize, usize)> = gabriel_edges.into_iter().collect();
let rng_set: HashSet<(usize, usize)> = rng_edges.into_iter().collect();
for edge in &rng_set {
assert!(
gabriel_set.contains(edge),
"RNG edge {:?} not in Gabriel graph",
edge
);
}
}
#[test]
fn test_rng_square() {
let points = vec![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
let edges = relative_neighborhood_graph(&points).expect("compute");
assert_eq!(edges.len(), 4);
}
#[test]
fn test_rng_is_supergraph_of_mst() {
let points = vec![[0.0, 0.0], [1.0, 0.0], [2.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
let mst = euclidean_mst(&points).expect("compute");
let rng_edges = relative_neighborhood_graph(&points).expect("compute");
let rng_set: HashSet<(usize, usize)> = rng_edges.into_iter().collect();
for e in &mst {
let edge = (e.u.min(e.v), e.u.max(e.v));
assert!(rng_set.contains(&edge), "MST edge {:?} not in RNG", edge);
}
}
#[test]
fn test_alpha_shape_large_alpha() {
let points = vec![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
let edges = alpha_shape_edges(&points, 10.0).expect("compute");
assert!(edges.len() <= 6); }
#[test]
fn test_alpha_shape_small_alpha() {
let points = vec![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
let edges = alpha_shape_edges(&points, 0.1).expect("compute");
assert!(edges.len() >= 4);
}
#[test]
fn test_alpha_shape_invalid_alpha() {
let points = vec![[0.0, 0.0], [1.0, 0.0]];
assert!(alpha_shape_edges(&points, 0.0).is_err());
assert!(alpha_shape_edges(&points, -1.0).is_err());
}
#[test]
fn test_graph_hierarchy() {
let points = vec![[0.0, 0.0], [2.0, 0.0], [1.0, 1.5], [3.0, 1.0], [0.5, 2.5]];
let mst = euclidean_mst(&points).expect("mst");
let rng = relative_neighborhood_graph(&points).expect("rng");
let gabriel = gabriel_graph(&points).expect("gabriel");
let mst_edges: HashSet<(usize, usize)> =
mst.iter().map(|e| (e.u.min(e.v), e.u.max(e.v))).collect();
let rng_set: HashSet<(usize, usize)> = rng.into_iter().collect();
let gabriel_set: HashSet<(usize, usize)> = gabriel.into_iter().collect();
for edge in &mst_edges {
assert!(rng_set.contains(edge), "MST edge {:?} not in RNG", edge);
}
for edge in &rng_set {
assert!(
gabriel_set.contains(edge),
"RNG edge {:?} not in Gabriel",
edge
);
}
assert!(mst_edges.len() <= rng_set.len());
assert!(rng_set.len() <= gabriel_set.len());
}
}