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}