use std::f64::consts::PI;
#[must_use]
pub fn rock_surface_area_m2(diameters_cm: &[f64]) -> f64 {
if diameters_cm.len() < 2 {
return f64::NAN;
}
let p = 1.6075;
let d_p: Vec<f64> = diameters_cm.iter().map(|d| (d / 100.0).powf(p)).collect();
let mut products = Vec::new();
for i in 0..d_p.len() {
for j in (i + 1)..d_p.len() {
products.push(d_p[i] * d_p[j]);
}
}
if products.is_empty() {
return f64::NAN;
}
let mean_prod: f64 = products.iter().sum::<f64>() / products.len() as f64;
2.0 * PI * mean_prod.powf(1.0 / p)
}
#[must_use]
pub fn per_m2(
value: f64,
total_volume_ml: f64,
volume_filtered_ml: f64,
surface_area_m2: f64,
) -> f64 {
if volume_filtered_ml == 0.0 || surface_area_m2 == 0.0 {
return f64::NAN;
}
value * total_volume_ml / (volume_filtered_ml * surface_area_m2)
}
#[must_use]
pub fn benthic_afdm_per_m2(
afdm_g_filter: f64,
diameters_cm: &[f64],
volume_filtered_ml: f64,
total_volume_ml: f64,
) -> f64 {
let area = rock_surface_area_m2(diameters_cm);
per_m2(afdm_g_filter, total_volume_ml, volume_filtered_ml, area)
}
#[must_use]
pub fn benthic_chla_per_m2(
chla_ug_l: f64,
diameters_cm: &[f64],
volume_filtered_ml: f64,
total_volume_ml: f64,
) -> f64 {
let area = rock_surface_area_m2(diameters_cm);
per_m2(chla_ug_l * 0.005, total_volume_ml, volume_filtered_ml, area)
}
#[cfg(test)]
mod tests {
use super::*;
const TOL: f64 = 1e-6;
#[test]
fn test_rock_surface_area_sphere() {
let area = rock_surface_area_m2(&[10.0, 10.0, 10.0]);
let expected = 2.0 * PI * 0.1_f64.powi(2);
assert!(
(area - expected).abs() < 0.001,
"expected ~{expected:.6}, got {area:.6}"
);
}
#[test]
fn test_rock_surface_area_ellipsoid() {
let area = rock_surface_area_m2(&[10.0, 8.0, 6.0]);
assert!(
area > 0.0 && area.is_finite(),
"expected positive area, got {area}"
);
let half_10 = 2.0 * PI * 0.1_f64.powi(2);
let half_6 = 2.0 * PI * 0.06_f64.powi(2);
assert!(
area < half_10 && area > half_6,
"area {area:.6} should be between {half_6:.6} and {half_10:.6}"
);
}
#[test]
fn test_rock_surface_area_insufficient_dims() {
assert!(rock_surface_area_m2(&[10.0]).is_nan());
assert!(rock_surface_area_m2(&[]).is_nan());
}
#[test]
fn test_per_m2_basic() {
let result = per_m2(0.5, 100.0, 50.0, 0.01);
assert!((result - 100.0).abs() < TOL, "expected 100.0, got {result}");
}
#[test]
fn test_per_m2_zero_area() {
assert!(per_m2(0.5, 100.0, 50.0, 0.0).is_nan());
}
#[test]
fn test_benthic_afdm() {
let result = benthic_afdm_per_m2(0.005, &[10.0, 8.0, 6.0], 50.0, 100.0);
assert!(
result > 0.0 && result.is_finite(),
"expected positive AFDM/m², got {result}"
);
}
#[test]
fn test_benthic_chla() {
let result = benthic_chla_per_m2(15.0, &[10.0, 8.0, 6.0], 50.0, 100.0);
assert!(
result > 0.0 && result.is_finite(),
"expected positive Chl-a/m², got {result}"
);
}
}