use std::collections::HashMap;
use nalgebra::{DMatrix, Matrix3, Vector3};
use crate::Error;
pub(crate) type ProjectionEntries = (Vec<(usize, usize, f64)>, usize);
const DEGENERATE_RATIO: f64 = 1e-9;
#[derive(Debug)]
pub(crate) struct BlockGeometry {
pub atoms: Vec<usize>,
pub col: usize,
pub dof: usize,
pub com: Vector3<f64>,
pub total_mass: f64,
pub isqrt: Option<Matrix3<f64>>,
}
pub(crate) fn projection(
positions: &[[f64; 3]],
weights: &[f64],
blocks: &[usize],
) -> Result<DMatrix<f64>, Error> {
let (entries, nb6) = projection_entries(positions, weights, blocks)?;
let mut p = DMatrix::zeros(3 * positions.len(), nb6);
for (r, c, v) in entries {
p[(r, c)] = v;
}
Ok(p)
}
pub(crate) fn projection_entries(
positions: &[[f64; 3]],
weights: &[f64],
blocks: &[usize],
) -> Result<ProjectionEntries, Error> {
let geometry = block_geometry(positions, weights, blocks)?;
let nb6 = geometry.iter().map(|g| g.dof).sum();
let mut entries = Vec::new();
for block in &geometry {
emit_block_columns(
&mut |r, c, v| entries.push((r, c, v)),
positions,
weights,
block,
);
}
Ok((entries, nb6))
}
pub(crate) fn block_geometry(
positions: &[[f64; 3]],
weights: &[f64],
blocks: &[usize],
) -> Result<Vec<BlockGeometry>, Error> {
let groups = group_by_block(blocks);
let mut geometry = Vec::with_capacity(groups.len());
let mut col = 0;
for atoms in groups {
let dof = block_dof(atoms.len());
let total_mass: f64 = atoms.iter().map(|&i| weights[i]).sum();
let mut com = Vector3::zeros();
for &i in &atoms {
com += weights[i] * Vector3::from(positions[i]);
}
com /= total_mass;
let isqrt = if atoms.len() == 1 {
None
} else {
let mut inertia = Matrix3::zeros();
for &i in &atoms {
let x = Vector3::from(positions[i]) - com;
inertia += weights[i] * (x.dot(&x) * Matrix3::identity() - x * x.transpose());
}
Some(inverse_sqrt(inertia)?)
};
geometry.push(BlockGeometry {
atoms,
col,
dof,
com,
total_mass,
isqrt,
});
col += dof;
}
Ok(geometry)
}
const fn block_dof(size: usize) -> usize {
if size == 1 {
3
} else {
6
}
}
fn group_by_block(blocks: &[usize]) -> Vec<Vec<usize>> {
let mut slot_of: HashMap<usize, usize> = HashMap::new();
let mut groups: Vec<Vec<usize>> = Vec::new();
for (atom, &id) in blocks.iter().enumerate() {
let slot = *slot_of.entry(id).or_insert_with(|| {
groups.push(Vec::new());
groups.len() - 1
});
groups[slot].push(atom);
}
groups
}
fn emit_block_columns(
emit: &mut impl FnMut(usize, usize, f64),
positions: &[[f64; 3]],
weights: &[f64],
block: &BlockGeometry,
) {
let sqrt_total = block.total_mass.sqrt();
for &i in &block.atoms {
let s = weights[i].sqrt() / sqrt_total;
for axis in 0..3 {
emit(3 * i + axis, block.col + axis, s);
}
}
let Some(isqrt) = block.isqrt else {
return;
};
let generators: [Vector3<f64>; 3] =
std::array::from_fn(|axis| Vector3::from(isqrt.row(axis).transpose()));
for &i in &block.atoms {
let x = Vector3::from(positions[i]) - block.com;
let s = weights[i].sqrt();
for (axis, generator) in generators.iter().enumerate() {
let rot = generator.cross(&x);
for coord in 0..3 {
emit(3 * i + coord, block.col + 3 + axis, s * rot[coord]);
}
}
}
}
fn inverse_sqrt(m: Matrix3<f64>) -> Result<Matrix3<f64>, Error> {
let eig = m.symmetric_eigen();
let max = eig.eigenvalues.max();
if max <= 0.0 || eig.eigenvalues.iter().any(|&l| l < DEGENERATE_RATIO * max) {
return Err(Error::DegenerateBlock);
}
let inv_sqrt = eig.eigenvalues.map(|l| 1.0 / l.sqrt());
Ok(eig.eigenvectors * Matrix3::from_diagonal(&inv_sqrt) * eig.eigenvectors.transpose())
}