peclet-core 1.2.0__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {peclet_core-1.2.0 → peclet_core-1.3.0}/CITATION.cff +2 -2
- {peclet_core-1.2.0 → peclet_core-1.3.0}/CLAUDE.md +10 -2
- {peclet_core-1.2.0 → peclet_core-1.3.0}/PKG-INFO +1 -1
- {peclet_core-1.2.0 → peclet_core-1.3.0}/docs/Doxyfile +1 -1
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/halo/particle_halo_topology.hpp +63 -14
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/halo/particle_migrator.hpp +54 -7
- {peclet_core-1.2.0 → peclet_core-1.3.0}/packaging/pyproject-core.toml +1 -1
- {peclet_core-1.2.0 → peclet_core-1.3.0}/pyproject.toml +1 -1
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/CMakeLists.txt +8 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_particle_halo_exchange.cpp +70 -45
- peclet_core-1.3.0/tests/test_particle_halo_images_mpi.cpp +374 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/.clang-format +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/.clang-tidy +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/.github/dependabot.yml +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/.github/workflows/ci.yml +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/.github/workflows/docs.yml +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/.github/workflows/quality.yml +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/.github/workflows/release.yml +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/.gitignore +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/CMakeLists.txt +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/LICENSE +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/README.md +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/benchmarks/CMakeLists.txt +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/benchmarks/bench_halo.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/cmake/PecletCorePinMpiexec.cmake +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/cmake/PecletCoreTest.cmake +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/cmake/SuiteNanobind.cmake +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/cmake/peclet-coreConfig.cmake.in +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/docs/archive/README.md +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/docs/archive/cuda-aware-mpi.md +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/common/cpu_budget.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/common/host_parallel.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/common/mpi.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/common/mpi_stub.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/common/portable.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/common/types.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/common/view.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/decomp/block_decomposer.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/decomp/block_indexer.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/decomp/gather_by_global_id.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/decomp/grid_redistribute.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/decomp/morton_indexer.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/decomp/redistribute_topology.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/decomp/stage_comm.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/decomp/stage_target.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/domain.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/field/field_set.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/geom/body_properties.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/geom/device_scene.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/geom/grid_sdf.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/geom/primitives.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/geom/quadrature.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/geom/scene.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/geom/scene_builder.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/geom/scene_query.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/geom/sdf.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/geom/vti_io.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/halo/grid_halo.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/halo/grid_halo_topology.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/halo/nbx.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/halo/particle_halo.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/halo/particle_migrator_view.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/halo/particle_rebalance.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/interp/particle_grid.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/python/kokkos_teardown.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/python/ndarray_interop.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/scheme/cut_cell_closure.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/scheme/ghost_closure.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/solver/coloring.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/solver/csr_bicgstab.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/solver/csr_operator.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/solver/face_csr.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/solver/graph_amg.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/solver/graph_amg_device.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/solver/vector_ops.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/vof/curvature.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/vof/cutcell.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/vof/plic.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/include/peclet/core/vof/wetting.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/packaging/shell/core_geom.py +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/packaging/shell/core_init.py +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/packaging/shell/core_mpi.py +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/python/CMakeLists.txt +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/python/halo_bindings.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/python/packaging/_halo.pyi +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/python/packaging/halo_init.py +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/python/state_hash.py +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/python/state_hash_reference.json +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/python/test_mpi.py +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/data/rcp_pack_seed3_unit.txt +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/python/CMakeLists.txt +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/python/interop_test_module.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/python/test_ndarray_interop.py +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_aligned_weighted.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_aligned_weighted_mpi.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_cpu_budget.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_csr_solver.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_cut_cell_closure.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_decomposition.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_diffusion.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_diffusion_sdf.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_domain.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_field_set.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_geom_batch_device.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_geom_body.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_geom_owner.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_geom_primitives.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_geom_properties.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_geom_quadrature.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_geom_scene.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_geom_scene_builder.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_geom_scene_query.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_geom_tree_grad.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_ghost_particles_mpi.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_graph_amg.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_graph_amg_device.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_grid_halo.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_grid_halo_exchange.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_grid_redistribute_mpi.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_host_parallel.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_morton_indexer.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_nbx_rounds.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_particle_grid.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_particle_halo_mpi.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_particle_migration.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_particle_migrator_view_mpi.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_particle_rebalance.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_sdf.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_skip_mpi.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_stage_redistribute_mpi.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_stage_repartition_mpi.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_stage_target.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_util.hpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tests/test_vti.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tools/cuda_aware_mpi_check.cpp +0 -0
- {peclet_core-1.2.0 → peclet_core-1.3.0}/tools/cudampi-env.sh +0 -0
|
@@ -16,8 +16,8 @@ authors:
|
|
|
16
16
|
orcid: "https://orcid.org/0000-0001-6099-3583"
|
|
17
17
|
email: e.a.j.f.peters@tue.nl
|
|
18
18
|
affiliation: "Eindhoven University of Technology"
|
|
19
|
-
version: 1.
|
|
20
|
-
date-released: 2026-09-
|
|
19
|
+
version: 1.3.0
|
|
20
|
+
date-released: 2026-09-26
|
|
21
21
|
license: MIT
|
|
22
22
|
doi: 10.5281/zenodo.21132435
|
|
23
23
|
identifiers:
|
|
@@ -31,13 +31,13 @@ judgement call in the moment.
|
|
|
31
31
|
```bash
|
|
32
32
|
# CPU library + tests (no device dependency):
|
|
33
33
|
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build -j
|
|
34
|
-
ctest --test-dir build --output-on-failure -LE bench #
|
|
34
|
+
ctest --test-dir build --output-on-failure -LE bench # 76 ctests (77 with `bench`): decomposition, MPI halo, particle migration, MG stages, diffusion, geometry
|
|
35
35
|
|
|
36
36
|
# Portable Kokkos device halo (CUDA / HIP / OpenMP) -- opt-in, find_package(Kokkos):
|
|
37
37
|
export PATH=/usr/local/cuda-13.2/bin:$PATH # if the Kokkos install targets the CUDA backend
|
|
38
38
|
cmake -S . -B build_kokkos -DPECLET_CORE_ENABLE_KOKKOS=ON \
|
|
39
39
|
-DCMAKE_PREFIX_PATH=../extern/install/nvidia-cuda
|
|
40
|
-
cmake --build build_kokkos -j && ctest --test-dir build_kokkos --output-on-failure -LE bench #
|
|
40
|
+
cmake --build build_kokkos -j && ctest --test-dir build_kokkos --output-on-failure -LE bench # 91 ctests (92 with `bench`): + device halo / geometry / solver, np=1,2,4,8
|
|
41
41
|
mpirun -np 4 ./build/benchmarks/bench_halo 48 1 300
|
|
42
42
|
|
|
43
43
|
# Python modules + their ctests (test_mpi.py np=1,2,4,8; state_hash; ndarray interop). Release is
|
|
@@ -158,6 +158,14 @@ Header-only under `include/peclet/core/`:
|
|
|
158
158
|
particle proximity; `forward` (owner→ghost), `reverse` (ghost→owner, accumulate) and
|
|
159
159
|
`forwardPositions` (periodic image shift) are the cheap per-step exchanges. The standard distributed
|
|
160
160
|
particle schemes (frozen/replicate, Newton-on, force-accumulate) are compositions of these.
|
|
161
|
+
`build(pos, rcut, includePeriodicSelf = false, allImages = false)`: by default each particle goes
|
|
162
|
+
to each other rank ONCE, at the image nearest that rank's block — on a periodic axis the ORB left
|
|
163
|
+
undecomposed that is always the unshifted image, so a wrapped image inside `rcut` is dropped and a
|
|
164
|
+
pair crossing a rank face while wrapping that axis is seen by neither owner. `allImages = true`
|
|
165
|
+
(opt-in, 1.3.0; dem's distributed step, `../dem/docs/contact_solve_framework.md` §5.3)
|
|
166
|
+
sends every image within `rcut`, one send entry per image, so an owned index may repeat in one
|
|
167
|
+
rank's `sendIdx` and a ghost is identified by (owner id, shift); forward/reverse work per entry.
|
|
168
|
+
Off is byte-identical to the one-image rule. Gate: `test_particle_halo_images_mpi` (np 1–8).
|
|
161
169
|
- `halo/particle_halo.hpp` — `ParticleHalo<Dim>`: the Kokkos GPU-resident driver for
|
|
162
170
|
`ParticleHaloTopology` (on-device forward gather + reverse atomic-accumulate; host-staged or
|
|
163
171
|
GPU-aware MPI). Built from `ParticleHaloTopology::flatten()`; consumed by dem's distributed step.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: peclet-core
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: peclet.core — compatibility shell: peclet.core.geom is now peclet.geom, peclet.core.mpi is now peclet.halo
|
|
5
5
|
Project-URL: Homepage, https://github.com/computational-chemical-engineering/peclet
|
|
6
6
|
Project-URL: Documentation, https://computational-chemical-engineering.github.io/peclet/
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
DOXYFILE_ENCODING = UTF-8
|
|
7
7
|
PROJECT_NAME = "core"
|
|
8
|
-
PROJECT_NUMBER = 1.
|
|
8
|
+
PROJECT_NUMBER = 1.3.0
|
|
9
9
|
PROJECT_BRIEF = "Shared MPI block decomposition, asynchronous ghost-layer exchange, SDF geometry, load balancing (header-only C++20)"
|
|
10
10
|
OUTPUT_DIRECTORY = docs
|
|
11
11
|
HTML_OUTPUT = html
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
#ifndef PECLET_CORE_HALO_PARTICLE_HALO_TOPOLOGY_HPP
|
|
22
22
|
#define PECLET_CORE_HALO_PARTICLE_HALO_TOPOLOGY_HPP
|
|
23
23
|
|
|
24
|
+
#include <algorithm>
|
|
24
25
|
#include <cstring>
|
|
25
26
|
#include <map>
|
|
26
27
|
#include <vector>
|
|
@@ -49,7 +50,20 @@ class ParticleHaloTopology {
|
|
|
49
50
|
/// byte-identical to the cross-rank-only behaviour (no self-ghosts, no MPI self-messages).
|
|
50
51
|
/// Self-ghosts occupy the ghost slots AFTER the received ones ([numReceived, numGhost)) and are
|
|
51
52
|
/// filled locally (no MPI).
|
|
52
|
-
|
|
53
|
+
///
|
|
54
|
+
/// `allImages` changes what the CROSS-RANK exchange sends. Off (the default), each owned particle
|
|
55
|
+
/// goes to each other rank at most once, at the single periodic image nearest that rank's block
|
|
56
|
+
/// (`withinRcutOfBlock`). On a periodic axis the ORB leaves undecomposed, that nearest image is
|
|
57
|
+
/// always the unshifted one (its gap on the axis is 0), so a wrapped image inside `rcut` is never
|
|
58
|
+
/// sent. On, EVERY periodic image of the particle within `rcut` of rank r's block is sent
|
|
59
|
+
/// (`imagesWithinRcutOfBlock` with the identity allowed), one send entry per image: the same
|
|
60
|
+
/// owned index may then appear several times in one rank's send list, each with its own shift,
|
|
61
|
+
/// and a receiver identifies a ghost by (owner id, shift). Every exchange works per entry, so
|
|
62
|
+
/// forwardPositions places each image, and reverse sums every image's contribution onto the
|
|
63
|
+
/// owner. The shift of an allImages entry is exactly 0 or +-L per axis. Off => byte-identical to
|
|
64
|
+
/// the one-image behaviour.
|
|
65
|
+
void build(const std::vector<Vec<Dim>>& pos, double rcut, bool includePeriodicSelf = false,
|
|
66
|
+
bool allImages = false) {
|
|
53
67
|
numOwned_ = pos.size();
|
|
54
68
|
int nranks = 0;
|
|
55
69
|
MPI_Comm_size(mig_->comm(), &nranks);
|
|
@@ -59,10 +73,35 @@ class ParticleHaloTopology {
|
|
|
59
73
|
std::map<int, std::vector<Index>> sendMap;
|
|
60
74
|
std::map<int, std::vector<Vec<Dim>>> shiftMap;
|
|
61
75
|
Vec<Dim> img;
|
|
76
|
+
std::vector<Vec<Dim>> imgShifts;
|
|
77
|
+
// Candidate ranks (ascending): those whose block comes within rcut of the bounding box of my
|
|
78
|
+
// particles, images included (boxWithinRcutOfBlock). A superset of every rank any particle can
|
|
79
|
+
// qualify for, so the send lists -- entries and order -- are those of the all-ranks loop; the
|
|
80
|
+
// per-particle work drops from O(numRanks) to O(neighbours).
|
|
81
|
+
std::vector<int> cand;
|
|
82
|
+
if (!pos.empty()) {
|
|
83
|
+
Vec<Dim> bmin = pos[0], bmax = pos[0];
|
|
84
|
+
for (const auto& x : pos)
|
|
85
|
+
for (int d = 0; d < Dim; ++d) {
|
|
86
|
+
bmin[d] = std::min(bmin[d], x[d]);
|
|
87
|
+
bmax[d] = std::max(bmax[d], x[d]);
|
|
88
|
+
}
|
|
89
|
+
for (int r = 0; r < nranks; ++r)
|
|
90
|
+
if (r != me && mig_->boxWithinRcutOfBlock(bmin, bmax, r, rcut))
|
|
91
|
+
cand.push_back(r);
|
|
92
|
+
}
|
|
62
93
|
for (std::size_t i = 0; i < pos.size(); ++i) {
|
|
63
|
-
for (int r
|
|
64
|
-
if (
|
|
94
|
+
for (const int r : cand) {
|
|
95
|
+
if (allImages) {
|
|
96
|
+
// Every qualifying image, one send entry each (the identity included).
|
|
97
|
+
imgShifts.clear();
|
|
98
|
+
mig_->imagesWithinRcutOfBlock(pos[i], r, rcut, /*allowIdentity=*/true, imgShifts);
|
|
99
|
+
for (const auto& sh : imgShifts) {
|
|
100
|
+
sendMap[r].push_back(static_cast<Index>(i));
|
|
101
|
+
shiftMap[r].push_back(sh);
|
|
102
|
+
}
|
|
65
103
|
continue;
|
|
104
|
+
}
|
|
66
105
|
if (!mig_->withinRcutOfBlock(pos[i], r, rcut, img))
|
|
67
106
|
continue;
|
|
68
107
|
Vec<Dim> shift;
|
|
@@ -74,11 +113,13 @@ class ParticleHaloTopology {
|
|
|
74
113
|
}
|
|
75
114
|
sendRanks_.clear();
|
|
76
115
|
sendIdx_.clear();
|
|
116
|
+
sendShift_.clear();
|
|
77
117
|
sendRankPos_.clear();
|
|
78
118
|
for (auto& [r, idx] : sendMap) {
|
|
79
119
|
sendRankPos_[r] = sendRanks_.size();
|
|
80
120
|
sendRanks_.push_back(r);
|
|
81
121
|
sendIdx_.push_back(std::move(idx));
|
|
122
|
+
sendShift_.push_back(shiftMap[r]); // the image each entry is sent as (parallel to sendIdx_)
|
|
82
123
|
}
|
|
83
124
|
|
|
84
125
|
// Exchange the shift vectors (one NBX round) to size the ghost array + matched recv lists.
|
|
@@ -242,26 +283,33 @@ class ParticleHaloTopology {
|
|
|
242
283
|
// pack). recvOffsets index the contiguous [0,numGhost) ghost array (in recvRanks order); shift is
|
|
243
284
|
// per-ghost (for position forwards). Rebuild after each build().
|
|
244
285
|
struct FlatTopo {
|
|
245
|
-
std::vector<int> sendRanks;
|
|
246
|
-
std::vector<Index> sendIdx;
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
std::vector<
|
|
250
|
-
|
|
251
|
-
std::vector<
|
|
252
|
-
std::vector<
|
|
253
|
-
std::vector<
|
|
254
|
-
|
|
286
|
+
std::vector<int> sendRanks; // neighbour ranks I send owned copies to
|
|
287
|
+
std::vector<Index> sendIdx; // concatenated owned indices to send (all ranks); with
|
|
288
|
+
// allImages an index may repeat within one rank's slice
|
|
289
|
+
// (one entry per periodic image)
|
|
290
|
+
std::vector<Vec<Dim>> sendShift; // parallel to sendIdx: the periodic image offset the entry is
|
|
291
|
+
// sent with (the receiver's ghost shift for that entry)
|
|
292
|
+
std::vector<int> sendCounts; // per send rank
|
|
293
|
+
std::vector<int> sendOffsets; // prefix sum into sendIdx (size sendRanks+1)
|
|
294
|
+
std::vector<int> recvRanks; // neighbour ranks I receive ghosts from
|
|
295
|
+
std::vector<int> recvCounts; // per recv rank
|
|
296
|
+
std::vector<Index> recvOffsets; // per recv rank: start in the [0,numReceived) ghost array
|
|
297
|
+
std::vector<Vec<Dim>> shift; // per ghost: periodic image offset (add to forwarded position)
|
|
298
|
+
std::vector<Index> selfIdx; // owned index of each LOCAL periodic self-ghost (size numSelf)
|
|
299
|
+
Index numReceived = 0; // ghost slots [0,numReceived) are cross-rank (MPI), the rest
|
|
255
300
|
// [numReceived, numGhost) are local self-ghosts gathered from selfIdx
|
|
256
301
|
};
|
|
257
302
|
FlatTopo flatten() const {
|
|
258
303
|
FlatTopo t;
|
|
259
304
|
t.sendRanks = sendRanks_;
|
|
260
305
|
t.sendOffsets.push_back(0);
|
|
261
|
-
for (
|
|
306
|
+
for (std::size_t k = 0; k < sendIdx_.size(); ++k) {
|
|
307
|
+
const auto& idx = sendIdx_[k];
|
|
262
308
|
t.sendCounts.push_back(static_cast<int>(idx.size()));
|
|
263
309
|
for (Index id : idx)
|
|
264
310
|
t.sendIdx.push_back(id);
|
|
311
|
+
for (const auto& sh : sendShift_[k])
|
|
312
|
+
t.sendShift.push_back(sh);
|
|
265
313
|
t.sendOffsets.push_back(static_cast<int>(t.sendIdx.size()));
|
|
266
314
|
}
|
|
267
315
|
t.recvRanks = recvRanks_;
|
|
@@ -281,6 +329,7 @@ class ParticleHaloTopology {
|
|
|
281
329
|
// Owner side: particles I send as ghosts to each neighbour rank.
|
|
282
330
|
std::vector<int> sendRanks_;
|
|
283
331
|
std::vector<std::vector<Index>> sendIdx_;
|
|
332
|
+
std::vector<std::vector<Vec<Dim>>> sendShift_; // parallel to sendIdx_: each entry's image offset
|
|
284
333
|
std::map<int, std::size_t> sendRankPos_;
|
|
285
334
|
|
|
286
335
|
// Ghost side: ghosts I receive from each neighbour rank (contiguous slots, in recvRanks_ order).
|
|
@@ -199,6 +199,32 @@ class ParticleMigrator {
|
|
|
199
199
|
/// periodic face (an undecomposed periodic axis, or np=1). `allowIdentity=false` drops the
|
|
200
200
|
/// un-shifted image, so r==self yields only the periodic self-wrap copies (a particle is never
|
|
201
201
|
/// its own ghost).
|
|
202
|
+
/// True iff some point of the box [bmin, bmax] (a periodic image of it included) lies within
|
|
203
|
+
/// `rcut` of rank r's block. A point of the box has, per axis and per image, a gap no smaller
|
|
204
|
+
/// than the box's, so false proves that NO point of the box qualifies for r -- a candidate filter
|
|
205
|
+
/// that is a superset of every per-particle test (withinRcutOfBlock, imagesWithinRcutOfBlock).
|
|
206
|
+
bool boxWithinRcutOfBlock(const Vec<Dim>& bmin, const Vec<Dim>& bmax, int r, double rcut) const {
|
|
207
|
+
const auto& o = dec_->origins()[r];
|
|
208
|
+
const auto& s = dec_->sizes()[r];
|
|
209
|
+
const IVec<Dim>& gsize = dec_->globalSize();
|
|
210
|
+
double d2 = 0.0;
|
|
211
|
+
for (int d = 0; d < Dim; ++d) {
|
|
212
|
+
const double lo = map_.origin[d] + o[d] * map_.cellSize[d];
|
|
213
|
+
const double hi = map_.origin[d] + (o[d] + s[d]) * map_.cellSize[d];
|
|
214
|
+
auto gap = [&](double shift) {
|
|
215
|
+
const double a = bmin[d] + shift, b = bmax[d] + shift;
|
|
216
|
+
return std::max(0.0, std::max(lo - b, a - hi));
|
|
217
|
+
};
|
|
218
|
+
double g = gap(0.0);
|
|
219
|
+
if (map_.periodic[d]) {
|
|
220
|
+
const double L = map_.cellSize[d] * static_cast<double>(gsize[d]);
|
|
221
|
+
g = std::min(g, std::min(gap(-L), gap(L)));
|
|
222
|
+
}
|
|
223
|
+
d2 += g * g;
|
|
224
|
+
}
|
|
225
|
+
return d2 < rcut * rcut;
|
|
226
|
+
}
|
|
227
|
+
|
|
202
228
|
void imagesWithinRcutOfBlock(const Vec<Dim>& x, int r, double rcut, bool allowIdentity,
|
|
203
229
|
std::vector<Vec<Dim>>& outShifts) const {
|
|
204
230
|
const auto& o = dec_->origins()[r];
|
|
@@ -218,24 +244,45 @@ class ParticleMigrator {
|
|
|
218
244
|
nc[d] = 3;
|
|
219
245
|
}
|
|
220
246
|
}
|
|
247
|
+
// Per-axis prefilter (exact): an image qualifies only if its total squared gap is below rc2,
|
|
248
|
+
// so every one of its per-axis gaps is -- keep, per axis and in candidate order, the shifts
|
|
249
|
+
// whose own gap passes, then enumerate their product in the same nesting (axis 0 fastest) as
|
|
250
|
+
// the full 3^Dim walk. Same set, same order, typically one candidate per axis instead of 27
|
|
251
|
+
// combinations (this runs for every owned particle x every rank at every topology build).
|
|
252
|
+
const double rc2 = rcut * rcut;
|
|
253
|
+
int keep[Dim][3];
|
|
254
|
+
double keepGap2[Dim][3];
|
|
255
|
+
int nk[Dim];
|
|
256
|
+
for (int d = 0; d < Dim; ++d) {
|
|
257
|
+
nk[d] = 0;
|
|
258
|
+
for (int k = 0; k < nc[d]; ++k) {
|
|
259
|
+
const double p = x[d] + cand[d][k];
|
|
260
|
+
const double gap = (p < lo[d]) ? (lo[d] - p) : (p > hi[d]) ? (p - hi[d]) : 0.0;
|
|
261
|
+
if (gap * gap < rc2) {
|
|
262
|
+
keep[d][nk[d]] = k;
|
|
263
|
+
keepGap2[d][nk[d]] = gap * gap;
|
|
264
|
+
++nk[d];
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (nk[d] == 0)
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
221
270
|
int total = 1;
|
|
222
271
|
for (int d = 0; d < Dim; ++d)
|
|
223
|
-
total *=
|
|
224
|
-
const double rc2 = rcut * rcut;
|
|
272
|
+
total *= nk[d];
|
|
225
273
|
for (int idx = 0; idx < total; ++idx) {
|
|
226
274
|
Vec<Dim> shift{};
|
|
227
275
|
int t = idx;
|
|
228
276
|
bool identity = true;
|
|
229
277
|
double d2 = 0.0;
|
|
230
278
|
for (int d = 0; d < Dim; ++d) {
|
|
231
|
-
const int
|
|
232
|
-
t /=
|
|
279
|
+
const int j = t % nk[d];
|
|
280
|
+
t /= nk[d];
|
|
281
|
+
const int k = keep[d][j];
|
|
233
282
|
shift[d] = cand[d][k];
|
|
234
283
|
if (k != 0)
|
|
235
284
|
identity = false;
|
|
236
|
-
|
|
237
|
-
const double gap = (p < lo[d]) ? (lo[d] - p) : (p > hi[d]) ? (p - hi[d]) : 0.0;
|
|
238
|
-
d2 += gap * gap;
|
|
285
|
+
d2 += keepGap2[d][j];
|
|
239
286
|
}
|
|
240
287
|
if (d2 < rc2 && (allowIdentity || !identity))
|
|
241
288
|
outShifts.push_back(shift);
|
|
@@ -19,7 +19,7 @@ build-backend = "hatchling.build"
|
|
|
19
19
|
|
|
20
20
|
[project]
|
|
21
21
|
name = "peclet-core"
|
|
22
|
-
version = "1.
|
|
22
|
+
version = "1.3.0"
|
|
23
23
|
description = "peclet.core — compatibility shell: peclet.core.geom is now peclet.geom, peclet.core.mpi is now peclet.halo"
|
|
24
24
|
readme = "README.md"
|
|
25
25
|
requires-python = ">=3.10"
|
|
@@ -19,7 +19,7 @@ build-backend = "hatchling.build"
|
|
|
19
19
|
|
|
20
20
|
[project]
|
|
21
21
|
name = "peclet-core"
|
|
22
|
-
version = "1.
|
|
22
|
+
version = "1.3.0"
|
|
23
23
|
description = "peclet.core — compatibility shell: peclet.core.geom is now peclet.geom, peclet.core.mpi is now peclet.halo"
|
|
24
24
|
readme = "README.md"
|
|
25
25
|
requires-python = ">=3.10"
|
|
@@ -227,6 +227,13 @@ if(MPI_FOUND)
|
|
|
227
227
|
target_link_libraries(test_particle_halo_mpi PRIVATE peclet_halo)
|
|
228
228
|
target_include_directories(test_particle_halo_mpi PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
|
229
229
|
|
|
230
|
+
# ParticleHaloTopology::build(..., allImages = true) on a slab whose periodic z axis the ORB never
|
|
231
|
+
# splits (np 1..8): every periodic image within rcut is sent, keyed (id, image), against a
|
|
232
|
+
# brute-force image-count oracle; reverse sums every image; the default stays the one-image rule.
|
|
233
|
+
add_executable(test_particle_halo_images_mpi test_particle_halo_images_mpi.cpp)
|
|
234
|
+
target_link_libraries(test_particle_halo_images_mpi PRIVATE peclet_halo)
|
|
235
|
+
target_include_directories(test_particle_halo_images_mpi PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
|
236
|
+
|
|
230
237
|
# The device particle-migrator view: needs Kokkos + MPI. peclet_halo carries both when
|
|
231
238
|
# PECLET_CORE_ENABLE_KOKKOS is on (same target the Kokkos grid-halo test links).
|
|
232
239
|
if(PECLET_CORE_ENABLE_KOKKOS)
|
|
@@ -250,6 +257,7 @@ if(MPI_FOUND)
|
|
|
250
257
|
peclet_core_add_mpi_test(diffusion_sdf_np${np} test_diffusion_sdf ${np})
|
|
251
258
|
peclet_core_add_mpi_test(ghost_particles_np${np} test_ghost_particles_mpi ${np})
|
|
252
259
|
peclet_core_add_mpi_test(particle_halo_np${np} test_particle_halo_mpi ${np})
|
|
260
|
+
peclet_core_add_mpi_test(particle_halo_images_np${np} test_particle_halo_images_mpi ${np})
|
|
253
261
|
endforeach()
|
|
254
262
|
|
|
255
263
|
# Portable GPU-resident halo : device result must match the CPU path, on whatever backend
|
|
@@ -5,8 +5,12 @@
|
|
|
5
5
|
// to match the CPU result bit-for-bit:
|
|
6
6
|
// forward(id) -> each ghost carries its owner's id,
|
|
7
7
|
// reverse(ones, sum) -> each owned particle accumulates a count of how many ranks ghost it.
|
|
8
|
+
// Run twice: the default topology, and build(..., allImages = true), where one owned particle may
|
|
9
|
+
// sit several times in one rank's send list (one entry per periodic image) and the device reverse
|
|
10
|
+
// must still sum every image's contribution (atomic add over repeated sendIdx entries).
|
|
8
11
|
#include <mpi.h>
|
|
9
12
|
|
|
13
|
+
#include <algorithm>
|
|
10
14
|
#include <cstdint>
|
|
11
15
|
#include <cstdio>
|
|
12
16
|
#include <cstring>
|
|
@@ -78,56 +82,77 @@ int main(int argc, char** argv) {
|
|
|
78
82
|
ownId[i] = (double)id;
|
|
79
83
|
}
|
|
80
84
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
long long repeated = 0;
|
|
86
|
+
auto run = [&](bool allImages) {
|
|
87
|
+
ParticleHaloTopology<3> halo;
|
|
88
|
+
halo.init(mig);
|
|
89
|
+
halo.build(pos, rcut, /*includePeriodicSelf=*/allImages, allImages);
|
|
90
|
+
const std::size_t G = halo.numGhost();
|
|
91
|
+
if (allImages) { // count repeated owned indices within one rank's send slice
|
|
92
|
+
const auto t = halo.flatten();
|
|
93
|
+
for (std::size_t k = 0; k < t.sendRanks.size(); ++k) {
|
|
94
|
+
std::vector<Index> sl(t.sendIdx.begin() + t.sendOffsets[k],
|
|
95
|
+
t.sendIdx.begin() + t.sendOffsets[k + 1]);
|
|
96
|
+
std::sort(sl.begin(), sl.end());
|
|
97
|
+
for (std::size_t j = 1; j < sl.size(); ++j)
|
|
98
|
+
repeated += (sl[j] == sl[j - 1]) ? 1 : 0;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
85
101
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
102
|
+
// --- CPU reference exchanges ---
|
|
103
|
+
std::vector<double> ghCpu(G), ownCntCpu(Nown, 0.0), ones(G, 1.0);
|
|
104
|
+
halo.forward(ownId.data(), ghCpu.data());
|
|
105
|
+
halo.reverse(ones.data(), ownCntCpu.data());
|
|
90
106
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
107
|
+
// --- device exchanges ---
|
|
108
|
+
ParticleHalo<3> dhalo;
|
|
109
|
+
dhalo.init(halo);
|
|
94
110
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
View<double> dOwn(Kokkos::view_alloc("own", Kokkos::WithoutInitializing), Nown);
|
|
112
|
+
View<double> dGhost(Kokkos::view_alloc("ghost", Kokkos::WithoutInitializing), G);
|
|
113
|
+
{
|
|
114
|
+
auto hOwn = Kokkos::create_mirror_view(dOwn);
|
|
115
|
+
for (std::size_t i = 0; i < Nown; ++i)
|
|
116
|
+
hOwn(i) = ownId[i];
|
|
117
|
+
Kokkos::deep_copy(dOwn, hOwn);
|
|
118
|
+
}
|
|
119
|
+
dhalo.forward(dOwn, dGhost);
|
|
120
|
+
std::vector<double> ghDev(G);
|
|
121
|
+
{
|
|
122
|
+
auto hGhost = Kokkos::create_mirror_view(dGhost);
|
|
123
|
+
Kokkos::deep_copy(hGhost, dGhost);
|
|
124
|
+
for (std::size_t i = 0; i < G; ++i)
|
|
125
|
+
ghDev[i] = hGhost(i);
|
|
126
|
+
}
|
|
111
127
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
128
|
+
View<double> dGones(Kokkos::view_alloc("gones", Kokkos::WithoutInitializing), G);
|
|
129
|
+
View<double> dOwnCnt("owncnt", Nown); // zero-initialised
|
|
130
|
+
Kokkos::deep_copy(dGones, 1.0);
|
|
131
|
+
dhalo.reverse(dGones, dOwnCnt);
|
|
132
|
+
std::vector<double> ownCntDev(Nown);
|
|
133
|
+
{
|
|
134
|
+
auto hCnt = Kokkos::create_mirror_view(dOwnCnt);
|
|
135
|
+
Kokkos::deep_copy(hCnt, dOwnCnt);
|
|
136
|
+
for (std::size_t i = 0; i < Nown; ++i)
|
|
137
|
+
ownCntDev[i] = hCnt(i);
|
|
138
|
+
}
|
|
123
139
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
140
|
+
// --- device must match CPU bit-for-bit ---
|
|
141
|
+
for (std::size_t i = 0; i < G; ++i)
|
|
142
|
+
if (ghDev[i] != ghCpu[i])
|
|
143
|
+
++fail;
|
|
144
|
+
for (std::size_t i = 0; i < Nown; ++i)
|
|
145
|
+
if (ownCntDev[i] != ownCntCpu[i])
|
|
146
|
+
++fail;
|
|
147
|
+
};
|
|
148
|
+
run(false);
|
|
149
|
+
run(true);
|
|
150
|
+
// The allImages pass must actually exercise repeated send entries where a periodic axis is
|
|
151
|
+
// undecomposed (np 2 and 4 of this ORB; np 1 has no cross-rank exchange).
|
|
152
|
+
long long repeatedAll = 0;
|
|
153
|
+
MPI_Allreduce(&repeated, &repeatedAll, 1, MPI_LONG_LONG, MPI_SUM, MPI_COMM_WORLD);
|
|
154
|
+
if (size >= 2 && repeatedAll == 0)
|
|
155
|
+
++fail;
|
|
131
156
|
}
|
|
132
157
|
|
|
133
158
|
int total = 0;
|