scirs2_spatial/convex_hull/properties/
mod.rs

1//! Convex hull properties and analysis
2//!
3//! This module provides functions to compute various geometric properties
4//! of convex hulls, including volume, surface area, and point containment testing.
5//!
6//! # Module Organization
7//!
8//! - [`volume`] - Volume/area/length calculations for different dimensions
9//! - [`surface_area`] - Surface area/perimeter calculations and compactness measures
10//! - [`containment`] - Point-in-hull testing and distance calculations
11//!
12//! # Examples
13//!
14//! ## Volume Calculations
15//! ```rust
16//! use scirs2_spatial::convex_hull::ConvexHull;
17//! use scirs2_spatial::convex_hull::properties::volume::compute_volume;
18//! use ndarray::array;
19//!
20//! // 2D square with area 1
21//! let points = array![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
22//! let hull = ConvexHull::new(&points.view()).unwrap();
23//! let area = compute_volume(&hull).unwrap();
24//! assert!((area - 1.0).abs() < 1e-10);
25//! ```
26//!
27//! ## Surface Area Calculations
28//! ```rust
29//! use scirs2_spatial::convex_hull::ConvexHull;
30//! use scirs2_spatial::convex_hull::properties::surface_area::{compute_surface_area, compute_compactness};
31//! use ndarray::array;
32//!
33//! let points = array![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
34//! let hull = ConvexHull::new(&points.view()).unwrap();
35//!
36//! let perimeter = compute_surface_area(&hull).unwrap();
37//! assert!((perimeter - 4.0).abs() < 1e-10);
38//!
39//! let compactness = compute_compactness(&hull).unwrap();
40//! assert!(compactness > 0.7); // Square is fairly compact
41//! ```
42//!
43//! ## Point Containment Testing
44//! ```rust
45//! use scirs2_spatial::convex_hull::ConvexHull;
46//! use scirs2_spatial::convex_hull::properties::containment::{check_point_containment, distance_to_hull};
47//! use ndarray::array;
48//!
49//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
50//! let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]];
51//! let hull = ConvexHull::new(&points.view())?;
52//!
53//! // Test containment
54//! assert!(check_point_containment(&hull, &[0.1, 0.1])?);
55//! assert!(!check_point_containment(&hull, &[2.0, 2.0])?);
56//!
57//! // Compute distance
58//! let dist = distance_to_hull(&hull, &[0.5, 0.5])?;
59//! assert!(dist < 0.0); // Inside the hull
60//! # Ok(())
61//! # }
62//! ```
63//!
64//! ## Combined Analysis
65//! ```rust
66//! use scirs2_spatial::convex_hull::ConvexHull;
67//! use scirs2_spatial::convex_hull::properties::*;
68//! use ndarray::array;
69//!
70//! let points = array![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
71//! let hull = ConvexHull::new(&points.view()).unwrap();
72//!
73//! let vol = volume::compute_volume(&hull).unwrap();
74//! let area = surface_area::compute_surface_area(&hull).unwrap();
75//! let ratio = surface_area::compute_surface_to_volume_ratio(&hull).unwrap();
76//! let compactness = surface_area::compute_compactness(&hull).unwrap();
77//!
78//! println!("Volume: {}, Surface Area: {}, Ratio: {}, Compactness: {}",
79//!          vol, area, ratio, compactness);
80//! ```
81
82pub mod containment;
83pub mod surface_area;
84pub mod volume;
85
86// Re-export commonly used functions for convenience
87pub use volume::{
88    compute_volume, compute_volume_bounds, compute_volume_monte_carlo,
89    is_volume_computation_reliable,
90};
91
92pub use surface_area::{
93    compute_compactness, compute_surface_area, compute_surface_area_bounds,
94    compute_surface_to_volume_ratio, is_surface_area_computation_reliable,
95};
96
97pub use containment::{check_multiple_containment, check_point_containment, distance_to_hull};
98
99/// Comprehensive hull analysis results
100///
101/// This structure contains all major geometric properties of a convex hull.
102#[derive(Debug, Clone)]
103pub struct HullAnalysis {
104    /// Number of dimensions
105    pub ndim: usize,
106    /// Number of vertices
107    pub num_vertices: usize,
108    /// Number of facets/simplices
109    pub num_facets: usize,
110    /// Volume/area/length of the hull
111    pub volume: f64,
112    /// Surface area/perimeter of the hull
113    pub surface_area: f64,
114    /// Surface area to volume ratio
115    pub surface_to_volume_ratio: f64,
116    /// Compactness measure (0-1, higher is more compact)
117    pub compactness: f64,
118    /// Whether volume computation is considered reliable
119    pub volume_reliable: bool,
120    /// Whether surface area computation is considered reliable
121    pub surface_area_reliable: bool,
122    /// Bounding box dimensions
123    pub bounding_box_size: Vec<f64>,
124    /// Centroid of the hull vertices
125    pub centroid: Vec<f64>,
126}
127
128/// Perform comprehensive analysis of a convex hull
129///
130/// This function computes all major geometric properties of the hull
131/// and returns them in a single structure.
132///
133/// # Arguments
134///
135/// * `hull` - The convex hull to analyze
136///
137/// # Returns
138///
139/// * Result containing comprehensive hull analysis
140///
141/// # Examples
142///
143/// ```rust
144/// use scirs2_spatial::convex_hull::ConvexHull;
145/// use scirs2_spatial::convex_hull::properties::analyze_hull;
146/// use ndarray::array;
147///
148/// let points = array![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
149/// let hull = ConvexHull::new(&points.view()).unwrap();
150/// let analysis = analyze_hull(&hull).unwrap();
151///
152/// println!("Hull Analysis:");
153/// println!("  Dimensions: {}", analysis.ndim);
154/// println!("  Vertices: {}", analysis.num_vertices);
155/// println!("  Volume: {}", analysis.volume);
156/// println!("  Surface Area: {}", analysis.surface_area);
157/// println!("  Compactness: {:.3}", analysis.compactness);
158/// ```
159pub fn analyze_hull(
160    hull: &crate::convex_hull::core::ConvexHull,
161) -> crate::error::SpatialResult<HullAnalysis> {
162    let ndim = hull.ndim();
163    let num_vertices = hull.vertex_indices().len();
164    let num_facets = hull.simplices().len();
165
166    // Compute volume and surface area
167    let volume = volume::compute_volume(hull)?;
168    let surface_area = surface_area::compute_surface_area(hull)?;
169
170    // Compute derived properties
171    let surface_to_volume_ratio = surface_area::compute_surface_to_volume_ratio(hull)?;
172    let compactness = surface_area::compute_compactness(hull)?;
173
174    // Check reliability
175    let volume_reliable = volume::is_volume_computation_reliable(hull);
176    let surface_area_reliable = surface_area::is_surface_area_computation_reliable(hull);
177
178    // Compute bounding box
179    use crate::convex_hull::geometry::compute_bounding_box;
180    let (min_coords, max_coords) = compute_bounding_box(&hull.points.view(), &hull.vertex_indices);
181    let bounding_box_size: Vec<f64> = min_coords
182        .iter()
183        .zip(max_coords.iter())
184        .map(|(min, max)| max - min)
185        .collect();
186
187    // Compute centroid
188    use crate::convex_hull::geometry::compute_centroid;
189    let centroid = compute_centroid(&hull.points.view(), &hull.vertex_indices);
190
191    Ok(HullAnalysis {
192        ndim,
193        num_vertices,
194        num_facets,
195        volume,
196        surface_area,
197        surface_to_volume_ratio,
198        compactness,
199        volume_reliable,
200        surface_area_reliable,
201        bounding_box_size,
202        centroid,
203    })
204}
205
206/// Get geometric statistics for a convex hull
207///
208/// Returns basic statistics about the hull's vertices and structure.
209///
210/// # Arguments
211///
212/// * `hull` - The convex hull
213///
214/// # Returns
215///
216/// * Result containing geometric statistics
217///
218/// # Examples
219///
220/// ```rust
221/// use scirs2_spatial::convex_hull::ConvexHull;
222/// use scirs2_spatial::convex_hull::properties::get_hull_statistics;
223/// use ndarray::array;
224///
225/// let points = array![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
226/// let hull = ConvexHull::new(&points.view()).unwrap();
227/// let stats = get_hull_statistics(&hull).unwrap();
228///
229/// println!("Hull Statistics: {:#?}", stats);
230/// ```
231#[derive(Debug, Clone)]
232pub struct HullStatistics {
233    /// Number of input points
234    pub num_input_points: usize,
235    /// Number of hull vertices
236    pub num_hull_vertices: usize,
237    /// Fraction of input points that are hull vertices
238    pub hull_vertex_fraction: f64,
239    /// Number of facets/edges
240    pub num_facets: usize,
241    /// Average edge/facet size
242    pub avg_facet_size: f64,
243    /// Minimum distance between vertices
244    pub min_vertex_distance: f64,
245    /// Maximum distance between vertices  
246    pub max_vertex_distance: f64,
247    /// Average distance from centroid to vertices
248    pub avg_centroid_distance: f64,
249}
250
251/// Get geometric statistics for a convex hull
252pub fn get_hull_statistics(
253    hull: &crate::convex_hull::core::ConvexHull,
254) -> crate::error::SpatialResult<HullStatistics> {
255    let num_input_points = hull.points.nrows();
256    let num_hull_vertices = hull.vertex_indices.len();
257    let hull_vertex_fraction = num_hull_vertices as f64 / num_input_points as f64;
258    let num_facets = hull.simplices.len();
259
260    // Average facet size
261    let total_facet_size: usize = hull.simplices.iter().map(|s| s.len()).sum();
262    let avg_facet_size = if num_facets > 0 {
263        total_facet_size as f64 / num_facets as f64
264    } else {
265        0.0
266    };
267
268    // Distance calculations
269    let mut min_distance = f64::INFINITY;
270    let mut max_distance: f64 = 0.0;
271    let ndim = hull.ndim();
272
273    // Compute pairwise distances between vertices
274    for i in 0..num_hull_vertices {
275        for j in (i + 1)..num_hull_vertices {
276            let idx1 = hull.vertex_indices[i];
277            let idx2 = hull.vertex_indices[j];
278
279            let mut dist_sq: f64 = 0.0;
280            for d in 0..ndim {
281                let diff = hull.points[[idx2, d]] - hull.points[[idx1, d]];
282                dist_sq += diff * diff;
283            }
284            let distance = dist_sq.sqrt();
285
286            min_distance = min_distance.min(distance);
287            max_distance = max_distance.max(distance);
288        }
289    }
290
291    // Average distance from centroid to vertices
292    use crate::convex_hull::geometry::compute_centroid;
293    let centroid = compute_centroid(&hull.points.view(), &hull.vertex_indices);
294
295    let mut total_centroid_distance = 0.0;
296    for &vertex_idx in &hull.vertex_indices {
297        let mut dist_sq: f64 = 0.0;
298        for d in 0..ndim {
299            let diff = hull.points[[vertex_idx, d]] - centroid[d];
300            dist_sq += diff * diff;
301        }
302        total_centroid_distance += dist_sq.sqrt();
303    }
304    let avg_centroid_distance = if num_hull_vertices > 0 {
305        total_centroid_distance / num_hull_vertices as f64
306    } else {
307        0.0
308    };
309
310    if min_distance == f64::INFINITY {
311        min_distance = 0.0;
312    }
313
314    Ok(HullStatistics {
315        num_input_points,
316        num_hull_vertices,
317        hull_vertex_fraction,
318        num_facets,
319        avg_facet_size,
320        min_vertex_distance: min_distance,
321        max_vertex_distance: max_distance,
322        avg_centroid_distance,
323    })
324}
325
326#[cfg(test)]
327mod tests {
328    use super::*;
329    use crate::convex_hull::ConvexHull;
330    use ndarray::arr2;
331
332    #[test]
333    fn test_analyze_hull() {
334        let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
335        let hull = ConvexHull::new(&points.view()).unwrap();
336        let analysis = analyze_hull(&hull).unwrap();
337
338        assert_eq!(analysis.ndim, 2);
339        assert_eq!(analysis.num_vertices, 4);
340        assert!((analysis.volume - 1.0).abs() < 1e-10); // Area = 1
341        assert!((analysis.surface_area - 4.0).abs() < 1e-10); // Perimeter = 4
342        assert!((analysis.surface_to_volume_ratio - 4.0).abs() < 1e-10);
343        assert!(analysis.compactness > 0.7);
344        assert!(analysis.volume_reliable);
345        assert!(analysis.surface_area_reliable);
346        assert_eq!(analysis.bounding_box_size, vec![1.0, 1.0]);
347        assert_eq!(analysis.centroid, vec![0.5, 0.5]);
348    }
349
350    #[test]
351    fn test_get_hull_statistics() {
352        let points = arr2(&[
353            [0.0, 0.0],
354            [1.0, 0.0],
355            [1.0, 1.0],
356            [0.0, 1.0],
357            [0.5, 0.5], // Interior point
358        ]);
359        let hull = ConvexHull::new(&points.view()).unwrap();
360        let stats = get_hull_statistics(&hull).unwrap();
361
362        assert_eq!(stats.num_input_points, 5);
363        assert!(stats.num_hull_vertices <= 4); // Interior point should not be a vertex
364        assert!(stats.hull_vertex_fraction <= 0.8);
365        assert!(stats.num_facets >= 4); // At least 4 edges for square
366        assert!(stats.min_vertex_distance > 0.0);
367        assert!(stats.max_vertex_distance > stats.min_vertex_distance);
368        assert!(stats.avg_centroid_distance > 0.0);
369    }
370
371    #[test]
372    fn test_triangle_analysis() {
373        let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
374        let hull = ConvexHull::new(&points.view()).unwrap();
375        let analysis = analyze_hull(&hull).unwrap();
376
377        assert_eq!(analysis.ndim, 2);
378        assert_eq!(analysis.num_vertices, 3);
379        assert!((analysis.volume - 0.5).abs() < 1e-10); // Area = 0.5
380        assert!(analysis.compactness > 0.0 && analysis.compactness <= 1.0);
381    }
382
383    #[test]
384    fn test_3d_analysis() {
385        let points = arr2(&[
386            [0.0, 0.0, 0.0],
387            [1.0, 0.0, 0.0],
388            [0.0, 1.0, 0.0],
389            [0.0, 0.0, 1.0],
390        ]);
391        let hull = ConvexHull::new(&points.view()).unwrap();
392        let analysis = analyze_hull(&hull).unwrap();
393
394        assert_eq!(analysis.ndim, 3);
395        assert_eq!(analysis.num_vertices, 4);
396        assert!(analysis.volume > 0.0); // Tetrahedron has positive volume
397        assert!(analysis.surface_area > 0.0);
398        assert!(analysis.compactness > 0.0 && analysis.compactness <= 1.0);
399    }
400}