ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
AtomDecomposition.cpp
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
23
24#include "cell_system/Cell.hpp"
25
26#include "ghosts/HaloPlan.hpp"
29
30#include <utils/Vector.hpp>
31
32#include <boost/mpi/collectives/all_to_all.hpp>
33
34#include <cassert>
35#include <cstddef>
36#include <limits>
37#include <utility>
38#include <vector>
39
40void AtomDecomposition::configure_neighbors() {
41 std::vector<Cell *> red_neighbors;
42 std::vector<Cell *> black_neighbors;
43
44 /* distribute force calculation work */
45 for (int n = 0; n < m_comm.size(); n++) {
46 if (m_comm.rank() == n) {
47 continue;
48 }
49
50 if (n < m_comm.rank()) {
51 red_neighbors.push_back(&cells.at(n));
52 } else {
53 black_neighbors.push_back(&cells.at(n));
54 }
55 }
56
57 local().m_neighbors = Neighbors<Cell *>(red_neighbors, black_neighbors);
58}
59
60GhostComm::HaloPlan AtomDecomposition::make_halo_plan() {
64
65 HaloPlan plan;
66 plan.comm = m_comm;
67
68 // Single rank: no communication needed; collective section is None.
69 if (m_comm.size() == 1) {
70 plan.collective = CollectiveSection{CollectivePattern::None, {}};
71 return plan;
72 }
73
74 // One cell pointer per rank: cells[root] is the ParticleList for that root.
75 // The engine uses op.direction to pick Broadcast (Push) or ReduceSum
76 // (Reduce) at run time, so we store Broadcast as the canonical marker that
77 // this section is active. run_collective reads op.direction to decide which
78 // MPI collective to invoke.
79 std::vector<ParticleList *> cell_ptrs;
80 cell_ptrs.reserve(static_cast<std::size_t>(m_comm.size()));
81 for (int n = 0; n < m_comm.size(); ++n) {
82 cell_ptrs.push_back(&cells.at(static_cast<std::size_t>(n)).particles());
83 }
84 plan.collective =
85 CollectiveSection{CollectivePattern::Broadcast, std::move(cell_ptrs)};
86 return plan;
87}
88
89void AtomDecomposition::configure_comms() {
90 m_halo_plan = make_halo_plan();
91 // NOTE: validation is deferred to the constructor, AFTER mark_cells() has
92 // populated local_cells()/ghost_cells(). Validating here would check empty
93 // spans (vacuously) since mark_cells() runs later.
94}
95
96void AtomDecomposition::mark_cells() {
97 m_local_cells.resize(1, std::addressof(local()));
98 m_ghost_cells.clear();
99 for (int n = 0; n < m_comm.size(); n++) {
100 if (n != m_comm.rank()) {
101 m_ghost_cells.push_back(std::addressof(cells.at(n)));
102 }
103 }
104}
105
106void AtomDecomposition::resort(bool global_flag,
107 std::vector<ParticleChange> &diff) {
108 for (auto &p : local().particles()) {
109 m_box.fold_position(p.pos(), p.image_box());
110
111 p.pos_at_last_verlet_update() = p.pos();
112 }
113
114 /* Local updates are a NoOp for this decomposition. */
115 if (not global_flag) {
116 return;
117 }
118
119 /* Sort displaced particles by the node they belong to. */
120 std::vector<std::vector<Particle>> send_buf(m_comm.size());
121 for (auto it = local().particles().begin();
122 it != local().particles().end();) {
123 auto const target_node = id_to_rank(it->id());
124 if (target_node != m_comm.rank()) {
125 diff.emplace_back(RemovedParticle{it->id()});
126 send_buf.at(target_node).emplace_back(std::move(*it));
127 it = local().particles().erase(it);
128 } else {
129 ++it;
130 }
131 }
132
133 /* Exchange particles */
134 std::vector<std::vector<Particle>> recv_buf(m_comm.size());
135 boost::mpi::all_to_all(m_comm, send_buf, recv_buf);
136
137 diff.emplace_back(ModifiedList{local().particles()});
138
139 /* Add new particles belonging to this node */
140 for (auto &parts : recv_buf) {
141 for (auto &p : parts) {
142 local().particles().insert(std::move(p));
143 }
144 }
145}
146
148 : m_box(box_geo) {}
149
150AtomDecomposition::AtomDecomposition(boost::mpi::communicator comm,
151 BoxGeometry const &box_geo)
152 : m_comm(std::move(comm)), cells(m_comm.size()), m_box(box_geo) {
153 /* create communicators */
154 configure_comms();
155 /* configure neighbor relations */
156 configure_neighbors();
157 /* fill local and ghost cell lists */
158 mark_cells();
159 /* classify local cells as interior or boundary.
160 *
161 * AtomDecomposition has no spatial locality: the single local cell
162 * interacts with every other rank's cell and there is no subset of
163 * particles whose force contributions are guaranteed to arrive before
164 * the velocity update. Interior is therefore always empty and all local
165 * cells are boundary. mark_boundary_cells() handles the ghost-neighbour
166 * case (multi-rank); the explicit loop below catches the single-rank case
167 * where there are no ghost cells and no neighbours at all.
168 */
172 c->m_is_boundary = true;
173 }
174#ifdef ESPRESSO_ADDITIONAL_CHECKS
175 // Validate now that local_cells()/ghost_cells() are populated by
176 // mark_cells().
181 "AtomDecomposition"));
182 // NOTE: validate_halo_plan_symmetry is NOT called here.
183 // During checkpoint loading, decompositions are transiently rebuilt while
184 // maximal_cutoff is rank-divergent (ranks may have different cell grids for a
185 // brief window before the next consistent rebuild). The transient plan is
186 // never used — it is immediately replaced — so the asymmetry is harmless.
187 // A construction-time collective all_to_all inside a ctor is also dangerous:
188 // if one rank aborts the others block forever in the collective.
189 // Symmetry is instead validated at FIRST USE of the plan in
190 // halo_exchange_start (see GhostComm::halo_exchange_start in
191 // HaloExchange.cpp).
192#endif
193}
194
196 return Utils::Vector3d::broadcast(std::numeric_limits<double>::infinity());
197}
198
Vector implementation and trait types for boost qvm interoperability.
Utils::Vector3d max_range() const override
AtomDecomposition(BoxGeometry const &m_box)
Utils::Vector3d max_cutoff() const override
void resort(bool global_flag, std::vector< ParticleChange > &diff) override
std::span< Cell *const > local_cells() const override
std::span< Cell *const > ghost_cells() const override
void fold_position(Utils::Vector3d &pos, Utils::Vector3i &image_box) const
Fold coordinates to primary simulation box in-place.
Definition Cell.hpp:96
neighbors_type m_neighbors
Definition Cell.hpp:106
auto & particles()
Particles.
Definition Cell.hpp:103
static DEVICE_QUALIFIER constexpr Vector< T, N > broadcast(typename Base::value_type const &value) noexcept
Create a vector that has all entries set to the same value.
Definition Vector.hpp:134
void mark_boundary_cells(std::span< Cell *const > local_cells, std::span< Cell *const > ghost_cells, std::function< bool(Cell const *, Cell const *)> wrap_predicate=nullptr)
Classify each local cell as interior or boundary.
bool report_violations(std::vector< std::string > const &violations, char const *context)
Print violations to stderr and return whether the list was empty.
std::vector< std::string > validate_halo_plan(HaloPlan const &plan, std::span< Cell *const > local_cells, std::span< Cell *const > ghost_cells)
Validate a HaloPlan for correctness.
STL namespace.
boost::mpi::communicator comm
Definition HaloPlan.hpp:60