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