use ndarray::ArrayView1;
use crate::reduction::pairwise_sum;
const MONTH_STARTS_365: [usize; 12] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
const MONTH_STARTS_366: [usize; 12] = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335];
pub fn pci(rainfall: ArrayView1<'_, f64>) -> Option<f64> {
let month_starts: &[usize; 12] = match rainfall.len() {
365 => &MONTH_STARTS_365,
366 => &MONTH_STARTS_366,
_ => return None,
};
let mut monthly_totals = [0.0_f64; 12];
for (month, &start) in month_starts.iter().enumerate() {
let end = month_starts
.get(month + 1)
.copied()
.unwrap_or(rainfall.len());
monthly_totals[month] = rainfall[start] + pairwise_sum(start + 1..end, |day| rainfall[day]);
}
let total = pairwise_sum(0..12, |month| monthly_totals[month]);
let squared = pairwise_sum(0..12, |month| monthly_totals[month] * monthly_totals[month]);
Some((squared / (total * total)) * 100.0)
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::{Array1, Axis, Slice, array};
#[test]
fn a_year_of_rain_in_one_month_is_maximally_concentrated() {
let mut january_only = Array1::<f64>::zeros(365);
for day in &mut january_only.slice_axis_mut(Axis(0), Slice::from(..31)) {
*day = 1.0;
}
assert_eq!(pci(january_only.view()), Some(100.0));
}
#[test]
fn february_29_belongs_to_february_of_a_366_day_year() {
let mut february_29_only = Array1::<f64>::zeros(366);
february_29_only[58] = 1.0;
february_29_only[59] = 1.0;
assert_eq!(pci(february_29_only.view()), Some(100.0));
february_29_only[58] = 0.0;
february_29_only[60] = 1.0;
assert_eq!(pci(february_29_only.view()), Some(50.0));
}
#[test]
fn uniform_rainfall_over_the_year_concentrates_only_by_month_length() {
let uniform = Array1::<f64>::ones(365);
let value = pci(uniform.view()).unwrap();
assert!(
value > 8.0 && value < 9.0,
"PCI of uniform rain was {value}"
);
}
#[test]
fn a_year_without_rain_is_nan() {
assert!(pci(Array1::<f64>::zeros(366).view()).unwrap().is_nan());
}
#[test]
fn a_year_of_the_wrong_length_has_no_index() {
assert_eq!(pci(array![1.0, 2.0, 3.0].view()), None);
}
}