use molrs::signal as sig;
use molrs::types::F;
use ndarray::Array1;
use rustfft::FftPlanner;
use std::collections::HashMap;
use super::detect::HBondsResult;
use crate::compute::error::ComputeError;
#[derive(Debug, Clone)]
pub struct LifetimeResult {
pub lag_times: Array1<F>,
pub continuous: Array1<F>,
pub intermittent: Array1<F>,
pub tau_continuous: F,
pub tau_intermittent: F,
}
fn trapz(y: &Array1<F>, dt: F) -> F {
if y.len() < 2 {
return 0.0;
}
let mut s = 0.0;
for w in y.windows(2).into_iter() {
s += 0.5 * (w[0] + w[1]) * dt;
}
s
}
pub fn hbond_lifetimes(
present: &[Vec<bool>],
dt: F,
max_lag: usize,
) -> Result<LifetimeResult, ComputeError> {
if present.is_empty() {
return Err(ComputeError::EmptyInput);
}
let n_frames = present[0].len();
if n_frames < 2 {
return Err(ComputeError::EmptyInput);
}
for s in present {
if s.len() != n_frames {
return Err(ComputeError::DimensionMismatch {
expected: n_frames,
got: s.len(),
what: "hbond presence series length",
});
}
}
if dt <= 0.0 {
return Err(ComputeError::OutOfRange {
field: "hbond_lifetimes::dt",
value: dt.to_string(),
});
}
let max_lag = max_lag.min(n_frames - 1);
let n_series = present.len();
let (acc_c, acc_i) = accumulate_survival(present, n_frames, max_lag);
let acc_c = acc_c.as_slice().expect("contiguous acc_c");
let acc_i = acc_i.as_slice().expect("contiguous acc_i");
let mut cont = Array1::<F>::zeros(max_lag + 1);
let mut inter = Array1::<F>::zeros(max_lag + 1);
let mean = |acc: &[f64], tau: usize| -> F {
let n_pairs = (n_series * (n_frames - tau)) as F;
if n_pairs > 0.0 {
acc[tau] / n_pairs
} else {
0.0
}
};
let c0 = mean(acc_c, 0);
let i0 = mean(acc_i, 0);
for tau in 0..=max_lag {
cont[tau] = if c0 > 0.0 { mean(acc_c, tau) / c0 } else { 0.0 };
inter[tau] = if i0 > 0.0 { mean(acc_i, tau) / i0 } else { 0.0 };
}
let lag_times = Array1::from_iter((0..=max_lag).map(|i| i as F * dt));
let tau_continuous = trapz(&cont, dt);
let tau_intermittent = trapz(&inter, dt);
Ok(LifetimeResult {
lag_times,
continuous: cont,
intermittent: inter,
tau_continuous,
tau_intermittent,
})
}
fn accumulate_survival(
present: &[Vec<bool>],
n_frames: usize,
max_lag: usize,
) -> (Array1<f64>, Array1<f64>) {
let per_series = |planner: &mut FftPlanner<f64>, h: &Vec<bool>| -> (Array1<f64>, Array1<f64>) {
let mut c = Array1::<f64>::zeros(max_lag + 1);
for t0 in 0..n_frames {
if !h[t0] {
continue;
}
let lmax = max_lag.min(n_frames - 1 - t0);
c[0] += 1.0;
for tau in 1..=lmax {
if h[t0 + tau] {
c[tau] += 1.0;
} else {
break;
}
}
}
let series = Array1::from_iter(h.iter().map(|&b| if b { 1.0 } else { 0.0 }));
let i = sig::acf_fft_with_planner(planner, &series, max_lag)
.expect("max_lag < n_frames guaranteed by clamp");
(c, i)
};
let zeros = || {
(
Array1::<f64>::zeros(max_lag + 1),
Array1::<f64>::zeros(max_lag + 1),
)
};
#[cfg(feature = "rayon")]
{
use rayon::prelude::*;
present
.par_iter()
.map_init(FftPlanner::<f64>::new, |planner, h| per_series(planner, h))
.reduce(zeros, |(ca, ia), (cb, ib)| (ca + cb, ia + ib))
}
#[cfg(not(feature = "rayon"))]
{
let mut planner = FftPlanner::<f64>::new();
let (mut acc_c, mut acc_i) = zeros();
for h in present {
let (c, i) = per_series(&mut planner, h);
acc_c += &c;
acc_i += &i;
}
(acc_c, acc_i)
}
}
pub fn presence_from_hbonds(res: &HBondsResult) -> (Vec<(u32, u32)>, Vec<Vec<bool>>) {
let n_frames = res.per_frame.len();
let mut index: HashMap<(u32, u32), usize> = HashMap::new();
let mut keys: Vec<(u32, u32)> = Vec::new();
for frame in &res.per_frame {
for b in frame {
let key = (b.donor, b.acceptor);
if let std::collections::hash_map::Entry::Vacant(e) = index.entry(key) {
e.insert(keys.len());
keys.push(key);
}
}
}
let mut present = vec![vec![false; n_frames]; keys.len()];
for (t, frame) in res.per_frame.iter().enumerate() {
for b in frame {
let bi = index[&(b.donor, b.acceptor)];
present[bi][t] = true;
}
}
(keys, present)
}