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 scirs2_core::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 scirs2_core::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 scirs2_core::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 scirs2_core::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 scirs2_core::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 scirs2_core::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 scirs2_core::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}