#![forbid(unsafe_code)]
use super::conversions::safe_usize_to_scalar;
use super::norms::hypot;
use crate::geometry::point::Point;
use crate::geometry::traits::coordinate::{Coordinate, CoordinateScalar};
use rand::RngExt;
use rand::distr::uniform::SampleUniform;
pub use super::RandomPointGenerationError;
const MAX_GRID_BYTES_SAFETY_CAP_DEFAULT: usize = 4_294_967_296;
fn max_grid_bytes_safety_cap() -> usize {
if let Ok(v) = std::env::var("MAX_GRID_BYTES_SAFETY_CAP")
&& let Ok(n) = v.parse::<usize>()
{
return n;
}
MAX_GRID_BYTES_SAFETY_CAP_DEFAULT
}
fn format_bytes(bytes: usize) -> String {
const UNITS: &[&str] = &["B", "KiB", "MiB", "GiB", "TiB"];
let Ok(mut size) = safe_usize_to_scalar::<f64>(bytes) else {
return format!("{bytes} B");
};
let mut unit_index = 0;
while size >= 1024.0 && unit_index < UNITS.len() - 1 {
size /= 1024.0;
unit_index += 1;
}
if unit_index == 0 {
format!("{} {}", bytes, UNITS[0])
} else {
format!("{:.1} {}", size, UNITS[unit_index])
}
}
pub fn scaled_bounds_by_point_count<T: CoordinateScalar>(
n_points: usize,
) -> Result<(T, T), RandomPointGenerationError> {
let side_len = n_points.max(1);
let side = safe_usize_to_scalar::<T>(side_len).map_err(|e| {
RandomPointGenerationError::RandomGenerationFailed {
min: "n/a".to_string(),
max: "n/a".to_string(),
details: format!(
"Failed to convert n_points={side_len} to coordinate type {}: {e}",
std::any::type_name::<T>()
),
}
})?;
let half = side / (T::one() + T::one());
Ok((-half, half))
}
pub fn generate_random_points<T: CoordinateScalar + SampleUniform, const D: usize>(
n_points: usize,
range: (T, T),
) -> Result<Vec<Point<T, D>>, RandomPointGenerationError> {
#[cfg(debug_assertions)]
if std::env::var_os("DELAUNAY_DEBUG_UNUSED_IMPORTS").is_some() {
eprintln!("point_generation::generate_random_points called (n_points={n_points}, D={D})");
}
if range.0 >= range.1 {
return Err(RandomPointGenerationError::InvalidRange {
min: format!("{:?}", range.0),
max: format!("{:?}", range.1),
});
}
let mut rng = rand::rng();
let mut points = Vec::with_capacity(n_points);
for _ in 0..n_points {
let coords = [T::zero(); D].map(|_| rng.random_range(range.0..range.1));
points.push(Point::new(coords));
}
Ok(points)
}
pub fn generate_random_points_seeded<T: CoordinateScalar + SampleUniform, const D: usize>(
n_points: usize,
range: (T, T),
seed: u64,
) -> Result<Vec<Point<T, D>>, RandomPointGenerationError> {
use rand::SeedableRng;
#[cfg(debug_assertions)]
if std::env::var_os("DELAUNAY_DEBUG_UNUSED_IMPORTS").is_some() {
eprintln!(
"point_generation::generate_random_points_seeded called (n_points={n_points}, D={D}, seed={seed})"
);
}
if range.0 >= range.1 {
return Err(RandomPointGenerationError::InvalidRange {
min: format!("{:?}", range.0),
max: format!("{:?}", range.1),
});
}
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
let mut points = Vec::with_capacity(n_points);
for _ in 0..n_points {
let coords = [T::zero(); D].map(|_| rng.random_range(range.0..range.1));
points.push(Point::new(coords));
}
Ok(points)
}
pub fn generate_random_points_periodic<T: CoordinateScalar + SampleUniform, const D: usize>(
n_points: usize,
domain: [T; D],
seed: u64,
) -> Result<Vec<Point<T, D>>, RandomPointGenerationError> {
use rand::SeedableRng;
for period in domain {
if period <= T::zero() {
return Err(RandomPointGenerationError::InvalidRange {
min: String::from("0"),
max: format!("{period:?}"),
});
}
}
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
let mut points = Vec::with_capacity(n_points);
for _ in 0..n_points {
let coords = domain.map(|period| rng.random_range(T::zero()..period));
points.push(Point::new(coords));
}
Ok(points)
}
fn generate_random_points_in_ball_with_rng<T, R, const D: usize>(
n_points: usize,
radius: T,
rng: &mut R,
) -> Result<Vec<Point<T, D>>, RandomPointGenerationError>
where
T: CoordinateScalar + SampleUniform,
R: rand::Rng + ?Sized,
{
if !radius.is_finite() || radius <= T::zero() {
return Err(RandomPointGenerationError::InvalidRange {
min: format!("{:?}", -radius),
max: format!("{radius:?}"),
});
}
if n_points == 0 {
return Ok(Vec::new());
}
let bounds = (-radius, radius);
let radius_sq = radius * radius;
let mut points = Vec::with_capacity(n_points);
while points.len() < n_points {
let coords = [T::zero(); D].map(|_| rng.random_range(bounds.0..bounds.1));
let norm_sq = coords.iter().fold(T::zero(), |acc, &c| acc + c * c);
if norm_sq <= radius_sq {
points.push(Point::new(coords));
}
}
Ok(points)
}
pub fn generate_random_points_in_ball<T: CoordinateScalar + SampleUniform, const D: usize>(
n_points: usize,
radius: T,
) -> Result<Vec<Point<T, D>>, RandomPointGenerationError> {
let mut rng = rand::rng();
generate_random_points_in_ball_with_rng(n_points, radius, &mut rng)
}
pub fn generate_random_points_in_ball_seeded<
T: CoordinateScalar + SampleUniform,
const D: usize,
>(
n_points: usize,
radius: T,
seed: u64,
) -> Result<Vec<Point<T, D>>, RandomPointGenerationError> {
use rand::SeedableRng;
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
generate_random_points_in_ball_with_rng(n_points, radius, &mut rng)
}
pub fn generate_grid_points<T: CoordinateScalar, const D: usize>(
points_per_dim: usize,
spacing: T,
offset: [T; D],
) -> Result<Vec<Point<T, D>>, RandomPointGenerationError> {
if points_per_dim == 0 {
return Err(RandomPointGenerationError::InvalidPointCount { n_points: 0 });
}
let mut total_points: usize = 1;
for _ in 0..D {
total_points = total_points.checked_mul(points_per_dim).ok_or_else(|| {
RandomPointGenerationError::RandomGenerationFailed {
min: "0".into(),
max: format!("{}", points_per_dim.saturating_sub(1)),
details: format!("Requested grid size {points_per_dim}^{D} overflows usize"),
}
})?;
}
let per_point_bytes = D.saturating_mul(core::mem::size_of::<T>());
let total_bytes = total_points.saturating_mul(per_point_bytes);
let cap = max_grid_bytes_safety_cap();
if total_bytes > cap {
return Err(RandomPointGenerationError::RandomGenerationFailed {
min: "n/a".into(),
max: "n/a".into(),
details: format!(
"Requested grid requires {} (> cap {})",
format_bytes(total_bytes),
format_bytes(cap)
),
});
}
let mut points = Vec::with_capacity(total_points);
let mut idx = [0usize; D];
for _ in 0..total_points {
let mut coords = [T::zero(); D];
for d in 0..D {
let index_as_scalar = safe_usize_to_scalar::<T>(idx[d]).map_err(|_| {
RandomPointGenerationError::RandomGenerationFailed {
min: "0".to_string(),
max: format!("{}", points_per_dim - 1),
details: format!("Failed to convert grid index {idx:?} to coordinate type"),
}
})?;
coords[d] = offset[d] + index_as_scalar * spacing;
}
points.push(Point::new(coords));
for d in (0..D).rev() {
idx[d] += 1;
if idx[d] < points_per_dim {
break;
}
idx[d] = 0;
}
}
Ok(points)
}
pub fn generate_poisson_points<T: CoordinateScalar + SampleUniform, const D: usize>(
n_points: usize,
bounds: (T, T),
min_distance: T,
seed: u64,
) -> Result<Vec<Point<T, D>>, RandomPointGenerationError> {
use rand::RngExt;
use rand::SeedableRng;
if bounds.0 >= bounds.1 {
return Err(RandomPointGenerationError::InvalidRange {
min: format!("{:?}", bounds.0),
max: format!("{:?}", bounds.1),
});
}
if n_points == 0 {
return Ok(Vec::new());
}
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
if min_distance <= T::zero() {
let mut points = Vec::with_capacity(n_points);
for _ in 0..n_points {
let coords = [T::zero(); D].map(|_| rng.random_range(bounds.0..bounds.1));
points.push(Point::new(coords));
}
return Ok(points);
}
let mut points: Vec<Point<T, D>> = Vec::new();
let dimension_scaling = match D {
0..=2 => 1,
3..=4 => 2,
5..=6 => 4,
_ => 8, };
let max_attempts = (n_points * 30).saturating_mul(dimension_scaling);
let mut attempts = 0;
while points.len() < n_points && attempts < max_attempts {
attempts += 1;
let coords = [T::zero(); D].map(|_| rng.random_range(bounds.0..bounds.1));
let candidate = Point::new(coords);
let mut valid = true;
let candidate_coords: [T; D] = *candidate.coords();
for existing_point in &points {
let existing_coords: [T; D] = *existing_point.coords();
let mut diff_coords = [T::zero(); D];
for i in 0..D {
diff_coords[i] = candidate_coords[i] - existing_coords[i];
}
let distance = hypot(&diff_coords);
if distance < min_distance {
valid = false;
break;
}
}
if valid {
points.push(candidate);
}
}
if points.is_empty() {
return Err(RandomPointGenerationError::RandomGenerationFailed {
min: format!("{:?}", bounds.0),
max: format!("{:?}", bounds.1),
details: format!(
"Could not generate any points with minimum distance {min_distance:?} in given bounds"
),
});
}
Ok(points)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_generate_random_points_2d() {
let points = generate_random_points::<f64, 2>(100, (-10.0, 10.0)).unwrap();
assert_eq!(points.len(), 100);
for point in &points {
let coords = *point.coords();
assert!(coords[0] >= -10.0 && coords[0] < 10.0);
assert!(coords[1] >= -10.0 && coords[1] < 10.0);
}
}
#[test]
fn test_generate_random_points_3d() {
let points = generate_random_points::<f64, 3>(75, (0.0, 5.0)).unwrap();
assert_eq!(points.len(), 75);
for point in &points {
let coords = *point.coords();
assert!(coords[0] >= 0.0 && coords[0] < 5.0);
assert!(coords[1] >= 0.0 && coords[1] < 5.0);
assert!(coords[2] >= 0.0 && coords[2] < 5.0);
}
}
#[test]
fn test_generate_random_points_4d() {
let points = generate_random_points::<f32, 4>(50, (-2.0, 2.0)).unwrap();
assert_eq!(points.len(), 50);
for point in &points {
let coords = *point.coords();
for &coord in &coords {
assert!((-2.0..2.0).contains(&coord));
}
}
}
#[test]
fn test_generate_random_points_5d() {
let points = generate_random_points::<f64, 5>(25, (-1.0, 1.0)).unwrap();
assert_eq!(points.len(), 25);
for point in &points {
let coords = *point.coords();
for &coord in &coords {
assert!((-1.0..1.0).contains(&coord));
}
}
}
#[test]
fn test_generate_random_points_error_handling() {
let result = generate_random_points::<f64, 2>(100, (10.0, -10.0));
assert!(result.is_err());
match result {
Err(RandomPointGenerationError::InvalidRange { min, max }) => {
assert_eq!(min, "10.0");
assert_eq!(max, "-10.0");
}
_ => panic!("Expected InvalidRange error"),
}
let result = generate_random_points::<f64, 3>(50, (5.0, 5.0));
assert!(result.is_err());
let result = generate_random_points::<f32, 4>(25, (1.0, 0.5));
assert!(result.is_err());
let result = generate_random_points::<f64, 5>(10, (2.0, 2.0));
assert!(result.is_err());
let result = generate_random_points::<f64, 2>(10, (0.0, 0.001));
assert!(result.is_ok());
}
#[test]
fn test_generate_random_points_zero_points() {
let points_2d = generate_random_points::<f64, 2>(0, (-1.0, 1.0)).unwrap();
assert_eq!(points_2d.len(), 0);
let points_3d = generate_random_points::<f64, 3>(0, (-1.0, 1.0)).unwrap();
assert_eq!(points_3d.len(), 0);
let points_4d = generate_random_points::<f64, 4>(0, (-1.0, 1.0)).unwrap();
assert_eq!(points_4d.len(), 0);
let points_5d = generate_random_points::<f64, 5>(0, (-1.0, 1.0)).unwrap();
assert_eq!(points_5d.len(), 0);
}
#[test]
fn test_generate_random_points_seeded_2d() {
let seed = 42_u64;
let points1 = generate_random_points_seeded::<f64, 2>(50, (-5.0, 5.0), seed).unwrap();
let points2 = generate_random_points_seeded::<f64, 2>(50, (-5.0, 5.0), seed).unwrap();
assert_eq!(points1.len(), points2.len());
for (p1, p2) in points1.iter().zip(points2.iter()) {
let coords1 = *p1.coords();
let coords2 = *p2.coords();
for (c1, c2) in coords1.iter().zip(coords2.iter()) {
assert_relative_eq!(c1, c2, epsilon = 1e-15);
}
}
}
#[test]
fn test_generate_random_points_seeded_3d() {
let seed = 123_u64;
let points1 = generate_random_points_seeded::<f64, 3>(40, (0.0, 10.0), seed).unwrap();
let points2 = generate_random_points_seeded::<f64, 3>(40, (0.0, 10.0), seed).unwrap();
assert_eq!(points1.len(), points2.len());
for (p1, p2) in points1.iter().zip(points2.iter()) {
let coords1 = *p1.coords();
let coords2 = *p2.coords();
for (c1, c2) in coords1.iter().zip(coords2.iter()) {
assert_relative_eq!(c1, c2, epsilon = 1e-15);
}
}
}
#[test]
fn test_generate_random_points_seeded_4d() {
let seed = 789_u64;
let points1 = generate_random_points_seeded::<f32, 4>(30, (-2.5, 2.5), seed).unwrap();
let points2 = generate_random_points_seeded::<f32, 4>(30, (-2.5, 2.5), seed).unwrap();
assert_eq!(points1.len(), points2.len());
for (p1, p2) in points1.iter().zip(points2.iter()) {
let coords1 = *p1.coords();
let coords2 = *p2.coords();
for (c1, c2) in coords1.iter().zip(coords2.iter()) {
assert_relative_eq!(c1, c2, epsilon = 1e-6); }
}
}
#[test]
fn test_generate_random_points_seeded_5d() {
let seed = 456_u64;
let points1 = generate_random_points_seeded::<f64, 5>(20, (-1.0, 3.0), seed).unwrap();
let points2 = generate_random_points_seeded::<f64, 5>(20, (-1.0, 3.0), seed).unwrap();
assert_eq!(points1.len(), points2.len());
for (p1, p2) in points1.iter().zip(points2.iter()) {
let coords1 = *p1.coords();
let coords2 = *p2.coords();
for (c1, c2) in coords1.iter().zip(coords2.iter()) {
assert_relative_eq!(c1, c2, epsilon = 1e-15);
}
}
}
#[test]
fn test_generate_random_points_seeded_different_seeds() {
let points1_2d = generate_random_points_seeded::<f64, 2>(50, (0.0, 1.0), 42).unwrap();
let points2_2d = generate_random_points_seeded::<f64, 2>(50, (0.0, 1.0), 123).unwrap();
assert_ne!(points1_2d, points2_2d);
let points1_3d = generate_random_points_seeded::<f64, 3>(30, (-5.0, 5.0), 42).unwrap();
let points2_3d = generate_random_points_seeded::<f64, 3>(30, (-5.0, 5.0), 999).unwrap();
assert_ne!(points1_3d, points2_3d);
let points1_4d = generate_random_points_seeded::<f32, 4>(25, (-1.0, 1.0), 1337).unwrap();
let points2_4d = generate_random_points_seeded::<f32, 4>(25, (-1.0, 1.0), 7331).unwrap();
assert_ne!(points1_4d, points2_4d);
let points1_5d = generate_random_points_seeded::<f64, 5>(15, (0.0, 10.0), 2021).unwrap();
let points2_5d = generate_random_points_seeded::<f64, 5>(15, (0.0, 10.0), 2024).unwrap();
assert_ne!(points1_5d, points2_5d);
}
#[test]
fn test_generate_random_points_periodic_2d_in_domain() {
let points = generate_random_points_periodic::<f64, 2>(100, [1.0, 2.0], 42).unwrap();
assert_eq!(points.len(), 100);
for point in &points {
let coords = *point.coords();
assert!((0.0..1.0).contains(&coords[0]));
assert!((0.0..2.0).contains(&coords[1]));
}
}
#[test]
fn test_generate_random_points_periodic_seeded_reproducible() {
let points1 = generate_random_points_periodic::<f64, 3>(50, [1.0, 1.0, 1.0], 123).unwrap();
let points2 = generate_random_points_periodic::<f64, 3>(50, [1.0, 1.0, 1.0], 123).unwrap();
assert_eq!(points1, points2);
}
#[test]
fn test_generate_random_points_periodic_invalid_domain() {
let zero_period = generate_random_points_periodic::<f64, 2>(10, [1.0, 0.0], 7);
assert!(matches!(
zero_period,
Err(RandomPointGenerationError::InvalidRange { .. })
));
let negative_period = generate_random_points_periodic::<f64, 2>(10, [1.0, -2.0], 7);
assert!(matches!(
negative_period,
Err(RandomPointGenerationError::InvalidRange { .. })
));
}
#[test]
fn test_generate_random_points_periodic_zero_points() {
let points = generate_random_points_periodic::<f64, 4>(0, [1.0, 2.0, 3.0, 4.0], 9).unwrap();
assert!(points.is_empty());
}
#[test]
fn test_generate_random_points_in_ball_4d() {
let radius = 3.0_f64;
let points = generate_random_points_in_ball::<f64, 4>(200, radius).unwrap();
assert_eq!(points.len(), 200);
let radius_sq = radius * radius;
for point in &points {
let coords = *point.coords();
let mut norm_sq = 0.0_f64;
for &c in &coords {
assert!(c >= -radius && c <= radius);
norm_sq += c * c;
}
assert!(norm_sq <= radius_sq + 1e-12);
}
}
#[test]
fn test_generate_random_points_in_ball_seeded_reproducible_4d() {
let points1 = generate_random_points_in_ball_seeded::<f64, 4>(50, 2.5, 42).unwrap();
let points2 = generate_random_points_in_ball_seeded::<f64, 4>(50, 2.5, 42).unwrap();
assert_eq!(points1, points2);
}
#[test]
fn test_generate_random_points_in_ball_seeded_different_seeds_4d() {
let points1 = generate_random_points_in_ball_seeded::<f64, 4>(50, 2.5, 42).unwrap();
let points2 = generate_random_points_in_ball_seeded::<f64, 4>(50, 2.5, 123).unwrap();
assert_ne!(points1, points2);
}
#[test]
fn test_generate_random_points_in_ball_rejects_zero_radius() {
let result = generate_random_points_in_ball::<f64, 4>(10, 0.0);
assert!(matches!(
result,
Err(RandomPointGenerationError::InvalidRange { .. })
));
}
#[test]
fn test_generate_random_points_in_ball_rejects_negative_radius() {
let result = generate_random_points_in_ball::<f64, 4>(10, -1.0);
assert!(matches!(
result,
Err(RandomPointGenerationError::InvalidRange { .. })
));
}
#[test]
fn test_generate_random_points_in_ball_zero_points() {
let points = generate_random_points_in_ball::<f64, 4>(0, 1.0).unwrap();
assert!(points.is_empty());
}
#[test]
fn test_generate_random_points_in_ball_seeded_zero_points() {
let points = generate_random_points_in_ball_seeded::<f64, 4>(0, 1.0, 7).unwrap();
assert!(points.is_empty());
}
#[test]
fn test_generate_random_points_in_ball_rejects_nan_radius() {
let result = generate_random_points_in_ball::<f64, 4>(10, f64::NAN);
assert!(matches!(
result,
Err(RandomPointGenerationError::InvalidRange { .. })
));
}
#[test]
fn test_generate_random_points_in_ball_rejects_infinite_radius() {
let result = generate_random_points_in_ball::<f64, 4>(10, f64::INFINITY);
assert!(matches!(
result,
Err(RandomPointGenerationError::InvalidRange { .. })
));
}
#[test]
fn test_generate_random_points_in_ball_seeded_in_ball_constraints_4d() {
let radius = 1.25_f32;
let points = generate_random_points_in_ball_seeded::<f32, 4>(100, radius, 99).unwrap();
assert_eq!(points.len(), 100);
let radius_sq = radius * radius;
for point in &points {
let coords = *point.coords();
let mut norm_sq = 0.0_f32;
for &c in &coords {
assert!(c >= -radius && c <= radius);
norm_sq += c * c;
}
assert!(norm_sq <= radius_sq + 1e-5);
}
}
#[test]
fn test_generate_random_points_in_ball_seeded_same_seed_is_deterministic_4d() {
let points1 = generate_random_points_in_ball_seeded::<f64, 4>(10, 1.0, 0xBEEF).unwrap();
let points2 = generate_random_points_in_ball_seeded::<f64, 4>(10, 1.0, 0xBEEF).unwrap();
assert_eq!(points1, points2);
}
#[test]
fn test_generate_random_points_distribution_coverage_all_dimensions() {
let points_2d = generate_random_points::<f64, 2>(500, (0.0, 10.0)).unwrap();
let mut min_2d = [f64::INFINITY; 2];
let mut max_2d = [f64::NEG_INFINITY; 2];
for point in &points_2d {
let coords = *point.coords();
for (i, &coord) in coords.iter().enumerate() {
min_2d[i] = min_2d[i].min(coord);
max_2d[i] = max_2d[i].max(coord);
}
}
for i in 0..2 {
assert!(
min_2d[i] < 2.0,
"Min in dimension {i} should be close to lower bound"
);
assert!(
max_2d[i] > 8.0,
"Max in dimension {i} should be close to upper bound"
);
}
let points_5d = generate_random_points::<f64, 5>(200, (-5.0, 5.0)).unwrap();
let mut min_5d = [f64::INFINITY; 5];
let mut max_5d = [f64::NEG_INFINITY; 5];
for point in &points_5d {
let coords = *point.coords();
for (i, &coord) in coords.iter().enumerate() {
min_5d[i] = min_5d[i].min(coord);
max_5d[i] = max_5d[i].max(coord);
}
}
for i in 0..5 {
assert!(
min_5d[i] < -2.0,
"Min in 5D dimension {i} should be reasonably low"
);
assert!(
max_5d[i] > 2.0,
"Max in 5D dimension {i} should be reasonably high"
);
}
}
#[test]
fn test_generate_random_points_common_ranges() {
let unit_2d = generate_random_points::<f64, 2>(50, (0.0, 1.0)).unwrap();
let unit_3d = generate_random_points::<f64, 3>(50, (0.0, 1.0)).unwrap();
let unit_4d = generate_random_points::<f64, 4>(50, (0.0, 1.0)).unwrap();
let unit_5d = generate_random_points::<f64, 5>(50, (0.0, 1.0)).unwrap();
assert_eq!(unit_2d.len(), 50);
assert_eq!(unit_3d.len(), 50);
assert_eq!(unit_4d.len(), 50);
assert_eq!(unit_5d.len(), 50);
let centered_2d = generate_random_points::<f64, 2>(30, (-1.0, 1.0)).unwrap();
let centered_3d = generate_random_points::<f64, 3>(30, (-1.0, 1.0)).unwrap();
let centered_4d = generate_random_points::<f64, 4>(30, (-1.0, 1.0)).unwrap();
let centered_5d = generate_random_points::<f64, 5>(30, (-1.0, 1.0)).unwrap();
assert_eq!(centered_2d.len(), 30);
assert_eq!(centered_3d.len(), 30);
assert_eq!(centered_4d.len(), 30);
assert_eq!(centered_5d.len(), 30);
for point in ¢ered_5d {
let coords = *point.coords();
for &coord in &coords {
assert!((-1.0..1.0).contains(&coord));
}
}
}
#[test]
fn test_generate_grid_points_2d() {
let grid = generate_grid_points::<f64, 2>(3, 1.0, [0.0, 0.0]).unwrap();
assert_eq!(grid.len(), 9);
let expected_coords = [
[0.0, 0.0],
[1.0, 0.0],
[2.0, 0.0],
[0.0, 1.0],
[1.0, 1.0],
[2.0, 1.0],
[0.0, 2.0],
[1.0, 2.0],
[2.0, 2.0],
];
for point in &grid {
let coords = *point.coords();
assert!(
expected_coords.iter().any(|&expected| {
(coords[0] - expected[0]).abs() < 1e-10
&& (coords[1] - expected[1]).abs() < 1e-10
}),
"Point {coords:?} not found in expected coordinates"
);
}
}
#[test]
fn test_generate_grid_points_3d() {
let grid = generate_grid_points::<f64, 3>(2, 2.0, [1.0, 1.0, 1.0]).unwrap();
assert_eq!(grid.len(), 8);
for point in &grid {
let coords = *point.coords();
for &coord in &coords {
assert!((1.0..=3.0).contains(&coord)); }
}
}
#[test]
fn test_generate_grid_points_4d() {
let grid = generate_grid_points::<f32, 4>(2, 0.5, [-0.5, -0.5, -0.5, -0.5]).unwrap();
assert_eq!(grid.len(), 16);
for point in &grid {
let coords = *point.coords();
for &coord in &coords {
assert!((-0.5..=0.0).contains(&coord)); }
}
}
#[test]
fn test_generate_grid_points_5d() {
let grid = generate_grid_points::<f64, 5>(2, 1.0, [0.0, 0.0, 0.0, 0.0, 0.0]).unwrap();
assert_eq!(grid.len(), 32);
for point in &grid {
let coords = *point.coords();
for &coord in &coords {
assert!((0.0..=1.0).contains(&coord)); }
}
}
#[test]
fn test_generate_grid_points_edge_cases() {
let grid = generate_grid_points::<f64, 3>(1, 1.0, [0.0, 0.0, 0.0]).unwrap();
assert_eq!(grid.len(), 1);
let coords = *grid[0].coords();
for (actual, expected) in coords.iter().zip([0.0, 0.0, 0.0].iter()) {
assert!((actual - expected).abs() < 1e-15);
}
let grid = generate_grid_points::<f64, 2>(2, 0.0, [5.0, 5.0]).unwrap();
assert_eq!(grid.len(), 4);
for point in &grid {
let coords = *point.coords();
for (actual, expected) in coords.iter().zip([5.0, 5.0].iter()) {
assert!((actual - expected).abs() < 1e-15);
}
}
}
#[test]
fn test_generate_grid_points_error_handling() {
let result = generate_grid_points::<f64, 2>(0, 1.0, [0.0, 0.0]);
assert!(result.is_err());
match result {
Err(RandomPointGenerationError::InvalidPointCount { n_points }) => {
assert_eq!(n_points, 0);
}
_ => panic!("Expected InvalidPointCount error"),
}
let result = generate_grid_points::<f64, 3>(1000, 1.0, [0.0, 0.0, 0.0]);
assert!(result.is_err());
let error_msg = format!("{}", result.unwrap_err());
assert!(error_msg.contains("cap"));
assert!(
error_msg.contains("GiB") || error_msg.contains("MiB") || error_msg.contains("KiB"),
"Error message should contain human-readable byte units: {error_msg}"
);
}
#[test]
fn test_generate_grid_points_overflow_detection() {
const LARGE_D: usize = 64; let offset = [0.0; LARGE_D];
let spacing = 0.1; let points_per_dim = 10;
let result = generate_grid_points::<f64, LARGE_D>(points_per_dim, spacing, offset);
assert!(result.is_err(), "Expected error due to usize overflow");
if let Err(RandomPointGenerationError::RandomGenerationFailed {
min: _,
max: _,
details,
}) = result
{
assert!(
details.contains("overflows usize"),
"Expected overflow error, got: {details}"
);
} else {
panic!("Expected RandomGenerationFailed error due to overflow");
}
}
#[test]
fn test_generate_poisson_points_2d() {
let points = generate_poisson_points::<f64, 2>(50, (0.0, 10.0), 0.5, 42).unwrap();
assert!(!points.is_empty());
assert!(points.len() <= 50);
for point in &points {
let coords = *point.coords();
assert!((0.0..10.0).contains(&coords[0]));
assert!((0.0..10.0).contains(&coords[1]));
}
for (i, p1) in points.iter().enumerate() {
for (j, p2) in points.iter().enumerate() {
if i != j {
let coords1 = *p1.coords();
let coords2 = *p2.coords();
let diff = [coords1[0] - coords2[0], coords1[1] - coords2[1]];
let distance = hypot(&diff);
assert!(
distance >= 0.5 - 1e-10,
"Distance {distance} violates minimum distance constraint"
);
}
}
}
}
#[test]
fn test_generate_poisson_points_3d() {
let points = generate_poisson_points::<f64, 3>(30, (-1.0, 1.0), 0.2, 123).unwrap();
assert!(!points.is_empty());
for point in &points {
let coords = *point.coords();
for &coord in &coords {
assert!((-1.0..1.0).contains(&coord));
}
}
for (i, p1) in points.iter().enumerate() {
for (j, p2) in points.iter().enumerate() {
if i != j {
let coords1 = *p1.coords();
let coords2 = *p2.coords();
let diff = [
coords1[0] - coords2[0],
coords1[1] - coords2[1],
coords1[2] - coords2[2],
];
let distance = hypot(&diff);
assert!(distance >= 0.2 - 1e-10);
}
}
}
}
#[test]
fn test_generate_poisson_points_4d() {
let points =
generate_poisson_points::<f32, 4>(15, (0.0_f32, 5.0_f32), 0.5_f32, 333).unwrap();
assert!(!points.is_empty());
for point in &points {
let coords = *point.coords();
for &coord in &coords {
assert!((0.0..5.0).contains(&coord));
}
}
for (i, p1) in points.iter().enumerate() {
for (j, p2) in points.iter().enumerate() {
if i != j {
let coords1 = *p1.coords();
let coords2 = *p2.coords();
let diff = [
coords1[0] - coords2[0],
coords1[1] - coords2[1],
coords1[2] - coords2[2],
coords1[3] - coords2[3],
];
let distance = hypot(&diff);
assert!(distance >= 0.5 - 1e-6); }
}
}
}
#[test]
fn test_generate_poisson_points_5d() {
let points = generate_poisson_points::<f64, 5>(10, (-2.0, 2.0), 0.4, 777).unwrap();
assert!(!points.is_empty());
for point in &points {
let coords = *point.coords();
for &coord in &coords {
assert!((-2.0..2.0).contains(&coord));
}
}
for (i, p1) in points.iter().enumerate() {
for (j, p2) in points.iter().enumerate() {
if i != j {
let coords1 = *p1.coords();
let coords2 = *p2.coords();
let diff = [
coords1[0] - coords2[0],
coords1[1] - coords2[1],
coords1[2] - coords2[2],
coords1[3] - coords2[3],
coords1[4] - coords2[4],
];
let distance = hypot(&diff);
assert!(distance >= 0.4 - 1e-10);
}
}
}
}
#[test]
fn test_generate_poisson_points_reproducible() {
let points1 = generate_poisson_points::<f64, 2>(25, (0.0, 5.0), 0.3, 456).unwrap();
let points2 = generate_poisson_points::<f64, 2>(25, (0.0, 5.0), 0.3, 456).unwrap();
assert_eq!(points1.len(), points2.len());
for (p1, p2) in points1.iter().zip(points2.iter()) {
let coords1 = *p1.coords();
let coords2 = *p2.coords();
for (c1, c2) in coords1.iter().zip(coords2.iter()) {
assert_relative_eq!(c1, c2, epsilon = 1e-15);
}
}
let points3 = generate_poisson_points::<f64, 2>(25, (0.0, 5.0), 0.3, 789).unwrap();
assert_ne!(points1, points3);
}
#[test]
fn test_generate_poisson_points_error_handling() {
let result = generate_poisson_points::<f64, 2>(50, (10.0, 5.0), 0.1, 42);
assert!(result.is_err());
match result {
Err(RandomPointGenerationError::InvalidRange { min, max }) => {
assert_eq!(min, "10.0");
assert_eq!(max, "5.0");
}
_ => panic!("Expected InvalidRange error"),
}
let result = generate_poisson_points::<f64, 2>(100, (0.0, 1.0), 10.0, 42);
match result {
Ok(points) => {
assert!(points.len() < 5);
}
Err(RandomPointGenerationError::RandomGenerationFailed { .. }) => {
}
_ => panic!("Unexpected error type"),
}
let result = generate_poisson_points::<f64, 2>(100, (0.0, 10.0), 0.0, 42);
assert!(result.is_ok());
let points = result.unwrap();
assert_eq!(points.len(), 100);
let result = generate_poisson_points::<f64, 2>(50, (0.0, 10.0), -1.0, 42);
assert!(result.is_ok());
let points = result.unwrap();
assert_eq!(points.len(), 50); }
#[test]
fn test_generate_random_points_invalid_range() {
let result = generate_random_points::<f64, 2>(100, (10.0, 5.0));
assert!(matches!(
result,
Err(RandomPointGenerationError::InvalidRange { .. })
));
let result = generate_random_points::<f64, 2>(100, (5.0, 5.0));
assert!(matches!(
result,
Err(RandomPointGenerationError::InvalidRange { .. })
));
let result = generate_random_points::<f64, 2>(10, (0.0, 1.0));
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 10);
}
#[test]
fn test_generate_random_points_seeded_invalid_range() {
let result = generate_random_points_seeded::<f64, 3>(50, (100.0, 10.0), 42);
assert!(matches!(
result,
Err(RandomPointGenerationError::InvalidRange { .. })
));
let points1 = generate_random_points_seeded::<f64, 3>(5, (0.0, 1.0), 42).unwrap();
let points2 = generate_random_points_seeded::<f64, 3>(5, (0.0, 1.0), 42).unwrap();
assert_eq!(points1, points2);
let points3 = generate_random_points_seeded::<f64, 3>(5, (0.0, 1.0), 123).unwrap();
assert_ne!(points1, points3);
}
#[test]
fn test_generate_grid_points_overflow_detection_edge_cases() {
let result = generate_grid_points::<f64, 2>(10, 0.1, [0.0, 0.0]);
assert!(result.is_ok());
let result = generate_grid_points::<f64, 2>(1000, 0.0001, [0.0, 0.0]);
if let Ok(points) = result {
assert!(!points.is_empty());
}
}
#[test]
fn test_generate_poisson_points_edge_cases() {
let result = generate_poisson_points::<f64, 2>(100, (0.0, 1.0), 0.001, 42);
if let Ok(points) = result {
assert!(!points.is_empty());
}
let result = generate_poisson_points::<f64, 2>(0, (0.0, 1.0), 0.1, 42);
if let Ok(points) = result {
assert!(points.is_empty());
}
let result = generate_poisson_points::<f64, 2>(10, (0.0, 1.0), 2.0, 42);
if let Ok(points) = result {
assert!(points.len() <= 10);
}
}
#[test]
fn test_format_bytes_edge_cases() {
assert_eq!(format_bytes(0), "0 B");
assert_eq!(format_bytes(1), "1 B");
assert_eq!(format_bytes(1023), "1023 B");
assert_eq!(format_bytes(1024), "1.0 KiB");
assert_eq!(format_bytes(1536), "1.5 KiB"); assert_eq!(format_bytes(1024 * 1024), "1.0 MiB");
let large_bytes = 7 * 1024 * 1024 * 1024; let formatted = format_bytes(large_bytes);
assert!(formatted.contains("GiB"));
assert!(formatted.contains("7."));
}
#[test]
fn test_max_grid_bytes_safety_cap() {
let expected = std::env::var("MAX_GRID_BYTES_SAFETY_CAP")
.ok()
.and_then(|v| v.parse::<usize>().ok())
.unwrap_or(MAX_GRID_BYTES_SAFETY_CAP_DEFAULT);
let cap = max_grid_bytes_safety_cap();
assert!(cap > 0);
assert_eq!(cap, expected);
assert_eq!(MAX_GRID_BYTES_SAFETY_CAP_DEFAULT, 4_294_967_296);
}
}