use molrs::store::frame_access::FrameAccess;
use molrs::types::F;
use ndarray::{Array2, Array3, Array4};
use super::kabsch::{kabsch, rotate};
use crate::compute::error::ComputeError;
use crate::compute::result::ComputeResult;
use crate::compute::traits::Compute;
use crate::compute::util::{MicHelper, get_positions_ref};
#[derive(Debug, Clone, Copy)]
pub struct GridSpec {
pub n: [usize; 3],
pub extent: [F; 3],
}
impl GridSpec {
fn voxel_size(&self) -> [F; 3] {
[
self.extent[0] / self.n[0] as F,
self.extent[1] / self.n[1] as F,
self.extent[2] / self.n[2] as F,
]
}
fn voxel_volume(&self) -> F {
let v = self.voxel_size();
v[0] * v[1] * v[2]
}
fn index(&self, r: [F; 3]) -> Option<[usize; 3]> {
let vs = self.voxel_size();
let mut idx = [0usize; 3];
for d in 0..3 {
let shifted = r[d] + 0.5 * self.extent[d];
if shifted < 0.0 {
return None;
}
let i = (shifted / vs[d]).floor() as isize;
if i < 0 || i >= self.n[d] as isize {
return None;
}
idx[d] = i as usize;
}
Some(idx)
}
}
#[derive(Debug, Clone)]
pub struct SpatialDistribution {
reference: Vec<usize>,
template: Array2<F>,
target: Vec<usize>,
grid: GridSpec,
orientation: Option<Vec<(usize, usize)>>,
bulk_density: Option<F>,
}
impl SpatialDistribution {
pub fn new(
reference: Vec<usize>,
template: Array2<F>,
target: Vec<usize>,
grid: GridSpec,
) -> Result<Self, ComputeError> {
if template.nrows() != reference.len() {
return Err(ComputeError::DimensionMismatch {
expected: reference.len(),
got: template.nrows(),
what: "SDF template rows",
});
}
if reference.len() < 3 {
return Err(ComputeError::OutOfRange {
field: "SpatialDistribution::reference",
value: reference.len().to_string(),
});
}
if grid.n.contains(&0) || grid.extent.iter().any(|&e| e <= 0.0 || e.is_nan()) {
return Err(ComputeError::OutOfRange {
field: "SpatialDistribution::grid",
value: format!("n={:?}, extent={:?}", grid.n, grid.extent),
});
}
Ok(Self {
reference,
template,
target,
grid,
orientation: None,
bulk_density: None,
})
}
pub fn with_orientation(mut self, pairs: Vec<(usize, usize)>) -> Self {
self.orientation = Some(pairs);
self
}
pub fn with_bulk_density(mut self, rho: F) -> Self {
self.bulk_density = Some(rho);
self
}
fn reference_coords(&self, xs: &[F], ys: &[F], zs: &[F]) -> Array2<F> {
let mut a = Array2::<F>::zeros((self.reference.len(), 3));
for (row, &i) in self.reference.iter().enumerate() {
a[[row, 0]] = xs[i];
a[[row, 1]] = ys[i];
a[[row, 2]] = zs[i];
}
a
}
fn accumulate_frame<FA: FrameAccess>(
&self,
frame: &FA,
counts: &mut Array3<F>,
mut orient: Option<(&mut Array4<F>, &mut Array3<F>)>,
) -> Result<(), ComputeError> {
let simbox = frame.simbox_ref();
let mic = MicHelper::from_simbox(simbox);
let (xs_p, ys_p, zs_p) = get_positions_ref(frame)?;
let xs = xs_p.slice();
let ys = ys_p.slice();
let zs = zs_p.slice();
let ref_coords = self.reference_coords(xs, ys, zs);
let (r, _rmsd) = kabsch(self.template.view(), ref_coords.view())?;
let com = centroid(&ref_coords);
for (t, &ai) in self.target.iter().enumerate() {
let disp = mic.disp(com, [xs[ai], ys[ai], zs[ai]]);
let body = rotate(&r, disp);
let Some([ix, iy, iz]) = self.grid.index(body) else {
continue;
};
counts[[ix, iy, iz]] += 1.0;
if let Some((osum, ocount)) = orient.as_mut()
&& let Some(pairs) = &self.orientation
{
let (tail, head) = pairs[t];
let v = mic.disp(
[xs[tail], ys[tail], zs[tail]],
[xs[head], ys[head], zs[head]],
);
let n = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
if n > 0.0 {
let u = [v[0] / n, v[1] / n, v[2] / n];
let bu = rotate(&r, u);
for d in 0..3 {
osum[[ix, iy, iz, d]] += bu[d];
}
ocount[[ix, iy, iz]] += 1.0;
}
}
}
Ok(())
}
}
impl Compute for SpatialDistribution {
type Args<'a> = ();
type Output = SpatialDistributionResult;
fn compute<'a, FA: FrameAccess + Sync + 'a>(
&self,
frames: &[&'a FA],
_args: (),
) -> Result<Self::Output, ComputeError> {
if frames.is_empty() {
return Err(ComputeError::EmptyInput);
}
if let Some(pairs) = &self.orientation
&& pairs.len() != self.target.len()
{
return Err(ComputeError::DimensionMismatch {
expected: self.target.len(),
got: pairs.len(),
what: "SDF orientation pairs",
});
}
let [nx, ny, nz] = self.grid.n;
let mut counts = Array3::<F>::zeros((nx, ny, nz));
let mut orient_sum: Option<Array4<F>> = self
.orientation
.as_ref()
.map(|_| Array4::zeros((nx, ny, nz, 3)));
let mut orient_count: Option<Array3<F>> = self
.orientation
.as_ref()
.map(|_| Array3::zeros((nx, ny, nz)));
#[cfg(feature = "rayon")]
const PAR_THRESHOLD: usize = 4;
#[cfg(feature = "rayon")]
if self.orientation.is_none() && frames.len() >= PAR_THRESHOLD {
use rayon::prelude::*;
let partials: Result<Vec<Array3<F>>, ComputeError> = frames
.par_iter()
.map(|frame| {
let mut c = Array3::<F>::zeros((nx, ny, nz));
self.accumulate_frame(*frame, &mut c, None)?;
Ok(c)
})
.collect();
for partial in partials? {
counts += &partial;
}
} else {
for frame in frames {
let orient = orient_sum.as_mut().zip(orient_count.as_mut());
self.accumulate_frame(*frame, &mut counts, orient)?;
}
}
#[cfg(not(feature = "rayon"))]
for frame in frames {
let orient = orient_sum.as_mut().zip(orient_count.as_mut());
self.accumulate_frame(*frame, &mut counts, orient)?;
}
let mut result = SpatialDistributionResult {
counts,
density: Array3::zeros((nx, ny, nz)),
g_sdf: None,
orientation: None,
orient_sum,
orient_count,
n: self.grid.n,
extent: self.grid.extent,
voxel_volume: self.grid.voxel_volume(),
n_frames: frames.len(),
bulk_density: self.bulk_density,
finalized: false,
};
result.finalize();
Ok(result)
}
}
fn centroid(a: &Array2<F>) -> [F; 3] {
let m = a.nrows().max(1) as F;
let mut c = [0.0_f64; 3];
for row in a.rows() {
for d in 0..3 {
c[d] += row[d];
}
}
for cd in &mut c {
*cd /= m;
}
c
}
#[derive(Debug, Clone)]
pub struct SpatialDistributionResult {
pub counts: Array3<F>,
pub density: Array3<F>,
pub g_sdf: Option<Array3<F>>,
pub orientation: Option<Array4<F>>,
orient_sum: Option<Array4<F>>,
orient_count: Option<Array3<F>>,
pub n: [usize; 3],
pub extent: [F; 3],
pub voxel_volume: F,
pub n_frames: usize,
bulk_density: Option<F>,
finalized: bool,
}
impl ComputeResult for SpatialDistributionResult {
fn finalize(&mut self) {
if self.finalized {
return;
}
let denom = self.n_frames.max(1) as F * self.voxel_volume;
self.density = self.counts.mapv(|c| c / denom);
if let Some(rho) = self.bulk_density
&& rho > 0.0
{
self.g_sdf = Some(self.density.mapv(|d| d / rho));
}
if let (Some(sum), Some(count)) = (&self.orient_sum, &self.orient_count) {
let [nx, ny, nz] = self.n;
let mut mean = Array4::<F>::zeros((nx, ny, nz, 3));
for ix in 0..nx {
for iy in 0..ny {
for iz in 0..nz {
let c = count[[ix, iy, iz]];
if c > 0.0 {
for d in 0..3 {
mean[[ix, iy, iz, d]] = sum[[ix, iy, iz, d]] / c;
}
}
}
}
}
self.orientation = Some(mean);
}
self.finalized = true;
}
}