ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
particle_node.cpp
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#include "particle_node.hpp"
23
24#include "BoxGeometry.hpp"
25#include "Particle.hpp"
27#include "cells.hpp"
28#include "communication.hpp"
30#include "system/System.hpp"
31
32#include <utils/Cache.hpp>
33#include <utils/Span.hpp>
34#include <utils/Vector.hpp>
35#include <utils/keys.hpp>
36#include <utils/mpi/gatherv.hpp>
37
38#include <boost/mpi/collectives/all_gather.hpp>
39#include <boost/mpi/collectives/all_reduce.hpp>
40#include <boost/mpi/collectives/gather.hpp>
41#include <boost/mpi/collectives/reduce.hpp>
42#include <boost/mpi/collectives/scatter.hpp>
43#include <boost/optional.hpp>
44#include <boost/range/algorithm/sort.hpp>
45#include <boost/range/numeric.hpp>
46
47#include <algorithm>
48#include <cmath>
49#include <functional>
50#include <iterator>
51#include <stdexcept>
52#include <string>
53#include <unordered_map>
54#include <unordered_set>
55#include <utility>
56#include <vector>
57
58constexpr auto some_tag = 42;
59
60/** @brief Enable particle type tracking in @ref particle_type_map. */
61static bool type_list_enable;
62
63/** @brief Mapping particle types to lists of particle ids. */
64static std::unordered_map<int, std::unordered_set<int>> particle_type_map;
65
66/** @brief Mapping particle ids to MPI ranks. */
67static std::unordered_map<int, int> particle_node;
68
69static auto &get_cell_structure() {
71}
72
73/**
74 * @brief Keep track of the largest particle id.
75 * This book-keeping variable is necessary to make particle insertion run
76 * in constant time. Traversing the @ref particle_node to find the largest
77 * particle id scales with O(N) and traversing the local cells in parallel
78 * followed by a reduction scales with O(N^2).
79 */
80static int max_seen_pid = -1;
81
82static auto rebuild_needed() {
83 auto is_rebuild_needed = ::particle_node.empty();
84 boost::mpi::broadcast(::comm_cart, is_rebuild_needed, 0);
85 return is_rebuild_needed;
86}
87
89 boost::mpi::broadcast(::comm_cart, ::max_seen_pid, 0);
90}
91
92void init_type_map(int type) {
93 if (type < 0) {
94 throw std::runtime_error("Types may not be negative");
95 }
96 ::type_list_enable = true;
97 auto &nonbonded_ias = *System::get_system().nonbonded_ias;
98 nonbonded_ias.make_particle_type_exist(type);
99
100 std::vector<int> local_pids;
101 for (auto const &p : get_cell_structure().local_particles()) {
102 if (p.type() == type) {
103 local_pids.emplace_back(p.id());
104 }
105 }
106
107 std::vector<std::vector<int>> global_pids;
108 boost::mpi::all_gather(::comm_cart, local_pids, global_pids);
109 ::particle_type_map[type].clear();
110 for (auto const &vec : global_pids) {
111 for (auto const &p_id : vec) {
112 ::particle_type_map[type].insert(p_id);
113 }
114 }
115}
116
117static void remove_id_from_map(int p_id, int type) {
118 auto it = particle_type_map.find(type);
119 if (it != particle_type_map.end())
120 it->second.erase(p_id);
121}
122
123static void add_id_to_type_map(int p_id, int type) {
124 auto it = particle_type_map.find(type);
125 if (it != particle_type_map.end())
126 it->second.insert(p_id);
127}
128
129void on_particle_type_change(int p_id, int old_type, int new_type) {
130 if (::type_list_enable) {
131 if (old_type == type_tracking::any_type) {
132 for (auto &kv : ::particle_type_map) {
133 auto it = kv.second.find(p_id);
134 if (it != kv.second.end()) {
135 kv.second.erase(it);
136#ifndef NDEBUG
137 if (auto p = get_cell_structure().get_local_particle(p_id)) {
138 assert(p->type() == kv.first);
139 }
140#endif
141 break;
142 }
143 }
144 } else if (old_type != type_tracking::new_part) {
145 if (old_type != new_type) {
146 remove_id_from_map(p_id, old_type);
147 }
148 }
149 add_id_to_type_map(p_id, new_type);
150 }
151}
152
153namespace {
154/* Limit cache to 100 MiB */
155std::size_t const max_cache_size = (100ul * 1048576ul) / sizeof(Particle);
157} // namespace
158
160std::size_t fetch_cache_max_size() { return particle_fetch_cache.max_size(); }
161
162static void mpi_send_particle_data_local(int p_id) {
163 auto const p = get_cell_structure().get_local_particle(p_id);
164 auto const found = p and not p->is_ghost();
165 assert(1 == boost::mpi::all_reduce(::comm_cart, static_cast<int>(found),
166 std::plus<>()) &&
167 "Particle not found");
168 if (found) {
169 ::comm_cart.send(0, 42, *p);
170 }
171}
172
174
175const Particle &get_particle_data(int p_id) {
176 auto const pnode = get_particle_node(p_id);
177
178 if (pnode == this_node) {
179 auto const p = get_cell_structure().get_local_particle(p_id);
180 assert(p != nullptr);
181 return *p;
182 }
183
184 /* Query the cache */
185 auto const p_ptr = particle_fetch_cache.get(p_id);
186 if (p_ptr) {
187 return *p_ptr;
188 }
189
190 /* Cache miss, fetch the particle,
191 * put it into the cache and return a pointer into the cache. */
193 Particle result{};
194 ::comm_cart.recv(boost::mpi::any_source, boost::mpi::any_tag, result);
195 return *(particle_fetch_cache.put(p_id, std::move(result)));
196}
197
199 std::vector<int> ids;
200 boost::mpi::scatter(comm_cart, ids, 0);
201
202 std::vector<Particle> parts(ids.size());
203 std::transform(ids.begin(), ids.end(), parts.begin(), [](int p_id) {
204 auto const p = get_cell_structure().get_local_particle(p_id);
205 assert(p != nullptr);
206 return *p;
207 });
208
209 Utils::Mpi::gatherv(comm_cart, parts.data(), static_cast<int>(parts.size()),
210 0);
211}
212
214
215/**
216 * @brief Get multiple particles at once.
217 *
218 * *WARNING* Particles are returned in an arbitrary order.
219 *
220 * @param ids The ids of the particles that should be returned.
221 *
222 * @returns The particle list.
223 */
224static std::vector<Particle> mpi_get_particles(Utils::Span<const int> ids) {
226 /* Return value */
227 std::vector<Particle> parts(ids.size());
228
229 /* Group ids per node */
230 static std::vector<std::vector<int>> node_ids(comm_cart.size());
231 for (auto &per_node : node_ids) {
232 per_node.clear();
233 }
234
235 for (auto const &p_id : ids) {
236 auto const p_node = get_particle_node(p_id);
237
238 if (p_node != this_node)
239 node_ids[p_node].push_back(p_id);
240 }
241
242 /* Distributed ids to the nodes */
243 {
244 static std::vector<int> ignore;
245 boost::mpi::scatter(comm_cart, node_ids, ignore, 0);
246 }
247
248 /* Copy local particles */
249 std::transform(node_ids[this_node].cbegin(), node_ids[this_node].cend(),
250 parts.begin(), [](int p_id) {
251 auto const p = get_cell_structure().get_local_particle(p_id);
252 assert(p != nullptr);
253 return *p;
254 });
255
256 static std::vector<int> node_sizes(comm_cart.size());
257 std::transform(
258 node_ids.cbegin(), node_ids.cend(), node_sizes.begin(),
259 [](std::vector<int> const &ids) { return static_cast<int>(ids.size()); });
260
261 Utils::Mpi::gatherv(comm_cart, parts.data(), static_cast<int>(parts.size()),
262 parts.data(), node_sizes.data(), 0);
263
264 return parts;
265}
266
268 /* Nothing to do on a single node. */
269 // NOLINTNEXTLINE(clang-analyzer-core.NonNullParamChecker)
270 if (comm_cart.size() == 1)
271 return;
272
273 static std::vector<int> ids;
274 ids.clear();
275 auto out_ids = std::back_inserter(ids);
276
277 std::copy_if(in_ids.begin(), in_ids.end(), out_ids, [](int id) {
278 return (get_particle_node(id) != this_node) && particle_fetch_cache.has(id);
279 });
280
281 /* Don't prefetch more particles than fit the cache. */
282 if (ids.size() > particle_fetch_cache.max_size())
283 ids.resize(particle_fetch_cache.max_size());
284
285 /* Fetch the particles... */
286 for (auto &p : mpi_get_particles(ids)) {
287 auto id = p.id();
288 particle_fetch_cache.put(id, std::move(p));
289 }
290}
291
292static void mpi_who_has_local() {
293 static std::vector<int> sendbuf;
294
295 auto local_particles = get_cell_structure().local_particles();
296 auto const n_part = static_cast<int>(local_particles.size());
297 boost::mpi::gather(comm_cart, n_part, 0);
298
299 if (n_part == 0) {
301 return;
302 }
303
304 sendbuf.resize(n_part);
305
306 std::transform(local_particles.begin(), local_particles.end(),
307 sendbuf.begin(), [](Particle const &p) { return p.id(); });
308
309 comm_cart.send(0, some_tag, sendbuf);
311}
312
314
315static void mpi_who_has_head() {
316 auto local_particles = get_cell_structure().local_particles();
317
318 static std::vector<int> n_parts;
319 boost::mpi::gather(comm_cart, static_cast<int>(local_particles.size()),
320 n_parts, 0);
321
322 static std::vector<int> pdata;
323 auto const n_nodes = ::comm_cart.size();
324 max_seen_pid = -1;
325
326 /* then fetch particle locations */
327 for (int pnode = 0; pnode < n_nodes; pnode++) {
328 if (pnode == this_node) {
329 for (auto const &p : local_particles) {
330 particle_node[p.id()] = this_node;
331 max_seen_pid = std::max(max_seen_pid, p.id());
332 }
333 } else if (n_parts[pnode] > 0) {
334 pdata.resize(n_parts[pnode]);
335 comm_cart.recv(pnode, some_tag, pdata);
336 for (int i = 0; i < n_parts[pnode]; i++) {
337 particle_node[pdata[i]] = pnode;
338 max_seen_pid = std::max(max_seen_pid, pdata[i]);
339 }
340 }
341 }
343}
344
345/**
346 * @brief Rebuild the particle index.
347 */
352
353/**
354 * @brief Rebuild the particle index.
355 */
357 if (this_node == 0) {
359 } else {
361 }
362}
363
364int get_particle_node(int p_id) {
365 if (p_id < 0) {
366 throw std::domain_error("Invalid particle id: " + std::to_string(p_id));
367 }
368
369 if (particle_node.empty())
371
372 auto const needle = particle_node.find(p_id);
373
374 // Check if particle has a node, if not, we assume it does not exist.
375 if (needle == particle_node.end()) {
376 throw std::runtime_error("Particle node for id " + std::to_string(p_id) +
377 " not found!");
378 }
379 return needle->second;
380}
381
383 if (p_id < 0) {
384 throw std::domain_error("Invalid particle id: " + std::to_string(p_id));
385 }
386
387 if (rebuild_needed()) {
389 }
390
391 if (this_node != 0) {
392 return -1;
393 }
394
395 auto const needle = particle_node.find(p_id);
396
397 // Check if particle has a node, if not, we assume it does not exist.
398 if (needle == particle_node.end()) {
399 throw std::runtime_error("Particle node for id " + std::to_string(p_id) +
400 " not found!");
401 }
402 return needle->second;
403}
404
406 ::max_seen_pid = -1;
407 particle_node.clear();
408}
409
411 for (auto &kv : ::particle_type_map) {
412 kv.second.clear();
413 }
414}
415
416/**
417 * @brief Calculate the largest particle id.
418 * Traversing the @ref particle_node to find the largest particle id
419 * scales with O(N). Consider using the cached value in @ref max_seen_pid
420 * if possible. This function is only necessary when the cached value is
421 * invalidated, for example when removing the particle which has the
422 * largest id.
423 */
425 return boost::accumulate(particle_node, -1,
426 [](int max, const std::pair<int, int> &kv) {
427 return std::max(max, kv.first);
428 });
429}
430
431/**
432 * @brief Create a new particle and attach it to a cell.
433 * @param p_id The identity of the particle to create.
434 * @param pos The particle position.
435 * @return Whether the particle was created on that node.
436 */
437static bool maybe_insert_particle(int p_id, Utils::Vector3d const &pos) {
438 auto const &box_geo = *System::get_system().box_geo;
439 auto folded_pos = pos;
440 auto image_box = Utils::Vector3i{};
441 box_geo.fold_position(folded_pos, image_box);
442
443 Particle new_part;
444 new_part.id() = p_id;
445 new_part.pos() = folded_pos;
446 new_part.image_box() = image_box;
447
448 return get_cell_structure().add_local_particle(std::move(new_part)) !=
449 nullptr;
450}
451
452/**
453 * @brief Move particle to a new position.
454 * @param p_id The identity of the particle to move.
455 * @param pos The new particle position.
456 * @return Whether the particle was moved from that node.
457 */
458static bool maybe_move_particle(int p_id, Utils::Vector3d const &pos) {
459 auto const &system = System::get_system();
460 auto const &box_geo = *system.box_geo;
461 auto p = system.cell_structure->get_local_particle(p_id);
462 if (p == nullptr) {
463 return false;
464 }
465 auto folded_pos = pos;
466 auto image_box = Utils::Vector3i{};
467 box_geo.fold_position(folded_pos, image_box);
468 p->pos() = folded_pos;
469 p->image_box() = image_box;
470 return true;
471}
472
479
480void remove_particle(int p_id) {
481 if (::type_list_enable) {
482 auto p = get_cell_structure().get_local_particle(p_id);
483 auto p_type = -1;
484 if (p != nullptr and not p->is_ghost()) {
485 if (this_node == 0) {
486 p_type = p->type();
487 } else {
488 ::comm_cart.send(0, 42, p->type());
489 }
490 } else if (this_node == 0) {
491 ::comm_cart.recv(boost::mpi::any_source, 42, p_type);
492 }
493 assert(this_node != 0 or p_type != -1);
494 boost::mpi::broadcast(::comm_cart, p_type, 0);
495 remove_id_from_map(p_id, p_type);
496 }
497
498 if (this_node == 0) {
499 particle_node[p_id] = -1;
500 }
501 get_cell_structure().remove_particle(p_id);
504 if (this_node == 0) {
505 particle_node.erase(p_id);
506 if (p_id == ::max_seen_pid) {
508 // if there is a gap (i.e. there is no particle with id max_seen_pid - 1,
509 // then the cached value is invalidated and has to be recomputed (slow)
510 if (particle_node.count(::max_seen_pid) == 0 or
513 }
514 }
515 }
517}
518
519void make_new_particle(int p_id, Utils::Vector3d const &pos) {
520 if (rebuild_needed()) {
522 }
523 auto const has_created = maybe_insert_particle(p_id, pos);
525
526 auto node = -1;
527 auto const node_local = (has_created) ? ::comm_cart.rank() : 0;
528 boost::mpi::reduce(::comm_cart, node_local, node, std::plus<int>{}, 0);
529 if (::this_node == 0) {
530 particle_node[p_id] = node;
531 max_seen_pid = std::max(max_seen_pid, p_id);
532 assert(not has_created or node == 0);
533 }
535}
536
537void set_particle_pos(int p_id, Utils::Vector3d const &pos) {
538 auto const has_moved = maybe_move_particle(p_id, pos);
539 get_cell_structure().set_resort_particles(Cells::RESORT_GLOBAL);
541
542 auto success = false;
543 boost::mpi::reduce(::comm_cart, has_moved, success, std::plus<bool>{}, 0);
544 if (::this_node == 0 and !success) {
545 throw std::runtime_error("Particle node for id " + std::to_string(p_id) +
546 " not found!");
547 }
548}
549
550int get_random_p_id(int type, int random_index_in_type_map) {
551 auto it = particle_type_map.find(type);
552 if (it == particle_type_map.end()) {
553 throw std::runtime_error("The provided particle type " +
554 std::to_string(type) +
555 " is currently not tracked by the system.");
556 }
557
558 if (random_index_in_type_map + 1 > it->second.size())
559 throw std::runtime_error("The provided index exceeds the number of "
560 "particle types listed in the particle_type_map");
561 // there is no guarantee of order across MPI ranks
562 auto p_id = *std::next(it->second.begin(), random_index_in_type_map);
563 boost::mpi::broadcast(::comm_cart, p_id, 0);
564 return p_id;
565}
566
568 auto it = particle_type_map.find(type);
569 if (it == particle_type_map.end()) {
570 throw std::runtime_error("The provided particle type " +
571 std::to_string(type) +
572 " is currently not tracked by the system.");
573 }
574
575 return static_cast<int>(it->second.size());
576}
577
578bool particle_exists(int p_id) {
579 if (particle_node.empty())
581 return particle_node.count(p_id);
582}
583
584std::vector<int> get_particle_ids() {
585 if (particle_node.empty())
587
588 auto ids = Utils::keys(particle_node);
589 boost::sort(ids);
590
591 return ids;
592}
593
594std::vector<int> get_particle_ids_parallel() {
595 if (rebuild_needed()) {
597 }
598 auto pids = Utils::keys(particle_node);
599 boost::mpi::broadcast(::comm_cart, pids, 0);
600 return pids;
601}
602
604 if (rebuild_needed()) {
606 }
607
608 return max_seen_pid;
609}
610
612 if (particle_node.empty())
614
615 return static_cast<int>(particle_node.size());
616}
#define REGISTER_CALLBACK(cb)
Register a static callback without return value.
Vector implementation and trait types for boost qvm interoperability.
__shared__ int pos[MAXDEPTH *THREADS5/WARPSIZE]
__shared__ int node[MAXDEPTH *THREADS5/WARPSIZE]
This file contains everything related to the global cell structure / cell system.
auto call_all(void(*fp)(Args...), ArgRef &&...args) const -> std::enable_if_t< std::is_void_v< decltype(fp(args...))> >
Call a callback on all nodes.
void on_particle_change()
Called every time a particle property changes.
std::shared_ptr< CellStructure > cell_structure
std::shared_ptr< BoxGeometry > box_geo
std::shared_ptr< InteractionsNonBonded > nonbonded_ias
A stripped-down version of std::span from C++17.
Definition Span.hpp:38
DEVICE_QUALIFIER constexpr iterator end() const
Definition Span.hpp:91
DEVICE_QUALIFIER constexpr iterator begin() const
Definition Span.hpp:89
boost::mpi::communicator comm_cart
The communicator.
int this_node
The number of this node.
void mpi_call(void(*fp)(Args...), ArgRef &&...args)
Call a local function.
MpiCallbacks & mpiCallbacks()
Returns a reference to the global callback class instance.
System & get_system()
void gatherv(const boost::mpi::communicator &comm, const T *in_values, int in_size, T *out_values, const int *sizes, const int *displs, int root)
Definition gatherv.hpp:87
auto keys(Map const &m) -> std::vector< typename Map::key_type >
Return the keys of a map type.
Definition keys.hpp:34
Utils::Cache< int, Particle > particle_fetch_cache(max_cache_size)
auto constexpr new_part
auto constexpr any_type
Various procedures concerning interactions between particles.
std::vector< int > get_particle_ids_parallel()
static int max_seen_pid
Keep track of the largest particle id.
static void add_id_to_type_map(int p_id, int type)
void prefetch_particle_data(Utils::Span< const int > in_ids)
Fetch a range of particle into the fetch cache.
static bool maybe_move_particle(int p_id, Utils::Vector3d const &pos)
Move particle to a new position.
static void build_particle_node()
Rebuild the particle index.
void make_new_particle(int p_id, Utils::Vector3d const &pos)
Create a new particle and attach it to a cell.
const Particle & get_particle_data(int p_id)
Get particle data.
static std::unordered_map< int, int > particle_node
Mapping particle ids to MPI ranks.
int get_particle_node(int p_id)
Get the MPI rank which owns the a specific particle.
int number_of_particles_with_type(int type)
void set_particle_pos(int p_id, Utils::Vector3d const &pos)
Move particle to a new position.
void init_type_map(int type)
static void remove_id_from_map(int p_id, int type)
static void mpi_synchronize_max_seen_pid_local()
void remove_all_particles()
Remove all particles.
int get_random_p_id(int type, int random_index_in_type_map)
Find a particle of given type and return its id.
static int calculate_max_seen_id()
Calculate the largest particle id.
void remove_particle(int p_id)
Remove particle with a given identity.
static void mpi_get_particles_local()
static void mpi_who_has_head()
int get_maximal_particle_id()
Get maximal particle id.
int get_particle_node_parallel(int p_id)
constexpr auto some_tag
std::vector< int > get_particle_ids()
Get all particle ids.
std::size_t fetch_cache_max_size()
Return the maximal number of particles that are kept in the fetch cache.
void invalidate_fetch_cache()
Invalidate the fetch cache for get_particle_data.
static auto rebuild_needed()
static auto & get_cell_structure()
static void build_particle_node_parallel()
Rebuild the particle index.
static bool maybe_insert_particle(int p_id, Utils::Vector3d const &pos)
Create a new particle and attach it to a cell.
void clear_particle_node()
Invalidate particle_node.
static bool type_list_enable
Enable particle type tracking in particle_type_map.
static void mpi_who_has_local()
void on_particle_type_change(int p_id, int old_type, int new_type)
static void mpi_send_particle_data_local(int p_id)
int get_n_part()
Get number of particles.
static void clear_particle_type_map()
static std::vector< Particle > mpi_get_particles(Utils::Span< const int > ids)
Get multiple particles at once.
static std::unordered_map< int, std::unordered_set< int > > particle_type_map
Mapping particle types to lists of particle ids.
bool particle_exists(int p_id)
Check if particle exists.
Particles creation and deletion.
Struct holding all information for one particle.
Definition Particle.hpp:393