use crate::compute::result::ComputeResult;
use molrs::store::frame_access::FrameAccess;
use molrs::types::F;
use ndarray::Array1;
use crate::compute::error::ComputeError;
use crate::compute::traits::Compute;
use crate::compute::util::{MicHelper, get_positions_ref};
#[derive(Debug, Clone)]
pub struct LegendreReorientation {
max_lag: usize,
stride: usize,
}
impl LegendreReorientation {
pub fn new(max_lag: usize) -> Self {
Self { max_lag, stride: 1 }
}
pub fn with_stride(mut self, stride: usize) -> Self {
self.stride = stride.max(1);
self
}
}
fn p1(x: F) -> F {
x
}
fn p2(x: F) -> F {
0.5 * (3.0 * x * x - 1.0)
}
impl Compute for LegendreReorientation {
type Args<'a> = &'a [(u32, u32)];
type Output = LegendreReorientationResult;
fn compute<'a, FA: FrameAccess + Sync + 'a>(
&self,
frames: &[&'a FA],
pairs: &'a [(u32, u32)],
) -> Result<LegendreReorientationResult, ComputeError> {
if frames.is_empty() {
return Err(ComputeError::EmptyInput);
}
if pairs.is_empty() {
return Err(ComputeError::OutOfRange {
field: "LegendreReorientation::pairs",
value: "empty".to_string(),
});
}
let n_frames = frames.len();
let n_vec = pairs.len();
let mut vecs: Vec<[F; 3]> = Vec::with_capacity(n_frames * n_vec);
for frame in frames {
let (xp, yp, zp) = get_positions_ref(*frame)?;
let (xs, ys, zs) = (xp.slice(), yp.slice(), zp.slice());
let mic = MicHelper::from_simbox(frame.simbox_ref());
for &(a, b) in pairs {
let (a, b) = (a as usize, b as usize);
if a >= xs.len() || b >= xs.len() {
return Err(ComputeError::DimensionMismatch {
expected: xs.len(),
got: a.max(b) + 1,
what: "LegendreReorientation atom index",
});
}
let d = mic.disp([xs[a], ys[a], zs[a]], [xs[b], ys[b], zs[b]]);
let norm = (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt();
if !(norm.is_finite() && norm > 0.0) {
return Err(ComputeError::NonFinite {
where_: "LegendreReorientation unit vector (zero-length)",
index: 0,
});
}
let inv = norm.recip();
vecs.push([d[0] * inv, d[1] * inv, d[2] * inv]);
}
}
let max_lag = self.max_lag.min(n_frames - 1);
let stride = self.stride;
let corr_at = |t: usize| -> (F, F) {
let mut s1 = 0.0;
let mut s2 = 0.0;
let mut count: usize = 0;
let mut tau = 0usize;
while tau + t < n_frames {
let base_a = tau * n_vec;
let base_b = (tau + t) * n_vec;
for m in 0..n_vec {
let a = vecs[base_a + m];
let b = vecs[base_b + m];
let x = a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
s1 += p1(x);
s2 += p2(x);
count += 1;
}
tau += stride;
}
let inv = if count > 0 { 1.0 / count as F } else { 0.0 };
(s1 * inv, s2 * inv)
};
#[cfg(feature = "rayon")]
let (c1v, c2v): (Vec<F>, Vec<F>) = {
use rayon::prelude::*;
(0..=max_lag).into_par_iter().map(corr_at).unzip()
};
#[cfg(not(feature = "rayon"))]
let (c1v, c2v): (Vec<F>, Vec<F>) = (0..=max_lag).map(corr_at).unzip();
Ok(LegendreReorientationResult {
lags: (0..=max_lag).collect(),
c1: Array1::from_vec(c1v),
c2: Array1::from_vec(c2v),
})
}
}
#[derive(Debug, Clone)]
pub struct LegendreReorientationResult {
pub lags: Vec<usize>,
pub c1: Array1<F>,
pub c2: Array1<F>,
}
impl ComputeResult for LegendreReorientationResult {}