Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .buildkite/pipeline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
steps:
- label: "CUDA"
plugins:
- JuliaCI/julia#v1:
version: "1.12"
agents:
queue: "juliagpu"
cuda: "*"
command: |
julia --color=yes --project=test -e 'using Pkg; Pkg.add("CUDA"); Pkg.develop(path="."); Pkg.instantiate()'
julia --color=yes --project=test -e 'include("test/runtests.jl")'
env:
POINTNEIGHBORS_TEST: cuda
timeout_in_minutes: 60

- label: "AMDGPU"
plugins:
- JuliaCI/julia#v1:
version: "1.12"
agents:
queue: "juliagpu"
rocm: "*"
command: |
julia --color=yes --project=test -e 'using Pkg; Pkg.add("AMDGPU"); Pkg.develop(path="."); Pkg.instantiate()'
julia --color=yes --project=test -e 'include("test/runtests.jl")'
env:
POINTNEIGHBORS_TEST: amdgpu
timeout_in_minutes: 60

- label: "Metal"
plugins:
- JuliaCI/julia#v1:
version: "1.12"
agents:
queue: "juliaecosystem"
os: "macos"
arch: "aarch64"
command: |
julia --color=yes --project=test -e 'using Pkg; Pkg.add("Metal"); Pkg.develop(path="."); Pkg.instantiate()'
julia --color=yes --project=test -e 'include("test/runtests.jl")'
env:
POINTNEIGHBORS_TEST: metal
timeout_in_minutes: 60

- label: "oneAPI"
plugins:
- JuliaCI/julia#v1:
version: "1.12"
agents:
queue: "juliagpu"
intel: "*"
command: |
julia --color=yes --project=test -e 'using Pkg; Pkg.add("oneAPI"); Pkg.develop(path="."); Pkg.instantiate()'
julia --color=yes --project=test -e 'include("test/runtests.jl")'
env:
POINTNEIGHBORS_TEST: oneapi
timeout_in_minutes: 60
18 changes: 18 additions & 0 deletions .github/workflows/TriggerGPUTests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: "Trigger GPU Tests"

on:
issue_comment:
types:
- created

jobs:
trigger-buildkite:
if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, '/run-gpu-tests') }}
runs-on: ubuntu-latest
steps:
- name: Trigger Buildkite Pipeline
uses: "buildkite/trigger-pipeline-action@v2.4.1"
with:
buildkite_api_access_token: ${{ secrets.TRIGGER_BK_BUILD_TOKEN }}
pipeline: "julialang/pointneighbors"
branch: refs/pull/${{ github.event.issue.number }}/head
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ docs/build
docs/src/index.md
docs/src/authors.md
docs/src/license.md
docs/src/tutorials
public/
coverage/
coverage_report/
Expand Down
6 changes: 6 additions & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
[deps]
Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c"
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"

[compat]
Adapt = "4"
Documenter = "1"
KernelAbstractions = "0.9"
Literate = "2"
46 changes: 46 additions & 0 deletions docs/literate/src/tut_advanced_usage.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# # [Advanced Usage: Copying Neighborhood Searches](@id tut_advanced_usage)

using PointNeighbors
# When working with multiple point-sets and therefore multiple neighborhood searches,
# it is often desirable to hide these details from the user-facing API.
# We would like the user to simply pass the type of neighborhood search they want to use,
# and the library should take care of creating the neighborhood searches internally.
#
# For exactly this purpose, PointNeighbors.jl provides the concept of a "template
# neighborhood search", which is a neighborhood search created without essential information
# like search radius or number of points, and the function
# [`copy_neighborhood_search`](@ref), which creates a copy of an existing neighborhood
# search or template, but with a (different) concrete configuration.
#
# For the simplest example, we can work with the [`TrivialNeighborhoodSearch`](@ref),
# which simply loops over all points, resulting in quadratic runtime for finding neighbors
# of a particle.
n_points = 1000
search_radius = 1.0
nhs = TrivialNeighborhoodSearch{2}(; search_radius, eachpoint = 1:n_points)
nothing # hide

# This constructor requires knowledge of the search radius and size of the point set,
# which might differ between different point sets in the same simulation.
# Instead, we can create a template neighborhood search without this information:
template_nhs = TrivialNeighborhoodSearch{2}()
nothing # hide

# This template can now be copied with different configurations for different point sets.
nhs1 = copy_neighborhood_search(template_nhs, search_radius, n_points)
nhs2 = copy_neighborhood_search(template_nhs, search_radius * 2, n_points * 2)
nothing # hide

# The same concept can be applied to all neighborhood search implementations in
# PointNeighbors.jl. All templates can be copied with exactly the same function call.
template_nhs2 = GridNeighborhoodSearch{2}()
nhs3 = copy_neighborhood_search(template_nhs2, search_radius, n_points)
nothing # hide

# Additional configuration options can be passed to the templates and will be preserved
# through the copying process. This applies for example to the periodic box or the
# update strategy of the [`GridNeighborhoodSearch`](@ref).
periodic_box = PeriodicBox(min_corner = (0.0, 0.0), max_corner = (10.0, 10.0))
template_nhs3 = GridNeighborhoodSearch{2}(; periodic_box, update_strategy = SerialUpdate())
nhs4 = copy_neighborhood_search(template_nhs3, search_radius, n_points)
nhs4.update_strategy, nhs4.periodic_box
115 changes: 115 additions & 0 deletions docs/literate/src/tut_basic_usage.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# # [Basic Usage: Fixed-Radius Neighbor Search](@id tut_basic_usage)

# This tutorial shows the standard workflow for PointNeighbors.jl:
# create coordinates, configure a neighborhood search, initialize it,
# and loop over neighbors.
using PointNeighbors

# ## Generate a regular 2D point cloud

# We create a regular grid of points in 2D for this example, with distances of 1
# between neighboring points.
# The coordinates need to be stored in a 2×N array, where N is the number of points.
n_points_per_dimension = (100, 100)
n_points = prod(n_points_per_dimension)
coordinates = Array{Float64}(undef, 2, n_points)
cartesian_indices = CartesianIndices(n_points_per_dimension)

for i in axes(coordinates, 2)
coordinates[:, i] .= Tuple(cartesian_indices[i])
end

# ## Create and initialize the neighborhood search

# We choose a search radius and create the neighborhood search.
# For the [`GridNeighborhoodSearch`](@ref), we need to pass `n_points` as a maximum
# number of points in the neighbor coordinates array, which is required to allocate
# the data structures for the update step.
search_radius = 3.0
nhs = GridNeighborhoodSearch{2}(; search_radius, n_points)
nothing # hide

# Initialize the neighborhood search with the coordinates.
# !!! warning
# This neighborhood search is now configured for the given coordinates.
# In general, it is only possible to use this neighborhood search to find neighbors
# for points contained in `coordinates` and not for arbitrary points in space.
# See below for more information.
initialize!(nhs, coordinates, coordinates)
nothing # hide

# ## Count neighbors for each point

# With the neighborhood search initialized, we can now loop over neighbors.
# We use the function [`foreach_point_neighbor`](@ref) to loop over all pairs of points
# and neighbors within the search radius.
# !!! warning
# The `foreach_point_neighbor` function is multithreaded over the points by default.
# Only remove `parallelization_backend = SerialBackend()` if you are sure that your code
# is thread-safe and that there are no race conditions.
n_neighbors = zeros(Int, n_points)

function count_neighbors!(n_neighbors, coordinates, neighbor_coordinates, nhs)
n_neighbors .= 0

foreach_point_neighbor(coordinates, neighbor_coordinates, nhs,
parallelization_backend = SerialBackend()) do i, j,
pos_diff, distance
n_neighbors[i] += 1
end

return n_neighbors
end

count_neighbors!(n_neighbors, coordinates, coordinates, nhs)

# Interior particles have many neighbors, boundary particles fewer.
extrema(n_neighbors)

# ## Different point sets for points and neighbors

# The neighborhood search is currently configured to find neighbors in `coordinates`
# for points in `coordinates`. However, it is also possible to find neighbors in a
# different set of coordinates, e.g., `neighbor_coordinates`.
neighbor_coordinates = coordinates .+ 0.5
nothing # hide

# In order to find neighbors in `neighbor_coordinates`, we need to re-initialize
# the neighborhood search with `neighbor_coordinates` as the second argument.
initialize!(nhs, coordinates, neighbor_coordinates)
nothing # hide

# Now the neighborhood search is configured to find neighbors in `neighbor_coordinates`
# for points in `coordinates`.
count_neighbors!(n_neighbors, coordinates, neighbor_coordinates, nhs)
extrema(n_neighbors)

# ## Updating the neighborhood search

# If the coordinates of either the points or the neighbors change, the neighborhood search
# needs to be updated with [`update!`](@ref).
# Depending on the neighborhood search implementation, this can be done more efficiently
# than re-initializing the neighborhood search.
# !!! warning
# An `update!` requires that the sizes of the point sets do not change.
#
# If we don't update the neighborhood search, we will get incorrect neighbors:
neighbor_coordinates .+= 10
count_neighbors!(n_neighbors, coordinates, neighbor_coordinates, nhs)
extrema(n_neighbors)

# After updating the neighborhood search, we get the correct new neighbors.
update!(nhs, coordinates, neighbor_coordinates)
count_neighbors!(n_neighbors, coordinates, neighbor_coordinates, nhs)
extrema(n_neighbors)

# If the first coordinates are updated but the second coordinates are not, we generally
# also need to update the neighborhood search.
# For some neighborhood search implementations, notably the [`GridNeighborhoodSearch`](@ref),
# this is not necessary, since this implementation can find neighbors for arbitrary points in space.
# To check whether an update is necessary, we can call [`requires_update`](@ref):
requires_update(nhs)

# The first `false` indicates that an update is not required when the first coordinates are
# updated, and the second `true` indicates that an update is required when the second
# coordinates are updated.
Loading
Loading