use super::{CellGrid, CellList, Grid, NEIGHBOUR_OFFSETS};
use crate::brute::{canonicalise, distance_squared};
use crate::{NeighborPair, SpatialError};
use molframe_core::ExecutionContext;
use molframe_core::parallel::{
BlockExecutionError, BlockPlan, map_blocks_in, try_for_each_block_in,
};
pub(crate) fn pairs_same_selection(
list: &CellList<'_>,
query: &[u32],
cutoff: f32,
) -> Result<Vec<NeighborPair>, SpatialError> {
pairs_same_selection_with_order(list, query, cutoff, true)
}
pub(crate) fn pairs_same_selection_unordered(
list: &CellList<'_>,
query: &[u32],
cutoff: f32,
) -> Result<Vec<NeighborPair>, SpatialError> {
pairs_same_selection_with_order(list, query, cutoff, false)
}
pub(crate) fn for_each_pairs_same_selection_unordered<F>(
list: &CellList<'_>,
query: &[u32],
cutoff: f32,
mut emit: F,
) -> Result<(), SpatialError>
where
F: FnMut(NeighborPair),
{
super::validate_query_cutoff(cutoff, list.cutoff)?;
super::validate_indices(query, list.positions.len())?;
match &list.grid {
Some(CellGrid::Cartesian(grid)) => {
grid_for_each_pairs(list.positions, grid, cutoff * cutoff, &mut emit)
}
Some(CellGrid::Periodic(grid)) => {
let Some(periodic) = list.periodic else {
return Err(SpatialError::InvalidCell);
};
grid.for_each_pairs_same_selection(
list.positions,
query,
cutoff * cutoff,
&periodic,
&mut emit,
)
}
None => Ok(()),
}
}
fn pairs_same_selection_with_order(
list: &CellList<'_>,
query: &[u32],
cutoff: f32,
sort_result: bool,
) -> Result<Vec<NeighborPair>, SpatialError> {
super::validate_query_cutoff(cutoff, list.cutoff)?;
super::validate_indices(query, list.positions.len())?;
match &list.grid {
Some(CellGrid::Cartesian(grid)) => {
let mut found = Vec::with_capacity(query.len());
grid_for_each_pairs(list.positions, grid, cutoff * cutoff, &mut |pair| {
found.push(pair);
})?;
if sort_result {
canonicalise(&mut found);
}
Ok(found)
}
Some(CellGrid::Periodic(grid)) => {
let Some(periodic) = list.periodic else {
return Err(SpatialError::InvalidCell);
};
grid.pairs_same_selection(
list.positions,
query,
cutoff * cutoff,
&periodic,
sort_result,
)
}
None => Ok(Vec::new()),
}
}
fn grid_for_each_pairs<F>(
positions: &[[f32; 3]],
grid: &Grid,
cutoff_squared: f32,
emit: &mut F,
) -> Result<(), SpatialError>
where
F: FnMut(NeighborPair),
{
for &cell in &grid.non_empty_cells {
emit_cell_pairs(positions, grid, cell, cutoff_squared, emit)?;
}
Ok(())
}
pub(super) fn emit_cell_pairs<F>(
positions: &[[f32; 3]],
grid: &Grid,
cell: usize,
cutoff_squared: f32,
emit: &mut F,
) -> Result<(), SpatialError>
where
F: FnMut(NeighborPair),
{
let members = super::members_of_cell(grid, cell).ok_or(SpatialError::NumericRangeExceeded)?;
append_same_cell_pairs(positions, members, cutoff_squared, emit)?;
let centre = cell_coordinates(grid.dims, cell).ok_or(SpatialError::NumericRangeExceeded)?;
for delta in NEIGHBOUR_OFFSETS {
let Some(neighbour) = super::neighbour_cell(grid.dims, centre, delta) else {
continue;
};
if neighbour <= cell {
continue;
}
let targets =
super::members_of_cell(grid, neighbour).ok_or(SpatialError::NumericRangeExceeded)?;
append_cross_cell_pairs(positions, members, targets, cutoff_squared, emit)?;
}
Ok(())
}
pub(crate) fn pairs_same_selection_parallel(
list: &CellList<'_>,
query: &[u32],
cutoff: f32,
context: &ExecutionContext,
sort_result: bool,
) -> Result<Vec<NeighborPair>, SpatialError> {
const BLOCK_CELLS: usize = 64;
const EXPECTED_PAIRS_PER_ATOM: usize = 64;
const MIN_PARALLEL_ATOMS: usize = 2_048;
super::validate_query_cutoff(cutoff, list.cutoff)?;
super::validate_indices(query, list.positions.len())?;
let Some(CellGrid::Cartesian(grid)) = &list.grid else {
return pairs_same_selection_with_order(list, query, cutoff, sort_result);
};
if query.len() < MIN_PARALLEL_ATOMS {
return pairs_same_selection_with_order(list, query, cutoff, sort_result);
}
let cutoff_squared = cutoff * cutoff;
let cells = &grid.non_empty_cells;
let positions = list.positions;
let plan = BlockPlan::new(cells.len(), BLOCK_CELLS);
let atoms_per_block = query
.len()
.div_ceil(cells.len().max(1))
.saturating_mul(BLOCK_CELLS);
let bytes_per_block = atoms_per_block
.saturating_mul(EXPECTED_PAIRS_PER_ATOM)
.saturating_mul(size_of::<NeighborPair>());
let mut found = Vec::new();
try_for_each_block_in(
plan,
context,
bytes_per_block,
|_, range| match cells.get(range) {
Some(chunk) => collect_cells(positions, grid, chunk, cutoff_squared),
None => Ok(Vec::new()),
},
|mut block| {
found.append(&mut block);
Ok(())
},
)
.map_err(|error| match error {
BlockExecutionError::Memory(error) => SpatialError::Memory(error),
BlockExecutionError::Cancelled => SpatialError::Cancelled,
BlockExecutionError::Worker(_) => SpatialError::WorkerPanicked,
BlockExecutionError::Operation(error) => error,
})?;
if sort_result {
canonicalise(&mut found);
}
Ok(found)
}
pub(crate) fn reduce_pairs_same_selection_parallel<T, I, F>(
list: &CellList<'_>,
query: &[u32],
cutoff: f32,
context: &ExecutionContext,
init: I,
fold: F,
) -> Result<Option<Vec<T>>, SpatialError>
where
T: Send,
I: Fn() -> T + Sync,
F: Fn(&mut T, NeighborPair) + Sync,
{
const BLOCK_CELLS: usize = 64;
super::validate_query_cutoff(cutoff, list.cutoff)?;
super::validate_indices(query, list.positions.len())?;
let Some(CellGrid::Cartesian(grid)) = &list.grid else {
return Ok(None);
};
let cutoff_squared = cutoff * cutoff;
let cells = &grid.non_empty_cells;
let positions = list.positions;
let plan = BlockPlan::new(cells.len(), BLOCK_CELLS);
let produced = map_blocks_in(plan, context, |_, range| {
let mut accumulator = init();
let Some(chunk) = cells.get(range) else {
return Ok(accumulator);
};
for &cell in chunk {
emit_cell_pairs(positions, grid, cell, cutoff_squared, &mut |pair| {
fold(&mut accumulator, pair);
})?;
}
Ok(accumulator)
})
.map_err(|_| SpatialError::WorkerPanicked)?;
produced
.into_iter()
.collect::<Result<Vec<T>, _>>()
.map(Some)
}
fn collect_cells(
positions: &[[f32; 3]],
grid: &Grid,
cells: &[usize],
cutoff_squared: f32,
) -> Result<Vec<NeighborPair>, SpatialError> {
let mut found = Vec::new();
for &cell in cells {
emit_cell_pairs(positions, grid, cell, cutoff_squared, &mut |pair| {
found.push(pair);
})?;
}
Ok(found)
}
#[inline]
fn cell_coordinates(dims: [usize; 3], cell: usize) -> Option<[usize; 3]> {
let plane = dims[1].checked_mul(dims[2])?;
if plane == 0 || cell >= dims[0].checked_mul(plane)? {
return None;
}
let first = cell / plane;
let remainder = cell % plane;
Some([first, remainder / dims[2], remainder % dims[2]])
}
fn append_same_cell_pairs<F>(
positions: &[[f32; 3]],
members: &[u32],
cutoff_squared: f32,
emit: &mut F,
) -> Result<(), SpatialError>
where
F: FnMut(NeighborPair),
{
for &atom in members {
let index = usize::try_from(atom).map_err(|_| SpatialError::NumericRangeExceeded)?;
let Some(position) = positions.get(index).copied() else {
continue;
};
for &target in members {
if atom >= target {
continue;
}
let target_index =
usize::try_from(target).map_err(|_| SpatialError::NumericRangeExceeded)?;
let Some(target_position) = positions.get(target_index).copied() else {
continue;
};
let squared = distance_squared(position, target_position, None);
if squared <= cutoff_squared {
emit(NeighborPair::new(atom, target, squared));
}
}
}
Ok(())
}
fn append_cross_cell_pairs<F>(
positions: &[[f32; 3]],
members: &[u32],
targets: &[u32],
cutoff_squared: f32,
emit: &mut F,
) -> Result<(), SpatialError>
where
F: FnMut(NeighborPair),
{
for &atom in members {
let index = usize::try_from(atom).map_err(|_| SpatialError::NumericRangeExceeded)?;
let Some(position) = positions.get(index).copied() else {
continue;
};
for &target in targets {
let target_index =
usize::try_from(target).map_err(|_| SpatialError::NumericRangeExceeded)?;
let Some(target_position) = positions.get(target_index).copied() else {
continue;
};
let squared = distance_squared(position, target_position, None);
if squared <= cutoff_squared {
emit(NeighborPair::new(atom, target, squared));
}
}
}
Ok(())
}