scirs2_spatial/convex_hull/algorithms/
mod.rs

1//! Convex hull algorithm implementations (Pure Rust)
2//!
3//! This module provides different algorithms for computing convex hulls,
4//! each with their own strengths and use cases. All implementations are
5//! pure Rust with no external C library dependencies.
6//!
7//! # Available Algorithms
8//!
9//! ## Quickhull (Pure Rust)
10//! - **Module**: [`quickhull`]
11//! - **Dimensions**: Any (1D, 2D, 3D, nD)
12//! - **Time Complexity**: O(n log n) for 2D/3D, O(n^⌊d/2⌋) for d dimensions
13//! - **Use Case**: General purpose, robust for all dimensions
14//! - **Features**: Pure Rust, handles degenerate cases, provides facet equations
15//!
16//! ## Graham Scan (Pure Rust)
17//! - **Module**: [`graham_scan`]
18//! - **Dimensions**: 2D only
19//! - **Time Complexity**: O(n log n)
20//! - **Use Case**: Educational, guaranteed output order
21//! - **Features**: Simple to understand, produces vertices in counterclockwise order
22//!
23//! ## Jarvis March (Gift Wrapping) (Pure Rust)
24//! - **Module**: [`jarvis_march`]
25//! - **Dimensions**: 2D only
26//! - **Time Complexity**: O(nh) where h is the number of hull vertices
27//! - **Use Case**: When hull has few vertices (h << n)
28//! - **Features**: Output-sensitive, good for small hulls
29//!
30//! ## Special Cases
31//! - **Module**: [`special_cases`]
32//! - **Purpose**: Handle degenerate cases (collinear points, identical points, etc.)
33//! - **Features**: Robust handling of edge cases that might break standard algorithms
34//!
35//! # Examples
36//!
37//! ## Using Quickhull (Recommended)
38//! ```rust
39//! use scirs2_spatial::convex_hull::algorithms::quickhull::compute_quickhull;
40//! use scirs2_core::ndarray::array;
41//!
42//! let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [0.5, 0.5]];
43//! let hull = compute_quickhull(&points.view()).expect("Operation failed");
44//! println!("Hull has {} vertices", hull.vertex_indices().len());
45//! ```
46//!
47//! ## Using Graham Scan for 2D
48//! ```rust
49//! use scirs2_spatial::convex_hull::algorithms::graham_scan::compute_graham_scan;
50//! use scirs2_core::ndarray::array;
51//!
52//! let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [0.5, 0.5]];
53//! let hull = compute_graham_scan(&points.view()).expect("Operation failed");
54//! println!("Hull vertices: {:?}", hull.vertex_indices());
55//! ```
56//!
57//! ## Using Jarvis March for Small Hulls
58//! ```rust
59//! use scirs2_spatial::convex_hull::algorithms::jarvis_march::compute_jarvis_march;
60//! use scirs2_core::ndarray::array;
61//!
62//! let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [0.5, 0.5]];
63//! let hull = compute_jarvis_march(&points.view()).expect("Operation failed");
64//! println!("Hull computed with {} vertices", hull.vertex_indices().len());
65//! ```
66//!
67//! ## Handling Special Cases
68//! ```rust
69//! use scirs2_spatial::convex_hull::algorithms::special_cases::handle_degenerate_case;
70//! use scirs2_core::ndarray::array;
71//!
72//! // Collinear points
73//! let collinear = array![[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]];
74//! if let Some(result) = handle_degenerate_case(&collinear.view()) {
75//!     let hull = result.expect("Operation failed");
76//!     println!("Handled degenerate case with {} vertices", hull.vertex_indices().len());
77//! }
78//! ```
79//!
80//! # Algorithm Selection Guidelines
81//!
82//! - **For general use**: Use Quickhull - it's robust and handles all dimensions
83//! - **For 2D educational purposes**: Use Graham Scan for its simplicity
84//! - **For 2D with small expected hulls**: Use Jarvis March for output-sensitive performance
85//! - **For edge cases**: All algorithms automatically fall back to special case handlers
86//!
87//! # Performance Characteristics
88//!
89//! | Algorithm | 2D Time | 3D Time | nD Time | Space | Robustness |
90//! |-----------|---------|---------|---------|-------|------------|
91//! | Quickhull | O(n log n) | O(n log n) | O(n^⌊d/2⌋) | O(n) | High |
92//! | Graham Scan | O(n log n) | N/A | N/A | O(n) | Medium |
93//! | Jarvis March | O(nh) | N/A | N/A | O(n) | Medium |
94//!
95//! Where:
96//! - n = number of input points
97//! - h = number of hull vertices
98//! - d = dimension
99
100pub mod graham_scan;
101pub mod jarvis_march;
102pub mod quickhull;
103pub mod special_cases;
104
105// Re-export the main computation functions for convenience
106pub use graham_scan::compute_graham_scan;
107pub use jarvis_march::compute_jarvis_march;
108pub use quickhull::compute_quickhull;
109pub use special_cases::{handle_degenerate_case, has_all_identical_points, is_all_collinear};
110
111/// Algorithm performance characteristics for selection guidance
112#[derive(Debug, Clone, Copy, PartialEq)]
113pub enum AlgorithmComplexity {
114    /// O(n log n) complexity - good for general use
115    NLogN,
116    /// O(nh) complexity - output sensitive, good for small hulls
117    OutputSensitive,
118    /// O(n^⌊d/2⌋) complexity - exponential in dimension
119    ExponentialDimension,
120}
121
122/// Get performance characteristics for each algorithm
123///
124/// # Arguments
125///
126/// * `algorithm` - The algorithm to query
127/// * `dimension` - The dimension of the problem
128///
129/// # Returns
130///
131/// * Tuple of (time_complexity, space_complexity, max_dimension)
132///
133/// # Examples
134///
135/// ```rust
136/// use scirs2_spatial::convex_hull::algorithms::{get_algorithm_complexity, AlgorithmComplexity};
137/// use scirs2_spatial::convex_hull::ConvexHullAlgorithm;
138///
139/// let (time, space, max_dim) = get_algorithm_complexity(ConvexHullAlgorithm::GrahamScan, 2);
140/// assert_eq!(time, AlgorithmComplexity::NLogN);
141/// assert_eq!(max_dim, Some(2));
142/// ```
143pub fn get_algorithm_complexity(
144    algorithm: crate::convex_hull::core::ConvexHullAlgorithm,
145    dimension: usize,
146) -> (AlgorithmComplexity, AlgorithmComplexity, Option<usize>) {
147    use crate::convex_hull::core::ConvexHullAlgorithm;
148
149    match algorithm {
150        ConvexHullAlgorithm::Quickhull => {
151            let time_complexity = if dimension <= 3 {
152                AlgorithmComplexity::NLogN
153            } else {
154                AlgorithmComplexity::ExponentialDimension
155            };
156            (time_complexity, AlgorithmComplexity::NLogN, None) // No dimension limit
157        }
158        ConvexHullAlgorithm::GrahamScan => (
159            AlgorithmComplexity::NLogN,
160            AlgorithmComplexity::NLogN,
161            Some(2),
162        ),
163        ConvexHullAlgorithm::JarvisMarch => (
164            AlgorithmComplexity::OutputSensitive,
165            AlgorithmComplexity::NLogN,
166            Some(2),
167        ),
168    }
169}
170
171/// Recommend the best algorithm for given constraints
172///
173/// # Arguments
174///
175/// * `num_points` - Number of input points
176/// * `dimension` - Dimension of the points
177/// * `expected_hull_size` - Expected number of hull vertices (None if unknown)
178///
179/// # Returns
180///
181/// * Recommended algorithm
182///
183/// # Examples
184///
185/// ```rust
186/// use scirs2_spatial::convex_hull::algorithms::recommend_algorithm;
187/// use scirs2_spatial::convex_hull::ConvexHullAlgorithm;
188///
189/// // Large 2D dataset with small expected hull
190/// let algo = recommend_algorithm(10000, 2, Some(8));
191/// assert_eq!(algo, ConvexHullAlgorithm::JarvisMarch);
192///
193/// // 3D dataset
194/// let algo = recommend_algorithm(1000, 3, None);
195/// assert_eq!(algo, ConvexHullAlgorithm::Quickhull);
196/// ```
197pub fn recommend_algorithm(
198    num_points: usize,
199    dimension: usize,
200    expected_hull_size: Option<usize>,
201) -> crate::convex_hull::core::ConvexHullAlgorithm {
202    use crate::convex_hull::core::ConvexHullAlgorithm;
203
204    // For dimensions > 2, only Quickhull is available
205    if dimension > 2 {
206        return ConvexHullAlgorithm::Quickhull;
207    }
208
209    // For 2D, we have choices
210    if dimension == 2 {
211        if let Some(hull_size) = expected_hull_size {
212            // If hull is expected to be small relative to input size, use Jarvis March
213            if hull_size * ((num_points as f64).log2() as usize) < num_points {
214                return ConvexHullAlgorithm::JarvisMarch;
215            }
216        }
217
218        // For educational purposes or when you need guaranteed vertex order
219        // you might prefer Graham Scan, but for general robustness, use Quickhull
220        if num_points < 100 {
221            ConvexHullAlgorithm::GrahamScan // Simple and fast for small datasets
222        } else {
223            ConvexHullAlgorithm::Quickhull // Most robust for larger datasets
224        }
225    } else {
226        // For 1D or other edge cases
227        ConvexHullAlgorithm::Quickhull
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234    use crate::convex_hull::core::ConvexHullAlgorithm;
235
236    #[test]
237    fn test_get_algorithm_complexity() {
238        let (time, space, max_dim) = get_algorithm_complexity(ConvexHullAlgorithm::GrahamScan, 2);
239        assert_eq!(time, AlgorithmComplexity::NLogN);
240        assert_eq!(space, AlgorithmComplexity::NLogN);
241        assert_eq!(max_dim, Some(2));
242
243        let (time, space, max_dim) = get_algorithm_complexity(ConvexHullAlgorithm::JarvisMarch, 2);
244        assert_eq!(time, AlgorithmComplexity::OutputSensitive);
245        assert_eq!(max_dim, Some(2));
246
247        let (time, _space, max_dim) = get_algorithm_complexity(ConvexHullAlgorithm::Quickhull, 5);
248        assert_eq!(time, AlgorithmComplexity::ExponentialDimension);
249        assert_eq!(max_dim, None);
250    }
251
252    #[test]
253    fn test_recommend_algorithm() {
254        // Large 2D dataset with small expected hull
255        let algo = recommend_algorithm(10000, 2, Some(8));
256        assert_eq!(algo, ConvexHullAlgorithm::JarvisMarch);
257
258        // 3D dataset
259        let algo = recommend_algorithm(1000, 3, None);
260        assert_eq!(algo, ConvexHullAlgorithm::Quickhull);
261
262        // Small 2D dataset
263        let algo = recommend_algorithm(50, 2, None);
264        assert_eq!(algo, ConvexHullAlgorithm::GrahamScan);
265
266        // Large 2D dataset without hull size hint
267        let algo = recommend_algorithm(5000, 2, None);
268        assert_eq!(algo, ConvexHullAlgorithm::Quickhull);
269    }
270}