1use super::errors::{validate_cutoff, validate_indices};
4use crate::brute::{canonicalise, finite};
5use crate::{CellGridOptions, NeighborPair, PeriodicBox, SpatialError};
6
7mod budget;
8mod build;
9use build::build_grid;
10mod coords;
11use coords::{cell_of, linear, linear_index, members_of_cell, neighbour_cell, raw_cell};
12mod geometry;
13mod ordered_merge;
14mod owned;
15use geometry::{Layout, choose_layout, finite_bounds};
16mod periodic_grid;
17use periodic_grid::PeriodicGrid;
18mod unique;
19pub(crate) use unique::{
20 for_each_pairs_same_selection_unordered, pairs_same_selection, pairs_same_selection_parallel,
21 pairs_same_selection_unordered, reduce_pairs_same_selection_parallel,
22};
23mod visit;
24use visit::grid_for_each_pair;
25
26const NEIGHBOUR_OFFSETS: [[isize; 3]; 27] = [
27 [-1, -1, -1],
28 [-1, -1, 0],
29 [-1, -1, 1],
30 [-1, 0, -1],
31 [-1, 0, 0],
32 [-1, 0, 1],
33 [-1, 1, -1],
34 [-1, 1, 0],
35 [-1, 1, 1],
36 [0, -1, -1],
37 [0, -1, 0],
38 [0, -1, 1],
39 [0, 0, -1],
40 [0, 0, 0],
41 [0, 0, 1],
42 [0, 1, -1],
43 [0, 1, 0],
44 [0, 1, 1],
45 [1, -1, -1],
46 [1, -1, 0],
47 [1, -1, 1],
48 [1, 0, -1],
49 [1, 0, 0],
50 [1, 0, 1],
51 [1, 1, -1],
52 [1, 1, 0],
53 [1, 1, 1],
54];
55
56type CellId = u128;
59
60#[derive(Debug)]
61struct Grid {
62 origin: [f32; 3],
63 edge: f32,
64 dims: [usize; 3],
65 offsets: Vec<usize>,
66 members: Vec<u32>,
67 non_empty_cells: Vec<CellId>,
69 largest_cell: usize,
70 sparse: bool,
73}
74
75#[derive(Debug)]
76enum CellGrid {
77 Cartesian(Grid),
78 Periodic(PeriodicGrid),
79}
80
81#[derive(Debug)]
83pub struct CellList<'a> {
84 positions: &'a [[f32; 3]],
85 cutoff: f32,
86 periodic: Option<PeriodicBox>,
87 grid: Option<CellGrid>,
88 reservation: Option<molframe_core::MemoryReservation>,
89}
90
91impl<'a> CellList<'a> {
92 pub fn build(
98 positions: &'a [[f32; 3]],
99 targets: &[u32],
100 cutoff: f32,
101 periodic: Option<PeriodicBox>,
102 ) -> Result<Self, SpatialError> {
103 Self::build_with_options(
104 positions,
105 targets,
106 cutoff,
107 periodic,
108 CellGridOptions::default(),
109 )
110 }
111
112 pub fn build_with_options(
118 positions: &'a [[f32; 3]],
119 targets: &[u32],
120 cutoff: f32,
121 periodic: Option<PeriodicBox>,
122 options: CellGridOptions,
123 ) -> Result<Self, SpatialError> {
124 validate_cutoff(cutoff)?;
125 validate_indices(targets, positions.len())?;
126 options.validate()?;
127
128 let grid = match periodic.as_ref() {
129 Some(periodic) => Some(CellGrid::Periodic(PeriodicGrid::build(
130 positions, targets, cutoff, periodic, options,
131 )?)),
132 None => build_grid(positions, targets, cutoff, options)?.map(CellGrid::Cartesian),
133 };
134
135 Ok(Self {
136 positions,
137 cutoff,
138 periodic,
139 grid,
140 reservation: None,
141 })
142 }
143
144 pub fn pairs(&self, query: &[u32], cutoff: f32) -> Result<Vec<NeighborPair>, SpatialError> {
151 let mut found = Vec::with_capacity(query.len());
152 self.for_each_pair(query, cutoff, |pair| found.push(pair))?;
153 canonicalise(&mut found);
154 Ok(found)
155 }
156
157 pub(crate) fn for_each_pair(
159 &self,
160 query: &[u32],
161 cutoff: f32,
162 mut emit: impl FnMut(NeighborPair),
163 ) -> Result<(), SpatialError> {
164 self.for_each_candidate(query, cutoff, |atom, target, squared| {
165 emit(NeighborPair::new(atom, target, squared));
166 })
167 }
168
169 pub fn for_each_candidate(
178 &self,
179 query: &[u32],
180 cutoff: f32,
181 mut emit: impl FnMut(u32, u32, f32),
182 ) -> Result<(), SpatialError> {
183 validate_query_cutoff(cutoff, self.cutoff)?;
184 validate_indices(query, self.positions.len())?;
185
186 match &self.grid {
187 Some(CellGrid::Cartesian(grid)) => {
188 grid_for_each_pair::<false>(self.positions, grid, query, cutoff * cutoff, &mut emit)
189 }
190 Some(CellGrid::Periodic(grid)) => {
191 let Some(periodic) = &self.periodic else {
192 return Err(SpatialError::InvalidCell);
193 };
194 grid.for_each_pair(self.positions, query, cutoff * cutoff, periodic, &mut emit)
195 }
196 None => Ok(()),
197 }
198 }
199
200 pub fn for_each_neighbor(
210 &self,
211 position: [f32; 3],
212 cutoff: f32,
213 mut emit: impl FnMut(u32, f32),
214 ) -> Result<(), SpatialError> {
215 validate_query_cutoff(cutoff, self.cutoff)?;
216 if !finite(position) {
217 return Err(SpatialError::NumericRangeExceeded);
218 }
219 match &self.grid {
220 Some(CellGrid::Cartesian(grid)) => visit::grid_for_each_neighbor(
221 self.positions,
222 grid,
223 position,
224 cutoff * cutoff,
225 &mut emit,
226 ),
227 Some(CellGrid::Periodic(grid)) => {
228 let periodic = self.periodic.as_ref().ok_or(SpatialError::InvalidCell)?;
229 grid.for_each_neighbor(
230 self.positions,
231 position,
232 cutoff * cutoff,
233 periodic,
234 &mut emit,
235 )
236 }
237 None => Ok(()),
238 }
239 }
240}
241
242fn validate_query_cutoff(cutoff: f32, build_cutoff: f32) -> Result<(), SpatialError> {
246 if cutoff.is_finite() && cutoff >= 0.0 && cutoff <= build_cutoff {
247 Ok(())
248 } else {
249 Err(SpatialError::InvalidCutoff)
250 }
251}
252
253#[cfg(test)]
254#[path = "cell_tests.rs"]
255mod tests;