ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
CellStructure.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
28
29#include "BoxGeometry.hpp"
30#include "LocalBondState.hpp"
31#include "LocalBox.hpp"
32#include "Particle.hpp"
33#include "aosoa_pack.hpp"
35#include "communication.hpp"
36#include "ghosts.hpp"
39#include "kokkos_helpers.hpp"
42#include "particle_node.hpp"
44#include "system/System.hpp"
45
46#include <utils/Vector.hpp>
47#include <utils/contains.hpp>
49#include <utils/math/sqr.hpp>
50
51#ifdef ESPRESSO_CALIPER
52#include "caliper_utils.hpp"
53#endif
54
55#include <boost/mpi/collectives/all_reduce.hpp>
56
57#include <omp.h>
58
59#include <algorithm>
60#include <cassert>
61#include <cmath>
62#include <cstddef>
63#include <cstdint>
64#include <iterator>
65#include <memory>
66#include <numbers>
67#include <optional>
68#include <ranges>
69#include <set>
70#include <stdexcept>
71#include <string>
72#include <unordered_set>
73#include <utility>
74#include <variant>
75#include <vector>
76
78 assert(not m_pending_ghost_reduce.has_value() &&
79 "~CellStructure: ghost force reduction still in flight at destruction "
80 "— ghosts_reduce_forces_finish() was not called");
82 // Kokkos handle can only be freed after all Cabana containers have been freed
83 m_kokkos_handle.reset();
84}
85
87 m_scatter_force.reset();
88 m_local_force.reset();
89#ifdef ESPRESSO_ROTATION
90 m_scatter_torque.reset();
91 m_local_torque.reset();
92#endif
93#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
94 m_scatter_dip_fld.reset();
95 m_local_dip_fld.reset();
96#endif
97#ifdef ESPRESSO_NPT
98 m_scatter_virial.reset();
99 m_local_virial.reset();
100#endif
101 m_id_to_index.reset();
102 m_aosoa.reset();
103 m_verlet_list_cabana.reset();
104 m_bond_state->clear();
105 m_rebuild_verlet_list_cabana = true;
106}
107void CellStructure::clear_bond_properties() { m_bond_state->reset(); }
108
109void CellStructure::set_kokkos_handle(std::shared_ptr<KokkosHandle> handle) {
110 m_kokkos_handle = std::move(handle);
111 m_bond_state = std::make_unique<LocalBondState>();
112}
113
114static auto estimate_max_counts(double pair_cutoff,
115 std::size_t number_of_unique_particles,
116 double local_box_volume,
117 std::size_t num_local_particles) {
118 if (std::isinf(pair_cutoff)) {
119 return number_of_unique_particles;
120 }
121 if (pair_cutoff < 0.) {
122 pair_cutoff = 0.;
123 }
124 // Estimate number of neighbors based on local density and cutoff sphere:
125 // volume n_neighbors = rho * (4/3) * pi * r^3, where rho = n_particles /
126 // volume
127 auto const local_density =
128 (local_box_volume > 0. && num_local_particles > 0)
129 ? static_cast<double>(num_local_particles) / local_box_volume
130 : 0.;
131 auto const cutoff_sphere_volume =
132 (4. / 3.) * std::numbers::pi * Utils::int_pow<3>(pair_cutoff);
133 // account for local fluctuations. Empirical.
134 auto const fluctuation_factor = 2.;
135 auto max_counts = static_cast<std::size_t>(
136 std::ceil(fluctuation_factor * local_density * cutoff_sphere_volume));
137 std::size_t constexpr threshold_num = 16;
138 if (max_counts < threshold_num) {
139 max_counts = std::min(threshold_num, number_of_unique_particles);
140 }
141 return max_counts;
142}
143
144void CellStructure::rebuild_local_properties(double const pair_cutoff) {
145#ifdef ESPRESSO_CALIPER
147#endif
148 assert(m_kokkos_handle);
149 auto const num_part = get_unique_particles().size();
150 auto const &system = get_system();
151 auto const local_box_volume = system.local_geo->volume();
152 auto max_counts = estimate_max_counts(pair_cutoff, num_part, local_box_volume,
154#ifdef ESPRESSO_COLLISION_DETECTION
155 if (system.has_collision_detection_enabled()) {
156 // TODO: use other types of Verlet list data structures
157 max_counts = num_part * 2ul;
158 }
159#endif
160 if (m_local_force) { // local properties are reallocated
161 if (get_local_force().extent(0) == num_part) {
162 // Extents unchanged (always the case with a single MPI rank): zero the
163 // existing buffers in place instead of freeing and reallocating the
164 // O(n_threads * N) ScatterView scratch on every Verlet rebuild.
166 reset_torque_replicas_if_dirty();
167 reset_dip_fld_replicas_if_dirty();
168 } else {
169 Kokkos::realloc(get_local_force(), num_part);
170 // underlying View extent changed -> scratch buffers must be rebuilt
171 m_scatter_force.emplace(
172 Kokkos::Experimental::create_scatter_view(get_local_force()));
173#ifdef ESPRESSO_ROTATION
174 Kokkos::realloc(get_local_torque(), num_part);
175 // underlying View extent changed -> scratch buffers must be rebuilt
176 m_scatter_torque.emplace(
177 Kokkos::Experimental::create_scatter_view(get_local_torque()));
178 m_torque_replicas_dirty = false;
179#endif
180#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
181 Kokkos::realloc(get_local_dip_fld(), num_part);
182 // underlying View extent changed -> scratch buffers must be rebuilt
183 m_scatter_dip_fld.emplace(
184 Kokkos::Experimental::create_scatter_view(get_local_dip_fld()));
185 m_dip_fld_replicas_dirty = false;
186#endif
187 }
188 auto const required_index_size = get_cached_max_local_particle_id() + 1;
189 if (get_id_to_index().extent(0) !=
190 static_cast<std::size_t>(required_index_size)) {
191 Kokkos::realloc(Kokkos::WithoutInitializing, get_id_to_index(),
192 required_index_size);
193 }
195 // Resize particle views using AoSoA_pack's resize method
196 m_aosoa->resize(num_part);
197 kokkos_deep_copy(execution_space{}, m_aosoa->flags, uint8_t{0});
198 m_verlet_list_cabana->reallocData(num_part, max_counts);
199 } else { // local properties are initialized
200 m_local_force = std::make_unique<ForceType>("local_force", num_part);
201 m_scatter_force.emplace(
202 Kokkos::Experimental::create_scatter_view(*m_local_force));
203#ifdef ESPRESSO_ROTATION
204 m_local_torque = std::make_unique<ForceType>("local_torque", num_part);
205 m_scatter_torque.emplace(
206 Kokkos::Experimental::create_scatter_view(*m_local_torque));
207#endif
208#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
209 m_local_dip_fld = std::make_unique<ForceType>("local_dip_fld", num_part);
210 m_scatter_dip_fld.emplace(
211 Kokkos::Experimental::create_scatter_view(*m_local_dip_fld));
212#endif
213 m_id_to_index = std::make_unique<Kokkos::View<int *, memory_space>>(
214 Kokkos::view_alloc(execution_space{}, Kokkos::WithoutInitializing,
215 "id_to_index"),
218 // Create AoSoA_pack and initialize with resize
219 m_aosoa = std::make_unique<AoSoA_pack>();
220 m_aosoa->resize(num_part);
221 kokkos_deep_copy(execution_space{}, m_aosoa->flags, uint8_t{0});
222
223 m_verlet_list_cabana =
224 std::make_unique<ListType>(0ul, num_part, max_counts);
225 }
226#ifdef ESPRESSO_NPT
227 if (not m_local_virial) {
228 m_local_virial = std::make_unique<VirialType>("local_virial");
229 m_scatter_virial.emplace(
230 Kokkos::Experimental::create_scatter_view(*m_local_virial));
231 } else {
232 reset_virial_replicas_if_dirty();
233 }
234#endif
235}
236
239 m_scatter_force->reset();
240}
241
242void CellStructure::reset_torque_replicas_if_dirty() {
243#ifdef ESPRESSO_ROTATION
244 if (m_torque_replicas_dirty) {
246 m_scatter_torque->reset();
247 m_torque_replicas_dirty = false;
248 }
249#endif
250}
251
252void CellStructure::reset_dip_fld_replicas_if_dirty() {
253#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
254 if (m_dip_fld_replicas_dirty) {
256 m_scatter_dip_fld->reset();
257 m_dip_fld_replicas_dirty = false;
258 }
259#endif
260}
261
262void CellStructure::reset_virial_replicas_if_dirty() {
263#ifdef ESPRESSO_NPT
264 if (m_virial_replicas_dirty) {
266 m_scatter_virial->reset();
267 m_virial_replicas_dirty = false;
268 }
269#endif
270}
271
273#ifdef ESPRESSO_CALIPER
275#endif
277 reset_torque_replicas_if_dirty();
278 reset_dip_fld_replicas_if_dirty();
279 reset_virial_replicas_if_dirty();
280 kokkos_deep_copy(execution_space{}, get_aosoa().flags, uint8_t{0});
281}
282
283void CellStructure::update_bond_storage(int &pair_count, int &angle_count,
284 int &dihedral_count,
285 Particle const &p) {
286 auto &pair_list = m_bond_state->pair_list;
287 auto &pair_ids = m_bond_state->pair_ids;
288 auto &angle_list = m_bond_state->angle_list;
289 auto &angle_ids = m_bond_state->angle_ids;
290 auto &dihedral_list = m_bond_state->dihedral_list;
291 auto &dihedral_ids = m_bond_state->dihedral_ids;
292 for (auto const bond : p.bonds()) {
293 auto const partner_ids = bond.partner_ids();
294 try {
295 auto const partners = resolve_bond_partners(partner_ids);
296 if (partners.size() == 1u) { // pair bonds
297 auto p_index = Kokkos::atomic_fetch_add(&pair_count, 1);
298 pair_list(p_index, 0) = p.id();
299 pair_list(p_index, 1) = partners[0]->id();
300 pair_ids(p_index) = bond.bond_id();
301 } else if (partners.size() == 2u) { // angle bond
302 auto a_index = Kokkos::atomic_fetch_add(&angle_count, 1);
303 angle_list(a_index, 0) = p.id();
304 angle_list(a_index, 1) = partners[0]->id();
305 angle_list(a_index, 2) = partners[1]->id();
306 angle_ids(a_index) = bond.bond_id();
307 } else if (partners.size() == 3u) { // dihedral bond
308 auto d_index = Kokkos::atomic_fetch_add(&dihedral_count, 1);
309 dihedral_list(d_index, 0) = p.id();
310 dihedral_list(d_index, 1) = partners[0]->id();
311 dihedral_list(d_index, 2) = partners[1]->id();
312 dihedral_list(d_index, 3) = partners[2]->id();
313 dihedral_ids(d_index) = bond.bond_id();
314 }
315 } catch (BondResolutionError const &) {
316 bond_resolution_error(partner_ids);
317 }
318 }
319}
320
322#ifdef ESPRESSO_CALIPER
324#endif
325 auto &unique_particles = m_unique_particles;
326 unique_particles.clear();
327 unique_particles.resize(count_local_particles());
328 std::unordered_set<int> registered_index{};
329 using execution_space = Kokkos::DefaultHostExecutionSpace;
330 int n_threads = execution_space().concurrency();
331
332 m_bond_state->reset_counts();
333 // one cache line per thread: these counters are written on every particle,
334 // so packing them into shared cache lines makes the sweep bounce lines
335 // between L3 domains
336 struct alignas(64) PerThreadCounts {
337 int max_id = 0;
338 int pair = 0;
339 int angle = 0;
340 int dihedral = 0;
341 };
342 std::vector<PerThreadCounts> thread_counts(n_threads);
343
344 enumerate_local_particles(*this, [&unique_particles, &thread_counts](
345 std::size_t index, Particle &p) {
346 unique_particles[index] = &p;
347 auto &counts = thread_counts[omp_get_thread_num()];
348 counts.max_id = std::max(p.id(), counts.max_id);
349 for (auto const bond : p.bonds()) {
350 if (not bond.partner_ids().empty()) {
351 auto const partner_ids = bond.partner_ids();
352 if (partner_ids.size() == 1u) {
353 counts.pair += 1;
354 } else if (partner_ids.size() == 2u) {
355 counts.angle += 1;
356 } else if (partner_ids.size() == 3u) {
357 counts.dihedral += 1;
358 }
359 }
360 }
361 });
362 Kokkos::fence();
363 int pair_count = 0;
364 int angle_count = 0;
365 int dihedral_count = 0;
366 int max_id = 0;
367 for (auto const &counts : thread_counts) {
368 pair_count += counts.pair;
369 angle_count += counts.angle;
370 dihedral_count += counts.dihedral;
371 max_id = std::max(counts.max_id, max_id);
372 }
373 set_local_bond_numbers(pair_count, angle_count, dihedral_count);
374 m_bond_state->allocate();
375 for (auto &p : ghost_particles()) {
376 auto const *local_particle = get_local_particle(p.id());
377 if (not local_particle or not local_particle->is_ghost()) {
378 continue;
379 }
380 if (registered_index.contains(p.id())) {
381 continue;
382 }
383 registered_index.insert(p.id());
384 unique_particles.emplace_back(&p);
385 max_id = std::max(p.id(), max_id);
386 }
387 registered_index.clear();
388 m_cached_max_local_particle_id = max_id;
389 m_num_local_particles_cached = unique_particles.size();
390}
391
393 : m_decomposition{std::make_unique<AtomDecomposition>(box)} {}
394
396 auto const max_id = get_max_local_particle_id();
397
398 for (auto const &p : local_particles()) {
399 auto const id = p.id();
400
401 if (id < 0 or id > max_id) {
402 throw std::runtime_error("Particle id out of bounds.");
403 }
404
405 if (get_local_particle(id) != &p) {
406 throw std::runtime_error("Invalid local particle index entry.");
407 }
408 }
409
410 /* checks: local particle id */
411 std::size_t local_part_cnt = 0u;
412 for (int n = 0; n < get_max_local_particle_id() + 1; n++) {
413 if (get_local_particle(n) != nullptr) {
414 local_part_cnt++;
415 if (get_local_particle(n)->id() != n) {
416 throw std::runtime_error("local_particles part has corrupted id.");
417 }
418 }
419 }
420
421 if (local_part_cnt != local_particles().size()) {
422 throw std::runtime_error(
423 std::to_string(local_particles().size()) + " parts in cells but " +
424 std::to_string(local_part_cnt) + " parts in local_particles");
425 }
426}
427
429 for (auto cell : decomposition().local_cells()) {
430 for (auto const &p : cell->particles()) {
431 if (particle_to_cell(p) != cell) {
432 throw std::runtime_error("misplaced particle with id " +
433 std::to_string(p.id()));
434 }
435 }
436 }
437}
438
440 auto remove_all_bonds_to = [id](BondList &bl) {
441 for (auto it = bl.begin(); it != bl.end();) {
442 if (Utils::contains(it->partner_ids(), id)) {
443 it = bl.erase(it);
444 } else {
445 std::advance(it, 1);
446 }
447 }
448 };
449
450 for (auto cell : decomposition().local_cells()) {
451 auto &parts = cell->particles();
452 for (auto it = parts.begin(); it != parts.end();) {
453 if (it->id() == id) {
454 it = parts.erase(it);
455 update_particle_index(id, nullptr);
457 } else {
458 remove_all_bonds_to(it->bonds());
459 it++;
460 }
461 }
462 }
463}
464
466 auto const sort_cell = particle_to_cell(p);
467 if (sort_cell) {
468 return std::addressof(
469 append_indexed_particle(sort_cell->particles(), std::move(p)));
470 }
471
472 return {};
473}
474
476 auto const sort_cell = particle_to_cell(p);
477 /* There is always at least one cell, so if the particle
478 * does not belong to a cell on this node we can put it there. */
479 auto cell = sort_cell ? sort_cell : decomposition().local_cells()[0];
480
481 /* If the particle isn't local a global resort may be
482 * needed, otherwise a local resort if sufficient. */
484
485 return std::addressof(
486 append_indexed_particle(cell->particles(), std::move(p)));
487}
488
490 auto it = std::ranges::find_if(std::ranges::views::reverse(m_particle_index),
491 [](auto const *p) { return p != nullptr; });
492
493 return (it != m_particle_index.rend()) ? (*it)->id() : -1;
494}
495
497 return m_bond_state->pair_count;
498}
500 return m_bond_state->angle_count;
501}
503 return m_bond_state->dihedral_count;
504}
505void CellStructure::set_local_bond_numbers(int pair_value, int angle_value,
506 int dihedral_value) {
507 m_bond_state->set_counts(pair_value, angle_value, dihedral_value);
508}
509#ifdef ESPRESSO_COLLISION_DETECTION
510void CellStructure::clear_new_bonds() { m_bond_state->clear_new_bonds(); }
512 std::vector<int> const &particle_ids) {
513 m_bond_state->add_new_bond(bond_id, particle_ids, get_id_to_index());
514}
515void CellStructure::rebuild_bond_list() { m_bond_state->rebuild(); }
516#endif // ESPRESSO_COLLISION_DETECTION
517
519 for (auto cell : decomposition().local_cells()) {
520 cell->particles().clear();
521 }
522
523 m_particle_index.clear();
526 get_system().on_particle_change();
527}
528
529/* Map the data parts flags from cells to those used internally
530 * by the ghost communication */
531unsigned map_data_parts(unsigned data_parts) {
532 using namespace Cells;
533
534 /* clang-format off */
535 return GHOSTTRANS_NONE
536 | ((data_parts & DATA_PART_PROPERTIES) ? GHOSTTRANS_PROPRTS : 0u)
537 | ((data_parts & DATA_PART_POSITION) ? GHOSTTRANS_POSITION : 0u)
538 | ((data_parts & DATA_PART_MOMENTUM) ? GHOSTTRANS_MOMENTUM : 0u)
539 | ((data_parts & DATA_PART_FORCE) ? GHOSTTRANS_FORCE : 0u)
540#ifdef ESPRESSO_BOND_CONSTRAINT
541 | ((data_parts & DATA_PART_RATTLE) ? GHOSTTRANS_RATTLE : 0u)
542#endif
543#ifdef ESPRESSO_ROTATION
544 | ((data_parts & DATA_PART_QUAT) ? GHOSTTRANS_QUAT : 0u)
545 | ((data_parts & DATA_PART_TORQUE) ? GHOSTTRANS_TORQUE : 0u)
546#endif
547#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
548 | ((data_parts & DATA_PART_DIPFLD) ? GHOSTTRANS_DIPFLD : 0u)
549#endif
550 | ((data_parts & DATA_PART_BONDS) ? GHOSTTRANS_BONDS : 0u);
551 /* clang-format on */
552}
553
555#ifdef ESPRESSO_CALIPER
557#endif
559 *decomposition().halo_plan(), *get_system().box_geo, GHOSTTRANS_PARTNUM,
561 m_ghost_buffers);
562}
563
564void CellStructure::ghosts_update(unsigned data_parts) {
565#ifdef ESPRESSO_CALIPER
567#endif
568 auto const parts = map_data_parts(data_parts);
570 *decomposition().halo_plan(), *get_system().box_geo, parts,
572 m_ghost_buffers);
573}
574
576#ifdef ESPRESSO_CALIPER
578#endif
580 *decomposition().halo_plan(), *get_system().box_geo,
581 get_system().get_force_reduce_ghost_flags(),
583}
584
585#ifdef ESPRESSO_CALIPER
586// caliper annotation for split phase ghost forces reduction
587static cali_id_t ghost_reduce_async_attr() {
588 static const cali_id_t id =
590 ? cali_create_attribute("ghosts_reduce_forces_async",
591 CALI_TYPE_STRING,
592 CALI_ATTR_ASVALUE | CALI_ATTR_SCOPE_THREAD)
593 : CALI_INV_ID;
594 return id;
595}
596#endif // ESPRESSO_CALIPER
597
599 assert(not m_pending_ghost_reduce.has_value() &&
600 "ghosts_reduce_forces_start: a reduction is already in flight");
601 // BEGIN fires only after emplace succeeds so a throwing start cannot
602 // leave the Caliper region open without a matching END.
603 m_pending_ghost_reduce.emplace(GhostComm::halo_exchange_start(
604 *decomposition().halo_plan(), *get_system().box_geo,
605 get_system().get_force_reduce_ghost_flags(),
607 m_ghost_buffers));
608#ifdef ESPRESSO_CALIPER
609 if (auto id = ghost_reduce_async_attr(); id != CALI_INV_ID)
610 cali_begin_string(id, "in_flight");
611#endif
612}
613
615 assert(m_pending_ghost_reduce.has_value() &&
616 "ghosts_reduce_forces_finish: no reduction is in flight");
617 try {
618 GhostComm::halo_exchange_finish(*m_pending_ghost_reduce);
619 } catch (...) {
620 // A failed finish cannot be retried: the exchange state is half-consumed
621 // (some requests waited, some buffers unpacked). Drop the pending state so
622 // a later finish attempt (e.g. the ReduceGuard in integrate.cpp) does not
623 // re-run MPI waits on completed requests.
624 m_pending_ghost_reduce.reset();
625#ifdef ESPRESSO_CALIPER
626 if (auto id = ghost_reduce_async_attr(); id != CALI_INV_ID)
627 cali_end(id);
628#endif
629 throw;
630 }
631#ifdef ESPRESSO_CALIPER
632 // END before reset so the region is closed before the optional is cleared.
633 if (auto id = ghost_reduce_async_attr(); id != CALI_INV_ID)
634 cali_end(id);
635#endif
636 m_pending_ghost_reduce.reset();
637}
638#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
644#endif
645#ifdef ESPRESSO_BOND_CONSTRAINT
647#ifdef ESPRESSO_CALIPER
649#endif
651 *decomposition().halo_plan(), *get_system().box_geo, GHOSTTRANS_RATTLE,
653}
654#endif
655
656namespace {
657/**
658 * @brief Apply a @ref ParticleChange to a particle index.
659 */
662
664 cs->update_particle_index(rp.id, nullptr);
665 }
667};
668} // namespace
669
670void CellStructure::resort_particles(bool global_flag) {
671#ifdef ESPRESSO_CALIPER
673#endif
674 assert(not m_pending_ghost_reduce.has_value() &&
675 "resort_particles: ghost force reduction is still in flight — "
676 "call ghosts_reduce_forces_finish() first");
677 invalidate_ghosts();
678
679 std::vector<ParticleChange> diff;
680
681 m_decomposition->resort(global_flag, diff);
682
683 for (auto d : diff) {
684 std::visit(UpdateParticleIndexVisitor{this}, d);
685 }
686
687 auto const &lebc = get_system().box_geo->lees_edwards_bc();
688 m_rebuild_verlet_list = true;
689 m_rebuild_verlet_list_cabana = true;
690 m_le_pos_offset_at_last_resort = lebc.pos_offset;
691
692#ifdef ESPRESSO_ADDITIONAL_CHECKS
695#endif
696}
697
699 auto &system = get_system();
700 auto &local_geo = *system.local_geo;
701 auto const &box_geo = *system.box_geo;
702 set_particle_decomposition(
703 std::make_unique<AtomDecomposition>(::comm_cart, box_geo));
705 local_geo.set_cell_structure_type(m_type);
706 system.on_cell_structure_change();
707}
708
710 double range, std::optional<std::pair<int, int>> fully_connected_boundary) {
711 auto &system = get_system();
712 auto &local_geo = *system.local_geo;
713 auto const &box_geo = *system.box_geo;
714 set_particle_decomposition(std::make_unique<RegularDecomposition>(
715 ::comm_cart, range, box_geo, local_geo, fully_connected_boundary));
717 local_geo.set_cell_structure_type(m_type);
718 system.on_cell_structure_change();
719}
720
722 std::set<int> n_square_types) {
723 auto &system = get_system();
724 auto &local_geo = *system.local_geo;
725 auto const &box_geo = *system.box_geo;
726 set_particle_decomposition(std::make_unique<HybridDecomposition>(
727 ::comm_cart, cutoff_regular, m_verlet_skin,
728 [&system]() { return system.get_global_ghost_flags(); }, box_geo,
729 local_geo, n_square_types));
731 local_geo.set_cell_structure_type(m_type);
732 system.on_cell_structure_change();
733}
734
736 assert(value >= 0.);
737 m_verlet_skin = value;
738 m_verlet_skin_set = true;
739 m_rebuild_verlet_list_cabana = true;
740 get_system().on_verlet_skin_change();
741}
742
744 assert(not is_verlet_skin_set());
745 auto const max_cut = get_system().maximal_cutoff();
746 if (max_cut <= 0.) {
747 throw std::runtime_error(
748 "cannot automatically determine skin, please set it manually");
749 }
750 /* maximal skin that can be used without resorting is the maximal
751 * range of the cell system minus what is needed for interactions. */
752 auto const max_range = std::ranges::min(max_cutoff());
753 auto const new_skin = std::min(0.4 * max_cut, max_range - max_cut);
754 set_verlet_skin(new_skin);
755}
756
758#ifdef ESPRESSO_CALIPER
760#endif
761 /* data parts that are only updated on resort */
762 auto constexpr resort_only_parts =
764
765 auto const global_resort = boost::mpi::all_reduce(
766 ::comm_cart, m_resort_particles, std::bit_or<unsigned>());
767
768 if (global_resort != Cells::RESORT_NONE) {
769 auto const do_global_resort = (global_resort & Cells::RESORT_GLOBAL) != 0;
770
771 /* Resort cell system */
772 resort_particles(do_global_resort);
773 ghosts_count();
774 ghosts_update(data_parts);
775
776 /* Add the ghost particles to the index if we don't already
777 * have them. */
778 for (auto &p : ghost_particles()) {
779 if (get_local_particle(p.id()) == nullptr) {
780 update_particle_index(p.id(), &p);
781 }
782 }
783
784 /* Particles are now sorted */
786 } else {
787 /* Communication step: ghost information */
788 ghosts_update(data_parts & ~resort_only_parts);
789 }
790}
791
793 Utils::Vector3d const &additional_offset) const {
794 auto const lim = Utils::sqr(m_verlet_skin / 2.) - additional_offset.norm2();
795
796 auto add_partial = [lim](bool &result, Particle const &p) {
797 if ((p.pos() - p.pos_at_last_verlet_update()).norm2() > lim) {
798 result = true;
799 }
800 };
801
802 auto reduce_op = [](bool &acc, bool const &val) { acc |= val; };
803
804 return reduce_over_local_particles<bool>(*this, add_partial, reduce_op);
805}
@ NSQUARE
Atom decomposition (N-square).
@ HYBRID
Hybrid decomposition.
@ REGULAR
Regular decomposition.
static cali_id_t ghost_reduce_async_attr()
unsigned map_data_parts(unsigned data_parts)
Map the data parts flags from cells to those used internally by the ghost communication.
static auto estimate_max_counts(double pair_cutoff, std::size_t number_of_unique_particles, double local_box_volume, std::size_t num_local_particles)
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_resolution_error(std::span< const int > partner_ids)
Zero-overhead Caliper guards for the inactive (no CALI_CONFIG) case.
bool espresso_cali_active() noexcept
Return true if Caliper is configured for this process.
#define ESPRESSO_CALI_MARK_FUNCTION
Guarded drop-in replacement for CALI_CXX_MARK_FUNCTION.
Atom decomposition cell system.
Bond storage.
Definition BondList.hpp:84
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 check_particle_sorting() const
Check that particles are in the correct cell.
auto & get_id_to_index()
std::size_t count_local_particles() const
virtual ~CellStructure()
int get_local_angle_bond_numbers() const
void clear_resort_particles()
Set the resort level to sorted.
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 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.
int get_local_dihedral_bond_numbers() const
int get_cached_max_local_particle_id() const
CellStructure(BoxGeometry const &box)
auto & get_local_torque()
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 ghosts_reduce_forces()
Add forces and torques from ghost particles to real particles.
auto const & get_unique_particles() const
void rebuild_local_properties(double pair_cutoff)
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,...
auto resolve_bond_partners(std::span< const int > partner_ids)
Resolve ids to particles.
void ghosts_count()
Synchronize number of ghosts.
void set_resort_particles(Cells::Resort level)
Increase the local resort level at least to level.
void remove_particle(int id)
Remove a particle.
Particle * add_particle(Particle &&p)
Add a particle.
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 ghosts_reduce_dipole_field()
Add dipole fields from ghost particles to real particles.
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 & get_local_dip_fld()
void remove_all_particles()
Remove all particles from the cell system.
ParticleRange local_particles() const
void ghosts_reduce_rattle_correction()
Add rattle corrections from ghost particles to real particles.
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 clear_bond_properties()
void reset_local_properties()
virtual std::span< Cell *const > local_cells() const =0
Get pointer to local cells.
constexpr T norm2() const
Definition Vector.hpp:163
boost::mpi::communicator comm_cart
The communicator.
Ghost particles and particle exchange.
@ GHOSTTRANS_MOMENTUM
transfer ParticleMomentum
Definition ghosts.hpp:41
@ GHOSTTRANS_RATTLE
transfer ParticleRattle
Definition ghosts.hpp:46
@ GHOSTTRANS_QUAT
transfer orientation quaternion (pushed with position; runtime-conditional)
Definition ghosts.hpp:54
@ GHOSTTRANS_DIPFLD
transfer dipole field tracking data
Definition ghosts.hpp:60
@ GHOSTTRANS_PARTNUM
resize the receiver particle arrays to the size of the senders
Definition ghosts.hpp:49
@ GHOSTTRANS_POSITION
transfer ParticlePosition
Definition ghosts.hpp:39
@ GHOSTTRANS_PROPRTS
transfer ParticleProperties
Definition ghosts.hpp:37
@ GHOSTTRANS_FORCE
transfer ParticleForce
Definition ghosts.hpp:43
@ GHOSTTRANS_NONE
Definition ghosts.hpp:35
@ GHOSTTRANS_TORQUE
transfer torque (reduced with force; runtime-conditional)
Definition ghosts.hpp:56
@ GHOSTTRANS_BONDS
Definition ghosts.hpp:50
ESPRESSO_ATTR_ALWAYS_INLINE void kokkos_deep_copy(auto const &exec_space, auto const &view, auto const &value)
Wrapper for Kokkos::deep_copy that skips fork/join when the number of threads is 1.
@ DATA_PART_PROPERTIES
Particle::p.
@ DATA_PART_BONDS
Particle::bonds.
void halo_exchange_finish(GhostExchange &st)
Complete a halo exchange: run same-rank copies (overlapping the in-flight messages),...
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).
GhostExchange halo_exchange_start(HaloPlan const &plan, BoxGeometry const &box, unsigned data_parts, ExchangeOp op, ExchangeBuffers &bufs)
Begin a halo exchange using a caller-owned buffer pool.
DEVICE_QUALIFIER constexpr T sqr(T x)
Calculates the SQuaRe of x.
Definition sqr.hpp:28
bool contains(Range &&rng, T const &value)
Check whether a range contains a value.
Definition contains.hpp:36
STL namespace.
void enumerate_local_particles(CellStructure const &cs, Kernel &&kernel)
Run a kernel on all local particles with enumeration.
void clear_particle_node()
Invalidate particle_node.
Particles creation and deletion.
Exception indicating that a particle id could not be resolved.
Struct holding all information for one particle.
Definition Particle.hpp:436
constexpr auto const & bonds() const
Definition Particle.hpp:473
constexpr auto const & id() const
Definition Particle.hpp:455