11"""
2- SpatialHashingCellList{NDIMS}(; list_size)
2+ SpatialHashingCellList{NDIMS}(; list_size,
3+ backend = DynamicVectorOfVectors{Int32},
4+ max_points_per_cell = 100)
35
46A basic spatial hashing implementation. Similar to [`DictionaryCellList`](@ref), the domain is discretized into cells,
57and the particles in each cell are stored in a hash map. The hash is computed using the spatial location of each cell,
@@ -9,80 +11,132 @@ to balance memory consumption against the likelihood of hash collisions.
911
1012# Arguments
1113- `NDIMS::Int`: Number of spatial dimensions (e.g., `2` or `3`).
12- - `list_size::Int`: Size of the hash map (e.g., `2 * n_points`) .
14+ - `list_size::Int`: Size of the hash map (e.g., `2 * n_points`).
15+ - `backend = DynamicVectorOfVectors{Int32}`: Type of the data structure to store the actual
16+ cell lists. Can be
17+ - `Vector{Vector{Int32}}`: Scattered memory, but very memory-efficient.
18+ - `DynamicVectorOfVectors{Int32}`: Contiguous memory, optimizing cache-hits.
19+ - `max_points_per_cell = 100`: Maximum number of points per cell. This will be used to
20+ allocate the `DynamicVectorOfVectors`. It is not used with
21+ the `Vector{Vector{Int32}}` backend.
1322"""
1423
1524struct SpatialHashingCellList{NDIMS, CL, CI, CF} <: AbstractCellList
16- points :: CL
25+ cells :: CL
1726 coords :: CI
1827 collisions :: CF
1928 list_size :: Int
29+
30+ # This constructor is necessary for Adapt.jl to work with this struct
31+ function SpatialHashingCellList (NDIMS, cells, coords, collisions, list_size)
32+ return new{NDIMS, typeof (cells),
33+ typeof (coords), typeof (collisions)}(cells, coords,
34+ collisions, list_size)
35+ end
2036end
2137
2238@inline index_type (:: SpatialHashingCellList ) = Int32
2339
2440@inline Base. ndims (:: SpatialHashingCellList{NDIMS} ) where {NDIMS} = NDIMS
2541
42+ function supported_update_strategies (:: SpatialHashingCellList {<: Any ,
43+ <: DynamicVectorOfVectors })
44+ return (ParallelUpdate, SerialUpdate)
45+ end
46+
2647function supported_update_strategies (:: SpatialHashingCellList )
2748 return (SerialUpdate,)
2849end
2950
30- function SpatialHashingCellList {NDIMS} (list_size) where {NDIMS}
31- points = [Int[] for _ in 1 : list_size]
51+ function SpatialHashingCellList {NDIMS} (; list_size,
52+ backend = DynamicVectorOfVectors{Int32},
53+ max_points_per_cell = 100 ) where {NDIMS}
54+ cells = construct_backend (backend, list_size,
55+ max_points_per_cell)
3256 collisions = [false for _ in 1 : list_size]
33- coords = [ntuple (_ -> typemin (Int), NDIMS ) for _ in 1 : list_size]
34- return SpatialHashingCellList{NDIMS, typeof (points), typeof (coords),
35- typeof (collisions)}(points , coords, collisions, list_size)
57+ coords = [typemin (UInt128 ) for _ in 1 : list_size]
58+
59+ return SpatialHashingCellList (NDIMS, cells , coords, collisions, list_size)
3660end
3761
3862function Base. empty! (cell_list:: SpatialHashingCellList )
39- (; list_size ) = cell_list
63+ (; cells ) = cell_list
4064 NDIMS = ndims (cell_list)
4165
42- Base. empty! .(cell_list. points)
43- cell_list. coords .= [ntuple (_ -> typemin (Int), NDIMS) for _ in 1 : list_size]
66+ # `Base.empty!.(cells)`, but for all backends
67+ @threaded default_backend (cells) for i in eachindex (cells)
68+ emptyat! (cells, i)
69+ end
70+
71+ fill! (cell_list. coords, typemin (UInt128))
4472 cell_list. collisions .= false
4573 return cell_list
4674end
4775
4876# For each entry in the hash table, store the coordinates of the cell of the first point being inserted at this entry.
4977# If a point with a different cell coordinate is being added, we have found a collision.
78+ # We flatten the coordinate tuples to an `UInt128` number to make it work with atomics.
5079function push_cell! (cell_list:: SpatialHashingCellList , cell, point)
51- (; points , coords, collisions, list_size) = cell_list
80+ (; cells , coords, collisions, list_size) = cell_list
5281 NDIMS = ndims (cell_list)
5382 hash_key = spatial_hash (cell, list_size)
54- push! (points[hash_key], point)
5583
56- cell_coord = coords[hash_key]
57- if cell_coord == ntuple (_ -> typemin (Int), NDIMS)
84+ @boundscheck check_cell_bounds (cell_list, hash_key)
85+ @inbounds pushat! (cells, hash_key, point)
86+
87+ cell_coord_hash = coordinates_flattened (cell)
88+ previous_cell_coord = coords[hash_key]
89+ if previous_cell_coord == typemin (UInt128)
5890 # If this cell is not used yet, set cell coordinates
59- coords[hash_key] = cell
60- elseif cell_coord != cell
91+ coords[hash_key] = cell_coord_hash
92+ elseif previous_cell_coord != cell_coord_hash
6193 # If it is already used by a different cell, mark as collision
6294 collisions[hash_key] = true
6395 end
6496end
6597
98+ function push_cell_atomic! (cell_list:: SpatialHashingCellList , cell, point)
99+ (; cells, coords, collisions, list_size) = cell_list
100+ NDIMS = ndims (cell_list)
101+ hash_key = spatial_hash (cell, list_size)
102+
103+ cell_coord_hash = coordinates_flattened (cell)
104+
105+ @boundscheck check_cell_bounds (cell_list, hash_key)
106+ @inbounds pushat_atomic! (cells, hash_key, point)
107+
108+ cell_coord = @inbounds coords[hash_key]
109+ if cell_coord == ntuple (_ -> typemin (UInt128), Val (NDIMS))
110+ # If this cell is not used yet, set cell coordinates
111+ @inbounds Atomix. @atomic coords[hash_key] = cell_coord_hash
112+ elseif cell_coord != cell_coord_hash
113+ # If it is already used by a different cell, mark as collision
114+ @inbounds Atomix. @atomic collisions[hash_key] = true
115+ end
116+ end
117+
66118function deleteat_cell! (cell_list:: SpatialHashingCellList , cell, i)
67119 deleteat! (cell_list[cell], i)
68120end
69121
70- @inline each_cell_index (cell_list:: SpatialHashingCellList ) = eachindex (cell_list. points )
122+ @inline each_cell_index (cell_list:: SpatialHashingCellList ) = eachindex (cell_list. cells )
71123
72124function copy_cell_list (cell_list:: SpatialHashingCellList , search_radius,
73125 periodic_box)
74126 (; list_size) = cell_list
75127 NDIMS = ndims (cell_list)
76128
77- return SpatialHashingCellList {NDIMS} (list_size)
129+ return SpatialHashingCellList {NDIMS} (list_size = list_size,
130+ backend = typeof (cell_list. cells),
131+ max_points_per_cell = max_points_per_cell (cell_list. cells))
78132end
79133
80134@inline function Base. getindex (cell_list:: SpatialHashingCellList , cell:: Tuple )
81- return cell_list. points [spatial_hash (cell, length (cell_list. points ))]
135+ return cell_list. cells [spatial_hash (cell, length (cell_list. cells ))]
82136end
83137
84138@inline function Base. getindex (cell_list:: SpatialHashingCellList , i:: Integer )
85- return cell_list. points [i]
139+ return cell_list. cells [i]
86140end
87141
88142@inline function is_correct_cell (cell_list:: SpatialHashingCellList{<:Any, Nothing} ,
@@ -106,3 +160,23 @@ function spatial_hash(cell::NTuple{3, Real}, list_size)
106160
107161 return mod (xor (i * 73856093 , j * 19349663 , k * 83492791 ), list_size) + 1
108162end
163+
164+ @inline function check_cell_bounds (cell_list:: SpatialHashingCellList , cell:: Tuple )
165+ check_cell_bounds (cell_list, spatial_hash (cell, cell_list. list_size))
166+ end
167+
168+ # Compute a compact 128-bit representation by reinterpreting each coordinate as a UInt32
169+ # and bit-shifting them into a UInt128 slot (appending the `UInt32` bitstrings).
170+ function coordinates_flattened (cell_coordinate)
171+ # Size check
172+ @assert length (cell_coordinate) <= 3
173+
174+ result = UInt128 (0 )
175+ for (i, coord) in enumerate (cell_coordinate)
176+ ucoord = reinterpret (UInt32, Int32 (coord))
177+ # Shift the `i`-th coordinate by (i - 1) x 32 bits, so the used bits don't overlap
178+ result = (UInt128 (ucoord) << ((i- 1 ) * 32 )) | result
179+ end
180+
181+ return result
182+ end
0 commit comments