scirs2_spatial/convex_hull/mod.rs
1//! Convex hull algorithms and utilities
2//!
3//! This module provides comprehensive convex hull computation capabilities,
4//! supporting multiple algorithms and dimensions. It includes geometric
5//! property calculations and robust point containment testing.
6//!
7//! # Overview
8//!
9//! A convex hull is the smallest convex set that contains all given points.
10//! This module provides:
11//!
12//! - **Multiple algorithms**: QHull (nD), Graham Scan (2D), Jarvis March (2D)
13//! - **Geometric properties**: Volume, surface area, compactness measures
14//! - **Point queries**: Containment testing, distance calculations
15//! - **Robust handling**: Special cases, degenerate inputs, numerical precision
16//!
17//! # Quick Start
18//!
19//! ## Basic Usage
20//! ```rust
21//! use scirs2_spatial::convex_hull::{ConvexHull, convex_hull};
22//! use scirs2_core::ndarray::array;
23//!
24//! // Create points for the convex hull
25//! let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [0.5, 0.5]];
26//!
27//! // Compute convex hull (automatic algorithm selection)
28//! let hull = ConvexHull::new(&points.view()).unwrap();
29//!
30//! // Or use the convenience function
31//! let hull_vertices = convex_hull(&points.view()).unwrap();
32//!
33//! // Access hull properties
34//! println!("Hull vertices: {:?}", hull.vertices());
35//! println!("Hull volume: {}", hull.volume().unwrap());
36//! println!("Hull surface area: {}", hull.area().unwrap());
37//!
38//! // Test point containment
39//! let is_inside = hull.contains(&[0.25, 0.25]).unwrap();
40//! println!("Point inside: {}", is_inside);
41//! ```
42//!
43//! ## Algorithm Selection
44//! ```rust
45//! use scirs2_spatial::convex_hull::{ConvexHull, ConvexHullAlgorithm, convex_hull_with_algorithm};
46//! use scirs2_core::ndarray::array;
47//!
48//! let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [0.5, 0.5]];
49//!
50//! // Use specific algorithm
51//! let hull = ConvexHull::new_with_algorithm(&points.view(), ConvexHullAlgorithm::GrahamScan).unwrap();
52//!
53//! // Or use convenience function
54//! let hull_vertices = convex_hull_with_algorithm(&points.view(), ConvexHullAlgorithm::JarvisMarch).unwrap();
55//! ```
56//!
57//! ## Comprehensive Analysis
58//! ```rust
59//! use scirs2_spatial::convex_hull::{ConvexHull, analyze_hull};
60//! use scirs2_core::ndarray::array;
61//!
62//! let points = array![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
63//! let hull = ConvexHull::new(&points.view()).unwrap();
64//!
65//! // Get comprehensive analysis
66//! let analysis = analyze_hull(&hull).unwrap();
67//! println!("Hull Analysis: {:#?}", analysis);
68//! ```
69//!
70//! # Module Organization
71//!
72//! - [`core`] - Core ConvexHull struct and basic functionality
73//! - [`algorithms`] - Algorithm implementations (QHull, Graham Scan, Jarvis March)
74//! - [`geometry`] - Geometric utility functions for different dimensions
75//! - [`properties`] - Volume, surface area, and containment calculations
76//!
77//! # Algorithm Guide
78//!
79//! ## QHull (Recommended for most uses)
80//! - **Dimensions**: 1D, 2D, 3D, nD
81//! - **Time Complexity**: O(n log n) for 2D/3D, O(n^⌊d/2⌋) for higher dimensions
82//! - **Features**: Robust, handles degenerate cases, provides facet equations
83//! - **Use when**: General-purpose convex hull computation
84//!
85//! ## Graham Scan (2D only)
86//! - **Dimensions**: 2D only
87//! - **Time Complexity**: O(n log n)
88//! - **Features**: Simple, produces vertices in counterclockwise order
89//! - **Use when**: Educational purposes, guaranteed vertex ordering needed
90//!
91//! ## Jarvis March / Gift Wrapping (2D only)
92//! - **Dimensions**: 2D only
93//! - **Time Complexity**: O(nh) where h is number of hull vertices
94//! - **Features**: Output-sensitive, good for sparse hulls
95//! - **Use when**: Hull has few vertices compared to input size
96//!
97//! # Performance Tips
98//!
99//! 1. **Use QHull for general cases** - it's the most robust and efficient
100//! 2. **For 2D with small hulls** - consider Jarvis March for output-sensitive performance
101//! 3. **For large datasets** - consider preprocessing to remove interior points
102//! 4. **For high dimensions** - expect exponential complexity, consider approximations
103//!
104//! # Error Handling
105//!
106//! The module handles various error conditions gracefully:
107//! - Insufficient points for given dimension
108//! - Algorithm/dimension mismatches
109//! - Degenerate point configurations
110//! - Numerical precision issues
111
112pub mod algorithms;
113pub mod core;
114pub mod geometry;
115pub mod properties;
116
117// Re-export the main types and functions for backward compatibility
118pub use core::{ConvexHull, ConvexHullAlgorithm};
119
120// Re-export algorithm functions
121pub use algorithms::{
122 compute_graham_scan, compute_jarvis_march, compute_qhull, get_algorithm_complexity,
123 recommend_algorithm,
124};
125
126// Re-export geometry utilities (most commonly used ones)
127pub use geometry::{
128 compute_polygon_area, compute_polygon_perimeter, compute_polyhedron_volume, cross_product_2d,
129 cross_product_3d, tetrahedron_volume, triangle_area_3d,
130};
131
132// Re-export properties functions
133pub use properties::{
134 analyze_hull, check_point_containment, compute_surface_area, compute_volume,
135 get_hull_statistics,
136};
137
138// Main convenience functions for backward compatibility with original API
139
140/// Compute the convex hull of a set of points using the default algorithm
141///
142/// This is the main convenience function that provides the same interface
143/// as the original monolithic implementation.
144///
145/// # Arguments
146///
147/// * `points` - Input points (shape: npoints x n_dim)
148///
149/// # Returns
150///
151/// * A result containing either the convex hull vertices (shape: n_vertices x n_dim)
152/// or an error
153///
154/// # Examples
155///
156/// ```rust
157/// use scirs2_spatial::convex_hull::convex_hull;
158/// use scirs2_core::ndarray::array;
159///
160/// let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [0.5, 0.5]];
161/// let hull_vertices = convex_hull(&points.view()).unwrap();
162///
163/// // The hull vertices should be the corners, not the interior point
164/// assert!(hull_vertices.nrows() >= 3);
165/// ```
166#[allow(dead_code)]
167pub fn convex_hull(
168 points: &scirs2_core::ndarray::ArrayView2<'_, f64>,
169) -> crate::error::SpatialResult<scirs2_core::ndarray::Array2<f64>> {
170 let hull = ConvexHull::new(points)?;
171 Ok(hull.vertices_array())
172}
173
174/// Compute the convex hull of a set of points using a specific algorithm
175///
176/// This function provides algorithm selection while maintaining the same
177/// interface as the original implementation.
178///
179/// # Arguments
180///
181/// * `points` - Input points (shape: npoints x n_dim)
182/// * `algorithm` - Algorithm to use for convex hull computation
183///
184/// # Returns
185///
186/// * A result containing either the convex hull vertices (shape: n_vertices x n_dim)
187/// or an error
188///
189/// # Examples
190///
191/// ```rust
192/// use scirs2_spatial::convex_hull::{convex_hull_with_algorithm, ConvexHullAlgorithm};
193/// use scirs2_core::ndarray::array;
194///
195/// let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [0.5, 0.5]];
196/// let hull_vertices = convex_hull_with_algorithm(&points.view(), ConvexHullAlgorithm::GrahamScan).unwrap();
197/// assert!(hull_vertices.nrows() >= 3);
198/// ```
199#[allow(dead_code)]
200pub fn convex_hull_with_algorithm(
201 points: &scirs2_core::ndarray::ArrayView2<'_, f64>,
202 algorithm: ConvexHullAlgorithm,
203) -> crate::error::SpatialResult<scirs2_core::ndarray::Array2<f64>> {
204 let hull = ConvexHull::new_with_algorithm(points, algorithm)?;
205 Ok(hull.vertices_array())
206}
207
208/// Extended analysis and statistics for debugging and research
209///
210/// Advanced analysis functions for convex hulls
211///
212/// This module provides extended functionality for detailed hull analysis,
213/// research applications, and debugging purposes.
214pub mod advanced {
215
216 use super::*;
217
218 /// Detailed performance metrics for hull computation
219 #[derive(Debug, Clone)]
220 pub struct HullComputationMetrics {
221 /// Time taken for hull computation (if measured)
222 pub computation_time: Option<std::time::Duration>,
223 /// Memory usage during computation (if measured)
224 pub memory_usage: Option<usize>,
225 /// Number of iterations/steps in the algorithm
226 pub algorithm_steps: Option<usize>,
227 /// Whether special case handling was used
228 pub used_special_case: bool,
229 /// Whether the result is considered reliable
230 pub result_reliable: bool,
231 }
232
233 /// Compare different algorithms on the same point set
234 ///
235 /// This function runs multiple algorithms on the same point set and
236 /// compares their results, useful for algorithm validation and research.
237 ///
238 /// # Arguments
239 ///
240 /// * `points` - Input points
241 ///
242 /// # Returns
243 ///
244 /// * Results from different algorithms with comparison metrics
245 pub fn compare_algorithms(
246 points: &scirs2_core::ndarray::ArrayView2<'_, f64>,
247 ) -> crate::error::SpatialResult<
248 Vec<(ConvexHullAlgorithm, crate::error::SpatialResult<ConvexHull>)>,
249 > {
250 let mut results = Vec::new();
251
252 // Always try QHull
253 results.push((
254 ConvexHullAlgorithm::QHull,
255 ConvexHull::new_with_algorithm(points, ConvexHullAlgorithm::QHull),
256 ));
257
258 // For 2D, also try other algorithms
259 if points.ncols() == 2 && points.nrows() >= 3 {
260 results.push((
261 ConvexHullAlgorithm::GrahamScan,
262 ConvexHull::new_with_algorithm(points, ConvexHullAlgorithm::GrahamScan),
263 ));
264
265 results.push((
266 ConvexHullAlgorithm::JarvisMarch,
267 ConvexHull::new_with_algorithm(points, ConvexHullAlgorithm::JarvisMarch),
268 ));
269 }
270
271 Ok(results)
272 }
273
274 /// Validate hull correctness using multiple methods
275 ///
276 /// This function performs comprehensive validation of a hull computation,
277 /// useful for testing and validation purposes.
278 ///
279 /// # Arguments
280 ///
281 /// * `hull` - The hull to validate
282 /// * `original_points` - The original input points
283 ///
284 /// # Returns
285 ///
286 /// * Validation results and any issues found
287 pub fn validate_hull(
288 hull: &ConvexHull,
289 original_points: &scirs2_core::ndarray::ArrayView2<'_, f64>,
290 ) -> crate::error::SpatialResult<Vec<String>> {
291 let mut issues = Vec::new();
292
293 // Check that all original points are either hull vertices or inside the hull
294 for i in 0..original_points.nrows() {
295 let point = original_points.row(i);
296 let point_slice = point.as_slice().unwrap();
297
298 // Check if this point is a hull vertex
299 let is_vertex = hull.vertex_indices().iter().any(|&idx| {
300 let vertex = hull.points.row(idx);
301 vertex
302 .as_slice()
303 .unwrap()
304 .iter()
305 .zip(point_slice.iter())
306 .all(|(a, b)| (a - b).abs() < 1e-10)
307 });
308
309 // If not a vertex, it should be inside or on the hull
310 if !is_vertex {
311 match hull.contains(point_slice) {
312 Ok(inside) => {
313 if !inside {
314 issues.push(format!("Point {} is outside its own hull", i));
315 }
316 }
317 Err(e) => {
318 issues.push(format!("Failed to test containment for point {}: {}", i, e));
319 }
320 }
321 }
322 }
323
324 // Check hull properties for consistency
325 if let Ok(volume) = hull.volume() {
326 if volume < 0.0 {
327 issues.push("Hull volume is negative".to_string());
328 }
329 }
330
331 if let Ok(area) = hull.area() {
332 if area < 0.0 {
333 issues.push("Hull surface area is negative".to_string());
334 }
335 }
336
337 // Check vertex indices are valid
338 for &idx in hull.vertex_indices() {
339 if idx >= hull.points.nrows() {
340 issues.push(format!("Invalid vertex index: {}", idx));
341 }
342 }
343
344 Ok(issues)
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351 use scirs2_core::ndarray::arr2;
352
353 #[test]
354 fn test_convex_hull_function() {
355 let points = arr2(&[
356 [0.0, 0.0],
357 [1.0, 0.0],
358 [0.0, 1.0],
359 [0.5, 0.5], // Interior point
360 ]);
361
362 let hull_vertices = convex_hull(&points.view()).unwrap();
363
364 // The hull should have vertices in 2D
365 assert!(hull_vertices.nrows() >= 3); // At least 3 for triangular hull
366 assert_eq!(hull_vertices.ncols(), 2);
367 }
368
369 #[test]
370 fn test_convex_hull_with_algorithm_function() {
371 let points = arr2(&[
372 [0.0, 0.0],
373 [1.0, 0.0],
374 [0.0, 1.0],
375 [0.5, 0.5], // Interior point
376 ]);
377
378 // Test with Graham scan
379 let hull_vertices =
380 convex_hull_with_algorithm(&points.view(), ConvexHullAlgorithm::GrahamScan).unwrap();
381 assert_eq!(hull_vertices.nrows(), 3); // Should exclude interior point
382 assert_eq!(hull_vertices.ncols(), 2);
383
384 // Test with Jarvis march
385 let hull_vertices =
386 convex_hull_with_algorithm(&points.view(), ConvexHullAlgorithm::JarvisMarch).unwrap();
387 assert_eq!(hull_vertices.nrows(), 3); // Should exclude interior point
388 assert_eq!(hull_vertices.ncols(), 2);
389 }
390
391 #[test]
392 fn test_backward_compatibility() {
393 // Test that the new modular implementation produces the same results
394 // as the original monolithic implementation would have
395 let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
396
397 let hull = ConvexHull::new(&points.view()).unwrap();
398 let vertices = hull.vertices();
399 let vertex_indices = hull.vertex_indices();
400 let simplices = hull.simplices();
401
402 // Check basic properties
403 assert_eq!(hull.ndim(), 2);
404 assert_eq!(vertices.len(), vertex_indices.len());
405 assert!(!simplices.is_empty());
406
407 // Check volume (area) and surface area (perimeter)
408 let volume = hull.volume().unwrap();
409 let area = hull.area().unwrap();
410 assert!((volume - 1.0).abs() < 1e-10); // Unit square area
411 assert!((area - 4.0).abs() < 1e-10); // Unit square perimeter
412
413 // Check containment
414 assert!(hull.contains([0.5, 0.5]).unwrap()); // Center should be inside
415 assert!(!hull.contains([2.0, 2.0]).unwrap()); // Far point should be outside
416 }
417
418 #[test]
419 fn test_error_cases() {
420 // Too few points for a 2D hull
421 let too_few = arr2(&[[0.0, 0.0], [1.0, 0.0]]);
422 let result = ConvexHull::new(&too_few.view());
423 assert!(result.is_err());
424
425 // Valid hull but invalid point dimensionality for containment
426 let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
427 let hull = ConvexHull::new(&points.view()).unwrap();
428 let result = hull.contains([0.5, 0.5, 0.5]);
429 assert!(result.is_err());
430 }
431
432 #[test]
433 fn test_3d_hull() {
434 let points = arr2(&[
435 [0.0, 0.0, 0.0],
436 [1.0, 0.0, 0.0],
437 [0.0, 1.0, 0.0],
438 [0.0, 0.0, 1.0],
439 [0.5, 0.5, 0.5], // Interior point
440 ]);
441
442 let hull = ConvexHull::new(&points.view()).unwrap();
443
444 assert_eq!(hull.ndim(), 3);
445 assert!(hull.vertex_indices().len() >= 4); // At least tetrahedron vertices
446
447 // Interior point should be inside
448 assert!(hull.contains([0.25, 0.25, 0.25]).unwrap());
449 // Far point should be outside
450 assert!(!hull.contains([2.0, 2.0, 2.0]).unwrap());
451
452 // Volume and surface area should be positive
453 let volume = hull.volume().unwrap();
454 let surface_area = hull.area().unwrap();
455 assert!(volume > 0.0);
456 assert!(surface_area > 0.0);
457 }
458
459 #[test]
460 fn test_analysis_functions() {
461 let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
462 let hull = ConvexHull::new(&points.view()).unwrap();
463
464 let analysis = analyze_hull(&hull).unwrap();
465 assert_eq!(analysis.ndim, 2);
466 assert_eq!(analysis.num_vertices, 4);
467 assert!((analysis.volume - 1.0).abs() < 1e-10);
468 assert!((analysis.surface_area - 4.0).abs() < 1e-10);
469
470 let stats = get_hull_statistics(&hull).unwrap();
471 assert_eq!(stats.num_input_points, 4);
472 assert_eq!(stats.num_hull_vertices, 4);
473 assert_eq!(stats.hull_vertex_fraction, 1.0); // All points are vertices
474 }
475}