use molrs::store::frame_access::FrameAccess;
use ndarray::{Array1, Array2};
use rustfft::FftPlanner;
use crate::compute::error::ComputeError;
use crate::compute::result::ComputeResult;
use crate::compute::traits::Compute;
use molrs::signal as sig;
#[derive(Debug, Clone)]
pub struct VacfResult {
pub lag_times: Array1<f64>,
pub acf: Array1<f64>,
}
impl ComputeResult for VacfResult {}
#[derive(Debug, Clone, Copy, Default)]
pub struct VACF;
pub type VacfArgs<'a> = (&'a Array2<f64>, f64, usize);
pub(super) fn velocity_acf(
velocities: &Array2<f64>,
dt: f64,
resolution: usize,
) -> Result<VacfResult, ComputeError> {
let n_frames = velocities.shape()[0];
let n_dof = velocities.shape()[1];
if n_frames < 2 {
return Err(ComputeError::EmptyInput);
}
if dt <= 0.0 {
return Err(ComputeError::OutOfRange {
field: "dt",
value: dt.to_string(),
});
}
let max_lag = resolution.min(n_frames - 1);
let inv_n_frames = 1.0 / n_frames as f64;
let mut planner = FftPlanner::new();
let mut acf_sum = Array1::<f64>::zeros(max_lag + 1);
for d in 0..n_dof {
let mut col: Array1<f64> = (0..n_frames).map(|t| velocities[[t, d]]).collect();
let mean: f64 = col.iter().sum::<f64>() * inv_n_frames;
for v in col.iter_mut() {
*v -= mean;
}
let acf = sig::acf_fft_with_planner(&mut planner, &col, max_lag).map_err(|e| {
ComputeError::OutOfRange {
field: "acf_fft",
value: e.to_string(),
}
})?;
for k in 0..=max_lag {
acf_sum[k] += acf[k];
}
}
let inv_n_dof = 1.0 / n_dof as f64;
for k in 0..=max_lag {
acf_sum[k] *= inv_n_dof;
}
let lag_times = Array1::from_iter((0..=max_lag).map(|i| i as f64 * dt));
Ok(VacfResult {
lag_times,
acf: acf_sum,
})
}
impl Compute for VACF {
type Args<'a> = VacfArgs<'a>;
type Output = VacfResult;
fn compute<'a, FA: FrameAccess + Sync + 'a>(
&self,
_frames: &[&'a FA],
args: Self::Args<'a>,
) -> Result<Self::Output, ComputeError> {
let (velocities, dt, resolution) = args;
velocity_acf(velocities, dt, resolution)
}
}
#[cfg(test)]
mod tests {
use super::*;
use molrs::Frame;
use ndarray::{Array1 as A1, Array2};
use rand::{RngExt, SeedableRng};
fn no_frames() -> Vec<&'static Frame> {
Vec::new()
}
fn rng_series(n: usize, cols: usize, seed: u64) -> Array2<f64> {
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
let mut s = Array2::zeros((n, cols));
for t in 0..n {
for c in 0..cols {
s[[t, c]] = rng.random_range(-1.0..1.0);
}
}
s
}
#[test]
fn vacf_equals_vdos_acf_sum() {
let n = 512;
let dt = 0.5;
let res = 100;
let v = rng_series(n, 9, 7);
let max_lag = res.min(n - 1);
let inv_n_frames = 1.0 / n as f64;
let mut planner = FftPlanner::new();
let mut acf_sum = A1::<f64>::zeros(max_lag + 1);
for d in 0..9 {
let mut col: A1<f64> = (0..n).map(|t| v[[t, d]]).collect();
let mean: f64 = col.iter().sum::<f64>() * inv_n_frames;
for x in col.iter_mut() {
*x -= mean;
}
let acf = sig::acf_fft_with_planner(&mut planner, &col, max_lag).unwrap();
for k in 0..=max_lag {
acf_sum[k] += acf[k];
}
}
for k in 0..=max_lag {
acf_sum[k] *= 1.0 / 9.0;
}
let raw = VACF.compute(&no_frames(), (&v, dt, res)).unwrap();
assert_eq!(raw.acf.len(), acf_sum.len());
for k in 0..raw.acf.len() {
assert!((raw.acf[k] - acf_sum[k]).abs() < 1e-12, "k={k}");
}
assert_eq!(raw.acf.len(), max_lag + 1);
}
#[test]
fn raw_max_lag_exceeds_length_clamps_not_panics() {
let v = rng_series(8, 3, 1);
let raw = VACF.compute(&no_frames(), (&v, 1.0, 1000)).unwrap();
assert_eq!(raw.acf.len(), 8); }
}