use super::ordered_merge::merge_sorted_blocks;
use super::{CellGrid, CellList, Grid, NEIGHBOUR_OFFSETS};
use crate::backends::errors::block_error;
use crate::brute::{canonicalise, distance_squared};
use crate::{NeighborPair, SpatialError};
use molframe_core::ExecutionContext;
use molframe_core::parallel::{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: super::CellId,
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();
let mut sorted_blocks = Vec::new();
try_for_each_block_in(
plan,
context,
bytes_per_block,
|_, range| {
let mut block = match cells.get(range) {
Some(chunk) => collect_cells(positions, grid, chunk, cutoff_squared)?,
None => Vec::new(),
};
if sort_result {
canonicalise(&mut block);
}
Ok(block)
},
|mut block| {
if sort_result {
sorted_blocks.push(block);
} else {
found.append(&mut block);
}
Ok(())
},
)
.map_err(|error| block_error(&error))?;
if sort_result {
return merge_sorted_blocks(&sorted_blocks, positions.len(), context);
}
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: &[super::CellId],
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: super::CellId) -> Option<[usize; 3]> {
let wide = |value: usize| value as super::CellId;
let plane = wide(dims[1]).checked_mul(wide(dims[2]))?;
if plane == 0 || cell >= wide(dims[0]).checked_mul(plane)? {
return None;
}
let first = usize::try_from(cell / plane).ok()?;
let remainder = usize::try_from(cell % plane).ok()?;
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(())
}