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-2022 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"
36#include "ghosts.hpp"
37#include "system/Leaf.hpp"
38
39#include <utils/Vector.hpp>
40
41#include <boost/container/static_vector.hpp>
42#include <boost/iterator/indirect_iterator.hpp>
43#include <boost/range/algorithm/transform.hpp>
44
45#include <algorithm>
46#include <cassert>
47#include <concepts>
48#include <functional>
49#include <iterator>
50#include <memory>
51#include <optional>
52#include <set>
53#include <span>
54#include <stdexcept>
55#include <unordered_set>
56#include <utility>
57#include <vector>
58
59#ifdef ESPRESSO_CALIPER
60#include <caliper/cali.h>
61#endif
62
63// forward declarations
64#ifdef ESPRESSO_SHARED_MEMORY_PARALLELISM
65namespace Kokkos {
66template <class DataType, class... Properties> class View;
67class HostSpace;
68struct LayoutRight;
69template <unsigned T> struct MemoryTraits;
70} // namespace Kokkos
71namespace Cabana {
72class HalfNeighborTag;
73struct VerletLayout2D;
74class TeamVectorOpTag;
75template <typename... Types> struct MemberTypes;
76template <class DataType, class MemorySpace, int, class MemoryTraits>
77class AoSoA;
78} // namespace Cabana
79struct KokkosHandle;
80template <class MemorySpace, class ListAlgorithm, class Layout, class BuildTag>
82#endif // ESPRESSO_SHARED_MEMORY_PARALLELISM
83
84template <typename Callable>
85concept ParticleCallback = requires(Callable c, Particle &p) {
86 { c(p) } -> std::same_as<void>;
87};
88
89using ParticleUnaryOp = std::function<void(Particle &)>;
90
91namespace Cells {
92enum Resort : unsigned {
95 RESORT_GLOBAL = 2u
96};
97
98/**
99 * @brief Flags to select particle parts for communication.
100 */
101enum DataPart : unsigned {
102 DATA_PART_NONE = 0u, /**< Nothing */
103 DATA_PART_PROPERTIES = 1u, /**< Particle::p */
104 DATA_PART_POSITION = 2u, /**< Particle::r */
105 DATA_PART_MOMENTUM = 8u, /**< Particle::m */
106 DATA_PART_FORCE = 16u, /**< Particle::f */
107#ifdef ESPRESSO_BOND_CONSTRAINT
108 DATA_PART_RATTLE = 32u, /**< Particle::rattle */
109#endif
110 DATA_PART_BONDS = 64u /**< Particle::bonds */
112} // namespace Cells
113
114/**
115 * @brief Map the data parts flags from cells to those
116 * used internally by the ghost communication.
117 *
118 * @param data_parts data parts flags
119 * @return ghost communication flags
120 */
121unsigned map_data_parts(unsigned data_parts);
122
123namespace Cells {
124inline ParticleRange particles(std::span<Cell *const> cells) {
125 /* Find first non-empty cell */
126 auto first_non_empty = std::ranges::find_if(
127 cells, [](const Cell *c) { return not c->particles().empty(); });
128
129 return {CellParticleIterator(first_non_empty, cells.end()),
130 CellParticleIterator(cells.end())};
131}
132} // namespace Cells
133
134/**
135 * @brief Distance vector and length handed to pair kernels.
136 */
137struct Distance {
139 : vec21(vec21), dist2(vec21.norm2()) {}
140
142 double dist2;
143};
144
145namespace detail {
146// NOLINTNEXTLINE(bugprone-exception-escape)
147struct MinimalImageDistance {
148 BoxGeometry const box;
149
150 Distance operator()(Particle const &p1, Particle const &p2) const {
151 return Distance(box.get_mi_vector(p1.pos(), p2.pos()));
152 }
153};
154
155struct EuclidianDistance {
156 Distance operator()(Particle const &p1, Particle const &p2) const {
157 return Distance(p1.pos() - p2.pos());
158 }
159};
160} // namespace detail
161
162/** Describes a cell structure / cell system. Contains information
163 * about the communication of cell contents (particles, ghosts, ...)
164 * between different nodes and the relation between particle
165 * positions and the cell system. All other properties of the cell
166 * system which are not common between different cell systems have to
167 * be stored in separate structures.
168 */
169class CellStructure : public System::Leaf<CellStructure> {
170#ifdef ESPRESSO_SHARED_MEMORY_PARALLELISM
171public:
172 static constexpr auto vector_length = 1;
173 struct AoSoA_pack;
176 using memory_space = Kokkos::HostSpace;
177 using ListAlgorithm = Cabana::HalfNeighborTag;
178 using ListType =
179 CustomVerletList<Kokkos::HostSpace, ListAlgorithm, Cabana::VerletLayout2D,
180 Cabana::TeamVectorOpTag>;
181#endif // ESPRESSO_SHARED_MEMORY_PARALLELISM
182
183private:
184 /** The local id-to-particle index */
185 std::vector<Particle *> m_particle_index;
186 /** Implementation of the primary particle decomposition */
187 std::unique_ptr<ParticleDecomposition> m_decomposition;
188 /** Active type in m_decomposition */
190 /** One of @ref Cells::Resort, announces the level of resort needed.
191 */
192 unsigned m_resort_particles = Cells::RESORT_NONE;
193 bool m_verlet_skin_set = false;
194 bool m_rebuild_verlet_list = true;
195 bool m_rebuild_verlet_list_cabana = true;
196 std::vector<std::pair<Particle *, Particle *>> m_verlet_list;
197 double m_le_pos_offset_at_last_resort = 0.;
198 /** @brief Verlet list skin. */
199 double m_verlet_skin = 0.;
200 double m_verlet_reuse = 0.;
201#ifdef ESPRESSO_SHARED_MEMORY_PARALLELISM
202 int m_cached_max_local_particle_id = 0;
203 int m_max_id = 0;
204 std::unique_ptr<Kokkos::View<int *>> m_id_to_index;
205 std::unique_ptr<ForceType> m_local_force;
206#ifdef ESPRESSO_ROTATION
207 std::unique_ptr<ForceType> m_local_torque;
208#endif
209#ifdef ESPRESSO_NPT
210 std::unique_ptr<VirialType> m_local_virial;
211#endif
212 std::unique_ptr<ListType> m_verlet_list_cabana;
213 /** particle properties using individual Kokkos Views */
214 std::unique_ptr<AoSoA_pack> m_aosoa;
215 /** The local id-to-index for aosoa data */
216 std::vector<Particle *> m_unique_particles;
217 std::shared_ptr<KokkosHandle> m_kokkos_handle;
218#endif // ESPRESSO_SHARED_MEMORY_PARALLELISM
219
220public:
221 CellStructure(BoxGeometry const &box);
222 virtual ~CellStructure();
223
224 bool use_verlet_list = true;
225
226 /**
227 * @brief Update local particle index.
228 *
229 * Update the entry for a particle in the local particle
230 * index.
231 *
232 * @param id Entry to update.
233 * @param p Pointer to the particle.
234 */
236 assert(id >= 0);
237 // cppcheck-suppress assertWithSideEffect
238 assert(not p or p->id() == id);
239
240 if (static_cast<unsigned int>(id) >= m_particle_index.size())
241 m_particle_index.resize(static_cast<unsigned int>(id + 1));
242
243 m_particle_index[static_cast<unsigned int>(id)] = p;
244 }
245
246 /**
247 * @brief Update local particle index.
248 *
249 * Update the entry for a particle in the local particle
250 * index.
251 *
252 * @param p Pointer to the particle.
253 */
255 update_particle_index(p.id(), std::addressof(p));
256 }
257
258 /**
259 * @brief Update local particle index.
260 *
261 * @param pl List of particles whose index entries should be updated.
262 */
264 for (auto &p : pl) {
265 update_particle_index(p.id(), std::addressof(p));
266 }
267 }
268
269 /**
270 * @brief Clear the particles index.
271 */
272 void clear_particle_index() { m_particle_index.clear(); }
273
274private:
275 /**
276 * @brief Append a particle to a list and update this
277 * particle index accordingly.
278 * @param pl List to add the particle to.
279 * @param p Particle to add.
280 */
281 Particle &append_indexed_particle(ParticleList &pl, Particle &&p) {
282 /* Check if cell may reallocate, in which case the index
283 * entries for all particles in this cell have to be
284 * updated. */
285 auto const may_reallocate = pl.size() >= pl.capacity();
286 auto &new_part = pl.insert(std::move(p));
287
288 if (may_reallocate)
290 else {
291 update_particle_index(new_part);
292 }
293
294 return new_part;
295 }
296
297public:
298 /**
299 * @brief Get a local particle by id.
300 *
301 * @param id Particle to get.
302 * @return Pointer to particle if it is local,
303 * nullptr otherwise.
304 */
306 assert(id >= 0);
307
308 if (static_cast<unsigned int>(id) >= m_particle_index.size())
309 return nullptr;
310
311 return m_particle_index[static_cast<unsigned int>(id)];
312 }
313
314 /** @overload */
315 const Particle *get_local_particle(int id) const {
316 assert(id >= 0);
317
318 if (static_cast<unsigned int>(id) >= m_particle_index.size())
319 return nullptr;
320
321 return m_particle_index[static_cast<unsigned int>(id)];
322 }
323
324 template <class InputRange, class OutputIterator>
325 void get_local_particles(InputRange ids, OutputIterator out) {
326 std::ranges::transform(ids, out,
327 [this](int id) { return get_local_particle(id); });
328 }
329
330 CellStructureType decomposition_type() const { return m_type; }
331
332 /** Maximal cutoff supported by current cell system. */
334
335 /** Maximal pair range supported by current cell system. */
337
339 return Cells::particles(decomposition().local_cells());
340 }
341
343 return Cells::particles(decomposition().ghost_cells());
344 }
345
346 std::size_t count_local_particles() const {
347 std::size_t count = 0;
348 for (auto const &cell : m_decomposition->local_cells()) {
349 count += cell->particles().size();
350 }
351 return count;
352 }
353
354 /** @brief whether to use parallel version of @ref for_each_local_particle */
356#ifdef ESPRESSO_SHARED_MEMORY_PARALLELISM
357 return true;
358#else
359 return false;
360#endif
361 }
362
363 /**
364 * @brief Run a kernel on all local particles.
365 * The kernel is assumed to be thread-safe.
366 */
368 bool parallel = true) const {
369#ifdef ESPRESSO_SHARED_MEMORY_PARALLELISM
370 if (parallel and use_parallel_for_each_local_particle()) {
371 parallel_for_each_particle_impl(decomposition().local_cells(), f);
372 return;
373 }
374#endif
375 for (auto &p : local_particles()) {
376 f(p);
377 }
378 }
379
380 /**
381 * @brief Run a kernel on all ghost particles.
382 * The kernel is assumed to be thread-safe.
383 */
385 for (auto &p : ghost_particles()) {
386 f(p);
387 }
388 }
389
390private:
391 /** Cell system dependent function to find the right cell for a
392 * particle.
393 * \param p Particle.
394 * \return pointer to cell where to put the particle, nullptr
395 * if the particle does not belong on this node.
396 */
397 Cell *particle_to_cell(const Particle &p) {
399 }
400 Cell const *particle_to_cell(const Particle &p) const {
402 }
403
404#ifdef ESPRESSO_SHARED_MEMORY_PARALLELISM
405 void parallel_for_each_particle_impl(std::span<Cell *const> cells,
406 ParticleUnaryOp &f) const;
407#endif
408
409public:
410 /**
411 * @brief Add a particle.
412 *
413 * Moves a particle into the cell system. This adds
414 * a particle to the local node, irrespective of where
415 * it belongs.
416 *
417 * @param p Particle to add.
418 * @return Pointer to the particle in the cell
419 * system.
420 */
422
423 /**
424 * @brief Add a particle.
425 *
426 * Moves a particle into the cell system, if it
427 * belongs to this node. Otherwise this does not
428 * have an effect and the particle is discarded.
429 * This can be used to add a particle without
430 * knowledge where it should be placed by calling
431 * the function on all nodes, it will then add
432 * the particle in exactly one place.
433 *
434 * @param p Particle to add.
435 * @return Pointer to particle if it is local, null
436 * otherwise.
437 */
439
440 /**
441 * @brief Remove a particle.
442 *
443 * Removes a particle and all bonds pointing
444 * to it. This is a collective call.
445 *
446 * @param id Id of particle to remove.
447 */
448 void remove_particle(int id);
449
450 /**
451 * @brief Get the maximal particle id on this node.
452 *
453 * This returns the highest particle id on
454 * this node, or -1 if there are no particles on this node.
455 */
456 int get_max_local_particle_id() const;
457#ifdef ESPRESSO_SHARED_MEMORY_PARALLELISM
459 return m_cached_max_local_particle_id;
460 }
461#endif
462
463 /**
464 * @brief Remove all particles from the cell system.
465 *
466 * This allows linear time removal of all particles from
467 * the system, removing each particle individually would
468 * be quadratic.
469 */
471
472 /**
473 * @brief Get the underlying particle decomposition.
474 *
475 * Should be used solely for informative purposes.
476 *
477 * @return The active particle decomposition.
478 */
480 return assert(m_decomposition), *m_decomposition;
481 }
482
483private:
485 return assert(m_decomposition), *m_decomposition;
486 }
487
488public:
489 /**
490 * @brief Increase the local resort level at least to @p level.
491 */
493 m_resort_particles |= level;
494 assert(m_resort_particles >= level);
495 }
496
497 /**
498 * @brief Get the currently scheduled resort level.
499 */
500 unsigned get_resort_particles() const { return m_resort_particles; }
501
502 /**
503 * @brief Set the resort level to sorted.
504 */
505 void clear_resort_particles() { m_resort_particles = Cells::RESORT_NONE; }
506
507 /**
508 * @brief Check whether a particle has moved further than half the skin
509 * since the last Verlet list update, thus requiring a resort.
510 * @param additional_offset Offset which is added to the distance the
511 * particle has travelled when comparing to half
512 * the Verlet skin (e.g., for Lees-Edwards BC).
513 * @return Whether a resort is needed.
514 */
515 bool
516 check_resort_required(Utils::Vector3d const &additional_offset = {}) const;
517
519 return m_le_pos_offset_at_last_resort;
520 }
521
522 /**
523 * @brief Synchronize number of ghosts.
524 */
525 void ghosts_count();
526
527 /**
528 * @brief Update ghost particles.
529 *
530 * Update ghost particles with data from the real particles.
531 *
532 * @param data_parts Particle parts to update, combination of @ref
533 * Cells::DataPart
534 */
535 void ghosts_update(unsigned data_parts);
536
537 /**
538 * @brief Update ghost particles, with particle resort if needed.
539 *
540 * Update ghost particles with data from the real particles.
541 * Resort particles if a resort is due.
542 *
543 * @param data_parts Particle parts to update, combination of @ref
544 * Cells::DataPart
545 */
546 void update_ghosts_and_resort_particle(unsigned data_parts);
547
548 /**
549 * @brief Add forces from ghost particles to real particles.
550 */
552
553#ifdef ESPRESSO_BOND_CONSTRAINT
554 /**
555 * @brief Add rattle corrections from ghost particles to real particles.
556 */
558#endif
559
560 /**
561 * @brief Resort particles.
562 */
563 void resort_particles(bool global_flag);
564
565 /** @brief Whether the Verlet skin is set. */
566 auto is_verlet_skin_set() const { return m_verlet_skin_set; }
567
568 /** @brief Get the Verlet skin. */
569 auto get_verlet_skin() const { return m_verlet_skin; }
570
571 /** @brief Set the Verlet skin. */
572 void set_verlet_skin(double value);
573
574 /** @brief Set the Verlet skin using a heuristic. */
576
577 void update_verlet_stats(int n_steps, int n_verlet_updates) {
578 if (n_verlet_updates > 0) {
579 m_verlet_reuse = n_steps / static_cast<double>(n_verlet_updates);
580 } else {
581 m_verlet_reuse = 0.;
582 }
583 }
584
585 /** @brief Average number of integration steps the Verlet list was re-used */
586 auto get_verlet_reuse() const { return m_verlet_reuse; }
587
588 /**
589 * @brief Resolve ids to particles.
590 *
591 * @throws BondResolutionError if one of the ids
592 * was not found.
593 *
594 * @param partner_ids Ids to resolve.
595 * @return Vector of Particle pointers.
596 */
597 auto resolve_bond_partners(std::span<const int> partner_ids) {
598 boost::container::static_vector<Particle *, 4> partners;
599 get_local_particles(partner_ids, std::back_inserter(partners));
600
601 /* Check if id resolution failed for any partner */
602 if (std::ranges::find(partners, nullptr) != partners.end()) {
603 throw BondResolutionError{};
604 }
605
606 return partners;
607 }
608
609private:
610 /**
611 * @brief Execute kernel for every bond on particle.
612 * @tparam Handler Callable, which can be invoked with
613 * (Particle, int, std::span<Particle *>),
614 * returning a bool.
615 * @param p Particles for whom the bonds are evaluated.
616 * @param handler is called for every bond, and handed
617 * p, the bond id and a span with the bond
618 * partners as arguments. Its return value
619 * should indicate if the bond was broken.
620 */
621 template <class Handler>
622 void execute_bond_handler(Particle &p, Handler handler) {
623 for (const BondView bond : p.bonds()) {
624 auto const partner_ids = bond.partner_ids();
625
626 try {
627 auto partners = resolve_bond_partners(partner_ids);
628 auto const partners_span = std::span(partners.data(), partners.size());
629 auto const bond_broken = handler(p, bond.bond_id(), partners_span);
630 if (bond_broken) {
631 bond_broken_error(p.id(), partner_ids);
632 }
633 } catch (const BondResolutionError &) {
634 bond_broken_error(p.id(), partner_ids);
635 }
636 }
637 }
638
639 /**
640 * @brief Go through ghost cells and remove the ghost entries from the
641 * local particle index.
642 */
643 void invalidate_ghosts() {
644 for (auto const &p : ghost_particles()) {
645 if (get_local_particle(p.id()) == &p) {
646 update_particle_index(p.id(), nullptr);
647 }
648 }
649 }
650
651 /** @brief Set the particle decomposition, keeping the particles. */
652 void set_particle_decomposition(
653 std::unique_ptr<ParticleDecomposition> &&decomposition) {
655
656 /* Swap in new cell system */
657 std::swap(m_decomposition, decomposition);
658
659 /* Add particles to new system */
660 for (auto &p : Cells::particles(decomposition->local_cells())) {
661 add_particle(std::move(p));
662 }
663 }
664
665public:
666 /**
667 * @brief Set the particle decomposition to @ref AtomDecomposition.
668 */
670
671 /**
672 * @brief Set the particle decomposition to @ref RegularDecomposition.
673 *
674 * @param range Interaction range.
675 * @param fully_connected_boundary neighbor cell directions for Lees-Edwards.
676 */
678 double range,
679 std::optional<std::pair<int, int>> fully_connected_boundary);
680
681 /**
682 * @brief Set the particle decomposition to @ref HybridDecomposition.
683 *
684 * @param cutoff_regular Interaction cutoff_regular.
685 * @param n_square_types Particle types to put into n_square decomposition.
686 */
687 void set_hybrid_decomposition(double cutoff_regular,
688 std::set<int> n_square_types);
689
690private:
691 /**
692 * @brief Run link_cell algorithm for local cells.
693 *
694 * @tparam Kernel Needs to be callable with (Particle, Particle, Distance).
695 * @param kernel Pair kernel functor.
696 */
697 template <class Kernel> void link_cell(Kernel kernel) {
698 auto const maybe_box = decomposition().minimum_image_distance();
699 auto const local_cells_span = decomposition().local_cells();
700 auto const first = boost::make_indirect_iterator(local_cells_span.begin());
701 auto const last = boost::make_indirect_iterator(local_cells_span.end());
702
703 if (maybe_box) {
705 first, last,
706 [&kernel, df = detail::MinimalImageDistance{decomposition().box()}](
707 Particle &p1, Particle &p2) { kernel(p1, p2, df(p1, p2)); });
708 } else {
709 if (decomposition().box().type() != BoxType::CUBOID) {
710 throw std::runtime_error("Non-cuboid box type is not compatible with a "
711 "particle decomposition that relies on "
712 "EuclideanDistance for distance calculation.");
713 }
715 first, last,
716 [&kernel, df = detail::EuclidianDistance{}](
717 Particle &p1, Particle &p2) { kernel(p1, p2, df(p1, p2)); });
718 }
719 }
720
721#ifdef ESPRESSO_SHARED_MEMORY_PARALLELISM
722public:
723 auto get_max_id() const { return m_max_id; }
724
725 void set_kokkos_handle(std::shared_ptr<KokkosHandle> handle);
726 void rebuild_local_properties(double pair_cutoff);
728 void reset_local_force();
729
730 auto &get_id_to_index() { return *m_id_to_index; }
731 auto &get_local_force() { return *m_local_force; }
732#ifdef ESPRESSO_ROTATION
733 auto &get_local_torque() { return *m_local_torque; }
734#endif
735#ifdef ESPRESSO_NPT
736 auto &get_local_virial() { return *m_local_virial; }
737#endif
738 auto &get_aosoa() { return *m_aosoa; }
739 auto const &get_unique_particles() const { return m_unique_particles; }
740 auto const &get_verlet_list_cabana() const { return *m_verlet_list_cabana; }
742
743 [[nodiscard]] auto is_verlet_list_cabana_rebuild_needed() const {
744 return m_rebuild_verlet_list_cabana;
745 }
746
747 /**
748 * @brief Reset local properties of the Verlet list.
749 * @param cutoff Pair interaction cutoff.
750 * @return True if a rebuild is needed.
751 */
752 [[nodiscard]] auto prepare_verlet_list_cabana(double cutoff) {
753 auto const rebuild = is_verlet_list_cabana_rebuild_needed();
754 if (rebuild) {
755 // If we have to rebuild, we need to count the particles
756 set_index_map(); // parallelized index_map
757 // Create essential variables for MD
759 } else {
760 // If we do not rebuild we can use the saved map
762 }
763 return rebuild;
764 }
765
766 void rebuild_verlet_list_cabana(auto &&kernel, bool rebuild_verlet_list) {
768 if (rebuild_verlet_list) {
769 kernel(m_decomposition->local_cells(), m_decomposition->box(),
770 *m_verlet_list_cabana);
771 }
772 m_rebuild_verlet_list_cabana = false;
773 }
774
775 void set_index_map();
776
777 inline void cell_list_loop(auto &&kernel) {
778 kernel(m_decomposition->local_cells(), m_decomposition->box());
779 }
780#endif
781
782private:
783 /** Non-bonded pair loop with verlet lists.
784 *
785 * @param pair_kernel Kernel to apply
786 * @param verlet_criterion Filter for verlet lists.
787 */
788 template <class PairKernel, class VerletCriterion>
789 void verlet_list_loop(PairKernel pair_kernel,
790 const VerletCriterion &verlet_criterion) {
791 /* In this case the verlet list update is attached to
792 * the pair kernel, and the verlet list is rebuilt as
793 * we go. */
794 if (m_rebuild_verlet_list) {
795 m_verlet_list.clear();
796
797 link_cell([&](Particle &p1, Particle &p2, Distance const &d) {
798 if (verlet_criterion(p1, p2, d)) {
799 m_verlet_list.emplace_back(&p1, &p2);
800 pair_kernel(p1, p2, d);
801 }
802 });
803
804 m_rebuild_verlet_list = false;
805 m_rebuild_verlet_list_cabana = true;
806 } else {
807 auto const maybe_box = decomposition().minimum_image_distance();
808 /* In this case the pair kernel is just run over the verlet list. */
809 if (maybe_box) {
810 auto const distance_function =
811 detail::MinimalImageDistance{decomposition().box()};
812 for (auto const &[p1, p2] : m_verlet_list) {
813 pair_kernel(*p1, *p2, distance_function(*p1, *p2));
814 }
815 } else {
816 auto const distance_function = detail::EuclidianDistance{};
817 for (auto const &[p1, p2] : m_verlet_list) {
818 pair_kernel(*p1, *p2, distance_function(*p1, *p2));
819 }
820 }
821 }
822 }
823
824public:
825 /** Bonded pair loop.
826 * @param bond_kernel Kernel to apply
827 */
828 template <class BondKernel> void bond_loop(BondKernel const &bond_kernel) {
829 for (auto &p : local_particles()) {
830 execute_bond_handler(p, bond_kernel);
831 }
832 }
833
834 /** Non-bonded pair loop.
835 * @param pair_kernel Kernel to apply
836 */
837 template <class PairKernel> void non_bonded_loop(PairKernel pair_kernel) {
838 link_cell(pair_kernel);
839 }
840
841 /** Non-bonded pair loop with potential use
842 * of verlet lists.
843 * @param pair_kernel Kernel to apply
844 * @param verlet_criterion Filter for verlet lists.
845 */
846 template <class PairKernel, class VerletCriterion>
847 void non_bonded_loop(PairKernel pair_kernel,
848 const VerletCriterion &verlet_criterion) {
849 if (use_verlet_list) {
850 verlet_list_loop(pair_kernel, verlet_criterion);
851 } else {
852 /* No verlet lists, just run the kernel with pairs from the cells. */
853 link_cell(pair_kernel);
854 }
855 }
856
857 /**
858 * @brief Check that particle index is commensurate with particles.
859 *
860 * For each local particles is checked that has a correct entry
861 * in the particles index, and that there are no excess (non-existing)
862 * particles in the index.
863 */
864 void check_particle_index() const;
865
866 /**
867 * @brief Check that particles are in the correct cell.
868 *
869 * This checks for all local particles that the result
870 * of particles_to_cell is the cell the particles is
871 * actually in, e.g. that the particles are sorted according
872 * to particles_to_cell.
873 */
874 void check_particle_sorting() const;
875
876public:
877 /**
878 * @brief Find cell a particle is stored in.
879 *
880 * For local particles, this returns the cell they
881 * are stored in, otherwise nullptr is returned.
882 *
883 * @param p Particle to find cell for
884 * @return Cell for particle or nullptr.
885 */
887 assert(not get_resort_particles());
888
889 if (p.is_ghost()) {
890 return nullptr;
891 }
892
893 return particle_to_cell(p);
894 }
895
896 /**
897 * @brief Run kernel on all particles inside local cell and its neighbors.
898 *
899 * @param p Particle to find cell for
900 * @param kernel Function with signature <tt>double(Particle const&,
901 * Particle const&, Utils::Vector3d const&)</tt>
902 * @return false if cell is not found, otherwise true
903 */
904 template <class Kernel>
906 Kernel &kernel) {
907 auto const cell = find_current_cell(p);
908
909 if (cell == nullptr) {
910 return false;
911 }
912
913 auto const maybe_box = decomposition().minimum_image_distance();
914
915 if (maybe_box) {
916 auto const distance_function =
917 detail::MinimalImageDistance{decomposition().box()};
918 short_range_neighbor_loop(p, cell, kernel, distance_function);
919 } else {
920 auto const distance_function = detail::EuclidianDistance{};
921 short_range_neighbor_loop(p, cell, kernel, distance_function);
922 }
923 return true;
924 }
925
926private:
927 template <class Kernel, class DistanceFunc>
928 void short_range_neighbor_loop(Particle const &p1, Cell *const cell,
929 Kernel &kernel, DistanceFunc const &df) {
930 /* Iterate over particles inside cell */
931 for (auto const &p2 : cell->particles()) {
932 if (p1.id() != p2.id()) {
933 auto const vec = df(p1, p2).vec21;
934 kernel(p1, p2, vec);
935 }
936 }
937 /* Iterate over all neighbors */
938 for (auto const neighbor : cell->neighbors().all()) {
939 /* Iterate over particles in neighbors */
940 if (neighbor != cell) {
941 for (auto const &p2 : neighbor->particles()) {
942 auto const vec = df(p1, p2).vec21;
943 kernel(p1, p2, vec);
944 }
945 }
946 }
947 }
948};
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.
std::function< void(Particle &)> ParticleUnaryOp
Vector implementation and trait types for boost qvm interoperability.
void bond_broken_error(int id, std::span< const int > partner_ids)
Immutable view on a bond.
Definition BondList.hpp:44
BoxType type() const
ESPRESSO_ATTR_ALWAYS_INLINE Utils::Vector< T, 3 > get_mi_vector(const Utils::Vector< T, 3 > &a, const Utils::Vector< T, 3 > &b) const
Get the minimum-image vector between two coordinates.
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 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
virtual ~CellStructure()
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 clear_particle_index()
Clear the particles index.
auto prepare_verlet_list_cabana(double cutoff)
Reset local properties of the Verlet list.
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
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)
auto & get_local_torque()
auto & get_local_virial()
void update_particle_index(int id, Particle *p)
Update local particle index.
void ghosts_reduce_forces()
Add forces 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)
void for_each_local_particle(ParticleUnaryOp &&f, bool parallel=true) const
Run a kernel on all local particles.
void non_bonded_loop(PairKernel pair_kernel)
Non-bonded pair loop.
void for_each_ghost_particle(ParticleUnaryOp &&f) const
Run a kernel on all ghost particles.
Utils::Vector3d max_range() const
Maximal pair range supported by current cell system.
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 resort_particles(bool global_flag)
Resort particles.
void check_particle_index() const
Check that particle index is commensurate with particles.
Cabana::HalfNeighborTag ListAlgorithm
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
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 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_FORCE
Particle::f.
@ DATA_PART_PROPERTIES
Particle::p.
@ DATA_PART_BONDS
Particle::bonds.
@ DATA_PART_NONE
Nothing.
@ DATA_PART_RATTLE
Particle::rattle.
@ 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)
Struct holding all information for one particle.
Definition Particle.hpp:450
bool is_ghost() const
Definition Particle.hpp:495
auto const & pos() const
Definition Particle.hpp:486
auto const & id() const
Definition Particle.hpp:469