use crate::convex_hull::core::ConvexHull;
use crate::convex_hull::geometry::{
compute_high_dim_volume, compute_polygon_area, compute_polyhedron_volume,
};
use crate::error::SpatialResult;
pub fn compute_volume(hull: &ConvexHull) -> SpatialResult<f64> {
match hull.ndim() {
1 => compute_1d_volume(hull),
2 => compute_2d_volume(hull),
3 => compute_3d_volume(hull),
_ => compute_nd_volume(hull),
}
}
fn compute_1d_volume(hull: &ConvexHull) -> SpatialResult<f64> {
if hull.vertex_indices.len() < 2 {
return Ok(0.0);
}
let min_idx = *hull.vertex_indices.iter().min().expect("Operation failed");
let max_idx = *hull.vertex_indices.iter().max().expect("Operation failed");
Ok((hull.points[[max_idx, 0]] - hull.points[[min_idx, 0]]).abs())
}
fn compute_2d_volume(hull: &ConvexHull) -> SpatialResult<f64> {
if hull.vertex_indices.len() < 3 {
return Ok(0.0);
}
compute_polygon_area(&hull.points.view(), &hull.vertex_indices)
}
fn compute_3d_volume(hull: &ConvexHull) -> SpatialResult<f64> {
if hull.vertex_indices.len() < 4 {
return Ok(0.0);
}
compute_polyhedron_volume(&hull.points.view(), &hull.simplices)
}
fn compute_nd_volume(hull: &ConvexHull) -> SpatialResult<f64> {
if let Some(equations) = &hull.equations {
compute_high_dim_volume(&hull.points.view(), &hull.vertex_indices, &equations.view())
} else {
use crate::error::SpatialError;
Err(SpatialError::NotImplementedError(
"Volume computation for dimensions > 3 requires facet equations".to_string(),
))
}
}
pub fn compute_volume_monte_carlo(hull: &ConvexHull, num_samples: usize) -> SpatialResult<f64> {
use crate::convex_hull::geometry::compute_bounding_box;
use scirs2_core::random::{Rng, RngExt};
if hull.vertex_indices.is_empty() {
return Ok(0.0);
}
let ndim = hull.ndim();
let (min_coords, max_coords) = compute_bounding_box(&hull.points.view(), &hull.vertex_indices);
let mut bbox_volume = 1.0;
for d in 0..ndim {
let size = max_coords[d] - min_coords[d];
if size <= 0.0 {
return Ok(0.0); }
bbox_volume *= size;
}
let mut inside_count = 0;
let mut rng = scirs2_core::random::rng();
for _ in 0..num_samples {
let mut sample_point = vec![0.0; ndim];
for d in 0..ndim {
sample_point[d] = rng.random_range(min_coords[d]..max_coords[d]);
}
if hull.contains(sample_point.as_slice()).unwrap_or(false) {
inside_count += 1;
}
}
let fraction_inside = inside_count as f64 / num_samples as f64;
let estimated_volume = fraction_inside * bbox_volume;
Ok(estimated_volume)
}
pub fn compute_volume_bounds(hull: &ConvexHull) -> SpatialResult<(f64, f64, Option<f64>)> {
if hull.ndim() <= 3 {
let exact = compute_volume(hull)?;
return Ok((exact, exact, Some(exact)));
}
let exact = compute_volume(hull).ok();
if let Some(vol) = exact {
Ok((vol, vol, Some(vol)))
} else {
let lower_samples = 1000;
let upper_samples = 10000;
let lower_bound = compute_volume_monte_carlo(hull, lower_samples)?;
let upper_bound = compute_volume_monte_carlo(hull, upper_samples)?;
let (min_bound, max_bound) = if lower_bound <= upper_bound {
(lower_bound * 0.9, upper_bound * 1.1) } else {
(upper_bound * 0.9, lower_bound * 1.1)
};
Ok((min_bound, max_bound, None))
}
}
pub fn is_volume_computation_reliable(hull: &ConvexHull) -> bool {
let ndim = hull.ndim();
let nvertices = hull.vertex_indices.len();
if ndim <= 3 {
return true;
}
if hull.equations.is_some() && nvertices > ndim {
return true;
}
if ndim > 10 && nvertices < 2 * ndim {
return false;
}
true
}
#[cfg(test)]
mod tests {
use super::*;
use crate::convex_hull::ConvexHull;
use scirs2_core::ndarray::arr2;
#[test]
fn test_compute_2d_volume() {
let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
let hull = ConvexHull::new(&points.view()).expect("Operation failed");
let area = compute_volume(&hull).expect("Operation failed");
assert!((area - 1.0).abs() < 1e-10);
let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
let hull = ConvexHull::new(&points.view()).expect("Operation failed");
let area = compute_volume(&hull).expect("Operation failed");
assert!((area - 0.5).abs() < 1e-10);
}
#[test]
fn test_compute_1d_volume() {
let points = arr2(&[[0.0], [3.0], [1.0], [2.0]]);
let hull = ConvexHull::new(&points.view()).expect("Operation failed");
let length = compute_volume(&hull).expect("Operation failed");
assert!((length - 3.0).abs() < 1e-10);
}
#[test]
fn test_compute_volume_bounds() {
let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
let hull = ConvexHull::new(&points.view()).expect("Operation failed");
let (lower, upper, exact) = compute_volume_bounds(&hull).expect("Operation failed");
assert!(exact.is_some());
assert!((exact.expect("Operation failed") - 1.0).abs() < 1e-10);
assert_eq!(lower, upper); }
#[test]
fn test_is_volume_computation_reliable() {
let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
let hull = ConvexHull::new(&points.view()).expect("Operation failed");
assert!(is_volume_computation_reliable(&hull));
let points = arr2(&[
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
]);
let hull = ConvexHull::new(&points.view()).expect("Operation failed");
assert!(is_volume_computation_reliable(&hull));
}
#[test]
fn test_compute_volume_monte_carlo() {
let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
let hull = ConvexHull::new(&points.view()).expect("Operation failed");
let estimated_area = compute_volume_monte_carlo(&hull, 10000).expect("Operation failed");
assert!((estimated_area - 1.0).abs() < 0.1); }
#[test]
fn test_degenerate_cases() {
let points = arr2(&[[1.0, 2.0]]);
let hull_result = ConvexHull::new(&points.view());
assert!(hull_result.is_err());
let points = arr2(&[[0.0, 0.0], [1.0, 1.0]]);
let hull_result = ConvexHull::new(&points.view());
assert!(hull_result.is_err());
let points = arr2(&[[0.0], [1.0], [2.0], [3.0]]);
let hull = ConvexHull::new(&points.view()).expect("Operation failed");
let length = compute_volume(&hull).expect("Operation failed");
assert!((length - 3.0).abs() < 1e-10); }
}