use crate::domain::hfbi::{AnagraficaHFBI, CampionamentoHFBI};
use crate::domain::posf32::PositiveF32;
pub fn calc_ddom(
campionamento: &CampionamentoHFBI,
anagrafica: &AnagraficaHFBI,
) -> Result<f32, String> {
let (s90, b90): (u32, f32) = calc_s90_b90(campionamento, anagrafica)?;
let ddom = (((s90 as f32 - 1.0) / b90) + 1.0).ln();
Ok((1000.0 * ddom).round() / 1000.0)
}
fn calc_s90_b90(
campionamento: &CampionamentoHFBI,
anagrafica: &AnagraficaHFBI,
) -> Result<(u32, f32), String> {
let width = anagrafica.get_larghezza_media();
let length = anagrafica.get_lunghezza_media();
let width_checked = PositiveF32::new(width).map_err(|e| e.to_string())?;
let length_checked = PositiveF32::new(length).map_err(|e| e.to_string())?;
let area: f32 = *width_checked * *length_checked;
let mut biomassa_tot = 0.0;
for cattura in campionamento {
biomassa_tot += cattura.peso;
}
let biomassa_90 = biomassa_tot * 0.9;
let mut n_specie_90: u32 = 0;
let mut biomassa_tmp: f32 = 0.0;
for cattura in campionamento.sorted_by_peso_desc() {
biomassa_tmp += cattura.peso;
n_specie_90 += 1;
if biomassa_tmp > biomassa_90 {
break;
}
}
let b90: f32 = ((biomassa_90 / area) * 100.0 + 1.0).ln();
Ok((n_specie_90, b90))
}
#[cfg(test)]
mod ddom_private_tests {
use super::*;
use crate::domain::hfbi::{
AnagraficaHFBI, CampionamentoHFBI, GruppoEcoHFBI, GruppoTrofHFBI, HabitatHFBI, RecordHFBI,
SpecieHFBI, StagioneHFBI, TipoLagunaCostieraHFBI,
};
use crate::domain::location::Location;
const EPSILON: f32 = 1e-6;
fn create_test_anagrafica(lunghezza: f32, larghezza: f32) -> AnagraficaHFBI {
AnagraficaHFBI::new_raw_unchecked(
"TestStazione".to_string(),
"TestCorpoIdrico".to_string(),
Location {
regione: "Test".to_string(),
provincia: "Test".to_string(),
},
"01/01/2025".to_string(),
TipoLagunaCostieraHFBI::MAt1,
StagioneHFBI::Primavera,
HabitatHFBI::NonVegetato,
lunghezza,
larghezza,
)
}
fn create_dummy_record(peso: f32) -> RecordHFBI {
RecordHFBI {
specie: SpecieHFBI {
nome_comune: "Dummy".to_string(),
codice_specie: "DM".to_string(),
autoctono: true,
gruppo_eco: GruppoEcoHFBI::ResidentiDiEstuario,
gruppo_trofico: GruppoTrofHFBI {
microbentivori: 0.0,
macrobentivori: 0.0,
iperbentivori: 0.0,
erbivori: 0.0,
detritivori: 0.0,
planctivori: 0.0,
onnivori: 0.0,
},
},
numero_individui: 1,
peso,
}
}
#[test]
fn test_s90_b90_order_invariant() {
let anagrafica = create_test_anagrafica(100.0, 5.0);
let campione = CampionamentoHFBI::new_raw_unsorted(vec![
create_dummy_record(1.0),
create_dummy_record(1.0),
create_dummy_record(900.0),
]);
let sorted = CampionamentoHFBI::new_raw_unsorted(vec![
create_dummy_record(900.0),
create_dummy_record(1.0),
create_dummy_record(1.0),
]);
let (n_specie_90, b90) = calc_s90_b90(&campione, &anagrafica)
.expect("Area fields of anagrafica should be positive and finite");
let (n_specie_90_sorted, b90_sorted) = calc_s90_b90(&sorted, &anagrafica)
.expect("Area fields of anagrafica should be positive and finite");
assert!((b90 - b90_sorted).abs() < EPSILON);
assert!(n_specie_90 == n_specie_90_sorted);
}
#[test]
fn test_ddom_order_invariant() {
let anagrafica = create_test_anagrafica(100.0, 5.0);
let campione = CampionamentoHFBI::new_raw_unsorted(vec![
create_dummy_record(1.0),
create_dummy_record(1.0),
create_dummy_record(900.0),
]);
let sorted = CampionamentoHFBI::new_raw_unsorted(vec![
create_dummy_record(900.0),
create_dummy_record(1.0),
create_dummy_record(1.0),
]);
let ddom = calc_ddom(&campione, &anagrafica)
.expect("Area fields of anagrafica should be positive and finite");
let ddom_sorted = calc_ddom(&sorted, &anagrafica)
.expect("Area fields of anagrafica should be positive and finite");
assert!((ddom - ddom_sorted).abs() < EPSILON);
}
#[test]
fn test_s90_b90_empty_input() {
let anagrafica = create_test_anagrafica(100.0, 5.0);
let campione = CampionamentoHFBI::new(vec![]);
let (s90, b90) = calc_s90_b90(&campione, &anagrafica)
.expect("Area fields of anagrafica should be positive and finite");
assert_eq!(s90, 0);
assert!((b90 - 0.0).abs() < EPSILON);
}
#[test]
fn test_s90_b90_single_species() {
let anagrafica = create_test_anagrafica(10.0, 10.0); let campione = CampionamentoHFBI::new(vec![create_dummy_record(200.0)]);
let (s90, b90) = calc_s90_b90(&campione, &anagrafica)
.expect("Area fields of anagrafica should be positive and finite");
assert_eq!(s90, 1);
let expected_b90 = 181.0_f32.ln();
assert!((b90 - expected_b90).abs() < EPSILON);
}
#[test]
fn test_s90_b90_zero_area() {
let anagrafica = create_test_anagrafica(10.0, 0.0); let campione = CampionamentoHFBI::new(vec![create_dummy_record(100.0)]);
let res = calc_s90_b90(&campione, &anagrafica);
assert!(res.is_err());
}
#[test]
fn test_ddom_empty_input() {
let anagrafica = create_test_anagrafica(100.0, 5.0);
let campione = CampionamentoHFBI::new(vec![]);
let result = calc_ddom(&campione, &anagrafica)
.expect("Area fields of anagrafica should be positive and finite");
assert!(result.is_nan());
}
#[test]
fn test_ddom_single_species() {
let anagrafica = create_test_anagrafica(100.0, 5.0);
let campione = CampionamentoHFBI::new(vec![create_dummy_record(100.0)]);
let result = calc_ddom(&campione, &anagrafica)
.expect("Area fields of anagrafica should be positive and finite");
assert!((result - 0.0).abs() < EPSILON);
}
#[test]
fn test_ddom_zero_area() {
let anagrafica = create_test_anagrafica(10.0, 0.0); let campione =
CampionamentoHFBI::new(vec![create_dummy_record(100.0), create_dummy_record(50.0)]);
let result = calc_ddom(&campione, &anagrafica);
assert!(result.is_err());
}
#[test]
fn test_ddom_standard_case() {
let anagrafica = create_test_anagrafica(10.0, 10.0); let campione = CampionamentoHFBI::new(vec![
create_dummy_record(100.0),
create_dummy_record(50.0),
create_dummy_record(30.0),
create_dummy_record(20.0), ]);
let s90 = 4.0_f32;
let b90 = 181.0_f32.ln();
let expected_result = (1000.0 * (((s90 - 1.0) / b90) + 1.0).ln()).round() / 1000.0;
let actual_result = calc_ddom(&campione, &anagrafica)
.expect("Area fields of anagrafica should be positive and finite");
assert!((actual_result - expected_result).abs() < EPSILON);
}
}