11@doc raw """
22 GridNeighborhoodSearch{NDIMS}(; search_radius = 0.0, n_points = 0,
3- periodic_box = nothing, threaded_update = true)
3+ periodic_box = nothing,
4+ cell_list = DictionaryCellList{NDIMS}(),
5+ threaded_update = true)
46
57Simple grid-based neighborhood search with uniform search radius.
68The domain is divided into a regular grid.
@@ -32,6 +34,8 @@ since not sorting makes our implementation a lot faster (although less paralleli
3234 with [`copy_neighborhood_search`](@ref).
3335- `periodic_box = nothing`: In order to use a (rectangular) periodic domain, pass a
3436 [`PeriodicBox`](@ref).
37+ - `cell_list`: The cell list that maps a cell index to a list of points inside
38+ the cell. By default, a [`DictionaryCellList`](@ref) is used.
3539- `threaded_update = true`: Can be used to deactivate thread parallelization in the
3640 neighborhood search update. This can be one of the largest
3741 sources of variations between simulations with different
@@ -47,23 +51,23 @@ since not sorting makes our implementation a lot faster (although less paralleli
4751 In: Computer Graphics Forum 30.1 (2011), pages 99–112.
4852 [doi: 10.1111/J.1467-8659.2010.01832.X](https://doi.org/10.1111/J.1467-8659.2010.01832.X)
4953"""
50- struct GridNeighborhoodSearch{NDIMS, ELTYPE, CL, PB} <: AbstractNeighborhoodSearch
54+ struct GridNeighborhoodSearch{NDIMS, ELTYPE, CL, CB, PB} <: AbstractNeighborhoodSearch
5155 cell_list :: CL
5256 search_radius :: ELTYPE
5357 periodic_box :: PB
5458 n_cells :: NTuple{NDIMS, Int} # Required to calculate periodic cell index
5559 cell_size :: NTuple{NDIMS, ELTYPE} # Required to calculate cell index
56- cell_buffer :: Array{NTuple{NDIMS, Int}, 2} # Multithreaded buffer for `update!`
60+ cell_buffer :: CB # Multithreaded buffer for `update!`
5761 cell_buffer_indices :: Vector{Int} # Store which entries of `cell_buffer` are initialized
5862 threaded_update :: Bool
5963
6064 function GridNeighborhoodSearch {NDIMS} (; search_radius = 0.0 , n_points = 0 ,
6165 periodic_box = nothing ,
66+ cell_list = DictionaryCellList {NDIMS} (),
6267 threaded_update = true ) where {NDIMS}
6368 ELTYPE = typeof (search_radius)
64- cell_list = DictionaryCellList {NDIMS} ()
6569
66- cell_buffer = Array {NTuple{NDIMS, Int} , 2} (undef, n_points, Threads. nthreads ())
70+ cell_buffer = Array {index_type(cell_list) , 2} (undef, n_points, Threads. nthreads ())
6771 cell_buffer_indices = zeros (Int, Threads. nthreads ())
6872
6973 if search_radius < eps () || isnothing (periodic_box)
@@ -86,7 +90,7 @@ struct GridNeighborhoodSearch{NDIMS, ELTYPE, CL, PB} <: AbstractNeighborhoodSear
8690 end
8791 end
8892
89- new{NDIMS, ELTYPE, typeof (cell_list),
93+ new{NDIMS, ELTYPE, typeof (cell_list), typeof (cell_buffer),
9094 typeof (periodic_box)}(cell_list, search_radius, periodic_box, n_cells,
9195 cell_size, cell_buffer, cell_buffer_indices,
9296 threaded_update)
@@ -156,13 +160,15 @@ function update_grid!(neighborhood_search::GridNeighborhoodSearch, coords_fun)
156160 for thread in 1 : Threads. nthreads ()
157161 # Only the entries `1:cell_buffer_indices[thread]` are initialized for `thread`.
158162 for i in 1 : cell_buffer_indices[thread]
159- cell = cell_buffer[i, thread]
160- points = cell_list[cell ]
163+ cell_index = cell_buffer[i, thread]
164+ points = cell_list[cell_index ]
161165
162166 # Find all points whose coordinates do not match this cell
163167 moved_point_indices = (i for i in eachindex (points)
164- if cell_coords (coords_fun (points[i]),
165- neighborhood_search) != cell)
168+ if ! is_correct_cell (cell_list,
169+ cell_coords (coords_fun (points[i]),
170+ neighborhood_search),
171+ cell_index))
166172
167173 # Add moved points to new cell
168174 for i in moved_point_indices
@@ -174,7 +180,7 @@ function update_grid!(neighborhood_search::GridNeighborhoodSearch, coords_fun)
174180 end
175181
176182 # Remove moved points from this cell
177- deleteat_cell! (cell_list, cell , moved_point_indices)
183+ deleteat_cell! (cell_list, cell_index , moved_point_indices)
178184 end
179185 end
180186
@@ -184,33 +190,38 @@ end
184190@inline function mark_changed_cell! (neighborhood_search, cell_list, coords_fun,
185191 threaded_update:: Val{true} )
186192 # `collect` the keyset to be able to loop over it with `@threaded`
187- @threaded for cell in collect ( eachcell ( cell_list) )
188- mark_changed_cell! (neighborhood_search, cell , coords_fun)
193+ @threaded for cell_index in each_cell_index_threadable ( cell_list)
194+ mark_changed_cell! (neighborhood_search, cell_index , coords_fun)
189195 end
190196end
191197
192198@inline function mark_changed_cell! (neighborhood_search, cell_list, coords_fun,
193199 threaded_update:: Val{false} )
194- for cell in eachcell (cell_list)
195- mark_changed_cell! (neighborhood_search, cell , coords_fun)
200+ for cell_index in each_cell_index (cell_list)
201+ mark_changed_cell! (neighborhood_search, cell_index , coords_fun)
196202 end
197203end
198204
199205# Use this function barrier and unpack inside to avoid passing closures to Polyester.jl
200206# with `@batch` (`@threaded`).
201207# Otherwise, `@threaded` does not work here with Julia ARM on macOS.
202208# See https://github.com/JuliaSIMD/Polyester.jl/issues/88.
203- @inline function mark_changed_cell! (neighborhood_search, cell , coords_fun)
209+ @inline function mark_changed_cell! (neighborhood_search, cell_index , coords_fun)
204210 (; cell_list, cell_buffer, cell_buffer_indices) = neighborhood_search
205211
206- for point in cell_list[cell]
207- if cell_coords (coords_fun (point), neighborhood_search) != cell
212+ for point in cell_list[cell_index]
213+ cell = cell_coords (coords_fun (point), neighborhood_search)
214+
215+ # `cell` is a tuple, `cell_index` is the linear index used internally by the
216+ # cell list to store cells inside `cell`.
217+ # These can be identical (see `DictionaryCellList`).
218+ if ! is_correct_cell (cell_list, cell, cell_index)
208219 # Mark this cell and continue with the next one.
209220 #
210221 # `cell_buffer` is preallocated,
211222 # but only the entries 1:i are used for this thread.
212223 i = cell_buffer_indices[Threads. threadid ()] += 1
213- cell_buffer[i, Threads. threadid ()] = cell
224+ cell_buffer[i, Threads. threadid ()] = cell_index
214225 break
215226 end
216227 end
@@ -290,28 +301,12 @@ end
290301 return Tuple (floor_to_int .(offset_coords ./ cell_size))
291302end
292303
293- # When points end up with coordinates so big that the cell coordinates
294- # exceed the range of Int, then `floor(Int, i)` will fail with an InexactError.
295- # In this case, we can just use typemax(Int), since we can assume that points
296- # that far away will not interact with anything, anyway.
297- # This usually indicates an instability, but we don't want the simulation to crash,
298- # since adaptive time integration methods may detect the instability and reject the
299- # time step.
300- # If we threw an error here, we would prevent the time integration method from
301- # retrying with a smaller time step, and we would thus crash perfectly fine simulations.
302- @inline function floor_to_int (i)
303- if isnan (i) || i > typemax (Int)
304- return typemax (Int)
305- elseif i < typemin (Int)
306- return typemin (Int)
307- end
308-
309- return floor (Int, i)
310- end
311-
312304function copy_neighborhood_search (nhs:: GridNeighborhoodSearch , search_radius, n_points;
313305 eachpoint = 1 : n_points)
314- return GridNeighborhoodSearch {ndims(nhs)} (; search_radius, n_points,
315- periodic_box = nhs. periodic_box,
316- threaded_update = nhs. threaded_update)
306+ (; periodic_box, threaded_update) = nhs
307+
308+ cell_list = copy_cell_list (nhs. cell_list, search_radius, periodic_box)
309+
310+ return GridNeighborhoodSearch {ndims(nhs)} (; search_radius, n_points, periodic_box,
311+ cell_list, threaded_update)
317312end
0 commit comments