scirs2_spatial/convex_hull/geometry/high_dimensional.rs
1//! High-dimensional geometric calculations for convex hull operations
2//!
3//! This module provides utility functions for geometric computations
4//! in dimensions higher than 3, commonly used in high-dimensional
5//! convex hull algorithms.
6
7use crate::error::SpatialResult;
8use scirs2_core::ndarray::ArrayView2;
9
10/// Compute volume for high-dimensional convex hulls using facet equations
11///
12/// Uses the divergence theorem approach for high-dimensional volume calculation.
13///
14/// # Arguments
15///
16/// * `points` - Input points array
17/// * `vertex_indices` - Indices of hull vertices
18/// * `equations` - Facet equations in the form [a₁, a₂, ..., aₙ, b] where
19/// aᵢx₍ᵢ₎ + b ≤ 0 defines the half-space
20///
21/// # Returns
22///
23/// * Result containing the high-dimensional volume
24///
25/// # Examples
26///
27/// ```
28/// use scirs2_spatial::convex_hull::geometry::high_dimensional::compute_high_dim_volume;
29/// use scirs2_core::ndarray::array;
30///
31/// // 4D hypercube vertices (subset)
32/// let points = array![
33/// [0.0, 0.0, 0.0, 0.0],
34/// [1.0, 0.0, 0.0, 0.0],
35/// [0.0, 1.0, 0.0, 0.0],
36/// [0.0, 0.0, 1.0, 0.0],
37/// [0.0, 0.0, 0.0, 1.0]
38/// ];
39/// let vertices = vec![0, 1, 2, 3, 4];
40/// let equations = array![
41/// [1.0, 0.0, 0.0, 0.0, 0.0],
42/// [-1.0, 0.0, 0.0, 0.0, 1.0]
43/// ];
44///
45/// let volume = compute_high_dim_volume(&points.view(), &vertices, &equations.view()).unwrap();
46/// assert!(volume >= 0.0);
47/// ```
48pub fn compute_high_dim_volume(
49 points: &ArrayView2<'_, f64>,
50 vertex_indices: &[usize],
51 equations: &ArrayView2<'_, f64>,
52) -> SpatialResult<f64> {
53 let ndim = points.ncols();
54 let nfacets = equations.nrows();
55
56 if nfacets == 0 {
57 return Ok(0.0);
58 }
59
60 // For high-dimensional convex hulls, we use the divergence theorem:
61 // V = (1/d) * Σ(A_i * h_i)
62 // where A_i is the area of facet i and h_i is the distance from origin to facet i
63
64 let mut total_volume = 0.0;
65
66 // Find a point inside the hull (centroid of vertices)
67 let mut centroid = vec![0.0; ndim];
68 for &vertex_idx in vertex_indices {
69 for d in 0..ndim {
70 centroid[d] += points[[vertex_idx, d]];
71 }
72 }
73 for item in centroid.iter_mut().take(ndim) {
74 *item /= vertex_indices.len() as f64;
75 }
76
77 // Process each facet
78 for facet_idx in 0..nfacets {
79 // Extract normal vector and offset from equation
80 let mut normal = vec![0.0; ndim];
81 for d in 0..ndim {
82 normal[d] = equations[[facet_idx, d]];
83 }
84 let offset = equations[[facet_idx, ndim]];
85
86 // Normalize the normal vector
87 let normal_length = (normal.iter().map(|x| x * x).sum::<f64>()).sqrt();
88 if normal_length < 1e-12 {
89 continue; // Skip degenerate facets
90 }
91
92 for item in normal.iter_mut().take(ndim) {
93 *item /= normal_length;
94 }
95 let normalized_offset = offset / normal_length;
96
97 // Distance from centroid to facet plane
98 let distance_to_centroid: f64 = normal
99 .iter()
100 .zip(centroid.iter())
101 .map(|(n, c)| n * c)
102 .sum::<f64>()
103 + normalized_offset;
104
105 // For volume computation, we need to use the absolute distance
106 // The contribution of this facet to the volume calculation
107 let height = distance_to_centroid.abs();
108
109 // For high-dimensional case, we approximate the facet area
110 // This is a simplified approach - a full implementation would compute exact facet areas
111 let facet_area = estimate_facet_area(points, vertex_indices, facet_idx, ndim)?;
112
113 // Add this facet's contribution to the total volume
114 total_volume += facet_area * height;
115 }
116
117 // Final volume is divided by dimension
118 let volume = total_volume / (ndim as f64);
119
120 Ok(volume)
121}
122
123/// Estimate the area of a facet in high dimensions
124///
125/// This is a simplified approach that works for well-formed convex hulls.
126///
127/// # Arguments
128///
129/// * `points` - Input points array
130/// * `vertex_indices` - Indices of hull vertices
131/// * `facet_idx` - Index of the facet to estimate (currently unused in simple estimation)
132/// * `ndim` - Number of dimensions
133///
134/// # Returns
135///
136/// * Result containing the estimated facet area
137pub fn estimate_facet_area(
138 points: &ArrayView2<'_, f64>,
139 vertex_indices: &[usize],
140 _facet_idx: usize,
141 ndim: usize,
142) -> SpatialResult<f64> {
143 // For high dimensions, computing exact facet areas is complex
144 // We use a simplified estimation based on the convex hull size
145
146 // Calculate the bounding box volume as a reference
147 let mut min_coords = vec![f64::INFINITY; ndim];
148 let mut max_coords = vec![f64::NEG_INFINITY; ndim];
149
150 for &vertex_idx in vertex_indices {
151 for d in 0..ndim {
152 let coord = points[[vertex_idx, d]];
153 min_coords[d] = min_coords[d].min(coord);
154 max_coords[d] = max_coords[d].max(coord);
155 }
156 }
157
158 // Calculate the characteristic size of the hull
159 let mut size_product = 1.0;
160 for d in 0..ndim {
161 let size = max_coords[d] - min_coords[d];
162 if size > 0.0 {
163 size_product *= size;
164 }
165 }
166
167 // Estimate facet area as a fraction of the hull's characteristic area
168 // This is approximate but provides reasonable results for well-formed hulls
169 let estimated_area =
170 size_product.powf((ndim - 1) as f64 / ndim as f64) / vertex_indices.len() as f64;
171
172 Ok(estimated_area)
173}
174
175/// Compute surface area (hypervolume) for high-dimensional convex hulls
176///
177/// # Arguments
178///
179/// * `points` - Input points array
180/// * `vertex_indices` - Indices of hull vertices
181/// * `equations` - Facet equations
182///
183/// # Returns
184///
185/// * Result containing the surface area/hypervolume
186///
187/// # Examples
188///
189/// ```
190/// use scirs2_spatial::convex_hull::geometry::high_dimensional::compute_high_dim_surface_area;
191/// use scirs2_core::ndarray::array;
192///
193/// // 4D simplex
194/// let points = array![
195/// [0.0, 0.0, 0.0, 0.0],
196/// [1.0, 0.0, 0.0, 0.0],
197/// [0.0, 1.0, 0.0, 0.0],
198/// [0.0, 0.0, 1.0, 0.0],
199/// [0.0, 0.0, 0.0, 1.0]
200/// ];
201/// let vertices = vec![0, 1, 2, 3, 4];
202/// let equations = array![
203/// [1.0, 0.0, 0.0, 0.0, 0.0],
204/// [0.0, 1.0, 0.0, 0.0, 0.0],
205/// [0.0, 0.0, 1.0, 0.0, 0.0],
206/// [0.0, 0.0, 0.0, 1.0, 0.0],
207/// [-1.0, -1.0, -1.0, -1.0, 1.0]
208/// ];
209///
210/// let surface_area = compute_high_dim_surface_area(&points.view(), &vertices, &equations.view()).unwrap();
211/// assert!(surface_area >= 0.0);
212/// ```
213pub fn compute_high_dim_surface_area(
214 points: &ArrayView2<'_, f64>,
215 vertex_indices: &[usize],
216 equations: &ArrayView2<'_, f64>,
217) -> SpatialResult<f64> {
218 let ndim = points.ncols();
219 let nfacets = equations.nrows();
220
221 if nfacets == 0 {
222 return Ok(0.0);
223 }
224
225 let mut total_surface_area = 0.0;
226
227 // Sum the areas of all facets
228 for facet_idx in 0..nfacets {
229 let facet_area = estimate_facet_area(points, vertex_indices, facet_idx, ndim)?;
230 total_surface_area += facet_area;
231 }
232
233 Ok(total_surface_area)
234}
235
236/// Compute the centroid of a set of high-dimensional points
237///
238/// # Arguments
239///
240/// * `points` - Input points array
241/// * `vertex_indices` - Indices of points to include in centroid calculation
242///
243/// # Returns
244///
245/// * Vector representing the centroid coordinates
246///
247/// # Examples
248///
249/// ```
250/// use scirs2_spatial::convex_hull::geometry::high_dimensional::compute_centroid;
251/// use scirs2_core::ndarray::array;
252///
253/// let points = array![
254/// [0.0, 0.0, 0.0],
255/// [3.0, 0.0, 0.0],
256/// [0.0, 3.0, 0.0],
257/// [0.0, 0.0, 3.0]
258/// ];
259/// let vertices = vec![0, 1, 2, 3];
260///
261/// let centroid = compute_centroid(&points.view(), &vertices);
262/// assert_eq!(centroid, vec![0.75, 0.75, 0.75]);
263/// ```
264pub fn compute_centroid(points: &ArrayView2<'_, f64>, vertex_indices: &[usize]) -> Vec<f64> {
265 let ndim = points.ncols();
266 let mut centroid = vec![0.0; ndim];
267
268 for &vertex_idx in vertex_indices {
269 for d in 0..ndim {
270 centroid[d] += points[[vertex_idx, d]];
271 }
272 }
273
274 for item in centroid.iter_mut().take(ndim) {
275 *item /= vertex_indices.len() as f64;
276 }
277
278 centroid
279}
280
281/// Compute the bounding box of a set of high-dimensional points
282///
283/// # Arguments
284///
285/// * `points` - Input points array
286/// * `vertex_indices` - Indices of points to include
287///
288/// # Returns
289///
290/// * Tuple of (min_coords, max_coords) vectors
291///
292/// # Examples
293///
294/// ```
295/// use scirs2_spatial::convex_hull::geometry::high_dimensional::compute_bounding_box;
296/// use scirs2_core::ndarray::array;
297///
298/// let points = array![
299/// [0.0, 5.0, -1.0],
300/// [3.0, 0.0, 2.0],
301/// [1.0, 3.0, 0.0]
302/// ];
303/// let vertices = vec![0, 1, 2];
304///
305/// let (min_coords, max_coords) = compute_bounding_box(&points.view(), &vertices);
306/// assert_eq!(min_coords, vec![0.0, 0.0, -1.0]);
307/// assert_eq!(max_coords, vec![3.0, 5.0, 2.0]);
308/// ```
309pub fn compute_bounding_box(
310 points: &ArrayView2<'_, f64>,
311 vertex_indices: &[usize],
312) -> (Vec<f64>, Vec<f64>) {
313 let ndim = points.ncols();
314 let mut min_coords = vec![f64::INFINITY; ndim];
315 let mut max_coords = vec![f64::NEG_INFINITY; ndim];
316
317 for &vertex_idx in vertex_indices {
318 for d in 0..ndim {
319 let coord = points[[vertex_idx, d]];
320 min_coords[d] = min_coords[d].min(coord);
321 max_coords[d] = max_coords[d].max(coord);
322 }
323 }
324
325 (min_coords, max_coords)
326}
327
328/// Compute the characteristic size of a high-dimensional convex hull
329///
330/// This is used as a reference measure for volume and surface area estimations.
331///
332/// # Arguments
333///
334/// * `points` - Input points array
335/// * `vertex_indices` - Indices of hull vertices
336///
337/// # Returns
338///
339/// * Characteristic size (geometric mean of bounding box dimensions)
340///
341/// # Examples
342///
343/// ```
344/// use scirs2_spatial::convex_hull::geometry::high_dimensional::compute_characteristic_size;
345/// use scirs2_core::ndarray::array;
346///
347/// let points = array![
348/// [0.0, 0.0, 0.0],
349/// [2.0, 0.0, 0.0],
350/// [0.0, 4.0, 0.0],
351/// [0.0, 0.0, 8.0]
352/// ];
353/// let vertices = vec![0, 1, 2, 3];
354///
355/// let size = compute_characteristic_size(&points.view(), &vertices);
356/// assert!(size > 0.0);
357/// ```
358pub fn compute_characteristic_size(points: &ArrayView2<'_, f64>, vertex_indices: &[usize]) -> f64 {
359 let (min_coords, max_coords) = compute_bounding_box(points, vertex_indices);
360 let ndim = min_coords.len();
361
362 let mut size_product = 1.0;
363 let mut valid_dims = 0;
364
365 for d in 0..ndim {
366 let size = max_coords[d] - min_coords[d];
367 if size > 1e-12 {
368 size_product *= size;
369 valid_dims += 1;
370 }
371 }
372
373 if valid_dims == 0 {
374 0.0
375 } else {
376 size_product.powf(1.0 / valid_dims as f64)
377 }
378}
379
380#[cfg(test)]
381mod tests {
382 use super::*;
383 use scirs2_core::ndarray::arr2;
384
385 #[test]
386 fn test_compute_centroid() {
387 let points = arr2(&[
388 [0.0, 0.0, 0.0],
389 [3.0, 0.0, 0.0],
390 [0.0, 3.0, 0.0],
391 [0.0, 0.0, 3.0],
392 ]);
393 let vertices = vec![0, 1, 2, 3];
394
395 let centroid = compute_centroid(&points.view(), &vertices);
396 assert_eq!(centroid, vec![0.75, 0.75, 0.75]);
397 }
398
399 #[test]
400 fn test_compute_bounding_box() {
401 let points = arr2(&[[0.0, 5.0, -1.0], [3.0, 0.0, 2.0], [1.0, 3.0, 0.0]]);
402 let vertices = vec![0, 1, 2];
403
404 let (min_coords, max_coords) = compute_bounding_box(&points.view(), &vertices);
405 assert_eq!(min_coords, vec![0.0, 0.0, -1.0]);
406 assert_eq!(max_coords, vec![3.0, 5.0, 2.0]);
407 }
408
409 #[test]
410 fn test_compute_characteristic_size() {
411 let points = arr2(&[
412 [0.0, 0.0, 0.0],
413 [2.0, 0.0, 0.0],
414 [0.0, 4.0, 0.0],
415 [0.0, 0.0, 8.0],
416 ]);
417 let vertices = vec![0, 1, 2, 3];
418
419 let size = compute_characteristic_size(&points.view(), &vertices);
420 assert!(size > 0.0);
421 // Geometric mean of [2, 4, 8] = (2*4*8)^(1/3) = 64^(1/3) = 4
422 assert!((size - 4.0).abs() < 1e-10);
423 }
424
425 #[test]
426 fn test_estimate_facet_area() {
427 let points = arr2(&[
428 [0.0, 0.0, 0.0, 0.0],
429 [1.0, 0.0, 0.0, 0.0],
430 [0.0, 1.0, 0.0, 0.0],
431 [0.0, 0.0, 1.0, 0.0],
432 [0.0, 0.0, 0.0, 1.0],
433 ]);
434 let vertices = vec![0, 1, 2, 3, 4];
435
436 let area = estimate_facet_area(&points.view(), &vertices, 0, 4).unwrap();
437 assert!(area > 0.0);
438 }
439
440 #[test]
441 fn test_high_dim_surface_area() {
442 let points = arr2(&[
443 [0.0, 0.0, 0.0, 0.0],
444 [1.0, 0.0, 0.0, 0.0],
445 [0.0, 1.0, 0.0, 0.0],
446 [0.0, 0.0, 1.0, 0.0],
447 [0.0, 0.0, 0.0, 1.0],
448 ]);
449 let vertices = vec![0, 1, 2, 3, 4];
450 let equations = arr2(&[
451 [1.0, 0.0, 0.0, 0.0, 0.0],
452 [0.0, 1.0, 0.0, 0.0, 0.0],
453 [0.0, 0.0, 1.0, 0.0, 0.0],
454 [0.0, 0.0, 0.0, 1.0, 0.0],
455 [-1.0, -1.0, -1.0, -1.0, 1.0],
456 ]);
457
458 let surface_area =
459 compute_high_dim_surface_area(&points.view(), &vertices, &equations.view()).unwrap();
460 assert!(surface_area >= 0.0);
461 }
462
463 #[test]
464 fn test_high_dim_volume() {
465 let points = arr2(&[
466 [0.0, 0.0, 0.0, 0.0],
467 [1.0, 0.0, 0.0, 0.0],
468 [0.0, 1.0, 0.0, 0.0],
469 [0.0, 0.0, 1.0, 0.0],
470 [0.0, 0.0, 0.0, 1.0],
471 ]);
472 let vertices = vec![0, 1, 2, 3, 4];
473 let equations = arr2(&[[1.0, 0.0, 0.0, 0.0, 0.0], [-1.0, 0.0, 0.0, 0.0, 1.0]]);
474
475 let volume = compute_high_dim_volume(&points.view(), &vertices, &equations.view()).unwrap();
476 assert!(volume >= 0.0);
477 }
478}