use crate::{NeighborPair, PeriodicBox};
pub(crate) fn pairs(
positions: &[[f32; 3]],
left: &[u32],
right: &[u32],
cutoff_squared: f32,
periodic: Option<&PeriodicBox>,
) -> Vec<NeighborPair> {
pairs_with_mode::<false>(positions, left, right, cutoff_squared, periodic, true)
}
pub(crate) fn pairs_same_selection(
positions: &[[f32; 3]],
selection: &[u32],
cutoff_squared: f32,
periodic: Option<&PeriodicBox>,
) -> Vec<NeighborPair> {
pairs_with_mode::<true>(
positions,
selection,
selection,
cutoff_squared,
periodic,
true,
)
}
pub(crate) fn pairs_same_selection_unordered(
positions: &[[f32; 3]],
selection: &[u32],
cutoff_squared: f32,
periodic: Option<&PeriodicBox>,
) -> Vec<NeighborPair> {
pairs_with_mode::<true>(
positions,
selection,
selection,
cutoff_squared,
periodic,
false,
)
}
pub(crate) fn for_each_candidate(
positions: &[[f32; 3]],
left: &[u32],
right: &[u32],
cutoff_squared: f32,
periodic: Option<&PeriodicBox>,
mut emit: impl FnMut(u32, u32, f32),
) {
for_each_candidate_with_mode::<false>(
positions,
left,
right,
cutoff_squared,
periodic,
&mut emit,
);
}
pub(crate) fn for_each_pair_same_selection(
positions: &[[f32; 3]],
selection: &[u32],
cutoff_squared: f32,
periodic: Option<&PeriodicBox>,
mut emit: impl FnMut(NeighborPair),
) {
for_each_candidate_with_mode::<true>(
positions,
selection,
selection,
cutoff_squared,
periodic,
&mut |left_atom, right_atom, squared| {
emit(NeighborPair::new(left_atom, right_atom, squared));
},
);
}
fn pairs_with_mode<const UNIQUE: bool>(
positions: &[[f32; 3]],
left: &[u32],
right: &[u32],
cutoff_squared: f32,
periodic: Option<&PeriodicBox>,
sort_result: bool,
) -> Vec<NeighborPair> {
let mut found = Vec::with_capacity(left.len());
for_each_candidate_with_mode::<UNIQUE>(
positions,
left,
right,
cutoff_squared,
periodic,
&mut |left_atom, right_atom, squared| {
found.push(NeighborPair::new(left_atom, right_atom, squared));
},
);
if sort_result {
canonicalise(&mut found);
}
found
}
fn for_each_candidate_with_mode<const UNIQUE: bool>(
positions: &[[f32; 3]],
left: &[u32],
right: &[u32],
cutoff_squared: f32,
periodic: Option<&PeriodicBox>,
emit: &mut impl FnMut(u32, u32, f32),
) {
match periodic {
Some(periodic) => {
for_each_pair_periodic::<UNIQUE>(
positions,
left,
right,
cutoff_squared,
periodic,
emit,
);
}
None => for_each_pair_simd::<UNIQUE>(positions, left, right, cutoff_squared, emit),
}
}
fn for_each_pair_periodic<const UNIQUE: bool>(
positions: &[[f32; 3]],
left: &[u32],
right: &[u32],
cutoff_squared: f32,
periodic: &PeriodicBox,
emit: &mut impl FnMut(u32, u32, f32),
) {
for &left_atom in left {
let Some(left_position) = valid_position(positions, left_atom) else {
continue;
};
let candidates = right_for_left::<UNIQUE>(right, left_atom);
append_periodic_pairs::<UNIQUE>(
positions,
left_atom,
left_position,
candidates,
cutoff_squared,
periodic,
emit,
);
}
}
fn append_periodic_pairs<const UNIQUE: bool>(
positions: &[[f32; 3]],
left_atom: u32,
left_position: [f32; 3],
right: &[u32],
cutoff_squared: f32,
periodic: &PeriodicBox,
emit: &mut impl FnMut(u32, u32, f32),
) {
for &right_atom in right {
if !allowed_pair::<UNIQUE>(left_atom, right_atom) {
continue;
}
let Some(right_position) = valid_position(positions, right_atom) else {
continue;
};
let squared = periodic.distance_squared(left_position, right_position);
if squared <= cutoff_squared {
emit(left_atom, right_atom, squared);
}
}
}
fn for_each_pair_simd<const UNIQUE: bool>(
positions: &[[f32; 3]],
left: &[u32],
right: &[u32],
cutoff_squared: f32,
emit: &mut impl FnMut(u32, u32, f32),
) {
for &left_atom in left {
let Some(left_position) = valid_position(positions, left_atom) else {
continue;
};
let candidates = right_for_left::<UNIQUE>(right, left_atom);
super::brute_simd::append_pairs::<UNIQUE>(
positions,
left_atom,
left_position,
candidates,
cutoff_squared,
emit,
);
}
}
#[inline]
fn right_for_left<const UNIQUE: bool>(right: &[u32], left_atom: u32) -> &[u32] {
if UNIQUE {
let first = right.partition_point(|&right_atom| right_atom <= left_atom);
&right[first..]
} else {
right
}
}
#[inline]
pub(super) fn allowed_pair<const UNIQUE: bool>(left_atom: u32, right_atom: u32) -> bool {
if UNIQUE {
left_atom < right_atom
} else {
left_atom != right_atom
}
}
#[inline]
pub(super) fn valid_position(positions: &[[f32; 3]], atom: u32) -> Option<[f32; 3]> {
let index = usize::try_from(atom).ok()?;
positions
.get(index)
.copied()
.filter(|position| finite(*position))
}
#[inline]
pub(crate) fn distance_squared(
left: [f32; 3],
right: [f32; 3],
periodic: Option<&PeriodicBox>,
) -> f32 {
match periodic {
Some(periodic) => periodic.distance_squared(left, right),
None => euclidean_distance_squared(left, right),
}
}
#[inline]
pub(super) fn euclidean_distance_squared(left: [f32; 3], right: [f32; 3]) -> f32 {
let dx = left[0] - right[0];
let dy = left[1] - right[1];
let dz = left[2] - right[2];
dx * dx + dy * dy + dz * dz
}
#[inline]
pub(crate) fn finite(position: [f32; 3]) -> bool {
position[0].is_finite() && position[1].is_finite() && position[2].is_finite()
}
pub(crate) fn canonicalise(found: &mut Vec<NeighborPair>) {
if found.len() < 2 {
return;
}
found.sort_unstable_by_key(|pair| (pair.first, pair.second));
found.dedup_by(|left, right| left.first == right.first && left.second == right.second);
}
#[cfg(test)]
#[path = "brute_tests.rs"]
mod tests;