35#include "communication.hpp"
42#include "system/System.hpp"
49#ifdef ESPRESSO_CALIPER
50#include <caliper/cali.h>
53#include <boost/mpi/collectives/all_reduce.hpp>
55#include <Cabana_Core.hpp>
56#include <Cabana_NeighborList.hpp>
57#include <Kokkos_Core.hpp>
73#include <unordered_set>
81 m_kokkos_handle.reset();
85 m_local_force.reset();
86#ifdef ESPRESSO_ROTATION
87 m_local_torque.reset();
90 m_local_virial.reset();
92 m_id_to_index.reset();
94 m_verlet_list_cabana.reset();
95 m_bond_state->clear();
96 m_rebuild_verlet_list_cabana =
true;
101 m_kokkos_handle = std::move(handle);
102 m_bond_state = std::make_unique<LocalBondState>();
106 std::size_t number_of_unique_particles,
107 double local_box_volume,
108 std::size_t num_local_particles) {
109 if (std::isinf(pair_cutoff)) {
110 return number_of_unique_particles;
112 if (pair_cutoff < 0.) {
118 auto const local_density =
119 (local_box_volume > 0. && num_local_particles > 0)
120 ?
static_cast<double>(num_local_particles) / local_box_volume
122 auto const cutoff_sphere_volume =
123 (4. / 3.) * std::numbers::pi * Utils::int_pow<3>(pair_cutoff);
125 auto const fluctuation_factor = 2.;
126 auto max_counts =
static_cast<std::size_t
>(
127 std::ceil(fluctuation_factor * local_density * cutoff_sphere_volume));
128 std::size_t
constexpr threshold_num = 16;
129 if (max_counts < threshold_num) {
130 max_counts = std::min(threshold_num, number_of_unique_particles);
136#ifdef ESPRESSO_CALIPER
137 CALI_CXX_MARK_FUNCTION;
139 assert(m_kokkos_handle);
140 using execution_space = Kokkos::DefaultExecutionSpace;
141 auto const num_threads = execution_space().concurrency();
144 auto const local_box_volume = system.local_geo->volume();
147#ifdef ESPRESSO_COLLISION_DETECTION
148 if (system.has_collision_detection_enabled()) {
150 max_counts = num_part * 2ul;
155#ifdef ESPRESSO_ROTATION
161 m_aosoa->resize(num_part);
162 Kokkos::deep_copy(m_aosoa->flags, uint8_t{0});
163 m_verlet_list_cabana->reallocData(num_part, max_counts);
166 std::make_unique<ForceType>(
"local_force", num_part, num_threads);
167#ifdef ESPRESSO_ROTATION
169 std::make_unique<ForceType>(
"local_torque", num_part, num_threads);
171 m_id_to_index = std::make_unique<Kokkos::View<int *>>(
172 Kokkos::ViewAllocateWithoutInitializing(
"id_to_index"),
176 m_aosoa = std::make_unique<AoSoA_pack>();
177 m_aosoa->resize(num_part);
178 Kokkos::deep_copy(m_aosoa->flags, uint8_t{0});
180 m_verlet_list_cabana =
181 std::make_unique<ListType>(0ul, num_part, max_counts);
184 m_local_virial = std::make_unique<VirialType>(
"local_virial", num_threads);
189#ifdef ESPRESSO_CALIPER
190 CALI_CXX_MARK_FUNCTION;
197#ifdef ESPRESSO_ROTATION
203 Kokkos::deep_copy(
get_aosoa().flags, uint8_t{0});
209 auto &pair_list = m_bond_state->pair_list;
210 auto &pair_ids = m_bond_state->pair_ids;
211 auto &angle_list = m_bond_state->angle_list;
212 auto &angle_ids = m_bond_state->angle_ids;
213 auto &dihedral_list = m_bond_state->dihedral_list;
214 auto &dihedral_ids = m_bond_state->dihedral_ids;
215 for (
auto const bond : p.
bonds()) {
216 auto const partner_ids = bond.partner_ids();
219 if (partners.size() == 1u) {
220 auto p_index = Kokkos::atomic_fetch_add(&pair_count, 1);
221 pair_list(p_index, 0) = p.
id();
222 pair_list(p_index, 1) = partners[0]->id();
223 pair_ids(p_index) = bond.bond_id();
224 }
else if (partners.size() == 2u) {
225 auto a_index = Kokkos::atomic_fetch_add(&angle_count, 1);
226 angle_list(a_index, 0) = p.
id();
227 angle_list(a_index, 1) = partners[0]->id();
228 angle_list(a_index, 2) = partners[1]->id();
229 angle_ids(a_index) = bond.bond_id();
230 }
else if (partners.size() == 3u) {
231 auto d_index = Kokkos::atomic_fetch_add(&dihedral_count, 1);
232 dihedral_list(d_index, 0) = p.
id();
233 dihedral_list(d_index, 1) = partners[0]->id();
234 dihedral_list(d_index, 2) = partners[1]->id();
235 dihedral_list(d_index, 3) = partners[2]->id();
236 dihedral_ids(d_index) = bond.bond_id();
245#ifdef ESPRESSO_CALIPER
246 CALI_CXX_MARK_FUNCTION;
248 auto &unique_particles = m_unique_particles;
249 unique_particles.clear();
251 std::unordered_set<int> registered_index{};
252 using execution_space = Kokkos::DefaultExecutionSpace;
253 int n_threads = execution_space().concurrency();
254 std::vector<int> max_ids(n_threads);
256 m_bond_state->reset_counts();
257 std::vector<int> pair_counts(n_threads, 0);
258 std::vector<int> angle_counts(n_threads, 0);
259 std::vector<int> dihedral_counts(n_threads, 0);
262 *
this, [&unique_particles, &max_ids, &pair_counts, &angle_counts,
263 &dihedral_counts](std::size_t index,
Particle &p) {
264 unique_particles[index] = &p;
265 auto const thread_num = omp_get_thread_num();
266 max_ids[thread_num] = std::max(p.
id(), max_ids[thread_num]);
267 for (
auto const bond : p.
bonds()) {
268 if (not bond.partner_ids().empty()) {
269 auto const partner_ids = bond.partner_ids();
270 if (partner_ids.size() == 1u) {
271 pair_counts[thread_num] += 1;
272 }
else if (partner_ids.size() == 2u) {
273 angle_counts[thread_num] += 1;
274 }
else if (partner_ids.size() == 3u) {
275 dihedral_counts[thread_num] += 1;
281 int pair_count = std::reduce(std::begin(pair_counts), std::end(pair_counts));
283 std::reduce(std::begin(angle_counts), std::end(angle_counts));
285 std::reduce(std::begin(dihedral_counts), std::end(dihedral_counts));
287 m_bond_state->allocate();
289 int max_id = *(std::max_element(max_ids.begin(), max_ids.end()));
292 if (not local_particle) {
295 if (not local_particle->is_ghost()) {
298 if (registered_index.contains(p.
id())) {
301 registered_index.insert(p.
id());
302 unique_particles.emplace_back(&p);
303 max_id = std::max(p.
id(), max_id);
305 registered_index.clear();
306 m_cached_max_local_particle_id = max_id;
307 m_num_local_particles_cached = unique_particles.size();
317 auto const id = p.id();
319 if (id < 0 || id > max_id) {
320 throw std::runtime_error(
"Particle id out of bounds.");
324 throw std::runtime_error(
"Invalid local particle index entry.");
329 std::size_t local_part_cnt = 0u;
334 throw std::runtime_error(
"local_particles part has corrupted id.");
340 throw std::runtime_error(
342 std::to_string(local_part_cnt) +
" parts in local_particles");
348 for (
auto const &p : cell->particles()) {
349 if (particle_to_cell(p) != cell) {
350 throw std::runtime_error(
"misplaced particle with id " +
351 std::to_string(p.id()));
358 auto remove_all_bonds_to = [id](
BondList &bl) {
359 for (
auto it = bl.begin(); it != bl.end();) {
369 auto &parts = cell->particles();
370 for (
auto it = parts.begin(); it != parts.end();) {
371 if (it->id() == id) {
372 it = parts.erase(it);
376 remove_all_bonds_to(it->bonds());
384 auto const sort_cell = particle_to_cell(p);
386 return std::addressof(
387 append_indexed_particle(sort_cell->particles(), std::move(p)));
394 auto const sort_cell = particle_to_cell(p);
403 return std::addressof(
404 append_indexed_particle(cell->particles(), std::move(p)));
408 auto it = std::ranges::find_if(std::ranges::views::reverse(m_particle_index),
409 [](
auto const *p) {
return p !=
nullptr; });
411 return (it != m_particle_index.rend()) ? (*it)->id() : -1;
415 return m_bond_state->pair_count;
418 return m_bond_state->angle_count;
421 return m_bond_state->dihedral_count;
424 int dihedral_value) {
425 m_bond_state->set_counts(pair_value, angle_value, dihedral_value);
427#ifdef ESPRESSO_COLLISION_DETECTION
430 std::vector<int>
const &particle_ids) {
438 cell->particles().clear();
441 m_particle_index.clear();
448 using namespace Cells;
456#ifdef ESPRESSO_BOND_CONSTRAINT
475#ifdef ESPRESSO_BOND_CONSTRAINT
499 std::vector<ParticleChange> diff;
501 m_decomposition->resort(global_flag, diff);
503 for (
auto d : diff) {
504 std::visit(UpdateParticleIndexVisitor{
this}, d);
507 auto const &lebc =
get_system().box_geo->lees_edwards_bc();
508 m_rebuild_verlet_list =
true;
509 m_rebuild_verlet_list_cabana =
true;
510 m_le_pos_offset_at_last_resort = lebc.pos_offset;
512#ifdef ESPRESSO_ADDITIONAL_CHECKS
520 auto &local_geo = *system.local_geo;
521 auto const &box_geo = *system.box_geo;
522 set_particle_decomposition(
523 std::make_unique<AtomDecomposition>(
::comm_cart, box_geo));
525 local_geo.set_cell_structure_type(m_type);
526 system.on_cell_structure_change();
530 double range, std::optional<std::pair<int, int>> fully_connected_boundary) {
532 auto &local_geo = *system.local_geo;
533 auto const &box_geo = *system.box_geo;
534 set_particle_decomposition(std::make_unique<RegularDecomposition>(
535 ::comm_cart, range, box_geo, local_geo, fully_connected_boundary));
537 local_geo.set_cell_structure_type(m_type);
538 system.on_cell_structure_change();
542 std::set<int> n_square_types) {
544 auto &local_geo = *system.local_geo;
545 auto const &box_geo = *system.box_geo;
546 set_particle_decomposition(std::make_unique<HybridDecomposition>(
548 [&system]() {
return system.get_global_ghost_flags(); }, box_geo,
549 local_geo, n_square_types));
551 local_geo.set_cell_structure_type(m_type);
552 system.on_cell_structure_change();
557 m_verlet_skin = value;
558 m_verlet_skin_set =
true;
559 m_rebuild_verlet_list_cabana =
true;
565 auto const max_cut =
get_system().maximal_cutoff();
567 throw std::runtime_error(
568 "cannot automatically determine skin, please set it manually");
573 auto const new_skin = std::min(0.4 * max_cut,
max_range - max_cut);
579 auto constexpr resort_only_parts =
582 auto const global_resort = boost::mpi::all_reduce(
583 ::comm_cart, m_resort_particles, std::bit_or<unsigned>());
609void CellStructure::parallel_for_each_particle_impl(
611 if (cells.size() > 1) {
612 Kokkos::parallel_for(
613 "for_each_local_particle", cells.size(), [&](
auto cell_idx) {
614 for (auto &p : cells[cell_idx]->particles())
617 }
else if (cells.size() == 1) {
618 auto &
particles = cells.front()->particles();
619 Kokkos::parallel_for(
621 [&](
auto part_idx) { f(*(particles.begin() + part_idx)); });
627 auto const lim =
Utils::sqr(m_verlet_skin / 2.) - additional_offset.
norm2();
630 [lim](
bool &result,
Particle const &p) {
631 if ((p.pos() - p.pos_at_last_verlet_update()).norm2() > lim) {
@ NSQUARE
Atom decomposition (N-square).
@ HYBRID
Hybrid decomposition.
@ REGULAR
Regular decomposition.
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.
std::function< void(Particle &)> ParticleUnaryOp
Vector implementation and trait types for boost qvm interoperability.
void bond_resolution_error(std::span< const int > partner_ids)
Atom decomposition cell system.
Describes a cell structure / cell system.
ParticleRange ghost_particles() const
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.
std::size_t count_local_particles() const
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.
int get_local_pair_bond_numbers() const
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
void resort_particles(bool global_flag)
Resort particles.
void check_particle_index() const
Check that particle index is commensurate with 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.
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.
base_type::size_type size() const
constexpr T norm2() const
boost::mpi::communicator comm_cart
The communicator.
void ghost_communicator(GhostCommunicator const &gcr, BoxGeometry const &box_geo, unsigned int data_parts)
Do a ghost communication with the specified data parts.
Ghost particles and particle exchange.
@ GHOSTTRANS_MOMENTUM
transfer ParticleMomentum
@ GHOSTTRANS_RATTLE
transfer ParticleRattle
@ GHOSTTRANS_PARTNUM
resize the receiver particle arrays to the size of the senders
@ GHOSTTRANS_POSITION
transfer ParticlePosition
@ GHOSTTRANS_PROPRTS
transfer ParticleProperties
@ GHOSTTRANS_FORCE
transfer ParticleForce
@ DATA_PART_PROPERTIES
Particle::p.
@ DATA_PART_BONDS
Particle::bonds.
ParticleRange particles(std::span< Cell *const > cells)
std::function< void(ResultType &, ResultType const &)> ReductionOp
Join two partial reduction results.
std::function< void(ResultType &, Particle const &)> AddPartialResultKernel
Kernel that adds the result from a single particle to a reduction.
DEVICE_QUALIFIER constexpr T sqr(T x)
Calculates the SQuaRe of x.
bool contains(Range &&rng, T const &value)
Check whether a range contains a value.
void enumerate_local_particles(CellStructure const &cs, Kernel &&kernel)
Run a kernel on all local particles with enumeration.
ResultType reduce_over_local_particles(CellStructure const &cs, Reduction::AddPartialResultKernel< ResultType > add_partial, Reduction::ReductionOp< ResultType > reduce_op)
performs a reduction over all particles
Exception indicating that a particle id could not be resolved.
Struct holding all information for one particle.
auto const & bonds() const
Apply a ParticleChange to a particle index.
void operator()(RemovedParticle rp) const
void operator()(ModifiedList mp) const