ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
HybridDecomposition.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"
26
27#include "BoxGeometry.hpp"
28#include "LocalBox.hpp"
29#include "ParticleList.hpp"
30#include "ghosts.hpp"
34
35#include <utils/Vector.hpp>
37
38#include <boost/mpi/collectives/reduce.hpp>
39#include <boost/mpi/communicator.hpp>
40
41#include <algorithm>
42#include <cassert>
43#include <cstddef>
44#include <functional>
45#include <iterator>
46#include <optional>
47#include <set>
48#include <utility>
49
50HybridDecomposition::HybridDecomposition(boost::mpi::communicator comm,
51 double cutoff_regular, double skin,
52 std::function<bool()> get_ghost_flags,
53 BoxGeometry const &box_geo,
54 LocalBox const &local_box,
55 std::set<int> n_square_types)
56 : m_comm(std::move(comm)), m_box(box_geo), m_cutoff_regular(cutoff_regular),
57 m_regular_decomposition(RegularDecomposition(
58 m_comm, cutoff_regular + skin, m_box, local_box, std::nullopt)),
59 m_n_square(AtomDecomposition(m_comm, m_box)),
60 m_n_square_types(std::move(n_square_types)),
61 m_get_global_ghost_flags(std::move(get_ghost_flags)) {
62
63 /* Vector containing cells of both child decompositions */
64 m_local_cells = m_regular_decomposition.get_local_cells();
65 auto local_cells_n_square = m_n_square.get_local_cells();
66 std::ranges::copy(local_cells_n_square, std::back_inserter(m_local_cells));
67
68 /* Vector containing ghost cells of both child decompositions */
69 m_ghost_cells = m_regular_decomposition.get_ghost_cells();
70 auto ghost_cells_n_square = m_n_square.get_ghost_cells();
71 std::ranges::copy(ghost_cells_n_square, std::back_inserter(m_ghost_cells));
72
73 /* coupling between the child decompositions via neighborship relation */
74 std::vector<Cell *> additional_reds = m_n_square.get_local_cells();
75 std::ranges::copy(ghost_cells_n_square, std::back_inserter(additional_reds));
76 for (auto &local_cell : m_regular_decomposition.local_cells()) {
77 std::vector<Cell *> red_neighbors(local_cell->m_neighbors.red().begin(),
78 local_cell->m_neighbors.red().end());
79 std::vector<Cell *> black_neighbors(local_cell->m_neighbors.black().begin(),
80 local_cell->m_neighbors.black().end());
81 std::ranges::copy(additional_reds, std::back_inserter(red_neighbors));
82 local_cell->m_neighbors = Neighbors<Cell *>(red_neighbors, black_neighbors);
83 }
84
85 m_halo_plan = make_halo_plan();
86
87 /* classify local cells as interior or boundary.
88 *
89 * HybridDecomposition combines a RegularDecomposition (which already has
90 * its own wrap-aware classification) with an AtomDecomposition n-square
91 * child. The combined cell set extends every regular local cell's
92 * neighbor list with the n-square cells, so regular cells that were
93 * interior under RegularDecomposition alone may now interact with
94 * n-square ghost cells. Determining which cells remain genuinely
95 * interior after the coupling is non-trivial; we conservatively mark all
96 * local cells as boundary. The compute/comm overlap degenerates
97 * gracefully to a no-op interior pass — identical to today's blocking
98 * path.
99 */
103 c->m_is_boundary = true;
104 }
105#ifdef ESPRESSO_ADDITIONAL_CHECKS
110 "HybridDecomposition"));
111 // NOTE: validate_halo_plan_symmetry is NOT called here.
112 // During checkpoint loading, decompositions are transiently rebuilt while
113 // maximal_cutoff is rank-divergent (ranks may have different cell grids for a
114 // brief window before the next consistent rebuild). The transient plan is
115 // never used — it is immediately replaced — so the asymmetry is harmless.
116 // A construction-time collective all_to_all inside a ctor is also dangerous:
117 // if one rank aborts the others block forever in the collective.
118 // Symmetry is instead validated at FIRST USE of the plan in
119 // halo_exchange_start (see GhostComm::halo_exchange_start in
120 // HaloExchange.cpp).
121#endif
122}
123
124GhostComm::HaloPlan HybridDecomposition::make_halo_plan() {
125 // Build the combined plan: p2p neighbors and local copies come from the
126 // regular child; the collective section comes from the n-square child.
128 plan.comm = m_comm; // use the world comm for HybridDecomposition's plan
129
130 auto const *regular_plan = m_regular_decomposition.halo_plan();
131 if (regular_plan) {
132 plan.neighbors = regular_plan->neighbors;
133 plan.local = regular_plan->local;
134 }
135
136 // Overlay the collective section from the n-square child.
137 auto const *nsq_plan = m_n_square.halo_plan();
138 if (nsq_plan && nsq_plan->collective) {
139 plan.collective = nsq_plan->collective;
140 }
141
142 return plan;
143}
144
146 std::vector<ParticleChange> &diff) {
147 ParticleList displaced_parts;
148
149 /* Check for n_square type particles in regular decomposition */
150 for (auto &cell_rd : m_regular_decomposition.local_cells()) {
151 for (auto it = cell_rd->particles().begin();
152 it != cell_rd->particles().end();) {
153 /* Particle is in the right decomposition, i.e. has no n_square type */
154 if (not is_n_square_type(it->type())) {
155 std::advance(it, 1);
156 continue;
157 }
158
159 /* else remove from current cell ... */
160 auto p = std::move(*it);
161 it = cell_rd->particles().erase(it);
162 diff.emplace_back(ModifiedList{cell_rd->particles()});
163 diff.emplace_back(RemovedParticle{p.id()});
164
165 /* ... and insert into a n_square cell */
166 auto const first_local_cell = m_n_square.get_local_cells()[0];
167 first_local_cell->particles().insert(std::move(p));
168 diff.emplace_back(ModifiedList{first_local_cell->particles()});
169 }
170
171 /* Now check for regular decomposition type particles in n_square */
172 for (auto &cell_ns : m_n_square.local_cells()) {
173 for (auto it = cell_ns->particles().begin();
174 it != cell_ns->particles().end();) {
175 /* Particle is of n_square type */
176 if (is_n_square_type(it->type())) {
177 std::advance(it, 1);
178 continue;
179 }
180
181 /* else remove from current cell ... */
182 auto p = std::move(*it);
183 it = cell_ns->particles().erase(it);
184 diff.emplace_back(ModifiedList{cell_ns->particles()});
185 diff.emplace_back(RemovedParticle{p.id()});
186
187 /* ... and insert in regular decomposition */
188 auto const target_cell = particle_to_cell(p);
189 /* if particle belongs to this node insert it into correct cell */
190 if (target_cell != nullptr) {
191 target_cell->particles().insert(std::move(p));
192 diff.emplace_back(ModifiedList{target_cell->particles()});
193 }
194 /* otherwise just put into regular decomposition */
195 else {
196 auto first_local_cell = m_regular_decomposition.get_local_cells()[0];
197 first_local_cell->particles().insert(std::move(p));
198 diff.emplace_back(ModifiedList{first_local_cell->particles()});
199 }
200 }
201 }
202 }
203
204 /* now resort into correct cells within the respective decompositions */
205 m_regular_decomposition.resort(global, diff);
206 m_n_square.resort(global, diff);
207
209 m_halo_plan, m_box, GHOSTTRANS_PARTNUM,
212 m_halo_plan, m_box, map_data_parts(m_get_global_ghost_flags()),
214}
215
216std::size_t HybridDecomposition::count_particles(
217 std::vector<Cell *> const &local_cells) const {
218 std::size_t count_local = 0;
219 std::size_t count_global = 0;
220 for (auto const &cell : local_cells) {
221 count_local += cell->particles().size();
222 }
223 boost::mpi::reduce(m_comm, count_local, count_global, std::plus<>{}, 0);
224 return count_global;
225}
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.
Atom decomposition cell system.
GhostComm::HaloPlan const * halo_plan() const override
auto const & get_local_cells() const
auto const & get_ghost_cells() const
void resort(bool global_flag, std::vector< ParticleChange > &diff) override
std::span< Cell *const > local_cells() const override
Definition Cell.hpp:96
std::span< Cell *const > local_cells() const override
Cell * particle_to_cell(Particle const &p) override
void resort(bool global, std::vector< ParticleChange > &diff) override
HybridDecomposition(boost::mpi::communicator comm, double cutoff_regular, double skin, std::function< bool()> get_ghost_flags, BoxGeometry const &box_geo, LocalBox const &local_box, std::set< int > n_square_types)
std::span< Cell *const > ghost_cells() const override
Ghost particles and particle exchange.
@ GHOSTTRANS_PARTNUM
resize the receiver particle arrays to the size of the senders
Definition ghosts.hpp:49
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.
void halo_exchange(HaloPlan const &plan, BoxGeometry const &box, unsigned data_parts, ExchangeOp op, ExchangeBuffers &bufs)
Blocking wrapper using a caller-owned buffer pool (no per-call alloc after warm-up).
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.
std::optional< CollectiveSection > collective
Definition HaloPlan.hpp:63
boost::mpi::communicator comm
Definition HaloPlan.hpp:60
std::vector< NeighborComm > neighbors
Definition HaloPlan.hpp:61
std::vector< LocalComm > local
Definition HaloPlan.hpp:62
Regular decomposition cell system.
void resort(bool global, std::vector< ParticleChange > &diff) override
std::span< Cell *const > local_cells() const override
auto const & get_local_cells() const
GhostComm::HaloPlan const * halo_plan() const override
auto const & get_ghost_cells() const