molframe_spatial/queries/
reduce.rs1use super::search::validate_cutoff;
11use super::visit::{emit_unique_cross_candidate, visit_indices};
12use crate::{
13 CellList, NeighborPair, PeriodicBox, SpatialBackend, SpatialError, SpatialSearchOptions,
14};
15use molframe_core::ExecutionContext;
16use molframe_core::parallel::{BlockPlan, map_blocks_in};
17use molframe_core::selection::AtomSelection;
18
19#[derive(Clone, Copy, Debug)]
21pub struct PairQuery<'a> {
22 pub positions: &'a [[f32; 3]],
24 pub left: &'a AtomSelection,
26 pub right: &'a AtomSelection,
28 pub cutoff: f32,
30 pub options: SpatialSearchOptions,
32 pub periodic: Option<&'a PeriodicBox>,
34 pub context: &'a ExecutionContext,
36}
37
38pub fn reduce_pairs_within_unsorted<T, I, F>(
53 query: &PairQuery<'_>,
54 init: I,
55 fold: F,
56) -> Result<Vec<T>, SpatialError>
57where
58 T: Send,
59 I: Fn() -> T + Sync,
60 F: Fn(&mut T, NeighborPair) + Sync,
61{
62 let &PairQuery {
63 positions,
64 left,
65 right,
66 cutoff,
67 options,
68 periodic,
69 context,
70 } = query;
71
72 validate_cutoff(cutoff)?;
73 let indices = super::indices::IndexWorkspace::new(left, right, positions.len(), context)?;
74 let left_indices = indices.left();
75 let right_indices = indices.right();
76 let plan = options.plan(left_indices.len(), right_indices.len(), cutoff)?;
77
78 let workers = context.worker_budget();
82 let blockable = plan.backend == SpatialBackend::CellList && periodic.is_none() && workers > 1;
83 let same_selection = left_indices == right_indices;
84
85 if blockable {
86 let index = CellList::build_in(
87 positions,
88 right_indices,
89 cutoff,
90 periodic.copied(),
91 options.cell_grid,
92 context,
93 )?;
94
95 if same_selection {
96 if let Some(parts) = crate::backends::cell::reduce_pairs_same_selection_parallel(
97 &index,
98 left_indices,
99 cutoff,
100 context,
101 &init,
102 &fold,
103 )? {
104 return Ok(parts);
105 }
106 } else {
107 return reduce_cross_selection(
108 &index,
109 left_indices,
110 right_indices,
111 cutoff,
112 context,
113 &init,
114 &fold,
115 );
116 }
117 }
118
119 let mut single = init();
120 visit_indices(query, left_indices, right_indices, |pair| {
121 fold(&mut single, pair);
122 })?;
123 Ok(vec![single])
124}
125
126#[cfg(test)]
127#[path = "reduce_tests.rs"]
128mod tests;
129
130fn reduce_cross_selection<T, I, F>(
137 index: &CellList<'_>,
138 left: &[u32],
139 right: &[u32],
140 cutoff: f32,
141 context: &ExecutionContext,
142 init: &I,
143 fold: &F,
144) -> Result<Vec<T>, SpatialError>
145where
146 T: Send,
147 I: Fn() -> T + Sync,
148 F: Fn(&mut T, NeighborPair) + Sync,
149{
150 const BLOCK_QUERIES: usize = 256;
153
154 let plan = BlockPlan::new(left.len(), BLOCK_QUERIES);
155 let produced = map_blocks_in(plan, context, |_, range| {
156 let mut accumulator = init();
157 let Some(chunk) = left.get(range) else {
158 return Ok(accumulator);
159 };
160 index.for_each_candidate(chunk, cutoff, |left_atom, right_atom, squared| {
161 emit_unique_cross_candidate(left_atom, right_atom, squared, left, right, &mut |pair| {
162 fold(&mut accumulator, pair);
163 });
164 })?;
165 Ok(accumulator)
166 })
167 .map_err(|_| SpatialError::WorkerPanicked)?;
168
169 produced.into_iter().collect()
170}