ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
CellStructure.hpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2010-2026 The ESPResSo project
3 * Copyright (C) 2002,2003,2004,2005,2006,2007,2008,2009,2010
4 * Max-Planck-Institute for Polymer Research, Theory Group
5 *
6 * This file is part of ESPResSo.
7 *
8 * ESPResSo is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * ESPResSo is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22#pragma once
23
25
26#include "BoxGeometry.hpp"
27#include "LocalBox.hpp"
28#include "Particle.hpp"
29#include "ParticleList.hpp"
30#include "ParticleRange.hpp"
32#include "bond_error.hpp"
33#include "cell_system/Cell.hpp"
35#include "config/config.hpp"
37#include "ghosts.hpp"
39#include "system/Leaf.hpp"
40
41#include <utils/Vector.hpp>
42
43#include <boost/container/static_vector.hpp>
44#include <boost/iterator/indirect_iterator.hpp>
45#include <boost/range/algorithm/transform.hpp>
46
47#include <Cabana_Core.hpp>
48#include <Cabana_NeighborList.hpp>
49#include <Kokkos_Core.hpp>
50#include <Kokkos_ScatterView.hpp>
51
52#include <algorithm>
53#include <cassert>
54#include <concepts>
55#include <cstddef>
56#include <iterator>
57#include <memory>
58#include <optional>
59#include <set>
60#include <span>
61#include <stdexcept>
62#include <unordered_set>
63#include <utility>
64#include <vector>
65
66#ifdef ESPRESSO_CALIPER
67#include <caliper/cali.h>
68#endif
69
70// forward declarations
71struct KokkosHandle;
72struct LocalBondState;
73
74template <typename Callable>
75concept ParticleCallback = requires(Callable c, Particle &p) {
76 { c(p) } -> std::same_as<void>;
77};
78
79namespace Cells {
80enum Resort : unsigned {
83 RESORT_GLOBAL = 2u
84};
85
86/**
87 * @brief Flags to select particle parts for communication.
88 */
89enum DataPart : unsigned {
90 DATA_PART_NONE = 0u, /**< Nothing */
91 DATA_PART_PROPERTIES = 1u, /**< Particle::p */
92 DATA_PART_POSITION = 2u, /**< Particle::r */
93 DATA_PART_MOMENTUM = 8u, /**< Particle::m */
94 DATA_PART_FORCE = 16u, /**< Particle::f */
95#ifdef ESPRESSO_BOND_CONSTRAINT
96 DATA_PART_RATTLE = 32u, /**< Particle::rattle */
97#endif
98 DATA_PART_BONDS = 64u, /**< Particle::bonds */
99#ifdef ESPRESSO_ROTATION
100 DATA_PART_QUAT = 128u, /**< orientation quaternion (pushed with position) */
101 DATA_PART_TORQUE = 256u, /**< torque (reduced with force) */
102#endif
103#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
104 DATA_PART_DIPFLD = 512u, /**< Particle::dip_fld */
105#endif
106};
107} // namespace Cells
108
109/**
110 * @brief Map the data parts flags from cells to those
111 * used internally by the ghost communication.
112 *
113 * @param data_parts data parts flags
114 * @return ghost communication flags
115 */
116unsigned map_data_parts(unsigned data_parts);
117
118namespace Cells {
119inline ParticleRange particles(std::span<Cell *const> cells) {
120 /* Find first non-empty cell */
121 auto first_non_empty = std::ranges::find_if(
122 cells, [](const Cell *c) { return not c->particles().empty(); });
123
124 return {CellParticleIterator(first_non_empty, cells.end()),
125 CellParticleIterator(cells.end())};
126}
127} // namespace Cells
128
129/**
130 * @brief Distance vector and length handed to pair kernels.
131 */
132struct Distance {
134 : vec21(vec21), dist2(vec21.norm2()) {}
135
137 double dist2;
138};
139
140namespace detail {
141// NOLINTNEXTLINE(bugprone-exception-escape)
142struct MinimalImageDistance {
143 BoxGeometry const box;
144
145 Distance operator()(Particle const &p1, Particle const &p2) const {
146 return Distance(box.get_mi_vector(p1.pos(), p2.pos()));
147 }
148};
149
150struct EuclidianDistance {
151 Distance operator()(Particle const &p1, Particle const &p2) const {
152 return Distance(p1.pos() - p2.pos());
153 }
154};
155} // namespace detail
156
157/** Describes a cell structure / cell system. Contains information
158 * about the communication of cell contents (particles, ghosts, ...)
159 * between different nodes and the relation between particle
160 * positions and the cell system. All other properties of the cell
161 * system which are not common between different cell systems have to
162 * be stored in separate structures.
163 */
164class CellStructure : public System::Leaf<CellStructure> {
165public:
166 static constexpr auto vector_length = 1;
167 using memory_space = Kokkos::HostSpace;
168 using execution_space = Kokkos::DefaultHostExecutionSpace;
169 struct AoSoA_pack;
170 using ForceType =
171 Kokkos::View<double *[3], Kokkos::LayoutRight, memory_space>;
172 using VirialType = Kokkos::View<double[3], Kokkos::LayoutRight, memory_space>;
174 Kokkos::Experimental::ScatterView<double *[3], Kokkos::LayoutRight,
177 Kokkos::Experimental::ScatterView<double[3], Kokkos::LayoutRight,
179 using ListAlgorithm = Cabana::HalfNeighborTag;
180 using ListType =
181 CustomVerletList<memory_space, ListAlgorithm, Cabana::VerletLayout2D,
182 Cabana::TeamVectorOpTag>;
183
184private:
185 /** The local id-to-particle index */
186 std::vector<Particle *> m_particle_index;
187 /** Implementation of the primary particle decomposition */
188 std::unique_ptr<ParticleDecomposition> m_decomposition;
189 /** Active type in m_decomposition */
191 /** One of @ref Cells::Resort, announces the level of resort needed.
192 */
193 unsigned m_resort_particles = Cells::RESORT_NONE;
194 bool m_verlet_skin_set = false;
195 bool m_rebuild_verlet_list = true;
196 bool m_rebuild_verlet_list_cabana = true;
197 std::vector<std::pair<Particle *, Particle *>> m_verlet_list;
198 double m_le_pos_offset_at_last_resort = 0.;
199 /** @brief Verlet list skin. */
200 double m_verlet_skin = 0.;
201 double m_verlet_reuse = 0.;
202 int m_cached_max_local_particle_id = 0;
203 std::size_t m_num_local_particles_cached = 0;
204 int m_max_id = 0;
205 std::unique_ptr<Kokkos::View<int *, memory_space>> m_id_to_index;
206 std::unique_ptr<ForceType> m_local_force;
207 std::optional<ScatterForce> m_scatter_force;
208#ifdef ESPRESSO_ROTATION
209 std::unique_ptr<ForceType> m_local_torque;
210 std::optional<ScatterForce> m_scatter_torque;
211 /**
212 * @brief True if a kernel may have written to @c m_scatter_torque since
213 * the last reset. Cleared by the resets; when false, the torque buffers
214 * are known to be all-zero and their O(n_threads * N) zeroing and
215 * reduction can be skipped.
216 */
217 bool m_torque_replicas_dirty = false;
218#endif
219#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
220 std::unique_ptr<ForceType> m_local_dip_fld;
221 std::optional<ScatterForce> m_scatter_dip_fld;
222 /** @brief Same contract as @c m_torque_replicas_dirty, for dipole fields. */
223 bool m_dip_fld_replicas_dirty = false;
224#endif
225#ifdef ESPRESSO_NPT
226 std::unique_ptr<VirialType> m_local_virial;
227 std::optional<ScatterVirial> m_scatter_virial;
228 /** @brief Same contract as @c m_torque_replicas_dirty, for the virial. */
229 bool m_virial_replicas_dirty = false;
230#endif
231 std::unique_ptr<LocalBondState> m_bond_state;
232 std::unique_ptr<ListType> m_verlet_list_cabana;
233 /**
234 * @brief Persistent per-neighbor buffer pool for ghost exchanges.
235 *
236 * Reused across calls to @ref ghosts_count / @ref ghosts_update /
237 * @ref ghosts_reduce_forces so that, after the first (warm-up) exchange,
238 * the underlying @c std::vector storage is retained and no per-step heap
239 * allocation occurs on the hot ghost-communication path.
240 *
241 * Mutable because the ghost methods are logically const with respect to
242 * particle data but mutate this scratch storage.
243 */
244 mutable GhostComm::ExchangeBuffers m_ghost_buffers;
245 /**
246 * @brief Scratch cell-pointer list for filtered particle iteration.
247 *
248 * Used by @c for_each_interior_particle and @c for_each_boundary_particle
249 * to hold the filtered subset of local cells. Declared mutable so that the
250 * const-qualified iteration helpers can write to it; not thread-safe — the
251 * two filtered passes must not run concurrently (they never do: interior pass
252 * completes before the ghost-reduce finish, which precedes the boundary
253 * pass).
254 */
255 mutable std::vector<Cell *> m_filtered_cells_scratch;
256 /**
257 * @brief In-flight ghost force reduction started by
258 * @ref ghosts_reduce_forces_start.
259 *
260 * When set, @ref ghosts_reduce_forces_finish must be called before any
261 * resort or decomposition change. Stored as optional so that the
262 * unfinished state is detectable at runtime.
263 */
264 mutable std::optional<GhostComm::GhostExchange> m_pending_ghost_reduce;
265 /** particle properties using individual Kokkos Views */
266 std::unique_ptr<AoSoA_pack> m_aosoa;
267 std::vector<Particle *> m_unique_particles;
268 std::shared_ptr<KokkosHandle> m_kokkos_handle;
269
270public:
271 CellStructure(BoxGeometry const &box);
272 virtual ~CellStructure();
273
274 bool use_verlet_list = true;
275
276 /**
277 * @brief Update local particle index.
278 *
279 * Update the entry for a particle in the local particle
280 * index.
281 *
282 * @param id Entry to update.
283 * @param p Pointer to the particle.
284 */
286 assert(id >= 0);
287 // cppcheck-suppress assertWithSideEffect
288 assert(not p or p->id() == id);
289
290 if (static_cast<unsigned int>(id) >= m_particle_index.size())
291 m_particle_index.resize(static_cast<unsigned int>(id + 1));
292
293 m_particle_index[static_cast<unsigned int>(id)] = p;
294 }
295
296 /**
297 * @brief Update local particle index.
298 *
299 * Update the entry for a particle in the local particle
300 * index.
301 *
302 * @param p Pointer to the particle.
303 */
305 update_particle_index(p.id(), std::addressof(p));
306 }
307
308 /**
309 * @brief Update local particle index.
310 *
311 * @param pl List of particles whose index entries should be updated.
312 */
314 for (auto &p : pl) {
315 update_particle_index(p.id(), std::addressof(p));
316 }
317 }
318
319 /**
320 * @brief Clear the particles index.
321 */
322 void clear_particle_index() { m_particle_index.clear(); }
323
324private:
325 /**
326 * @brief Append a particle to a list and update this
327 * particle index accordingly.
328 * @param pl List to add the particle to.
329 * @param p Particle to add.
330 */
331 Particle &append_indexed_particle(ParticleList &pl, Particle &&p) {
332 /* Check if cell may reallocate, in which case the index
333 * entries for all particles in this cell have to be
334 * updated. */
335 auto const may_reallocate = pl.size() >= pl.capacity();
336 auto &new_part = pl.insert(std::move(p));
337
338 if (may_reallocate)
340 else {
341 update_particle_index(new_part);
342 }
343
344 return new_part;
345 }
346
347public:
348 /**
349 * @brief Get a local particle by id.
350 *
351 * @param id Particle to get.
352 * @return Pointer to particle if it is local,
353 * nullptr otherwise.
354 */
356 assert(id >= 0);
357
358 if (static_cast<unsigned int>(id) >= m_particle_index.size())
359 return nullptr;
360
361 return m_particle_index[static_cast<unsigned int>(id)];
362 }
363
364 /** @overload */
365 const Particle *get_local_particle(int id) const {
366 assert(id >= 0);
367
368 if (static_cast<unsigned int>(id) >= m_particle_index.size())
369 return nullptr;
370
371 return m_particle_index[static_cast<unsigned int>(id)];
372 }
373
374 template <class InputRange, class OutputIterator>
375 void get_local_particles(InputRange ids, OutputIterator out) {
376 std::ranges::transform(ids, out,
377 [this](int id) { return get_local_particle(id); });
378 }
379
380 CellStructureType decomposition_type() const { return m_type; }
381
382 /** Maximal cutoff supported by current cell system. */
384
385 /** Maximal pair range supported by current cell system. */
387
389 return Cells::particles(decomposition().local_cells());
390 }
391
393 return Cells::particles(decomposition().ghost_cells());
394 }
395
396 std::size_t count_local_particles() const {
397 std::size_t count = 0;
398 for (auto const &cell : m_decomposition->local_cells()) {
399 count += cell->particles().size();
400 }
401 return count;
402 }
403
404 /** @brief whether to use parallel version of @ref for_each_local_particle */
405 bool use_parallel_for_each_local_particle() const { return true; }
406
407 /**
408 * @brief Run a kernel on all local particles.
409 * The kernel is assumed to be thread-safe.
410 */
412 bool parallel = true) const {
413 if (parallel and use_parallel_for_each_local_particle()) {
414 parallel_for_each_particle_impl(decomposition().local_cells(), f);
415 return;
416 }
417 for (auto &p : local_particles()) {
418 f(p);
419 }
420 }
421
422 /**
423 * @brief Run a kernel on interior (non-boundary) local particles only.
424 *
425 * A cell is interior iff none of its neighbors is a ghost cell
426 * (@ref Cell::is_boundary returns false). This filtered variant uses the
427 * same Kokkos parallelization as @ref for_each_local_particle — it builds
428 * a filtered cell list and delegates to @c parallel_for_each_particle_impl
429 * so that thread-level behavior is identical to the unfiltered path.
430 *
431 * The kernel is assumed to be thread-safe.
432 */
434 auto const all_cells = decomposition().local_cells();
435 // Build a filtered list of interior cells (those that are not boundary).
436 m_filtered_cells_scratch.clear();
437 for (auto *c : all_cells) {
438 if (not c->is_boundary())
439 m_filtered_cells_scratch.push_back(c);
440 }
441 if (m_filtered_cells_scratch.empty())
442 return;
443 std::span<Cell *const> span{m_filtered_cells_scratch};
445 parallel_for_each_particle_impl(span, f);
446 } else {
447 for (auto *c : span)
448 for (auto &p : c->particles())
449 f(p);
450 }
451 }
452
453 /**
454 * @brief Run a kernel on boundary local particles only.
455 *
456 * Complement of @c for_each_interior_particle: visits particles in cells
457 * where @ref Cell::is_boundary returns true. Uses the same Kokkos
458 * parallelization structure as @ref for_each_local_particle.
459 *
460 * The kernel is assumed to be thread-safe.
461 */
463 auto const all_cells = decomposition().local_cells();
464 // Build a filtered list of boundary cells.
465 m_filtered_cells_scratch.clear();
466 for (auto *c : all_cells) {
467 if (c->is_boundary())
468 m_filtered_cells_scratch.push_back(c);
469 }
470 if (m_filtered_cells_scratch.empty())
471 return;
472 std::span<Cell *const> span{m_filtered_cells_scratch};
474 parallel_for_each_particle_impl(span, f);
475 } else {
476 for (auto *c : span)
477 for (auto &p : c->particles())
478 f(p);
479 }
480 }
481
482 /**
483 * @brief Run a kernel on all ghost particles.
484 * The kernel is assumed to be thread-safe.
485 */
487 for (auto &p : ghost_particles()) {
488 f(p);
489 }
490 }
491
492private:
493 /** Cell system dependent function to find the right cell for a
494 * particle.
495 * \param p Particle.
496 * \return pointer to cell where to put the particle, nullptr
497 * if the particle does not belong on this node.
498 */
499 Cell *particle_to_cell(const Particle &p) {
501 }
502 Cell const *particle_to_cell(const Particle &p) const {
504 }
505
506 inline void parallel_for_each_particle_impl(std::span<Cell *const> cells,
507 ParticleCallback auto &&f) const;
508
509public:
510 /**
511 * @brief Add a particle.
512 *
513 * Moves a particle into the cell system. This adds
514 * a particle to the local node, irrespective of where
515 * it belongs.
516 *
517 * @param p Particle to add.
518 * @return Pointer to the particle in the cell
519 * system.
520 */
522
523 /**
524 * @brief Add a particle.
525 *
526 * Moves a particle into the cell system, if it
527 * belongs to this node. Otherwise this does not
528 * have an effect and the particle is discarded.
529 * This can be used to add a particle without
530 * knowledge where it should be placed by calling
531 * the function on all nodes, it will then add
532 * the particle in exactly one place.
533 *
534 * @param p Particle to add.
535 * @return Pointer to particle if it is local, null
536 * otherwise.
537 */
539
540 /**
541 * @brief Remove a particle.
542 *
543 * Removes a particle and all bonds pointing
544 * to it. This is a collective call.
545 *
546 * @param id Id of particle to remove.
547 */
548 void remove_particle(int id);
549
550 /**
551 * @brief Get the maximal particle id on this node.
552 *
553 * This returns the highest particle id on
554 * this node, or -1 if there are no particles on this node.
555 */
556 int get_max_local_particle_id() const;
558 return m_cached_max_local_particle_id;
559 }
560 std::size_t get_num_local_particles_cached() const {
561 return m_num_local_particles_cached;
562 }
563 int get_local_pair_bond_numbers() const;
566 void set_local_bond_numbers(int p, int a, int d);
567#ifdef ESPRESSO_COLLISION_DETECTION
568 void clear_new_bonds();
569 void add_new_bond(int bond_id, std::vector<int> const &particle_ids);
570 void rebuild_bond_list();
571#endif // ESPRESSO_COLLISION_DETECTION
572
573 /**
574 * @brief Remove all particles from the cell system.
575 *
576 * This allows linear time removal of all particles from
577 * the system, removing each particle individually would
578 * be quadratic.
579 */
581
582 /**
583 * @brief Get the underlying particle decomposition.
584 *
585 * Should be used solely for informative purposes.
586 *
587 * @return The active particle decomposition.
588 */
590 return assert(m_decomposition), *m_decomposition;
591 }
592
593private:
595 return assert(m_decomposition), *m_decomposition;
596 }
597
598public:
599 /**
600 * @brief Increase the local resort level at least to @p level.
601 */
603 m_resort_particles |= level;
604 assert(m_resort_particles >= level);
605 }
606
607 /**
608 * @brief Get the currently scheduled resort level.
609 */
610 unsigned get_resort_particles() const { return m_resort_particles; }
611
612 /**
613 * @brief Set the resort level to sorted.
614 */
615 void clear_resort_particles() { m_resort_particles = Cells::RESORT_NONE; }
616
617 /**
618 * @brief Check whether a particle has moved further than half the skin
619 * since the last Verlet list update, thus requiring a resort.
620 * @param additional_offset Offset which is added to the distance the
621 * particle has travelled when comparing to half
622 * the Verlet skin (e.g., for Lees-Edwards BC).
623 * @return Whether a resort is needed.
624 */
625 bool
626 check_resort_required(Utils::Vector3d const &additional_offset = {}) const;
627
629 return m_le_pos_offset_at_last_resort;
630 }
631
632 /**
633 * @brief Synchronize number of ghosts.
634 */
635 void ghosts_count();
636
637 /**
638 * @brief Update ghost particles.
639 *
640 * Update ghost particles with data from the real particles.
641 *
642 * @param data_parts Particle parts to update, combination of @ref
643 * Cells::DataPart
644 */
645 void ghosts_update(unsigned data_parts);
646
647 /**
648 * @brief Update ghost particles, with particle resort if needed.
649 *
650 * Update ghost particles with data from the real particles.
651 * Resort particles if a resort is due.
652 *
653 * @param data_parts Particle parts to update, combination of @ref
654 * Cells::DataPart
655 */
656 void update_ghosts_and_resort_particle(unsigned data_parts);
657
658 /**
659 * @brief Add forces and torques from ghost particles to real particles.
660 */
662
663 /**
664 * @brief Begin the split-phase ghost force reduction (non-blocking).
665 *
666 * Posts all MPI sends/receives for the force reduction and returns
667 * immediately. The caller must later call @ref ghosts_reduce_forces_finish.
668 * Asserts that no reduction is already in flight.
669 *
670 * Only meaningful when @c comm_cart.size() > 1. At one rank the blocking
671 * @ref ghosts_reduce_forces must be used instead (the reduction is then a
672 * pure local copy with nothing to hide).
673 */
675
676 /**
677 * @brief Complete the split-phase ghost force reduction.
678 *
679 * Waits for all outstanding MPI requests and unpacks/reduces force data
680 * into real particles. Clears the pending state afterwards.
681 *
682 * Must be called exactly once after @ref ghosts_reduce_forces_start.
683 */
685
686 /** @brief True when a split-phase force reduction is in flight. */
688 return m_pending_ghost_reduce.has_value();
689 }
690
691#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
692 /** Add dipole fields from ghost particles to real particles. */
694
695 /** Set dipole fields on all ghosts to zero. */
697 for_each_ghost_particle([](Particle &p) { p.dip_fld() = {}; });
698 }
699#endif
700
701 /** Set forces and torques on all ghosts to zero. */
704 }
705
706#ifdef ESPRESSO_BOND_CONSTRAINT
707 /**
708 * @brief Add rattle corrections from ghost particles to real particles.
709 */
711#endif
712
713 /**
714 * @brief Resort particles.
715 */
716 void resort_particles(bool global_flag);
717
718 /** @brief Whether the Verlet skin is set. */
719 auto is_verlet_skin_set() const { return m_verlet_skin_set; }
720
721 /** @brief Get the Verlet skin. */
722 auto get_verlet_skin() const { return m_verlet_skin; }
723
724 /** @brief Set the Verlet skin. */
725 void set_verlet_skin(double value);
726
727 /** @brief Set the Verlet skin using a heuristic. */
729
730 void update_verlet_stats(int n_steps, int n_verlet_updates) {
731 if (n_verlet_updates > 0) {
732 m_verlet_reuse = n_steps / static_cast<double>(n_verlet_updates);
733 } else {
734 m_verlet_reuse = 0.;
735 }
736 }
737
738 /** @brief Average number of integration steps the Verlet list was re-used */
739 auto get_verlet_reuse() const { return m_verlet_reuse; }
740
741 /**
742 * @brief Resolve ids to particles.
743 *
744 * @throws BondResolutionError if one of the ids
745 * was not found.
746 *
747 * @param partner_ids Ids to resolve.
748 * @return Vector of Particle pointers.
749 */
750 auto resolve_bond_partners(std::span<const int> partner_ids) {
751 boost::container::static_vector<Particle *, 4> partners;
752 get_local_particles(partner_ids, std::back_inserter(partners));
753
754 /* Check if id resolution failed for any partner */
755 if (std::ranges::find(partners, nullptr) != partners.end()) {
756 throw BondResolutionError{};
757 }
758
759 return partners;
760 }
761
762private:
763 /**
764 * @brief Execute kernel for every bond on particle.
765 * @tparam Handler Callable, which can be invoked with
766 * (Particle, int, std::span<Particle *>),
767 * returning a bool.
768 * @param p Particles for whom the bonds are evaluated.
769 * @param handler is called for every bond, and handed
770 * p, the bond id and a span with the bond
771 * partners as arguments. Its return value
772 * should indicate if the bond was broken.
773 */
774 void execute_bond_handler(Particle &p, auto const &handler) {
775 for (const BondView bond : p.bonds()) {
776 auto const partner_ids = bond.partner_ids();
777 try {
778 auto partners = resolve_bond_partners(partner_ids);
779 auto const partners_span = std::span(partners.data(), partners.size());
780 auto const bond_broken = handler(p, bond.bond_id(), partners_span);
781 if (bond_broken) {
782 bond_broken_error(p.id(), partner_ids);
783 }
784 } catch (BondResolutionError const &) {
785 bond_resolution_error(partner_ids);
786 }
787 }
788 }
789
790 /**
791 * @brief Go through ghost cells and remove the ghost entries from the
792 * local particle index.
793 */
794 void invalidate_ghosts() {
795 for (auto const &p : ghost_particles()) {
796 if (get_local_particle(p.id()) == &p) {
797 update_particle_index(p.id(), nullptr);
798 }
799 }
800 }
801
802 /** @brief Set the particle decomposition, keeping the particles. */
803 void set_particle_decomposition(
804 std::unique_ptr<ParticleDecomposition> &&decomposition) {
805 assert(not m_pending_ghost_reduce.has_value() &&
806 "set_particle_decomposition: ghost force reduction is still in "
807 "flight — call ghosts_reduce_forces_finish() first");
809
810 /* Swap in new cell system */
811 std::swap(m_decomposition, decomposition);
812
813 /* Add particles to new system */
814 for (auto &p : Cells::particles(decomposition->local_cells())) {
815 add_particle(std::move(p));
816 }
817 }
818
819public:
820 /**
821 * @brief Set the particle decomposition to @ref AtomDecomposition.
822 */
824
825 /**
826 * @brief Set the particle decomposition to @ref RegularDecomposition.
827 *
828 * @param range Interaction range.
829 * @param fully_connected_boundary neighbor cell directions for Lees-Edwards.
830 */
832 double range,
833 std::optional<std::pair<int, int>> fully_connected_boundary);
834
835 /**
836 * @brief Set the particle decomposition to @ref HybridDecomposition.
837 *
838 * @param cutoff_regular Interaction cutoff_regular.
839 * @param n_square_types Particle types to put into n_square decomposition.
840 */
841 void set_hybrid_decomposition(double cutoff_regular,
842 std::set<int> n_square_types);
843
844private:
845 /**
846 * @brief Run link_cell algorithm for local cells.
847 *
848 * @tparam Kernel Needs to be callable with (Particle, Particle, Distance).
849 * @param kernel Pair kernel functor.
850 */
851 void link_cell(auto kernel) {
852 auto const maybe_box = decomposition().minimum_image_distance();
853 auto const local_cells_span = decomposition().local_cells();
854 auto const first = boost::make_indirect_iterator(local_cells_span.begin());
855 auto const last = boost::make_indirect_iterator(local_cells_span.end());
856
857 if (maybe_box) {
859 first, last,
860 [&kernel, df = detail::MinimalImageDistance{decomposition().box()}](
861 Particle &p1, Particle &p2) { kernel(p1, p2, df(p1, p2)); });
862 } else {
863 if (decomposition().box().type() != BoxType::CUBOID) {
864 throw std::runtime_error("Non-cuboid box type is not compatible with a "
865 "particle decomposition that relies on "
866 "EuclideanDistance for distance calculation.");
867 }
869 first, last,
870 [&kernel, df = detail::EuclidianDistance{}](
871 Particle &p1, Particle &p2) { kernel(p1, p2, df(p1, p2)); });
872 }
873 }
874
875public:
876 auto get_max_id() const { return m_max_id; }
877
878 void set_kokkos_handle(std::shared_ptr<KokkosHandle> handle);
879 void rebuild_local_properties(double pair_cutoff);
881 /** @brief Zero the local force view and its scatter replicas. */
883
884 auto &get_id_to_index() { return *m_id_to_index; }
885 auto &get_local_force() { return *m_local_force; }
886 auto get_scatter_force() { return *m_scatter_force; }
887#ifdef ESPRESSO_ROTATION
888 auto &get_local_torque() { return *m_local_torque; }
889 auto get_scatter_torque() { return *m_scatter_torque; }
890 /** @brief Declare that a kernel scattering into the torque view is about
891 * to run. Must be called before the torque replicas are reduced via the
892 * force loop dispatch.
893 */
894 void mark_torque_replicas_dirty() { m_torque_replicas_dirty = true; }
895 auto torque_replicas_dirty() const { return m_torque_replicas_dirty; }
896#endif
897#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
898 auto &get_local_dip_fld() { return *m_local_dip_fld; }
899 auto get_scatter_dip_fld() { return *m_scatter_dip_fld; }
900 void mark_dip_fld_replicas_dirty() { m_dip_fld_replicas_dirty = true; }
901 auto dip_fld_replicas_dirty() const { return m_dip_fld_replicas_dirty; }
902#endif
903#ifdef ESPRESSO_NPT
904 auto &get_local_virial() { return *m_local_virial; }
905 auto get_scatter_virial() { return *m_scatter_virial; }
906 void mark_virial_replicas_dirty() { m_virial_replicas_dirty = true; }
907 auto virial_replicas_dirty() const { return m_virial_replicas_dirty; }
908#endif
909
910 auto &get_aosoa() { return *m_aosoa; }
911 auto const &get_aosoa() const { return *m_aosoa; }
912 auto const &get_unique_particles() const { return m_unique_particles; }
913 auto const &get_verlet_list_cabana() const { return *m_verlet_list_cabana; }
914 auto &bond_state() { return *m_bond_state; }
915 auto const &bond_state() const { return *m_bond_state; }
918
919 [[nodiscard]] auto is_verlet_list_cabana_rebuild_needed() const {
920 return m_rebuild_verlet_list_cabana;
921 }
922
923 /**
924 * @brief Update bond storage(m_*_bond_list_kokkos and m_*_bond_id_kokkos).
925 * @param pair_count Index for pair bond storage.
926 * @param angle_count Index for angle bond storage.
927 * @param dihedral_count Index for dihedral bond storage.
928 * @param p Particle pointer.
929 */
930 void update_bond_storage(int &pair_count, int &angle_count,
931 int &dihedral_count, Particle const &p);
932
933 /**
934 * @brief Reset local properties of the Verlet list.
935 * @param cutoff Pair interaction cutoff.
936 * @return True if a rebuild is needed.
937 */
938 [[nodiscard]] auto prepare_verlet_list_cabana(double cutoff) {
939 auto const rebuild = is_verlet_list_cabana_rebuild_needed();
940 if (rebuild) {
941 // If we have to rebuild, we need to count the particles
942 set_index_map(); // parallelized index_map
943 // Create essential variables for MD
945 } else {
946 // If we do not rebuild we can use the saved map
948 }
949 return rebuild;
950 }
951
952 void rebuild_verlet_list_cabana(auto &&kernel, bool rebuild_verlet_list) {
954 if (rebuild_verlet_list) {
955 kernel(m_decomposition->local_cells(), m_decomposition->box(),
956 *m_verlet_list_cabana);
957 }
958 m_rebuild_verlet_list_cabana = false;
959 }
960
961 void set_index_map();
962
963 inline void cell_list_loop(auto &&kernel) {
964 kernel(m_decomposition->local_cells(), m_decomposition->box());
965 }
966
967private:
968 /** @brief Zero the torque buffers iff a kernel scattered into them since
969 * the last reset (no-op otherwise, and without the ROTATION feature).
970 */
971 void reset_torque_replicas_if_dirty();
972 /** @brief Same contract for the dipolar fields buffers. */
973 void reset_dip_fld_replicas_if_dirty();
974 /** @brief Same contract for the virial buffers (NPT feature). */
975 void reset_virial_replicas_if_dirty();
976
977 /** Non-bonded pair loop with verlet lists.
978 *
979 * @param pair_kernel Kernel to apply
980 * @param verlet_criterion Filter for verlet lists.
981 */
982 template <class PairKernel, class VerletCriterion>
983 void verlet_list_loop(PairKernel pair_kernel,
984 const VerletCriterion &verlet_criterion) {
985 /* In this case the verlet list update is attached to
986 * the pair kernel, and the verlet list is rebuilt as
987 * we go. */
988 if (m_rebuild_verlet_list) {
989 m_verlet_list.clear();
990
991 link_cell([&](Particle &p1, Particle &p2, Distance const &d) {
992 if (verlet_criterion(p1, p2, d.dist2)) {
993 m_verlet_list.emplace_back(&p1, &p2);
994 pair_kernel(p1, p2, d);
995 }
996 });
997
998 m_rebuild_verlet_list = false;
999 m_rebuild_verlet_list_cabana = true;
1000 } else {
1001 auto const maybe_box = decomposition().minimum_image_distance();
1002 /* In this case the pair kernel is just run over the verlet list. */
1003 if (maybe_box) {
1004 auto const distance_function =
1005 detail::MinimalImageDistance{decomposition().box()};
1006 for (auto const &[p1, p2] : m_verlet_list) {
1007 pair_kernel(*p1, *p2, distance_function(*p1, *p2));
1008 }
1009 } else {
1010 auto const distance_function = detail::EuclidianDistance{};
1011 for (auto const &[p1, p2] : m_verlet_list) {
1012 pair_kernel(*p1, *p2, distance_function(*p1, *p2));
1013 }
1014 }
1015 }
1016 }
1017
1018public:
1019 /** Bonded pair loop.
1020 * @param bond_kernel Kernel to apply
1021 */
1022 template <class BondKernel> void bond_loop(BondKernel const &bond_kernel) {
1023 for (auto &p : local_particles()) {
1024 execute_bond_handler(p, bond_kernel);
1025 }
1026 }
1027
1028 /** Non-bonded pair loop.
1029 * @param pair_kernel Kernel to apply
1030 */
1031 template <class PairKernel> void non_bonded_loop(PairKernel pair_kernel) {
1032 link_cell(pair_kernel);
1033 }
1034
1035 /** Non-bonded pair loop with potential use
1036 * of verlet lists.
1037 * @param pair_kernel Kernel to apply
1038 * @param verlet_criterion Filter for verlet lists.
1039 */
1040 template <class PairKernel, class VerletCriterion>
1041 void non_bonded_loop(PairKernel pair_kernel,
1042 const VerletCriterion &verlet_criterion) {
1043 if (use_verlet_list) {
1044 verlet_list_loop(pair_kernel, verlet_criterion);
1045 } else {
1046 /* No verlet lists, just run the kernel with pairs from the cells. */
1047 link_cell(pair_kernel);
1048 }
1049 }
1050
1051 /**
1052 * @brief Check that particle index is commensurate with particles.
1053 *
1054 * For each local particles is checked that has a correct entry
1055 * in the particles index, and that there are no excess (non-existing)
1056 * particles in the index.
1057 */
1058 void check_particle_index() const;
1059
1060 /**
1061 * @brief Check that particles are in the correct cell.
1062 *
1063 * This checks for all local particles that the result
1064 * of particles_to_cell is the cell the particles is
1065 * actually in, e.g. that the particles are sorted according
1066 * to particles_to_cell.
1067 */
1068 void check_particle_sorting() const;
1069
1070public:
1071 /**
1072 * @brief Find cell a particle is stored in.
1073 *
1074 * For local particles, this returns the cell they
1075 * are stored in, otherwise nullptr is returned.
1076 *
1077 * @param p Particle to find cell for
1078 * @return Cell for particle or nullptr.
1079 */
1081 assert(not get_resort_particles());
1082
1083 if (p.is_ghost()) {
1084 return nullptr;
1085 }
1086
1087 return particle_to_cell(p);
1088 }
1089
1090 /**
1091 * @brief Run kernel on all particles inside local cell and its neighbors.
1092 *
1093 * @param p Particle to find cell for
1094 * @param kernel Function with signature <tt>double(Particle const&,
1095 * Particle const&, Utils::Vector3d const&)</tt>
1096 * @return false if cell is not found, otherwise true
1097 */
1098 template <class Kernel>
1100 Kernel &kernel) {
1101 auto const cell = find_current_cell(p);
1102
1103 if (cell == nullptr) {
1104 return false;
1105 }
1106
1107 auto const maybe_box = decomposition().minimum_image_distance();
1108
1109 if (maybe_box) {
1110 auto const distance_function =
1111 detail::MinimalImageDistance{decomposition().box()};
1112 short_range_neighbor_loop(p, cell, kernel, distance_function);
1113 } else {
1114 auto const distance_function = detail::EuclidianDistance{};
1115 short_range_neighbor_loop(p, cell, kernel, distance_function);
1116 }
1117 return true;
1118 }
1119
1120private:
1121 template <class Kernel, class DistanceFunc>
1122 void short_range_neighbor_loop(Particle const &p1, Cell *const cell,
1123 Kernel &kernel, DistanceFunc const &df) {
1124 /* Iterate over particles inside cell */
1125 for (auto const &p2 : cell->particles()) {
1126 if (p1.id() != p2.id()) {
1127 auto const vec = df(p1, p2).vec21;
1128 kernel(p1, p2, vec);
1129 }
1130 }
1131 /* Iterate over all neighbors */
1132 for (auto const neighbor : cell->neighbors().all()) {
1133 /* Iterate over particles in neighbors */
1134 if (neighbor != cell) {
1135 for (auto const &p2 : neighbor->particles()) {
1136 auto const vec = df(p1, p2).vec21;
1137 kernel(p1, p2, vec);
1138 }
1139 }
1140 }
1141 }
1142};
ParticleIterator< std::span< Cell *const >::iterator > CellParticleIterator
CellStructureType
Cell structure topology.
@ NSQUARE
Atom decomposition (N-square).
unsigned map_data_parts(unsigned data_parts)
Map the data parts flags from cells to those used internally by the ghost communication.
Asynchronous, split-phase ghost-communication engine.
Vector implementation and trait types for boost qvm interoperability.
void bond_broken_error(int id, std::span< const int > partner_ids)
void bond_resolution_error(std::span< const int > partner_ids)
Immutable view on a bond.
Definition BondList.hpp:44
ESPRESSO_ATTR_ALWAYS_INLINE Utils::Vector3< T > get_mi_vector(Utils::Vector3< T > const &a, Utils::Vector3< T > const &b) const noexcept
Get the minimum-image vector between two coordinates.
BoxType type() const
Describes a cell structure / cell system.
ParticleRange ghost_particles() const
auto & get_local_force()
Particle * get_local_particle(int id)
Get a local particle by id.
void for_each_local_particle(ParticleCallback auto &&f, bool parallel=true) const
Run a kernel on all local particles.
Kokkos::Experimental::ScatterView< double *[3], Kokkos::LayoutRight, memory_space > ScatterForce
void for_each_interior_particle(ParticleCallback auto &&f) const
Run a kernel on interior (non-boundary) local particles only.
void set_kokkos_handle(std::shared_ptr< KokkosHandle > handle)
void update_particle_index(ParticleList &pl)
Update local particle index.
void check_particle_sorting() const
Check that particles are in the correct cell.
void rebuild_verlet_list_cabana(auto &&kernel, bool rebuild_verlet_list)
auto & get_id_to_index()
std::size_t count_local_particles() const
auto get_scatter_virial()
auto const & bond_state() const
virtual ~CellStructure()
int get_local_angle_bond_numbers() const
void clear_resort_particles()
Set the resort level to sorted.
Cell * find_current_cell(const Particle &p)
Find cell a particle is stored in.
auto get_max_id() const
auto is_verlet_skin_set() const
Whether the Verlet skin is set.
void clear_local_properties()
ParticleDecomposition const & decomposition() const
Get the underlying particle decomposition.
void reset_local_force_buffers()
Zero the local force view and its scatter replicas.
int get_local_pair_bond_numbers() const
void ghosts_reduce_forces_start()
Begin the split-phase ghost force reduction (non-blocking).
void clear_particle_index()
Clear the particles index.
Kokkos::Experimental::ScatterView< double[3], Kokkos::LayoutRight, memory_space > ScatterVirial
auto prepare_verlet_list_cabana(double cutoff)
Reset local properties of the Verlet list.
void ghosts_reset_dipole_fields()
Set dipole fields on all ghosts to zero.
static constexpr auto vector_length
void update_ghosts_and_resort_particle(unsigned data_parts)
Update ghost particles, with particle resort if needed.
Particle * add_local_particle(Particle &&p)
Add a particle.
void set_verlet_skin_heuristic()
Set the Verlet skin using a heuristic.
void set_verlet_skin(double value)
Set the Verlet skin.
void ghosts_update(unsigned data_parts)
Update ghost particles.
auto get_le_pos_offset_at_last_resort() const
int get_local_dihedral_bond_numbers() const
Kokkos::HostSpace memory_space
int get_cached_max_local_particle_id() const
void get_local_particles(InputRange ids, OutputIterator out)
void update_verlet_stats(int n_steps, int n_verlet_updates)
Kokkos::View< double *[3], Kokkos::LayoutRight, memory_space > ForceType
auto & get_local_torque()
void ghosts_reset_forces()
Set forces and torques on all ghosts to zero.
Kokkos::View< double[3], Kokkos::LayoutRight, memory_space > VirialType
auto const & get_aosoa() const
auto & get_local_virial()
void update_particle_index(int id, Particle *p)
Update local particle index.
void set_local_bond_numbers(int p, int a, int d)
void mark_virial_replicas_dirty()
void ghosts_reduce_forces()
Add forces and torques from ghost particles to real particles.
auto const & get_unique_particles() const
unsigned get_resort_particles() const
Get the currently scheduled resort level.
auto get_verlet_reuse() const
Average number of integration steps the Verlet list was re-used.
void rebuild_local_properties(double pair_cutoff)
auto dip_fld_replicas_dirty() const
void non_bonded_loop(PairKernel pair_kernel)
Non-bonded pair loop.
bool has_pending_ghost_reduce() const
True when a split-phase force reduction is in flight.
auto get_scatter_dip_fld()
auto virial_replicas_dirty() const
Utils::Vector3d max_range() const
Maximal pair range supported by current cell system.
void add_new_bond(int bond_id, std::vector< int > const &particle_ids)
bool check_resort_required(Utils::Vector3d const &additional_offset={}) const
Check whether a particle has moved further than half the skin since the last Verlet list update,...
const Particle * get_local_particle(int id) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
auto resolve_bond_partners(std::span< const int > partner_ids)
Resolve ids to particles.
void bond_loop(BondKernel const &bond_kernel)
Bonded pair loop.
void ghosts_count()
Synchronize number of ghosts.
void set_resort_particles(Cells::Resort level)
Increase the local resort level at least to level.
void cell_list_loop(auto &&kernel)
void remove_particle(int id)
Remove a particle.
Particle * add_particle(Particle &&p)
Add a particle.
void for_each_boundary_particle(ParticleCallback auto &&f) const
Run a kernel on boundary local particles only.
auto torque_replicas_dirty() const
std::size_t get_num_local_particles_cached() const
Kokkos::DefaultHostExecutionSpace execution_space
void resort_particles(bool global_flag)
Resort particles.
void check_particle_index() const
Check that particle index is commensurate with particles.
void ghosts_reduce_forces_finish()
Complete the split-phase ghost force reduction.
void mark_dip_fld_replicas_dirty()
void ghosts_reduce_dipole_field()
Add dipole fields from ghost particles to real particles.
Cabana::HalfNeighborTag ListAlgorithm
auto get_scatter_torque()
auto get_verlet_skin() const
Get the Verlet skin.
void set_regular_decomposition(double range, std::optional< std::pair< int, int > > fully_connected_boundary)
Set the particle decomposition to RegularDecomposition.
void set_atom_decomposition()
Set the particle decomposition to AtomDecomposition.
auto const & get_verlet_list_cabana() const
bool run_on_particle_short_range_neighbors(Particle const &p, Kernel &kernel)
Run kernel on all particles inside local cell and its neighbors.
bool use_parallel_for_each_local_particle() const
whether to use parallel version of for_each_local_particle
auto & get_local_dip_fld()
void mark_torque_replicas_dirty()
Declare that a kernel scattering into the torque view is about to run.
void remove_all_particles()
Remove all particles from the cell system.
ParticleRange local_particles() const
void update_particle_index(Particle &p)
Update local particle index.
void ghosts_reduce_rattle_correction()
Add rattle corrections from ghost particles to real particles.
CellStructureType decomposition_type() const
auto is_verlet_list_cabana_rebuild_needed() const
void set_hybrid_decomposition(double cutoff_regular, std::set< int > n_square_types)
Set the particle decomposition to HybridDecomposition.
int get_max_local_particle_id() const
Get the maximal particle id on this node.
Utils::Vector3d max_cutoff() const
Maximal cutoff supported by current cell system.
void update_bond_storage(int &pair_count, int &angle_count, int &dihedral_count, Particle const &p)
Update bond storage(m_*_bond_list_kokkos and m_*_bond_id_kokkos).
void for_each_ghost_particle(ParticleCallback auto &&f) const
Run a kernel on all ghost particles.
void clear_bond_properties()
void non_bonded_loop(PairKernel pair_kernel, const VerletCriterion &verlet_criterion)
Non-bonded pair loop with potential use of verlet lists.
void reset_local_properties()
Definition Cell.hpp:96
auto & particles()
Particles.
Definition Cell.hpp:103
A distributed particle decomposition.
virtual Utils::Vector3d max_cutoff() const =0
Maximum supported cutoff.
virtual std::span< Cell *const > local_cells() const =0
Get pointer to local cells.
virtual Cell * particle_to_cell(Particle const &p)=0
Determine which cell a particle id belongs to.
virtual Utils::Vector3d max_range() const =0
Range in which calculations are performed.
virtual std::optional< BoxGeometry > minimum_image_distance() const =0
Return the box geometry needed for distance calculation if minimum image convention should be used ne...
virtual BoxGeometry const & box() const =0
A range of particles.
Abstract class that represents a component of the system.
std::size_t capacity() const
Capacity of the container.
Definition Bag.hpp:104
T & insert(T const &v)
Insert an element into the container.
Definition Bag.hpp:147
std::size_t size() const
Number of elements in the container.
Definition Bag.hpp:90
Returns true if the particles are to be considered for short range interactions.
Ghost particles and particle exchange.
void link_cell(CellIterator first, CellIterator last, PairKernel &&pair_kernel)
Iterates over all particles in the cell range, and over all pairs within the cells and with their nei...
Definition link_cell.hpp:32
DataPart
Flags to select particle parts for communication.
@ DATA_PART_MOMENTUM
Particle::m.
@ DATA_PART_DIPFLD
Particle::dip_fld.
@ DATA_PART_FORCE
Particle::f.
@ DATA_PART_TORQUE
torque (reduced with force)
@ DATA_PART_PROPERTIES
Particle::p.
@ DATA_PART_BONDS
Particle::bonds.
@ DATA_PART_NONE
Nothing.
@ DATA_PART_RATTLE
Particle::rattle.
@ DATA_PART_QUAT
orientation quaternion (pushed with position)
@ DATA_PART_POSITION
Particle::r.
ParticleRange particles(std::span< Cell *const > cells)
auto constexpr new_part
Exception indicating that a particle id could not be resolved.
Distance vector and length handed to pair kernels.
Utils::Vector3d vec21
Distance(Utils::Vector3d const &vec21)
Persistent per-neighbor buffer pool for halo exchanges.
Struct holding all information for one particle.
Definition Particle.hpp:436
constexpr auto const & dip_fld() const
Definition Particle.hpp:590
constexpr auto const & pos() const
Definition Particle.hpp:476
constexpr auto const & id() const
Definition Particle.hpp:455
constexpr auto const & force_and_torque() const
Definition Particle.hpp:482
constexpr bool is_ghost() const
Definition Particle.hpp:485