Skip to content

Commit e503620

Browse files
efaulhaberLasNikas
andauthored
Move cell list management to a new struct (trixi-framework#12)
* Move cell list management to a new struct * Move cell list to separate directory * Add missing file --------- Co-authored-by: Niklas Neher <[email protected]>
1 parent 989d0c0 commit e503620

File tree

5 files changed

+92
-53
lines changed

5 files changed

+92
-53
lines changed

src/PointNeighbors.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ include("util.jl")
1010
include("neighborhood_search.jl")
1111
include("nhs_trivial.jl")
1212
include("nhs_grid.jl")
13+
include("cell_lists/cell_lists.jl")
1314

1415
export for_particle_neighbor, foreach_neighbor
1516
export TrivialNeighborhoodSearch, GridNeighborhoodSearch

src/cell_lists/cell_lists.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include("dictionary.jl")

src/cell_lists/dictionary.jl

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
struct DictionaryCellList{NDIMS}
2+
hashtable :: Dict{NTuple{NDIMS, Int}, Vector{Int}}
3+
empty_vector :: Vector{Int} # Just an empty vector (used in `eachneighbor`)
4+
5+
function DictionaryCellList{NDIMS}() where {NDIMS}
6+
hashtable = Dict{NTuple{NDIMS, Int}, Vector{Int}}()
7+
empty_vector = Int[]
8+
9+
new{NDIMS}(hashtable, empty_vector)
10+
end
11+
end
12+
13+
function Base.empty!(cell_list::DictionaryCellList)
14+
Base.empty!(cell_list.hashtable)
15+
16+
return cell_list
17+
end
18+
19+
function push_cell!(cell_list::DictionaryCellList, cell, particle)
20+
(; hashtable) = cell_list
21+
22+
if haskey(hashtable, cell)
23+
append!(hashtable[cell], particle)
24+
else
25+
hashtable[cell] = [particle]
26+
end
27+
28+
return cell_list
29+
end
30+
31+
function deleteat_cell!(cell_list::DictionaryCellList, cell, i)
32+
(; hashtable) = cell_list
33+
34+
# This works for `i::Integer`, `i::AbstractVector`, and even `i::Base.Generator`
35+
if length(hashtable[cell]) <= count(_ -> true, i)
36+
delete_cell!(cell_list, cell)
37+
else
38+
deleteat!(hashtable[cell], i)
39+
end
40+
end
41+
42+
function delete_cell!(cell_list, cell)
43+
delete!(cell_list.hashtable, cell)
44+
end
45+
46+
@inline eachcell(cell_list::DictionaryCellList) = keys(cell_list.hashtable)
47+
48+
@inline function Base.getindex(cell_list::DictionaryCellList, cell)
49+
(; hashtable, empty_vector) = cell_list
50+
51+
# Return an empty vector when `cell_index` is not a key of `hashtable` and
52+
# reuse the empty vector to avoid allocations.
53+
return get(hashtable, cell, empty_vector)
54+
end

src/nhs_grid.jl

Lines changed: 31 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -48,25 +48,23 @@ since not sorting makes our implementation a lot faster (although less paralleli
4848
In: Computer Graphics Forum 30.1 (2011), pages 99–112.
4949
[doi: 10.1111/J.1467-8659.2010.01832.X](https://doi.org/10.1111/J.1467-8659.2010.01832.X)
5050
"""
51-
struct GridNeighborhoodSearch{NDIMS, ELTYPE, PB} <: AbstractNeighborhoodSearch
52-
hashtable :: Dict{NTuple{NDIMS, Int}, Vector{Int}}
51+
struct GridNeighborhoodSearch{NDIMS, ELTYPE, CL, PB} <: AbstractNeighborhoodSearch
52+
cell_list :: CL
5353
search_radius :: ELTYPE
54-
empty_vector :: Vector{Int} # Just an empty vector (used in `eachneighbor`)
54+
periodic_box :: PB
55+
n_cells :: NTuple{NDIMS, Int} # Required to calculate periodic cell index
56+
cell_size :: NTuple{NDIMS, ELTYPE} # Required to calculate cell index
5557
cell_buffer :: Array{NTuple{NDIMS, Int}, 2} # Multithreaded buffer for `update!`
5658
cell_buffer_indices :: Vector{Int} # Store which entries of `cell_buffer` are initialized
57-
periodic_box :: PB
58-
n_cells :: NTuple{NDIMS, Int}
59-
cell_size :: NTuple{NDIMS, ELTYPE}
6059
threaded_nhs_update :: Bool
6160

6261
function GridNeighborhoodSearch{NDIMS}(search_radius, n_particles;
6362
periodic_box_min_corner = nothing,
6463
periodic_box_max_corner = nothing,
6564
threaded_nhs_update = true) where {NDIMS}
6665
ELTYPE = typeof(search_radius)
66+
cell_list = DictionaryCellList{NDIMS}()
6767

68-
hashtable = Dict{NTuple{NDIMS, Int}, Vector{Int}}()
69-
empty_vector = Int[]
7068
cell_buffer = Array{NTuple{NDIMS, Int}, 2}(undef, n_particles, Threads.nthreads())
7169
cell_buffer_indices = zeros(Int, Threads.nthreads())
7270

@@ -98,10 +96,10 @@ struct GridNeighborhoodSearch{NDIMS, ELTYPE, PB} <: AbstractNeighborhoodSearch
9896
"must either be both `nothing` or both an array or tuple"))
9997
end
10098

101-
new{NDIMS, ELTYPE,
102-
typeof(periodic_box)}(hashtable, search_radius, empty_vector,
103-
cell_buffer, cell_buffer_indices,
104-
periodic_box, n_cells, cell_size, threaded_nhs_update)
99+
new{NDIMS, ELTYPE, typeof(cell_list),
100+
typeof(periodic_box)}(cell_list, search_radius, periodic_box, n_cells,
101+
cell_size, cell_buffer, cell_buffer_indices,
102+
threaded_nhs_update)
105103
end
106104
end
107105

@@ -122,20 +120,16 @@ function initialize_grid!(neighborhood_search::GridNeighborhoodSearch{NDIMS},
122120
end
123121

124122
function initialize_grid!(neighborhood_search::GridNeighborhoodSearch, coords_fun)
125-
(; hashtable) = neighborhood_search
123+
(; cell_list) = neighborhood_search
126124

127-
empty!(hashtable)
125+
empty!(cell_list)
128126

129127
for particle in 1:nparticles(neighborhood_search)
130128
# Get cell index of the particle's cell
131129
cell = cell_coords(coords_fun(particle), neighborhood_search)
132130

133-
# Add particle to corresponding cell or create cell if it does not exist
134-
if haskey(hashtable, cell)
135-
append!(hashtable[cell], particle)
136-
else
137-
hashtable[cell] = [particle]
138-
end
131+
# Add particle to corresponding cell
132+
push_cell!(cell_list, cell, particle)
139133
end
140134

141135
return neighborhood_search
@@ -159,22 +153,21 @@ end
159153

160154
# Modify the existing hash table by moving particles into their new cells
161155
function update_grid!(neighborhood_search::GridNeighborhoodSearch, coords_fun)
162-
(; hashtable, cell_buffer, cell_buffer_indices, threaded_nhs_update) = neighborhood_search
156+
(; cell_list, cell_buffer, cell_buffer_indices, threaded_nhs_update) = neighborhood_search
163157

164-
# Reset `cell_buffer` by moving all pointers to the beginning.
158+
# Reset `cell_buffer` by moving all pointers to the beginning
165159
cell_buffer_indices .= 0
166160

167-
# Find all cells containing particles that now belong to another cell.
168-
# `collect` the keyset to be able to loop over it with `@threaded`.
169-
mark_changed_cell!(neighborhood_search, hashtable, coords_fun,
161+
# Find all cells containing particles that now belong to another cell
162+
mark_changed_cell!(neighborhood_search, cell_list, coords_fun,
170163
Val(threaded_nhs_update))
171164

172165
# Iterate over all marked cells and move the particles into their new cells.
173166
for thread in 1:Threads.nthreads()
174167
# Only the entries `1:cell_buffer_indices[thread]` are initialized for `thread`.
175168
for i in 1:cell_buffer_indices[thread]
176169
cell = cell_buffer[i, thread]
177-
particles = hashtable[cell]
170+
particles = cell_list[cell]
178171

179172
# Find all particles whose coordinates do not match this cell
180173
moved_particle_indices = (i for i in eachindex(particles)
@@ -187,35 +180,28 @@ function update_grid!(neighborhood_search::GridNeighborhoodSearch, coords_fun)
187180
new_cell_coords = cell_coords(coords_fun(particle), neighborhood_search)
188181

189182
# Add particle to corresponding cell or create cell if it does not exist
190-
if haskey(hashtable, new_cell_coords)
191-
append!(hashtable[new_cell_coords], particle)
192-
else
193-
hashtable[new_cell_coords] = [particle]
194-
end
183+
push_cell!(cell_list, new_cell_coords, particle)
195184
end
196185

197-
# Remove moved particles from this cell or delete the cell if it is now empty
198-
if count(_ -> true, moved_particle_indices) == length(particles)
199-
delete!(hashtable, cell)
200-
else
201-
deleteat!(particles, moved_particle_indices)
202-
end
186+
# Remove moved particles from this cell
187+
deleteat_cell!(cell_list, cell, moved_particle_indices)
203188
end
204189
end
205190

206191
return neighborhood_search
207192
end
208193

209-
@inline function mark_changed_cell!(neighborhood_search, hashtable, coords_fun,
194+
@inline function mark_changed_cell!(neighborhood_search, cell_list, coords_fun,
210195
threaded_nhs_update::Val{true})
211-
@threaded for cell in collect(keys(hashtable))
196+
# `collect` the keyset to be able to loop over it with `@threaded`
197+
@threaded for cell in collect(eachcell(cell_list))
212198
mark_changed_cell!(neighborhood_search, cell, coords_fun)
213199
end
214200
end
215201

216-
@inline function mark_changed_cell!(neighborhood_search, hashtable, coords_fun,
202+
@inline function mark_changed_cell!(neighborhood_search, cell_list, coords_fun,
217203
threaded_nhs_update::Val{false})
218-
for cell in collect(keys(hashtable))
204+
for cell in eachcell(cell_list)
219205
mark_changed_cell!(neighborhood_search, cell, coords_fun)
220206
end
221207
end
@@ -225,9 +211,9 @@ end
225211
# Otherwise, `@threaded` does not work here with Julia ARM on macOS.
226212
# See https://github.com/JuliaSIMD/Polyester.jl/issues/88.
227213
@inline function mark_changed_cell!(neighborhood_search, cell, coords_fun)
228-
(; hashtable, cell_buffer, cell_buffer_indices) = neighborhood_search
214+
(; cell_list, cell_buffer, cell_buffer_indices) = neighborhood_search
229215

230-
for particle in hashtable[cell]
216+
for particle in cell_list[cell]
231217
if cell_coords(coords_fun(particle), neighborhood_search) != cell
232218
# Mark this cell and continue with the next one.
233219
#
@@ -277,12 +263,9 @@ end
277263
end
278264

279265
@inline function particles_in_cell(cell_index, neighborhood_search)
280-
(; hashtable, empty_vector) = neighborhood_search
266+
(; cell_list) = neighborhood_search
281267

282-
# Return an empty vector when `cell_index` is not a key of `hashtable` and
283-
# reuse the empty vector to avoid allocations
284-
return get(hashtable, periodic_cell_index(cell_index, neighborhood_search),
285-
empty_vector)
268+
return cell_list[periodic_cell_index(cell_index, neighborhood_search)]
286269
end
287270

288271
@inline function periodic_cell_index(cell_index, neighborhood_search)
@@ -351,8 +334,3 @@ function copy_neighborhood_search(nhs::GridNeighborhoodSearch, search_radius, x,
351334

352335
return search
353336
end
354-
355-
# Create a copy of a neighborhood search but with a different search radius
356-
function copy_neighborhood_search(nhs::TrivialNeighborhoodSearch, search_radius, x, y)
357-
return nhs
358-
end

src/nhs_trivial.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,8 @@ end
6161
end
6262

6363
@inline eachneighbor(coords, search::TrivialNeighborhoodSearch) = search.eachparticle
64+
65+
# Create a copy of a neighborhood search but with a different search radius
66+
function copy_neighborhood_search(nhs::TrivialNeighborhoodSearch, search_radius, x, y)
67+
return nhs
68+
end

0 commit comments

Comments
 (0)