use ndarray::{Array2, ArrayView2};
use crate::palmer::{py_max, py_min};
#[derive(Debug, Clone, Copy)]
pub struct PdiDurationFactors {
pub wetm: f64,
pub wetb: f64,
pub drym: f64,
pub dryb: f64,
}
impl PdiDurationFactors {
fn select(&self, x3: f64) -> (f64, f64) {
if x3 >= 0.0 {
(self.wetm, self.wetb)
} else {
(self.drym, self.dryb)
}
}
}
fn carry(m: f64, b: f64, previous: f64, z: f64) -> f64 {
b / (m + b) * previous + z / (m + b)
}
#[derive(Debug)]
pub struct PdiResult {
pub pdsi: Array2<f64>,
pub phdi: Array2<f64>,
pub pmdi: Array2<f64>,
}
fn case(prob: f64, x1: f64, x2: f64, x3: f64) -> f64 {
let near_normal = if x1.abs() > x2.abs() { x1 } else { x2 };
let pro = prob / 100.0;
let interpolated = if x3 <= 0.0 {
(1.0 - pro) * x3 + pro * x1
} else {
(1.0 - pro) * x3 + pro * x2
};
let established = if prob <= 0.0 || prob >= 100.0 {
x3
} else {
interpolated
};
if x3 == 0.0 { near_normal } else { established }
}
struct Cell<'a> {
factors: PdiDurationFactors,
z: &'a [f64],
t: usize,
ppr: Vec<f64>,
px1: Vec<f64>,
px2: Vec<f64>,
px3: Vec<f64>,
x: Vec<f64>,
pdsi: Vec<f64>,
phdi: Vec<f64>,
wplm: Vec<f64>,
index: Vec<usize>,
sx: Vec<f64>,
sx1: Vec<f64>,
sx2: Vec<f64>,
sx3: Vec<f64>,
k8: usize,
k8max: usize,
iass: u8,
v: f64,
pro: f64,
x1: f64,
x2: f64,
x3: f64,
ze: f64,
pv: f64,
}
impl<'a> Cell<'a> {
fn new(z: &'a [f64], factors: PdiDurationFactors) -> Self {
let n = z.len();
Self {
factors,
z,
t: 0,
ppr: vec![0.0; n],
px1: vec![0.0; n],
px2: vec![0.0; n],
px3: vec![0.0; n],
x: vec![0.0; n],
pdsi: vec![f64::NAN; n],
phdi: vec![f64::NAN; n],
wplm: vec![f64::NAN; n],
index: vec![0; n],
sx: vec![0.0; n],
sx1: vec![0.0; n],
sx2: vec![0.0; n],
sx3: vec![0.0; n],
k8: 0,
k8max: 0,
iass: 0,
v: 0.0,
pro: 0.0,
x1: 0.0,
x2: 0.0,
x3: 0.0,
ze: 0.0,
pv: 0.0,
}
}
fn record(&mut self, t: usize, value: f64) {
let px3 = self.px3[t];
self.pdsi[t] = value;
self.phdi[t] = if px3 == 0.0 { value } else { px3 };
self.wplm[t] = case(self.ppr[t], self.px1[t], self.px2[t], px3);
}
fn backtrack(&mut self) {
let mut isave = self.iass;
for i in (0..self.k8).rev() {
if isave == 2 {
if self.sx2[i] == 0.0 {
isave = 1;
self.sx[i] = self.sx1[i];
} else {
isave = 2;
self.sx[i] = self.sx2[i];
}
} else if self.sx1[i] == 0.0 {
isave = 2;
self.sx[i] = self.sx2[i];
} else {
isave = 1;
self.sx[i] = self.sx1[i];
}
}
}
fn flush(&mut self) {
if self.iass == 3 {
for i in 0..self.k8 {
self.sx[i] = self.sx3[i];
}
} else {
self.backtrack();
}
for i in 0..=self.k8 {
self.record(self.index[i], self.sx[i]);
}
}
fn assign(&mut self) {
let t = self.t;
self.sx[self.k8] = self.x[t];
if self.k8 == 0 {
self.record(t, self.x[t]);
} else {
self.flush();
}
self.k8 = 0;
}
fn statement_220(&mut self) {
let t = self.t;
self.v = self.pv;
self.pro = self.ppr[t];
self.x1 = self.px1[t];
self.x2 = self.px2[t];
self.x3 = self.px3[t];
}
fn statement_210(&mut self) {
let t = self.t;
self.pv = 0.0;
self.px1[t] = 0.0;
self.px2[t] = 0.0;
self.ppr[t] = 0.0;
let (m, b) = self.factors.select(self.x3);
self.px3[t] = carry(m, b, self.x3, self.z[t]);
self.x[t] = self.px3[t];
self.iass = 3;
self.assign();
self.statement_220();
}
fn statement_200(&mut self) {
let t = self.t;
let PdiDurationFactors {
wetm,
wetb,
drym,
dryb,
} = self.factors;
let px1 = carry(wetm, wetb, self.x1, self.z[t]);
self.px1[t] = if px1 > 0.0 { px1 } else { 0.0 };
let branch1 = self.px1[t] >= 1.0 && self.px3[t] == 0.0;
if branch1 {
self.px3[t] = self.px1[t];
self.x[t] = self.px1[t];
self.px1[t] = 0.0;
self.iass = 1;
} else {
let px2 = carry(drym, dryb, self.x2, self.z[t]);
self.px2[t] = if px2 < 0.0 { px2 } else { 0.0 };
}
let branch2 = !branch1 && self.px2[t] <= -1.0 && self.px3[t] == 0.0;
if branch2 {
self.px3[t] = self.px2[t];
self.x[t] = self.px2[t];
self.px2[t] = 0.0;
self.iass = 2;
}
let px3_still_zero = !(branch1 || branch2) && self.px3[t] == 0.0;
let branch3 = px3_still_zero && self.px1[t] == 0.0;
if branch3 {
self.x[t] = self.px2[t];
self.iass = 2;
}
let branch4 = px3_still_zero && !branch3 && self.px2[t] == 0.0;
if branch4 {
self.x[t] = self.px1[t];
self.iass = 1;
}
if branch1 || branch2 || branch3 || branch4 {
self.assign();
} else {
let k8 = self.k8;
self.sx1[k8] = self.px1[t];
self.sx2[k8] = self.px2[t];
self.sx3[k8] = self.px3[t];
self.x[t] = self.px3[t];
self.k8 += 1;
self.k8max = self.k8;
}
self.statement_220();
}
fn statement_190(&mut self) {
let t = self.t;
let q = if self.pro == 100.0 {
self.ze
} else {
self.ze + self.v
};
let ppr = (self.pv / q) * 100.0;
let (m, b) = self.factors.select(self.x3);
let px3 = carry(m, b, self.x3, self.z[t]);
if ppr >= 100.0 {
self.ppr[t] = 100.0;
self.px3[t] = 0.0;
} else {
self.ppr[t] = ppr;
self.px3[t] = px3;
}
self.statement_200();
}
fn statement_180(&mut self) {
let z = self.z[self.t];
self.pv = (z + 0.15) + py_max(self.v, 0.0);
if self.pv <= 0.0 {
self.statement_210();
} else {
let PdiDurationFactors { drym, dryb, .. } = self.factors;
self.ze = -dryb * self.x3 - 0.5 * (drym + dryb);
self.statement_190();
}
}
fn statement_170(&mut self) {
let z = self.z[self.t];
self.pv = (z - 0.15) + py_min(self.v, 0.0);
if self.pv >= 0.0 {
self.statement_210();
} else {
let PdiDurationFactors { wetm, wetb, .. } = self.factors;
self.ze = -wetb * self.x3 + 0.5 * (wetm + wetb);
self.statement_190();
}
}
fn advance_month(&mut self, t: usize) {
self.t = t;
self.index[self.k8] = t;
self.ze = 0.0;
let z = self.z[t];
let x3 = self.x3;
if self.pro == 100.0 || self.pro == 0.0 {
if (-0.5..=0.5).contains(&x3) {
self.pv = 0.0;
self.ppr[t] = 0.0;
self.px3[t] = 0.0;
self.statement_200();
} else if x3 > 0.5 {
if z >= 0.15 {
self.statement_210();
} else {
self.statement_170();
}
} else if x3 < -0.5 {
if z <= -0.15 {
self.statement_210();
} else {
self.statement_180();
}
} else {
self.statement_170();
}
} else if x3 > 0.0 || x3.is_nan() {
self.statement_170();
} else {
self.statement_180();
}
}
fn finish_up(&mut self) {
if self.k8max == 0 {
return;
}
let last = self.z.len() - 1;
let final_wplm = case(
self.ppr[last],
self.px1[last],
self.px2[last],
self.px3[last],
);
for k in 0..self.k8max {
let t = self.index[k];
let x = self.x[t];
let px3 = self.px3[t];
self.pdsi[t] = x;
self.phdi[t] = if px3 == 0.0 { x } else { px3 };
self.wplm[t] = final_wplm;
}
}
}
pub fn calculate(z: ArrayView2<'_, f64>, factors: PdiDurationFactors) -> PdiResult {
let (n_months, n_cells) = z.dim();
let mut result = PdiResult {
pdsi: Array2::<f64>::zeros((n_months, n_cells)),
phdi: Array2::<f64>::zeros((n_months, n_cells)),
pmdi: Array2::<f64>::zeros((n_months, n_cells)),
};
for cell in 0..n_cells {
let series = z.column(cell).to_vec();
let mut state = Cell::new(&series, factors);
for t in 0..n_months {
state.advance_month(t);
}
state.finish_up();
result
.pdsi
.column_mut(cell)
.assign(&ndarray::ArrayView1::from(&state.pdsi));
result
.phdi
.column_mut(cell)
.assign(&ndarray::ArrayView1::from(&state.phdi));
result
.pmdi
.column_mut(cell)
.assign(&ndarray::ArrayView1::from(&state.wplm));
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::Array2;
fn palmer_defaults() -> PdiDurationFactors {
let m = (1.0 - 0.897) / (1.0 / 3.0);
let b = 0.897 / (1.0 / 3.0);
PdiDurationFactors {
wetm: m,
wetb: b,
drym: m,
dryb: b,
}
}
#[test]
fn a_wet_month_starts_a_spell_at_x1() {
let factors = palmer_defaults();
let z = Array2::from_elem((1, 1), 4.0);
let result = calculate(z.view(), factors);
let expected = 4.0 / (factors.wetm + factors.wetb);
assert_eq!(result.pdsi[[0, 0]], expected);
assert_eq!(result.phdi[[0, 0]], expected);
}
#[test]
fn an_abating_spell_left_open_is_resolved_by_finish_up() {
let factors = palmer_defaults();
let z = ndarray::array![[4.0], [-1.0]];
let result = calculate(z.view(), factors);
let x3 = carry(factors.wetm, factors.wetb, result.pdsi[[0, 0]], -1.0);
assert_eq!(result.pdsi[[1, 0]], x3);
assert_eq!(result.phdi[[1, 0]], x3);
assert!(result.pmdi[[1, 0]] < x3);
}
#[test]
fn cells_recurse_independently() {
let factors = palmer_defaults();
let z = ndarray::array![[4.0, -4.0], [0.5, -0.5], [f64::NAN, 2.0]];
let block = calculate(z.view(), factors);
for cell in 0..2 {
let single = calculate(
z.slice_axis(ndarray::Axis(1), ndarray::Slice::from(cell..cell + 1)),
factors,
);
for t in 0..3 {
for (a, b) in [
(block.pdsi[[t, cell]], single.pdsi[[t, 0]]),
(block.phdi[[t, cell]], single.phdi[[t, 0]]),
(block.pmdi[[t, cell]], single.pmdi[[t, 0]]),
] {
assert!(a == b || (a.is_nan() && b.is_nan()));
}
}
}
}
}