use super::search::validate_cutoff;
use super::visit::{emit_unique_cross_candidate, visit_indices};
use crate::{
CellList, NeighborPair, PeriodicBox, SpatialBackend, SpatialError, SpatialSearchOptions,
};
use molframe_core::ExecutionContext;
use molframe_core::parallel::{BlockPlan, map_blocks_in};
use molframe_core::selection::AtomSelection;
#[derive(Clone, Copy, Debug)]
pub struct PairQuery<'a> {
pub positions: &'a [[f32; 3]],
pub left: &'a AtomSelection,
pub right: &'a AtomSelection,
pub cutoff: f32,
pub options: SpatialSearchOptions,
pub periodic: Option<&'a PeriodicBox>,
pub context: &'a ExecutionContext,
}
pub fn reduce_pairs_within_unsorted<T, I, F>(
query: &PairQuery<'_>,
init: I,
fold: F,
) -> Result<Vec<T>, SpatialError>
where
T: Send,
I: Fn() -> T + Sync,
F: Fn(&mut T, NeighborPair) + Sync,
{
let &PairQuery {
positions,
left,
right,
cutoff,
options,
periodic,
context,
} = query;
validate_cutoff(cutoff)?;
let indices = super::indices::IndexWorkspace::new(left, right, positions.len(), context)?;
let left_indices = indices.left();
let right_indices = indices.right();
let plan = options.plan(left_indices.len(), right_indices.len(), cutoff)?;
let workers = context.worker_budget();
let blockable = plan.backend == SpatialBackend::CellList && periodic.is_none() && workers > 1;
let same_selection = left_indices == right_indices;
if blockable {
let index = CellList::build_in(
positions,
right_indices,
cutoff,
periodic.copied(),
options.cell_grid,
context,
)?;
if same_selection {
if let Some(parts) = crate::backends::cell::reduce_pairs_same_selection_parallel(
&index,
left_indices,
cutoff,
context,
&init,
&fold,
)? {
return Ok(parts);
}
} else {
return reduce_cross_selection(
&index,
left_indices,
right_indices,
cutoff,
context,
&init,
&fold,
);
}
}
let mut single = init();
visit_indices(query, left_indices, right_indices, |pair| {
fold(&mut single, pair);
})?;
Ok(vec![single])
}
#[cfg(test)]
#[path = "reduce_tests.rs"]
mod tests;
fn reduce_cross_selection<T, I, F>(
index: &CellList<'_>,
left: &[u32],
right: &[u32],
cutoff: f32,
context: &ExecutionContext,
init: &I,
fold: &F,
) -> Result<Vec<T>, SpatialError>
where
T: Send,
I: Fn() -> T + Sync,
F: Fn(&mut T, NeighborPair) + Sync,
{
const BLOCK_QUERIES: usize = 256;
let plan = BlockPlan::new(left.len(), BLOCK_QUERIES);
let produced = map_blocks_in(plan, context, |_, range| {
let mut accumulator = init();
let Some(chunk) = left.get(range) else {
return Ok(accumulator);
};
index.for_each_candidate(chunk, cutoff, |left_atom, right_atom, squared| {
emit_unique_cross_candidate(left_atom, right_atom, squared, left, right, &mut |pair| {
fold(&mut accumulator, pair);
});
})?;
Ok(accumulator)
})
.map_err(|_| SpatialError::WorkerPanicked)?;
produced.into_iter().collect()
}