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}