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
31#include <Cabana_Core.hpp>
32#include <Cabana_NeighborList.hpp>
33
34#ifdef ESPRESSO_CALIPER
35#include <caliper/cali.h>
36#endif
37
38#include <iterator>
39#include <span>
40#include <utility>
41
42template <class KokkosRangePolicy =
43 Kokkos::RangePolicy<Kokkos::DefaultHostExecutionSpace>>
45kokkos_parallel_range_for(auto const &name, auto start, auto end,
46 auto const &kernel) {
47 if (Kokkos::num_threads() > 1) {
48 KokkosRangePolicy policy(start, end);
49 Kokkos::parallel_for(name, policy, kernel);
50 } else {
51 for (auto p_index = start; p_index < end; ++p_index) {
52 kernel(p_index);
53 }
54 }
55}
56
58commit_particle(Particle const &p, auto const index,
59 CellStructure::AoSoA_pack &aosoa, bool const rebuild) {
60 // Always commit: positions, velocities, charges, directors, dipm
61 aosoa.set_vector_at(aosoa.position, index, p.pos());
62#ifdef ESPRESSO_ELECTROSTATICS
63 aosoa.charge(index) = p.q();
64#endif
65 aosoa.set_vector_at(aosoa.velocity, index, p.v());
66#if defined(ESPRESSO_GAY_BERNE) or defined(ESPRESSO_DIPOLES)
67 aosoa.set_vector_at(aosoa.director, index,
69#endif
70#ifdef ESPRESSO_DIPOLES
71 aosoa.dipm(index) = p.dipm();
72#endif
73
74 // Only commit on rebuild: id, type
75 if (rebuild) {
76 aosoa.id(index) = p.id();
77 aosoa.type(index) = p.type();
78 aosoa.set_vector_at(aosoa.image, index, p.image_box());
79#ifdef ESPRESSO_MASS
80 aosoa.mass(index) = p.mass();
81#endif
82 }
83
84 // Always update exclusion flags (they can change during simulation)
85#ifdef ESPRESSO_EXCLUSIONS
86 aosoa.set_has_exclusion(index, !p.exclusions().empty());
87#else
88 aosoa.flags(index) = 0;
89#endif
90}
91
93 std::span<Cell *const> cells, BoxGeometry const &box_geo,
94 auto const &verlet_criterion,
95 Kokkos::View<int *, Kokkos::DefaultHostExecutionSpace> const &id_to_index,
96 int const max_id, auto const &intra_operator, auto const &inter_operator) {
97
98 // implementation detail: max_id refers to the max local particle id,
99 // but ghost particles from other ranks may have larger particle ids;
100 // -1 is used as a sentinel value for particle ids from other threads
101
102 auto intra_kernel = [&cells, &box_geo, &verlet_criterion, &id_to_index,
103 &intra_operator, max_id](const int i) {
104 auto &local_particles = cells[i]->particles();
105 for (auto it = local_particles.begin(); it != local_particles.end(); ++it) {
106 auto const &p1 = *it;
107 if (p1.id() <= max_id) {
108 auto const ii = id_to_index(p1.id());
109 if (ii >= 0) {
110 // pairs in this cell
111 for (auto jt = std::next(it); jt != local_particles.end(); ++jt) {
112 if ((*jt).id() <= max_id) {
113 if (verlet_criterion(p1, *jt,
114 box_geo.get_mi_dist2(p1.pos(), jt->pos()))) {
115 auto const jj = id_to_index((*jt).id());
116 if (jj >= 0) {
118 }
119 }
120 }
121 }
122 }
123 }
124 }
125 };
126
127 auto inter_kernel = [&cells, &box_geo, &verlet_criterion, &id_to_index,
128 &inter_operator, max_id](const int i) {
129 auto &local_particles = cells[i]->particles();
130 for (auto const &p1 : local_particles) {
131 if (p1.id() <= max_id) {
132 auto const ii = id_to_index(p1.id());
133 if (ii >= 0) {
134 // pairs with neighboring cells
135 for (auto &neighbor : cells[i]->neighbors().red()) {
136 for (auto const &p2 : neighbor->particles()) {
137 if (p2.id() <= max_id) {
139 p1, p2, box_geo.get_mi_dist2(p1.pos(), p2.pos()))) {
140 auto const jj = id_to_index(p2.id());
141 if (jj >= 0) {
143 }
144 }
145 }
146 }
147 }
148 }
149 }
150 }
151 };
152
153 Kokkos::parallel_for("intra",
154 Kokkos::RangePolicy<Kokkos::DefaultHostExecutionSpace>(
155 std::size_t{0}, cells.size()),
157 Kokkos::fence();
158
159 Kokkos::parallel_for("inter",
160 Kokkos::RangePolicy<Kokkos::DefaultHostExecutionSpace>(
161 std::size_t{0}, cells.size()),
163 Kokkos::fence();
164}
165
166template <class execution_space = Kokkos::DefaultHostExecutionSpace>
169 double const pair_cutoff, auto const integ_switch) {
170#ifdef ESPRESSO_CALIPER
172#endif
173 using policy_type = Kokkos::RangePolicy<execution_space>;
174 auto const rebuild = cell_structure.prepare_verlet_list_cabana(pair_cutoff);
175 auto const &unique_particles = cell_structure.get_unique_particles();
176 auto const n_part = unique_particles.size();
177 auto const max_id = cell_structure.get_cached_max_local_particle_id();
178 auto &aosoa = cell_structure.get_aosoa();
179
180 if (rebuild) {
181 auto &id_to_index = cell_structure.get_id_to_index();
182
183 // ===================================================
184 // Fill particle storage (full commit)
185 // ===================================================
186#ifdef ESPRESSO_CALIPER
187 CALI_MARK_BEGIN("AoSoA commit full");
188#endif
189 int pair_count = 0;
190 int angle_count = 0;
191 int dihedral_count = 0;
193 "AoSoA write", std::size_t{0}, n_part,
194 [&unique_particles, &aosoa, &id_to_index, &cell_structure, &pair_count,
195 &angle_count, &dihedral_count](int const index) {
196 auto const &p = *unique_particles.at(index);
197 commit_particle(p, index, aosoa, true);
198 id_to_index(p.id()) = index;
199 if (not p.is_ghost()) {
200 cell_structure.update_bond_storage(pair_count, angle_count,
201 dihedral_count, p);
202 }
203 });
204 Kokkos::fence();
205 auto &bs = cell_structure.bond_state();
206 auto &pair_bond_list = bs.pair_list;
207 Kokkos::parallel_for("resolve_pair_bond_indices",
208 Kokkos::RangePolicy<Kokkos::DefaultHostExecutionSpace>(
209 std::size_t{0}, pair_count),
210 [&pair_bond_list, &id_to_index](int idx) {
211 for (int col = 0; col < 2; ++col) {
212 pair_bond_list(idx, col) =
214 }
215 });
216 auto &angle_bond_list = bs.angle_list;
217 Kokkos::parallel_for("resolve_angle_bond_indices",
218 Kokkos::RangePolicy<Kokkos::DefaultHostExecutionSpace>(
219 std::size_t{0}, angle_count),
221 for (int col = 0; col < 3; ++col) {
222 angle_bond_list(idx, col) =
224 }
225 });
226 auto &dihedral_bond_list = bs.dihedral_list;
227 Kokkos::parallel_for("resolve_dihedral_bond_indices",
228 Kokkos::RangePolicy<Kokkos::DefaultHostExecutionSpace>(
229 std::size_t{0}, dihedral_count),
231 for (int col = 0; col < 4; ++col) {
232 dihedral_bond_list(idx, col) =
234 }
235 });
236 Kokkos::fence();
237#ifdef ESPRESSO_CALIPER
238 CALI_MARK_END("AoSoA commit full");
239#endif
240
241 // ===================================================
242 // Get Verlet pairs and fill Verlet list
243 // ===================================================
244 bool rebuild_vl = (integ_switch != INTEG_METHOD_STEEPEST_DESCENT and
245 cell_structure.use_verlet_list);
246#ifdef ESPRESSO_CALIPER
247 CALI_MARK_BEGIN("Verlet list creation");
248#endif
249 cell_structure.rebuild_verlet_list_cabana(
250 [&](std::span<Cell *const> cells, BoxGeometry const &box,
253 std::move(cells), box, verlet_criterion, id_to_index, max_id,
254 [&](const int i, const int j) {
255 // intra cell loop
256 verlet_list.addNeighborLB(i, j);
257 },
258 [&](const int i, const int j) {
259 // inter cell loop
260 verlet_list.addNeighbor(i, j);
261 });
262
263 if (verlet_list.hasOverflow()) {
264 cell_structure.use_verlet_list = false;
266 << "Verlet list overflow detected: neighbor count exceeded "
267 "max_counts. Falling back to the link cell algorithm. "
268 "Configured max is "
269 << Cabana::NeighborList<CellStructure::ListType>::maxNeighbor(
271 }
272 },
273 rebuild_vl);
274#ifdef ESPRESSO_CALIPER
275 CALI_MARK_END("Verlet list creation");
276#endif
277 } else {
278 // ===================================================
279 // Fill particle storage (partial update)
280 // ===================================================
281#ifdef ESPRESSO_CALIPER
282 CALI_MARK_BEGIN("AoSoA commit partial");
283#endif
285 "AoSoA write", std::size_t{0}, n_part,
286 [&unique_particles, &aosoa](int const index) {
287 auto const &p = *unique_particles.at(index);
288 commit_particle(p, index, aosoa, false);
289 });
290 Kokkos::fence();
291#ifdef ESPRESSO_CALIPER
292 CALI_MARK_END("AoSoA commit partial");
293#endif
294 }
295}
296
297#ifdef ESPRESSO_ELECTROSTATICS
298template <class execution_space = Kokkos::DefaultHostExecutionSpace>
301 using policy_type = Kokkos::RangePolicy<execution_space>;
302 auto const &unique_particles = cell_structure.get_unique_particles();
303 auto const n_part = unique_particles.size();
304 auto &aosoa = cell_structure.get_aosoa();
305
307 "Views update charges", std::size_t{0}, n_part,
308 [&unique_particles, &aosoa](std::size_t const index) {
309 aosoa.charge(index) = unique_particles.at(index)->q();
310 });
311}
312#endif
313
314template <class execution_space = Kokkos::DefaultHostExecutionSpace>
316 auto const &angle_bonds_kernel,
317 auto const &dihedral_bonds_kernel,
318 auto const &nonbonded_kernel,
319 CellStructure &cell_structure, double pair_cutoff,
320 double bond_cutoff, auto const &verlet_criterion,
321 auto const integ_switch) {
323
324 if (bond_cutoff >= 0.) {
325#ifdef ESPRESSO_CALIPER
326 CALI_MARK_BEGIN("cabana_bond_loop");
327#endif
328 auto const n_pair_bonds = cell_structure.get_local_pair_bond_numbers();
329 auto const n_angle_bonds = cell_structure.get_local_angle_bond_numbers();
330 auto const n_dihedral_bonds =
331 cell_structure.get_local_dihedral_bond_numbers();
332 if (n_pair_bonds > 0) {
333 Kokkos::parallel_for( // loop over bonds
334 "for_each_local_pair_bonds",
335 Kokkos::RangePolicy<Kokkos::DefaultHostExecutionSpace>(std::size_t{0},
338 Kokkos::fence();
339 }
340 if (n_angle_bonds > 0) {
341 Kokkos::parallel_for( // loop over bonds
342 "for_each_local_angle_bonds",
343 Kokkos::RangePolicy<Kokkos::DefaultHostExecutionSpace>(std::size_t{0},
346 Kokkos::fence();
347 }
348 if (n_dihedral_bonds > 0) {
349 Kokkos::parallel_for( // loop over bonds
350 "for_each_local_dihedral_bonds",
351 Kokkos::RangePolicy<Kokkos::DefaultHostExecutionSpace>(
352 std::size_t{0}, n_dihedral_bonds),
354 Kokkos::fence();
355 }
356#ifdef ESPRESSO_CALIPER
357 CALI_MARK_END("cabana_bond_loop");
358#endif
359 }
360
361 // Cabana short range loop
362 if (pair_cutoff > 0.) {
363#ifdef ESPRESSO_CALIPER
364 CALI_MARK_BEGIN("cabana_pair_loop");
365#endif
366 if (integ_switch != INTEG_METHOD_STEEPEST_DESCENT and
367 cell_structure.use_verlet_list) {
368 auto const &verlet_list = cell_structure.get_verlet_list_cabana();
369 Kokkos::RangePolicy<execution_space> policy(
370 std::size_t{0}, cell_structure.get_unique_particles().size());
371 Cabana::neighbor_parallel_for(policy, nonbonded_kernel, verlet_list,
372 Cabana::FirstNeighborsTag(),
373 Cabana::SerialOpTag());
374 } else {
375 cell_structure.cell_list_loop(
376 [&](std::span<Cell *const> cells, BoxGeometry const &box) {
378 std::move(cells), box, verlet_criterion,
379 cell_structure.get_id_to_index(),
380 cell_structure.get_cached_max_local_particle_id(),
381 [&](const int i, const int j) {
382 // intra cell loop
383 nonbonded_kernel(i, j);
384 },
385 [&](const int i, const int j) {
386 // inter cell loop
387 nonbonded_kernel(i, j);
388 });
389 });
390 }
391 Kokkos::fence();
392#ifdef ESPRESSO_CALIPER
393 CALI_MARK_END("cabana_pair_loop");
394#endif
395 }
396}
@ 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.
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)
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 kokkos_parallel_range_for(auto const &name, auto start, auto end, auto const &kernel)
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 &verlet_criterion, auto const integ_switch)
ESPRESSO_ATTR_ALWAYS_INLINE void update_aosoa_charges(CellStructure &cell_structure)
ESPRESSO_ATTR_ALWAYS_INLINE void update_cabana_state(CellStructure &cell_structure, auto const &verlet_criterion, double const pair_cutoff, auto const integ_switch)
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