use crate::compute::result::ComputeResult;
use molrs::spatial::neighbors::NeighborQuery;
use molrs::store::frame_access::FrameAccess;
use molrs::types::F;
use ndarray::{Array1, Array2};
use crate::compute::error::ComputeError;
use crate::compute::traits::Compute;
use crate::compute::util::get_positions_ref;
#[derive(Debug, Clone)]
pub struct VanHove {
n_rbins: usize,
r_max: F,
lags: Vec<usize>,
stride: usize,
}
impl VanHove {
pub fn new(n_rbins: usize, r_max: F, lags: Vec<usize>) -> Result<Self, ComputeError> {
if n_rbins == 0 {
return Err(ComputeError::OutOfRange {
field: "VanHove::n_rbins",
value: n_rbins.to_string(),
});
}
if !(r_max.is_finite() && r_max > 0.0) {
return Err(ComputeError::OutOfRange {
field: "VanHove::r_max",
value: r_max.to_string(),
});
}
if lags.is_empty() {
return Err(ComputeError::OutOfRange {
field: "VanHove::lags",
value: "empty".to_string(),
});
}
Ok(Self {
n_rbins,
r_max,
lags,
stride: 1,
})
}
pub fn with_stride(mut self, stride: usize) -> Self {
self.stride = stride.max(1);
self
}
fn shell_volume(&self, r_inner: F, r_outer: F) -> F {
(4.0 / 3.0) * std::f64::consts::PI * (r_outer.powi(3) - r_inner.powi(3))
}
}
impl Compute for VanHove {
type Args<'a> = ();
type Output = VanHoveResult;
fn compute<'a, FA: FrameAccess + Sync + 'a>(
&self,
frames: &[&'a FA],
_args: (),
) -> Result<VanHoveResult, ComputeError> {
if frames.is_empty() {
return Err(ComputeError::EmptyInput);
}
let n_frames = frames.len();
let mut pos: Vec<[Vec<F>; 3]> = Vec::with_capacity(n_frames);
let mut n_particles: Option<usize> = None;
for frame in frames {
let (xp, yp, zp) = get_positions_ref(*frame)?;
let (xs, ys, zs) = (xp.slice(), yp.slice(), zp.slice());
let m = xs.len();
match n_particles {
None => n_particles = Some(m),
Some(n0) if n0 != m => {
return Err(ComputeError::DimensionMismatch {
expected: n0,
got: m,
what: "VanHove particle count",
});
}
_ => {}
}
pos.push([xs.to_vec(), ys.to_vec(), zs.to_vec()]);
}
let n = n_particles.unwrap_or(0);
let simbox = frames[0].simbox_ref();
let volume = simbox.map(|b| b.volume());
let dr = self.r_max / self.n_rbins as F;
let edges = Array1::from_iter((0..=self.n_rbins).map(|i| i as F * dr));
let centers = Array1::from_iter((0..self.n_rbins).map(|i| (i as F + 0.5) * dr));
let used_lags: Vec<usize> = self
.lags
.iter()
.copied()
.filter(|&t| t < n_frames)
.collect();
let n_lags = used_lags.len();
let mut g_self = Array2::<F>::zeros((n_lags, self.n_rbins));
let mut g_distinct = Array2::<F>::zeros((n_lags, self.n_rbins));
let n_rbins = self.n_rbins;
let r_max = self.r_max;
let fac = n_rbins as F / r_max;
let bin = |counts: &mut [F], base: usize, d: F| {
if d < 0.0 || d > r_max {
return;
}
let mut ip = (d * fac) as usize;
if ip >= n_rbins {
ip = n_rbins - 1;
}
counts[base + ip] += 1.0;
};
let accumulate =
|tau: usize, self_counts: &mut [F], dist_counts: &mut [F], n_origins: &mut [usize]| {
let [axs, ays, azs] = &pos[tau];
let nq = simbox.map(|sb| NeighborQuery::from_columns(sb, axs, ays, azs, r_max));
for (li, &lag) in used_lags.iter().enumerate() {
if tau + lag >= n_frames {
continue;
}
n_origins[li] += 1;
let base = li * n_rbins;
let [bxs, bys, bzs] = &pos[tau + lag];
for i in 0..n {
let dx = bxs[i] - axs[i];
let dy = bys[i] - ays[i];
let dz = bzs[i] - azs[i];
bin(self_counts, base, (dx * dx + dy * dy + dz * dz).sqrt());
}
if let Some(ref nq) = nq {
let nlist = nq.query_columns(bxs, bys, bzs);
let ref_i = nlist.point_indices(); let oth_j = nlist.query_point_indices(); let d2 = nlist.dist_sq();
for k in 0..nlist.n_pairs() {
if ref_i[k] == oth_j[k] {
continue;
}
bin(dist_counts, base, d2[k].sqrt());
}
}
}
};
let origins: Vec<usize> = (0..n_frames).step_by(self.stride).collect();
let n_cells = n_lags * self.n_rbins;
let zero = || {
(
vec![0.0 as F; n_cells],
vec![0.0 as F; n_cells],
vec![0usize; n_lags],
)
};
#[cfg(feature = "rayon")]
let (self_counts, dist_counts, origins_per_lag) = {
use rayon::prelude::*;
origins
.par_iter()
.fold(zero, |(mut sc, mut dc, mut no), &tau| {
accumulate(tau, &mut sc, &mut dc, &mut no);
(sc, dc, no)
})
.reduce(zero, |(mut sca, mut dca, mut noa), (scb, dcb, nob)| {
for (x, y) in sca.iter_mut().zip(scb.iter()) {
*x += *y;
}
for (x, y) in dca.iter_mut().zip(dcb.iter()) {
*x += *y;
}
for (x, y) in noa.iter_mut().zip(nob.iter()) {
*x += *y;
}
(sca, dca, noa)
})
};
#[cfg(not(feature = "rayon"))]
let (self_counts, dist_counts, origins_per_lag) = {
let (mut sc, mut dc, mut no) = zero();
for &tau in &origins {
accumulate(tau, &mut sc, &mut dc, &mut no);
}
(sc, dc, no)
};
for (li, &n_orig) in origins_per_lag.iter().enumerate() {
let base = li * self.n_rbins;
let self_slice = &self_counts[base..base + self.n_rbins];
let binned: F = self_slice.iter().sum();
if binned > 0.0 {
let denom = binned * dr;
for (k, gk) in g_self.row_mut(li).iter_mut().enumerate() {
*gk = self_slice[k] / denom;
}
}
if volume.is_some() {
let denom = (n as F) * (n_orig.max(1) as F);
for (k, gk) in g_distinct.row_mut(li).iter_mut().enumerate() {
let shell = self.shell_volume(edges[k], edges[k + 1]);
if shell > 0.0 {
*gk = dist_counts[base + k] / (denom * shell);
}
}
}
}
Ok(VanHoveResult {
r_edges: edges,
r_centers: centers,
lags: used_lags,
g_self,
g_distinct,
dr,
has_distinct: volume.is_some(),
})
}
}
#[derive(Debug, Clone)]
pub struct VanHoveResult {
pub r_edges: Array1<F>,
pub r_centers: Array1<F>,
pub lags: Vec<usize>,
pub g_self: Array2<F>,
pub g_distinct: Array2<F>,
pub dr: F,
pub has_distinct: bool,
}
impl VanHoveResult {
pub fn self_second_moment(&self, li: usize) -> F {
let mut m = 0.0;
for k in 0..self.r_centers.len() {
let r = self.r_centers[k];
m += r * r * self.g_self[[li, k]] * self.dr;
}
m
}
}
impl ComputeResult for VanHoveResult {}