ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
short_range_cabana.hpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2025-2026 The ESPResSo project
3 *
4 * This file is part of ESPResSo.
5 *
6 * ESPResSo is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * ESPResSo is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#pragma once
21
22#include <config/config.hpp>
23
25
26#include "aosoa_pack.hpp"
29#include "forces_cabana.hpp"
30#include "kokkos_helpers.hpp"
31
32#include <Cabana_Core.hpp>
33#include <Cabana_NeighborList.hpp>
34
35#ifdef ESPRESSO_CALIPER
36#include <caliper/cali.h>
37#endif
38
39#include <functional>
40#include <iterator>
41#include <span>
42#include <utility>
43
45commit_particle(Particle const &p, auto const index,
46 CellStructure::AoSoA_pack &aosoa, bool const rebuild) {
47 // Always commit: positions, velocities, charges, directors, dipm
48 aosoa.set_vector_at(aosoa.position, index, p.pos());
49#ifdef ESPRESSO_ELECTROSTATICS
50 aosoa.charge(index) = p.q();
51#endif
52 aosoa.set_vector_at(aosoa.velocity, index, p.v());
53#if defined(ESPRESSO_GAY_BERNE) or defined(ESPRESSO_DIPOLES)
54 aosoa.set_vector_at(aosoa.director, index,
56#endif
57#ifdef ESPRESSO_DIPOLES
58 aosoa.dipm(index) = p.dipm();
59#endif
60
61 // Only commit on rebuild: id, type
62 if (rebuild) {
63 aosoa.id(index) = p.id();
64 aosoa.type(index) = p.type();
65 aosoa.set_vector_at(aosoa.image, index, p.image_box());
66#ifdef ESPRESSO_MASS
67 aosoa.mass(index) = p.mass();
68#endif
69 }
70
71 // Always update exclusion flags (they can change during simulation)
72#ifdef ESPRESSO_EXCLUSIONS
73 bool const has_exclusion = not p.exclusions().empty();
74 aosoa.set_has_exclusion(index, has_exclusion);
75 // Record the any-exclusion aggregate on the host. This is intentionally NOT
76 // done inside the device-qualified set_has_exclusion: this commit sweep runs
77 // on the host execution space, and the aggregate is a host std::atomic.
78 if (has_exclusion) {
79 aosoa.mark_any_exclusion();
80 }
81#else
82 aosoa.flags(index) = 0;
83#endif
84}
85
87 std::span<Cell *const> cells, BoxGeometry const &box_geo,
88 auto const &verlet_criterion,
89 Kokkos::View<int *, Kokkos::DefaultHostExecutionSpace> const &id_to_index,
90 int const max_id, auto const &intra_operator, auto const &inter_operator) {
91
92 // implementation detail: max_id refers to the max local particle id,
93 // but ghost particles from other ranks may have larger particle ids;
94 // -1 is used as a sentinel value for particle ids from other threads
95
96 // Hoist the cuboid minimum-image parameters by value: the fold runs once
97 // per candidate pair and must not chase the BoxGeometry reference for its
98 // box lengths every time. Lees-Edwards boxes take the full BoxGeometry
99 // path (shear offset handling).
100 bool const has_lees_edwards = box_geo.type() == BoxType::LEES_EDWARDS;
101 auto const cuboid_minimum_image = box_geo.cuboid_minimum_image();
102 auto const minimum_image_dist2 =
103 [&box_geo, has_lees_edwards, cuboid_minimum_image](
104 Utils::Vector3d const &a, Utils::Vector3d const &b) {
105 return has_lees_edwards ? box_geo.get_mi_dist2(a, b)
106 : cuboid_minimum_image.dist2(a, b);
107 };
108
110 &id_to_index, &intra_operator, max_id](const int i) {
111 auto &local_particles = cells[i]->particles();
112 for (auto it = local_particles.begin(); it != local_particles.end(); ++it) {
113 auto const &p1 = *it;
114 if (p1.id() <= max_id) {
115 auto const ii = id_to_index(p1.id());
116 if (ii >= 0) {
117 // pairs in this cell
118 for (auto jt = std::next(it); jt != local_particles.end(); ++jt) {
119 if ((*jt).id() <= max_id) {
120 if (verlet_criterion(p1, *jt,
121 minimum_image_dist2(p1.pos(), jt->pos()))) {
122 auto const jj = id_to_index((*jt).id());
123 if (jj >= 0) {
125 }
126 }
127 }
128 }
129 }
130 }
131 }
132 };
133
135 &id_to_index, &inter_operator, max_id](const int i) {
136 auto &local_particles = cells[i]->particles();
137 for (auto const &p1 : local_particles) {
138 if (p1.id() <= max_id) {
139 auto const ii = id_to_index(p1.id());
140 if (ii >= 0) {
141 // pairs with neighboring cells
142 for (auto &neighbor : cells[i]->neighbors().red()) {
143 for (auto const &p2 : neighbor->particles()) {
144 if (p2.id() <= max_id) {
146 minimum_image_dist2(p1.pos(), p2.pos()))) {
147 auto const jj = id_to_index(p2.id());
148 if (jj >= 0) {
150 }
151 }
152 }
153 }
154 }
155 }
156 }
157 }
158 };
159
161 "intra", std::size_t{0}, cells.size(), intra_kernel);
162 Kokkos::fence();
163
165 "inter", std::size_t{0}, cells.size(), inter_kernel);
166 Kokkos::fence();
167}
168
169// @p make_verlet_criterion is a nullary factory: constructing the criterion
170// fills an O(n_types^2) cutoff table, so it is only invoked when the Verlet
171// list is actually rebuilt, not on every force call.
172template <class execution_space = Kokkos::DefaultHostExecutionSpace>
175 auto const &make_verlet_criterion, double const pair_cutoff,
176 auto const integ_switch) {
177#ifdef ESPRESSO_CALIPER
179#endif
180 auto const rebuild = cell_structure.prepare_verlet_list_cabana(pair_cutoff);
181 auto const &unique_particles = cell_structure.get_unique_particles();
182 auto const n_part = unique_particles.size();
183 auto const max_id = cell_structure.get_cached_max_local_particle_id();
184 auto &aosoa = cell_structure.get_aosoa();
185
186 if (rebuild) {
187 auto &id_to_index = cell_structure.get_id_to_index();
188
189 // ===================================================
190 // Fill particle storage (full commit)
191 // ===================================================
192#ifdef ESPRESSO_CALIPER
193 CALI_MARK_BEGIN("AoSoA commit full");
194#endif
195 int pair_count = 0;
196 int angle_count = 0;
197 int dihedral_count = 0;
198#ifdef ESPRESSO_EXCLUSIONS
199 // commit_particle accumulates the any-exclusion aggregate below; clear it
200 // before the sweep repopulates it (read O(1) at the dispatch gate).
201 aosoa.reset_any_exclusion();
202#endif
204 "AoSoA write", std::size_t{0}, n_part,
205 [&unique_particles, &aosoa, &id_to_index, &cell_structure, &pair_count,
206 &angle_count, &dihedral_count](int const index) {
207 auto const &p = *unique_particles.at(index);
208 commit_particle(p, index, aosoa, true);
209 id_to_index(p.id()) = index;
210 if (not p.is_ghost()) {
211 cell_structure.update_bond_storage(pair_count, angle_count,
212 dihedral_count, p);
213 }
214 });
215 Kokkos::fence();
216 using host_space = Kokkos::DefaultHostExecutionSpace;
217 auto &bs = cell_structure.bond_state();
218 if (pair_count) {
219 auto &pair_bond_list = bs.pair_list;
221 "resolve_pair_bond_indices", std::size_t{0}, pair_count,
222 [&pair_bond_list, &id_to_index](int idx) {
223 for (int col = 0; col < 2; ++col) {
225 }
226 });
227 }
228 if (angle_count) {
229 auto &angle_bond_list = bs.angle_list;
231 "resolve_angle_bond_indices", std::size_t{0}, angle_count,
233 for (int col = 0; col < 3; ++col) {
234 angle_bond_list(idx, col) =
236 }
237 });
238 }
239 if (dihedral_count) {
240 auto &dihedral_bond_list = bs.dihedral_list;
242 "resolve_dihedral_bond_indices", std::size_t{0}, dihedral_count,
244 for (int col = 0; col < 4; ++col) {
245 dihedral_bond_list(idx, col) =
247 }
248 });
249 }
250 if (pair_count != 0 or angle_count != 0 or dihedral_count != 0) {
251 Kokkos::fence();
252 }
253#ifdef ESPRESSO_CALIPER
254 CALI_MARK_END("AoSoA commit full");
255#endif
256
257 // ===================================================
258 // Get Verlet pairs and fill Verlet list
259 // ===================================================
260 bool rebuild_vl = (integ_switch != INTEG_METHOD_STEEPEST_DESCENT and
261 cell_structure.use_verlet_list);
262#ifdef ESPRESSO_CALIPER
263 CALI_MARK_BEGIN("Verlet list creation");
264#endif
265 cell_structure.rebuild_verlet_list_cabana(
266 [&](std::span<Cell *const> cells, BoxGeometry const &box,
270 std::move(cells), box, verlet_criterion, id_to_index, max_id,
271 [&](const int i, const int j) {
272 // intra cell loop
273 verlet_list.addNeighborLB(i, j);
274 },
275 [&](const int i, const int j) {
276 // inter cell loop
277 verlet_list.addNeighbor(i, j);
278 });
279
280 if (verlet_list.hasOverflow()) {
281 cell_structure.use_verlet_list = false;
283 << "Verlet list overflow detected: neighbor count exceeded "
284 "max_counts. Falling back to the link cell algorithm. "
285 "Configured max is "
286 << Cabana::NeighborList<CellStructure::ListType>::maxNeighbor(
288 }
289 },
290 rebuild_vl);
291#ifdef ESPRESSO_CALIPER
292 CALI_MARK_END("Verlet list creation");
293#endif
294 } else {
295 // ===================================================
296 // Fill particle storage (partial update)
297 // ===================================================
298#ifdef ESPRESSO_CALIPER
299 CALI_MARK_BEGIN("AoSoA commit partial");
300#endif
301#ifdef ESPRESSO_EXCLUSIONS
302 // commit_particle accumulates the any-exclusion aggregate below; clear it
303 // before the sweep repopulates it (read O(1) at the dispatch gate).
304 aosoa.reset_any_exclusion();
305#endif
307 "AoSoA write", std::size_t{0}, n_part,
308 [&unique_particles, &aosoa](int const index) {
309 auto const &p = *unique_particles.at(index);
310 commit_particle(p, index, aosoa, false);
311 });
312 Kokkos::fence();
313#ifdef ESPRESSO_CALIPER
314 CALI_MARK_END("AoSoA commit partial");
315#endif
316 }
317}
318
319#ifdef ESPRESSO_ELECTROSTATICS
320template <class execution_space = Kokkos::DefaultHostExecutionSpace>
323 auto const &unique_particles = cell_structure.get_unique_particles();
324 auto const n_part = unique_particles.size();
325 auto &aosoa = cell_structure.get_aosoa();
326
328 "Views update charges", std::size_t{0}, n_part,
329 [&unique_particles, &aosoa](std::size_t const index) {
330 aosoa.charge(index) = unique_particles.at(index)->q();
331 });
332}
333#endif
334
335// Optional replacement for the Verlet-list pair loop. When set, it is invoked
336// with the current Verlet list and the pack particle count instead of the
337// generic Cabana::neighbor_parallel_for over @p nonbonded_kernel. forces.cpp
338// installs the compile-time-specialized pair kernel this way when the active
339// feature set allows it; energy/pressure leave it empty and keep the generic
340// loop.
342 std::function<void(CellStructure::ListType const &, std::size_t)>;
343
344// @p make_verlet_criterion is a nullary factory: constructing the criterion
345// fills an O(n_types^2) cutoff table, so it is only invoked on the link-cell
346// fallback path, which is the only consumer here.
347template <class execution_space = Kokkos::DefaultHostExecutionSpace>
349 auto const &angle_bonds_kernel,
350 auto const &dihedral_bonds_kernel,
351 auto const &nonbonded_kernel,
352 CellStructure &cell_structure, double pair_cutoff,
353 double bond_cutoff, auto const &make_verlet_criterion,
354 auto const integ_switch,
357
358 if (bond_cutoff >= 0.) {
359#ifdef ESPRESSO_CALIPER
360 CALI_MARK_BEGIN("cabana_bond_loop");
361#endif
362 using host_space = Kokkos::DefaultHostExecutionSpace;
363 auto const n_pair_bonds = cell_structure.get_local_pair_bond_numbers();
364 auto const n_angle_bonds = cell_structure.get_local_angle_bond_numbers();
365 auto const n_dihedral_bonds =
366 cell_structure.get_local_dihedral_bond_numbers();
367 if (n_pair_bonds > 0) {
368 kokkos_parallel_range_for<host_space>("for_each_local_pair_bonds",
369 std::size_t{0}, n_pair_bonds,
371 }
372 if (n_angle_bonds > 0) {
373 kokkos_parallel_range_for<host_space>("for_each_local_angle_bonds",
374 std::size_t{0}, n_angle_bonds,
376 }
377 if (n_dihedral_bonds > 0) {
378 kokkos_parallel_range_for<host_space>("for_each_local_dihedral_bonds",
379 std::size_t{0}, n_dihedral_bonds,
381 }
382 if (n_pair_bonds != 0 or n_angle_bonds != 0 or n_dihedral_bonds != 0) {
383 Kokkos::fence();
384 }
385#ifdef ESPRESSO_CALIPER
386 CALI_MARK_END("cabana_bond_loop");
387#endif
388 }
389
390 // Cabana short range loop
391 if (pair_cutoff > 0.) {
392#ifdef ESPRESSO_CALIPER
393 CALI_MARK_BEGIN("cabana_pair_loop");
394#endif
395 if (integ_switch != INTEG_METHOD_STEEPEST_DESCENT and
396 cell_structure.use_verlet_list) {
397 auto const &verlet_list = cell_structure.get_verlet_list_cabana();
398 auto const n_particles = cell_structure.get_unique_particles().size();
399 if (verlet_pair_loop) {
400 verlet_pair_loop(verlet_list, n_particles);
401 } else if (Kokkos::num_threads() == 1) {
402 using NL = Cabana::NeighborList<CellStructure::ListType>;
403 for (std::size_t i = 0; i < n_particles; ++i) {
404 auto const nn = NL::numNeighbor(verlet_list, i);
405 for (std::size_t n = 0; n < nn; ++n) {
406 nonbonded_kernel(i, NL::getNeighbor(verlet_list, i, n));
407 }
408 }
409 } else {
410 Kokkos::RangePolicy<execution_space> policy(std::size_t{0},
411 n_particles);
412 Cabana::neighbor_parallel_for(policy, nonbonded_kernel, verlet_list,
413 Cabana::FirstNeighborsTag(),
414 Cabana::SerialOpTag());
415 }
416 } else {
417 cell_structure.cell_list_loop(
418 [&](std::span<Cell *const> cells, BoxGeometry const &box) {
421 std::move(cells), box, verlet_criterion,
422 cell_structure.get_id_to_index(),
423 cell_structure.get_cached_max_local_particle_id(),
424 [&](const int i, const int j) {
425 // intra cell loop
426 nonbonded_kernel(i, j);
427 },
428 [&](const int i, const int j) {
429 // inter cell loop
430 nonbonded_kernel(i, j);
431 });
432 });
433 }
434 Kokkos::fence();
435#ifdef ESPRESSO_CALIPER
436 CALI_MARK_END("cabana_pair_loop");
437#endif
438 }
439}
@ LEES_EDWARDS
@ INTEG_METHOD_STEEPEST_DESCENT
#define ESPRESSO_ATTR_ALWAYS_INLINE
ESPRESSO_ATTR_ALWAYS_INLINE T get_mi_dist2(Utils::Vector3< T > const &a, Utils::Vector3< T > const &b) const
Get the squared minimum-image distance between two coordinates.
auto cuboid_minimum_image() const
Cuboid minimum-image fold parameters for hoisting into kernels.
BoxType type() const
Describes a cell structure / cell system.
auto & get_id_to_index()
int get_local_angle_bond_numbers() const
int get_local_pair_bond_numbers() const
auto prepare_verlet_list_cabana(double cutoff)
Reset local properties of the Verlet list.
int get_local_dihedral_bond_numbers() const
int get_cached_max_local_particle_id() const
auto const & get_unique_particles() const
unsigned get_resort_particles() const
Get the currently scheduled resort level.
void cell_list_loop(auto &&kernel)
auto const & get_verlet_list_cabana() const
cudaStream_t stream[1]
CUDA streams for parallel computing on CPU and GPU.
#define runtimeWarningMsg()
constexpr Vector< T, 3 > convert_quaternion_to_director(Quaternion< T > const &quat)
Convert quaternion to director.
ESPRESSO_ATTR_ALWAYS_INLINE void link_cell_kokkos(std::span< Cell *const > cells, BoxGeometry const &box_geo, auto const &verlet_criterion, Kokkos::View< int *, Kokkos::DefaultHostExecutionSpace > const &id_to_index, int const max_id, auto const &intra_operator, auto const &inter_operator)
void cabana_short_range(auto const &pair_bonds_kernel, auto const &angle_bonds_kernel, auto const &dihedral_bonds_kernel, auto const &nonbonded_kernel, CellStructure &cell_structure, double pair_cutoff, double bond_cutoff, auto const &make_verlet_criterion, auto const integ_switch, ShortRangeVerletPairLoop const &verlet_pair_loop={})
ESPRESSO_ATTR_ALWAYS_INLINE void commit_particle(Particle const &p, auto const index, CellStructure::AoSoA_pack &aosoa, bool const rebuild)
ESPRESSO_ATTR_ALWAYS_INLINE void update_cabana_state(CellStructure &cell_structure, auto const &make_verlet_criterion, double const pair_cutoff, auto const integ_switch)
std::function< void(CellStructure::ListType const &, std::size_t)> ShortRangeVerletPairLoop
ESPRESSO_ATTR_ALWAYS_INLINE void update_aosoa_charges(CellStructure &cell_structure)
DEVICE_QUALIFIER void set_has_exclusion(std::size_t i, bool value)
PositionViewType position
DEVICE_QUALIFIER void set_vector_at(Kokkos::View< T *[N], array_layout, Kokkos::HostSpace > &view, std::size_t i, Utils::Vector< T, N > const &value)
DirectorViewType director
VelocityViewType velocity
Struct holding all information for one particle.
Definition Particle.hpp:436
constexpr auto const & quat() const
Definition Particle.hpp:527
constexpr auto const & pos() const
Definition Particle.hpp:476
constexpr auto const & mass() const
Definition Particle.hpp:500
constexpr auto const & dipm() const
Definition Particle.hpp:543
Utils::compact_vector< int > & exclusions()
Definition Particle.hpp:666
constexpr auto const & type() const
Definition Particle.hpp:459
constexpr auto const & image_box() const
Definition Particle.hpp:489
constexpr auto const & id() const
Definition Particle.hpp:455
constexpr auto const & q() const
Definition Particle.hpp:597
constexpr auto const & v() const
Definition Particle.hpp:478