Skip to content

Commit f718f2b

Browse files
Merge branch 'main' into SpatialHashing
2 parents a3f622d + d4944f5 commit f718f2b

File tree

6 files changed

+123
-42
lines changed

6 files changed

+123
-42
lines changed

Project.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ uuid = "1c4d5385-0a27-49de-8e2c-43b175c8985c"
33
authors = ["Erik Faulhaber <erik.faulhaber@uni-koeln.de>"]
44
version = "0.6.2"
55

6-
76
[deps]
87
Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
98
Atomix = "a9b6321e-bd34-4604-b9c9-b65b8de01458"

src/neighborhood_search.jl

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ end
1414

1515
"""
1616
initialize!(search::AbstractNeighborhoodSearch, x, y;
17-
parallelization_backend = default_backend(x))
17+
parallelization_backend = default_backend(x),
18+
eachindex_y = axes(y, 2))
1819
1920
Initialize a neighborhood search with the two coordinate arrays `x` and `y`.
2021
@@ -27,16 +28,21 @@ If the neighborhood search type supports parallelization, the keyword argument
2728
`parallelization_backend` can be used to specify a parallelization backend.
2829
See [`@threaded`](@ref) for a list of available backends.
2930
31+
Optionally, when points in `y` are to be ignored, the keyword argument `eachindex_y` can be
32+
passed to specify the indices of the points in `y` that are to be used.
33+
3034
See also [`update!`](@ref).
3135
"""
3236
@inline function initialize!(search::AbstractNeighborhoodSearch, x, y;
33-
parallelization_backend = default_backend(x))
37+
parallelization_backend = default_backend(x),
38+
eachindex_y = axes(y, 2))
3439
return search
3540
end
3641

3742
"""
3843
update!(search::AbstractNeighborhoodSearch, x, y; points_moving = (true, true),
39-
parallelization_backend = default_backend(x))
44+
parallelization_backend = default_backend(x),
45+
eachindex_y = axes(y, 2))
4046
4147
Update an already initialized neighborhood search with the two coordinate arrays `x` and `y`.
4248
@@ -59,11 +65,15 @@ If the neighborhood search type supports parallelization, the keyword argument
5965
`parallelization_backend` can be used to specify a parallelization backend.
6066
See [`@threaded`](@ref) for a list of available backends.
6167
68+
Optionally, when points in `y` are to be ignored, the keyword argument `eachindex_y` can be
69+
passed to specify the indices of the points in `y` that are to be used.
70+
6271
See also [`initialize!`](@ref).
6372
"""
6473
@inline function update!(search::AbstractNeighborhoodSearch, x, y;
6574
points_moving = (true, true),
66-
parallelization_backend = default_backend(x))
75+
parallelization_backend = default_backend(x),
76+
eachindex_y = axes(y, 2))
6777
return search
6878
end
6979

src/nhs_grid.jl

Lines changed: 63 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -194,12 +194,14 @@ end
194194

195195
function initialize!(neighborhood_search::GridNeighborhoodSearch,
196196
x::AbstractMatrix, y::AbstractMatrix;
197-
parallelization_backend = default_backend(x))
198-
initialize_grid!(neighborhood_search, y; parallelization_backend)
197+
parallelization_backend = default_backend(x),
198+
eachindex_y = axes(y, 2))
199+
initialize_grid!(neighborhood_search, y; parallelization_backend, eachindex_y)
199200
end
200201

201202
function initialize_grid!(neighborhood_search::GridNeighborhoodSearch, y::AbstractMatrix;
202-
parallelization_backend = default_backend(y))
203+
parallelization_backend = default_backend(y),
204+
eachindex_y = axes(y, 2))
203205
(; cell_list) = neighborhood_search
204206

205207
empty!(cell_list)
@@ -210,8 +212,10 @@ function initialize_grid!(neighborhood_search::GridNeighborhoodSearch, y::Abstra
210212
return neighborhood_search
211213
end
212214

215+
@boundscheck checkbounds(y, eachindex_y)
216+
213217
# Ignore the parallelization backend here. This cannot be parallelized.
214-
for point in axes(y, 2)
218+
for point in eachindex_y
215219
# Get cell index of the point's cell
216220
point_coords = @inbounds extract_svector(y, Val(ndims(neighborhood_search)), point)
217221
cell = cell_coords(point_coords, neighborhood_search)
@@ -225,7 +229,8 @@ end
225229

226230
function initialize_grid!(neighborhood_search::GridNeighborhoodSearch{<:Any,
227231
ParallelUpdate},
228-
y::AbstractMatrix; parallelization_backend = default_backend(y))
232+
y::AbstractMatrix; parallelization_backend = default_backend(y),
233+
eachindex_y = axes(y, 2))
229234
(; cell_list) = neighborhood_search
230235

231236
empty!(cell_list)
@@ -236,7 +241,9 @@ function initialize_grid!(neighborhood_search::GridNeighborhoodSearch{<:Any,
236241
return neighborhood_search
237242
end
238243

239-
@threaded parallelization_backend for point in axes(y, 2)
244+
@boundscheck checkbounds(y, eachindex_y)
245+
246+
@threaded parallelization_backend for point in eachindex_y
240247
# Get cell index of the point's cell
241248
point_coords = @inbounds extract_svector(y, Val(ndims(neighborhood_search)), point)
242249
cell = cell_coords(point_coords, neighborhood_search)
@@ -250,19 +257,30 @@ end
250257

251258
function update!(neighborhood_search::GridNeighborhoodSearch,
252259
x::AbstractMatrix, y::AbstractMatrix;
253-
points_moving = (true, true), parallelization_backend = default_backend(x))
260+
points_moving = (true, true), parallelization_backend = default_backend(x),
261+
eachindex_y = axes(y, 2))
254262
# The coordinates of the first set of points are irrelevant for this NHS.
255263
# Only update when the second set is moving.
256264
points_moving[2] || return neighborhood_search
257265

258-
update_grid!(neighborhood_search, y; parallelization_backend)
266+
update_grid!(neighborhood_search, y; eachindex_y, parallelization_backend)
259267
end
260268

261269
# Update only with neighbor coordinates
262-
function update_grid!(neighborhood_search::GridNeighborhoodSearch{NDIMS}, y::AbstractMatrix;
263-
parallelization_backend = default_backend(y)) where {NDIMS}
270+
function update_grid!(neighborhood_search::Union{GridNeighborhoodSearch{<:Any,
271+
SerialIncrementalUpdate},
272+
GridNeighborhoodSearch{<:Any,
273+
SemiParallelUpdate}},
274+
y::AbstractMatrix;
275+
parallelization_backend = default_backend(y),
276+
eachindex_y = axes(y, 2))
264277
(; cell_list, update_buffer) = neighborhood_search
265278

279+
if eachindex_y != axes(y, 2)
280+
# Incremental update doesn't support inactive points
281+
error("this neighborhood search/update strategy does not support inactive points")
282+
end
283+
266284
# Empty each thread's list
267285
@threaded parallelization_backend for i in eachindex(update_buffer)
268286
emptyat!(update_buffer, i)
@@ -302,33 +320,46 @@ function update_grid!(neighborhood_search::GridNeighborhoodSearch{NDIMS}, y::Abs
302320
return neighborhood_search
303321
end
304322

305-
# The type annotation is to make Julia specialize on the type of the function.
306-
# Otherwise, unspecialized code will cause a lot of allocations and heavily impact performance.
307-
# See https://docs.julialang.org/en/v1/manual/performance-tips/#Be-aware-of-when-Julia-avoids-specializing
308323
@inline function mark_changed_cells!(neighborhood_search::GridNeighborhoodSearch{<:Any,
309324
SemiParallelUpdate},
310-
y, parallelization_backend) where {T}
311-
(; cell_list) = neighborhood_search
325+
y, parallelization_backend)
326+
(; cell_list, update_buffer) = neighborhood_search
312327

313328
# `each_cell_index(cell_list)` might return a `KeySet`, which has to be `collect`ed
314-
# first to be able to be used in a threaded loop. This function takes care of that.
315-
@threaded parallelization_backend for cell_index in each_cell_index_threadable(cell_list)
316-
mark_changed_cell!(neighborhood_search, cell_index, y)
329+
# first to support indexing.
330+
eachcell = each_cell_index_threadable(cell_list)
331+
332+
# Use chunks (usually one per thread) to index into the update buffer.
333+
# We cannot use `Iterators.partition` here, since the resulting iterator does not
334+
# support indexing and therefore cannot be used in a threaded loop.
335+
chunk_length = div(length(eachcell), length(update_buffer), RoundUp)
336+
337+
@threaded parallelization_backend for chunk_id in 1:length(update_buffer)
338+
# Manual partitioning of `eachcell`
339+
start = (chunk_length * (chunk_id - 1)) + 1
340+
end_ = min(chunk_length * chunk_id, length(eachcell))
341+
342+
for i in start:end_
343+
cell_index = eachcell[i]
344+
345+
mark_changed_cell!(neighborhood_search, cell_index, y, chunk_id)
346+
end
317347
end
318348
end
319349

320350
@inline function mark_changed_cells!(neighborhood_search::GridNeighborhoodSearch{<:Any,
321351
SerialIncrementalUpdate},
322-
y, _) where {T}
352+
y, _)
323353
(; cell_list) = neighborhood_search
324354

325355
# Ignore the parallelization backend here for `SerialIncrementalUpdate`.
326356
for cell_index in each_cell_index(cell_list)
327-
mark_changed_cell!(neighborhood_search, cell_index, y)
357+
# `chunk_id` is always `1` for `SerialIncrementalUpdate`
358+
mark_changed_cell!(neighborhood_search, cell_index, y, 1)
328359
end
329360
end
330361

331-
@inline function mark_changed_cell!(neighborhood_search, cell_index, y)
362+
@inline function mark_changed_cell!(neighborhood_search, cell_index, y, chunk_id)
332363
(; cell_list, update_buffer) = neighborhood_search
333364

334365
for point in cell_list[cell_index]
@@ -340,7 +371,7 @@ end
340371
# These can be identical (see `DictionaryCellList`).
341372
if !is_correct_cell(cell_list, cell, cell_index)
342373
# Mark this cell and continue with the next one
343-
pushat!(update_buffer, Threads.threadid(), cell_index)
374+
pushat!(update_buffer, chunk_id, cell_index)
344375
break
345376
end
346377
end
@@ -349,9 +380,15 @@ end
349380
# Fully parallel incremental update with atomic push.
350381
function update_grid!(neighborhood_search::GridNeighborhoodSearch{<:Any,
351382
ParallelIncrementalUpdate},
352-
y::AbstractMatrix; parallelization_backend = default_backend(y))
383+
y::AbstractMatrix; parallelization_backend = default_backend(y),
384+
eachindex_y = axes(y, 2))
353385
(; cell_list, update_buffer) = neighborhood_search
354386

387+
if eachindex_y != axes(y, 2)
388+
# Incremental update doesn't support inactive points
389+
error("this neighborhood search/update strategy does not support inactive points")
390+
end
391+
355392
# Note that we need two separate loops for adding and removing points.
356393
# `push_cell_atomic!` only guarantees thread-safety when different threads push
357394
# simultaneously, but it does not work when `deleteat_cell!` is called at the same time.
@@ -405,8 +442,9 @@ function update_grid!(neighborhood_search::Union{GridNeighborhoodSearch{<:Any,
405442
ParallelUpdate},
406443
GridNeighborhoodSearch{<:Any,
407444
SerialUpdate}},
408-
y::AbstractMatrix; parallelization_backend = default_backend(y))
409-
initialize_grid!(neighborhood_search, y; parallelization_backend)
445+
y::AbstractMatrix; parallelization_backend = default_backend(y),
446+
eachindex_y = axes(y, 2))
447+
initialize_grid!(neighborhood_search, y; parallelization_backend, eachindex_y)
410448
end
411449

412450
function check_collision(neighbor_cell_, neighbor_coords, cell_list, nhs)

src/nhs_precomputed.jl

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,15 @@ end
5353

5454
function initialize!(search::PrecomputedNeighborhoodSearch,
5555
x::AbstractMatrix, y::AbstractMatrix;
56-
parallelization_backend = default_backend(x))
56+
parallelization_backend = default_backend(x),
57+
eachindex_y = axes(y, 2))
5758
(; neighborhood_search, neighbor_lists) = search
5859

5960
# Initialize grid NHS
60-
initialize!(neighborhood_search, x, y; parallelization_backend)
61+
initialize!(neighborhood_search, x, y; eachindex_y, parallelization_backend)
6162

6263
initialize_neighbor_lists!(neighbor_lists, neighborhood_search, x, y,
63-
parallelization_backend)
64+
parallelization_backend, eachindex_y)
6465
end
6566

6667
# WARNING! Experimental feature:
@@ -70,21 +71,22 @@ end
7071
# `parallelization_backend = KernelAbstractions.CPU()`, even though `x isa Array`.
7172
function update!(search::PrecomputedNeighborhoodSearch,
7273
x::AbstractMatrix, y::AbstractMatrix;
73-
points_moving = (true, true), parallelization_backend = default_backend(x))
74+
points_moving = (true, true), parallelization_backend = default_backend(x),
75+
eachindex_y = axes(y, 2))
7476
(; neighborhood_search, neighbor_lists) = search
7577

7678
# Update grid NHS
77-
update!(neighborhood_search, x, y; points_moving, parallelization_backend)
79+
update!(neighborhood_search, x, y; eachindex_y, points_moving, parallelization_backend)
7880

7981
# Skip update if both point sets are static
8082
if any(points_moving)
8183
initialize_neighbor_lists!(neighbor_lists, neighborhood_search, x, y,
82-
parallelization_backend)
84+
parallelization_backend, eachindex_y)
8385
end
8486
end
8587

8688
function initialize_neighbor_lists!(neighbor_lists, neighborhood_search, x, y,
87-
parallelization_backend)
89+
parallelization_backend, eachindex_y)
8890
# Initialize neighbor lists
8991
empty!(neighbor_lists)
9092
resize!(neighbor_lists, size(x, 2))
@@ -93,8 +95,8 @@ function initialize_neighbor_lists!(neighbor_lists, neighborhood_search, x, y,
9395
end
9496

9597
# Fill neighbor lists
96-
foreach_point_neighbor(x, y, neighborhood_search;
97-
parallelization_backend) do point, neighbor, _, _
98+
foreach_point_neighbor(x, y, neighborhood_search; parallelization_backend,
99+
points = eachindex_y) do point, neighbor, _, _
98100
push!(neighbor_lists[point], neighbor)
99101
end
100102
end

src/nhs_trivial.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,15 @@ end
3333
@inline requires_update(::TrivialNeighborhoodSearch) = (false, false)
3434

3535
@inline function initialize!(search::TrivialNeighborhoodSearch, x, y;
36-
parallelization_backend = default_backend(x))
36+
parallelization_backend = default_backend(x),
37+
eachindex_y = axes(y, 2))
3738
return search
3839
end
3940

4041
@inline function update!(search::TrivialNeighborhoodSearch, x, y;
4142
points_moving = (true, true),
42-
parallelization_backend = default_backend(x))
43+
parallelization_backend = default_backend(x),
44+
eachindex_y = axes(y, 2))
4345
return search
4446
end
4547

test/nhs_grid.jl

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@
145145
@test neighbors5 == [36, 37, 38, 43, 44, 45]
146146
end
147147

148-
@testset "Rectangular Point Cloud 3D" begin
148+
@testset verbose=true "Rectangular Point Cloud 3D" begin
149149
#### Setup
150150
# Rectangle of equidistantly spaced points
151151
# from (x, y, z) = (-0.25, -0.25, -0.25) to (x, y, z) = (0.35, 0.35, 0.35).
@@ -188,6 +188,36 @@
188188
@test neighbors3 ==
189189
[115, 116, 117, 122, 123, 124, 129, 130, 131, 164, 165, 166, 171, 172,
190190
173, 178, 179, 180, 213, 214, 215, 220, 221, 222, 227, 228, 229]
191+
192+
update_strategies = (SerialUpdate(), ParallelUpdate())
193+
@testset verbose=true "eachindex_y $update_strategy" for update_strategy in
194+
update_strategies
195+
# Test that `eachindex_y` is passed correctly to the neighborhood search.
196+
# This requires `SerialUpdate` or `ParallelUpdate`.
197+
min_corner = min.(minimum(coordinates1, dims = 2),
198+
minimum(coordinates2, dims = 2))
199+
max_corner = max.(maximum(coordinates1, dims = 2),
200+
maximum(coordinates2, dims = 2))
201+
cell_list = FullGridCellList(; min_corner, max_corner, search_radius)
202+
nhs2 = GridNeighborhoodSearch{3}(; search_radius, n_points, update_strategy,
203+
cell_list)
204+
205+
# Initialize with all points
206+
initialize!(nhs2, coordinates1, coordinates1)
207+
208+
# Update with a subset of points
209+
update!(nhs2, coordinates2, coordinates2; eachindex_y = 120:220)
210+
211+
neighbors2 = sort(collect(PointNeighbors.eachneighbor(point_position1, nhs2)))
212+
neighbors3 = sort(collect(PointNeighbors.eachneighbor(point_position2, nhs2)))
213+
214+
# Check that the neighbors are the intersection of the previous neighbors
215+
# with the `eachindex_y` range.
216+
@test neighbors2 == Int[]
217+
@test neighbors3 ==
218+
[122, 123, 124, 129, 130, 131, 164, 165, 166, 171, 172, 173,
219+
178, 179, 180, 213, 214, 215, 220]
220+
end
191221
end
192222

193223
@testset verbose=true "Periodicity" begin

0 commit comments

Comments
 (0)