28#include "communication.hpp"
31#include "system/System.hpp"
38#include <boost/container/flat_set.hpp>
39#include <boost/mpi/collectives/all_reduce.hpp>
40#include <boost/mpi/communicator.hpp>
41#include <boost/mpi/request.hpp>
42#include <boost/range/numeric.hpp>
44#include <Kokkos_Core.hpp>
53#include <initializer_list>
64int RegularDecomposition::position_to_cell_index(
68 for (
auto i = 0u; i < 3u; i++) {
69 cpos[i] =
static_cast<int>(std::floor(pos[i] *
inv_cell_size[i])) + 1 -
95void RegularDecomposition::move_if_local(
97 std::vector<ParticleChange> &modified_cells) {
98 for (
auto &part : src) {
99 auto target_cell = position_to_cell(part.pos());
102 target_cell->particles().insert(std::move(part));
103 modified_cells.emplace_back(
ModifiedList{target_cell->particles()});
105 rest.
insert(std::move(part));
112void RegularDecomposition::move_left_or_right(
ParticleList &src,
118 auto const can_move_left =
m_box.
periodic(dir) or not is_open_boundary_left;
119 auto const can_move_right =
m_box.
periodic(dir) or not is_open_boundary_right;
122 for (
auto it = src.
begin(); it != src.
end();) {
123 auto const pos = it->pos()[dir];
125 right.
insert(std::move(*it));
128 left.
insert(std::move(*it));
136void RegularDecomposition::exchange_neighbors(
137 ParticleList &pl, std::vector<ParticleChange> &modified_cells) {
138 auto const node_neighbors = Utils::Mpi::cart_neighbors<3>(
m_comm);
139 static ParticleList send_buf_l, send_buf_r, recv_buf_l, recv_buf_r;
141 for (
int dir = 0; dir < 3; dir++) {
143 if (Utils::Mpi::cart_get<3>(
m_comm).dims[dir] == 1) {
148 if (Utils::Mpi::cart_get<3>(
m_comm).dims[dir] == 2) {
149 move_left_or_right(pl, send_buf_l, send_buf_l, dir);
152 node_neighbors[2 * dir], 0, recv_buf_l);
156 using boost::mpi::request;
159 move_left_or_right(pl, send_buf_l, send_buf_r, dir);
162 node_neighbors[2 * dir], 0, recv_buf_l);
163 auto req_r =
isendrecv(
m_comm, node_neighbors[2 * dir + 1], 0, send_buf_r,
164 node_neighbors[2 * dir + 1], 0, recv_buf_r);
166 std::array<request, 4> reqs{{req_l[0], req_l[1], req_r[0], req_r[1]}};
167 boost::mpi::wait_all(reqs.begin(), reqs.end());
173 move_if_local(recv_buf_l, pl, modified_cells);
174 move_if_local(recv_buf_r, pl, modified_cells);
188 std::vector<ParticleChange> &diff) {
192 auto const n_cells = cells_span.size();
201 if (target_cell ==
nullptr) {
203 displaced_parts.
insert(std::move(p));
207 target_cell->particles().insert(std::move(p));
208 diff.emplace_back(
ModifiedList{target_cell->particles()});
212 if (Kokkos::DefaultHostExecutionSpace().concurrency() == 1) {
215 for (
auto *
const c : cells_span) {
216 for (
auto it = c->particles().begin(); it != c->particles().end();) {
222 if (target_cell == c) {
227 auto p = std::move(*it);
228 it = c->particles().erase(it);
229 apply_move(c->particles(), std::move(p), target_cell);
233 using exec_space = Kokkos::DefaultHostExecutionSpace;
244 std::vector<std::vector<Move>> moves(n_cells);
245 std::mutex fold_error_mutex;
246 std::string fold_error_msg;
247 Kokkos::RangePolicy<exec_space> policy(std::size_t{0}, n_cells);
248 Kokkos::parallel_for(
249 "RegularDecomposition::resort::classify", policy,
250 [&](std::size_t
const ci) {
251 auto *cell = cells_span[ci];
253 for (
auto &p : cell->particles()) {
256 }
catch (std::exception
const &err) {
257 std::lock_guard<std::mutex> guard{fold_error_mutex};
258 if (fold_error_msg.empty()) {
259 fold_error_msg = err.what();
264 moves[ci].emplace_back(index, target);
270 if (not fold_error_msg.empty()) {
271 throw std::runtime_error(fold_error_msg);
279 std::vector<int> slot;
280 std::vector<Cell *> target_of;
281 for (std::size_t ci = 0; ci < n_cells; ++ci) {
282 if (moves[ci].empty()) {
285 auto *
const c = cells_span[ci];
286 auto &parts = c->particles();
287 auto const n =
static_cast<int>(parts.
size());
288 target_of.assign(n, c);
289 for (
auto const &move : moves[ci]) {
290 target_of[move.index] = move.target;
293 std::iota(slot.begin(), slot.end(), 0);
297 auto *
const target_cell = target_of[slot[i]];
300 if (target_cell == c) {
305 auto p = std::move(*(parts.
begin() + i));
307 slot[i] = slot[--end];
308 apply_move(parts, std::move(p), target_cell);
314 auto const grid = Utils::Mpi::cart_get<3>(
m_comm).dims;
318 int rounds_left = grid[0] + grid[1] + grid[2] - 3;
319 for (; rounds_left > 0; rounds_left--) {
320 exchange_neighbors(displaced_parts, diff);
322 auto left_over = boost::mpi::all_reduce(
m_comm, displaced_parts.
size(),
323 std::plus<std::size_t>());
325 if (left_over == 0) {
330 exchange_neighbors(displaced_parts, diff);
333 if (not displaced_parts.
empty()) {
336 for (
auto &part : displaced_parts) {
338 <<
"than one local box length in one timestep";
339 sort_cell->particles().insert(std::move(part));
341 diff.emplace_back(
ModifiedList{sort_cell->particles()});
346void RegularDecomposition::mark_cells() {
363 auto dir_max_range = [
this](
unsigned int i) {
367 return {dir_max_range(0u), dir_max_range(1u), dir_max_range(2u)};
371int RegularDecomposition::calc_processor_min_num_cells()
const {
377 return boost::accumulate(Utils::Mpi::cart_get<3>(
m_comm).dims, 1,
378 [](
int n_cells,
int grid) {
379 return (grid == 1) ? 2 * n_cells : n_cells;
383void RegularDecomposition::create_cell_grid(
double range) {
384 auto const cart_info = Utils::Mpi::cart_get<3>(
m_comm);
388 auto const min_num_cells = calc_processor_min_num_cells();
392 auto const cells_per_dir =
393 static_cast<int>(std::ceil(std::cbrt(min_num_cells)));
401 auto const scale = std::cbrt(RegularDecomposition::max_num_cells / volume);
403 for (
auto i = 0u; i < 3u; i++) {
405 cell_grid[i] =
static_cast<int>(std::ceil(local_box_l[i] * scale));
406 cell_range[i] = local_box_l[i] /
static_cast<double>(
cell_grid[i]);
408 if (cell_range[i] < range) {
410 cell_grid[i] =
static_cast<int>(std::floor(local_box_l[i] / range));
413 <<
"interaction range " << range <<
" in direction " << i
414 <<
" is larger than the local box size " << local_box_l[i];
417 cell_range[i] = local_box_l[i] /
static_cast<double>(
cell_grid[i]);
429 if (n_local_cells <= RegularDecomposition::max_num_cells)
434 auto min_size = cell_range[0];
436 for (
auto i = 1u; i < 3u; ++i) {
437 if (
cell_grid[i] > 1 and cell_range[i] < min_size) {
439 min_size = cell_range[i];
448 if (n_local_cells < min_num_cells) {
450 <<
" is smaller than minimum " << min_num_cells
451 <<
": either interaction range is too large for "
452 <<
"the current skin (range=" << range <<
", "
453 <<
"half_local_box_l=[" << local_box_l / 2. <<
"]) "
454 <<
"or min_num_cells too large";
458 if (n_local_cells > RegularDecomposition::max_num_cells) {
462 auto const node_pos = cart_info.coords;
466 for (
auto i = 0u; i < 3u; i++) {
476 cells.resize(
static_cast<unsigned int>(new_cells));
482 return boost::container::flat_set<K, std::remove_reference_t<Comparator>>(
483 std::forward<Comparator>(comp));
486void RegularDecomposition::init_cell_interactions() {
491 auto const cart_info = Utils::Mpi::cart_get<3>(
m_comm);
493 auto const &node_pos = cart_info.coords;
502 return (cell_idx[
coord] == 0 or cell_idx[
coord] == global_size[
coord] - 1);
508 auto const fcb_is_inner_connection = [&global_size,
this](
Utils::Vector3i a,
512 auto const involves_ghost_cell =
513 (a[fc_normal] == -1 or a[fc_normal] == global_size[fc_normal] or
514 b[fc_normal] == -1 or b[fc_normal] == global_size[fc_normal]);
515 if (not involves_ghost_cell) {
517 return std::abs((a - b)[fc_dir]) > 1;
527 return (global_halo_offset + local_index);
532 auto const folded_index = (global_index + global_size) % global_size;
540 return (global_index - global_halo_offset);
546 if (fc_normal == fc_dir) {
547 throw std::domain_error(
"fully_connected_boundary normal and connection "
548 "coordinates need to differ.");
550 if (node_grid[fc_dir] != 1) {
551 throw std::runtime_error(
552 "The MPI nodegrid must be 1 in the fully connected direction.");
555 throw std::runtime_error(
556 "The fully connected boundary requires periodicity in the "
557 "boundary normal direction.");
569 for (
int o = start[2]; o < end[2]; o++)
570 for (
int n = start[1]; n < end[1]; n++)
571 for (
int m = start[0]; m < end[0]; m++) {
583 if (at_boundary(fc_boundary, {m, n, o})) {
584 lower_index[fc_direction] = -1;
585 upper_index[fc_direction] = global_size[fc_direction];
591 for (
auto i = 0u; i < 3u; i++) {
593 lower_index[i] = std::max(0, lower_index[i]);
594 upper_index[i] = std::min(global_size[i] - 1, upper_index[i]);
600 auto neighbors = make_flat_set<Utils::Vector3i>(
602 return folded_linear_index(a) < folded_linear_index(b);
606 for (
int p = lower_index[2]; p <= upper_index[2]; p++)
607 for (
int q = lower_index[1]; q <= upper_index[1]; q++)
608 for (
int r = lower_index[0]; r <= upper_index[0]; r++) {
612 if (fcb_is_inner_connection({m, n, o}, {r, q, p}))
619 auto const ind1 = folded_linear_index({m, n, o});
621 std::vector<Cell *> red_neighbors;
622 std::vector<Cell *> black_neighbors;
628 for (
auto &neighbor : neighbors) {
630 for (
auto coord : {0u, 1u, 2u}) {
631 if (neighbor[
coord] == -1) {
638 auto const ind2 = folded_linear_index(neighbor);
643 auto cell = &
cells.at(
648 red_neighbors.push_back(cell);
650 black_neighbors.push_back(cell);
676 auto const cart_info = Utils::Mpi::cart_get<3>(
m_comm);
677 auto const &node_pos = cart_info.coords;
689 auto const owner_coords =
691 return Utils::Mpi::cart_rank<3>(
m_comm, owner_coords);
697 auto const folded = (global_cell + global_size) % global_size;
704 .at(
static_cast<std::size_t
>(
715 std::vector<std::pair<int, ParticleList *>> recv;
716 std::vector<std::pair<int, ParticleList *>> send;
718 std::map<int, PeerBucket> peers;
719 std::vector<std::pair<int, LocalComm>> local;
721 auto const this_rank =
m_comm.rank();
743 bool is_ghost =
false;
744 for (
auto d = 0u; d < 3u; ++d) {
757 auto const ghost_global = global_origin + (nc - one);
758 auto const peer = owner_of(ghost_global);
759 auto const recv_key = global_key(ghost_global);
761 if (peer == this_rank) {
764 auto const src_coord = ((ghost_global + global_size) % global_size) -
766 local.emplace_back(recv_key,
767 LocalComm{list_at(src_coord), list_at(nc), {}});
771 peers[peer].recv.emplace_back(recv_key, list_at(nc));
777 for (
auto d = 0u; d < 3u; ++d) {
780 else if (side[d] == +1)
783 auto const send_global = global_origin + (mc - one);
784 peers[peer].send.emplace_back(global_key(send_global), list_at(mc));
790 auto const by_key = [](
auto const &a,
auto const &b) {
791 return a.first < b.first;
793 for (
auto &[peer, bucket] : peers) {
794 std::ranges::sort(bucket.recv, by_key);
795 std::ranges::sort(bucket.send, by_key);
798 nc.recv.reserve(bucket.recv.size());
799 for (
auto const &[key, cell] : bucket.recv)
800 nc.recv.push_back(cell);
801 nc.send.reserve(bucket.send.size());
802 for (
auto const &[key, cell] : bucket.send)
803 nc.send.push_back(SendRegion{cell, {}});
804 plan.neighbors.push_back(std::move(nc));
810 local, [](
auto const &a,
auto const &b) {
return a.first < b.first; });
811 plan.local.reserve(local.size());
812 for (
auto &[key, lc] : local)
813 plan.local.push_back(lc);
819 boost::mpi::communicator comm,
double range,
BoxGeometry const &box_geo,
821 std::optional<std::pair<int, int>> fully_connected)
822 : m_comm(
std::move(comm)), m_box(box_geo), m_local_box(local_geo),
823 m_fully_connected_boundary(
std::move(fully_connected)) {
826 create_cell_grid(range);
829 init_cell_interactions();
855 auto const idx_of = [
this](
Cell const *c) {
856 return static_cast<int>(c -
cells.data());
869 std::array<bool, 3> wrap_axis;
870 for (
int i = 0; i < 3; ++i)
874 bool const has_wrap_axis = wrap_axis[0] || wrap_axis[1] || wrap_axis[2];
875 std::function<bool(
Cell const *,
Cell const *)> wrap_pred;
877 wrap_pred = [
this, wrap_axis, idx_of, ghost_coord_of](
878 Cell const *a_cell,
Cell const *b_cell) ->
bool {
879 auto const a_coord = ghost_coord_of(idx_of(a_cell));
880 auto const b_coord = ghost_coord_of(idx_of(b_cell));
881 for (
int i = 0; i < 3; ++i) {
883 bool const a_first = (a_coord[i] == 1);
884 bool const a_last = (a_coord[i] ==
cell_grid[i]);
885 bool const b_first = (b_coord[i] == 1);
886 bool const b_last = (b_coord[i] ==
cell_grid[i]);
887 if ((a_first && b_last) || (a_last && b_first))
911 for (
int i = 0; i < 3; ++i) {
914 c->m_is_boundary =
true;
927#ifdef ESPRESSO_ADDITIONAL_CHECKS
930 "RegularDecomposition"));
static int coord(std::string const &s)
auto make_flat_set(Comparator &&comp)
static void fold_and_reset(Particle &p, BoxGeometry const &box_geo)
Fold coordinates to box and reset the old position.
Vector implementation and trait types for boost qvm interoperability.
Utils::Vector3d const & length() const
Box length.
constexpr bool periodic(unsigned coord) const
Check periodicity in direction.
T get_mi_coord(T a, T b, unsigned coord) const noexcept
Get the minimum-image distance between two coordinates.
void fold_position(Utils::Vector3d &pos, Utils::Vector3i &image_box) const
Fold coordinates to primary simulation box in-place.
auto const & my_right() const
Right (top, back) corner of this nodes local box.
auto const & boundary() const
Boundary information for the local box.
auto const & my_left() const
Left (bottom, front) corner of this nodes local box.
auto const & length() const
Dimensions of the box a single node is responsible for.
void clear()
Remove all elements form container.
bool empty() const
Is the container empty?
T & insert(T const &v)
Insert an element into the container.
iterator erase(iterator it)
Remove element from the list.
std::size_t size() const
Number of elements in the container.
void resize(std::size_t new_size)
Resize container.
DEVICE_QUALIFIER constexpr size_type size() const noexcept
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.
Communicator communicator
This file contains the errorhandling code for severe errors, like a broken bond or illegal parameter ...
#define runtimeErrorMsg()
ParticleRange particles(std::span< Cell *const > cells)
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.
void mark_plan_cells_boundary(HaloPlan const &plan, std::span< Cell *const > local_cells)
Mark plan-exported local cells as boundary (source 2, see mark_boundary_cells).
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.
std::array< mpi::request, 2 > isendrecv(mpi::communicator const &comm, int dest, int stag, const T &sval, int src, int rtag, T &rval)
mpi::status sendrecv(mpi::communicator const &comm, int dest, int stag, const T &sval, int src, int rtag, T &rval)
T product(Vector< T, N > const &v)
DEVICE_QUALIFIER int get_linear_index(int a, int b, int c, Vector3i const &adim)
auto hadamard_division(Vector< T, N > const &a, Vector< U, N > const &b)
auto hadamard_product(Vector< T, N > const &a, Vector< U, N > const &b)
Utils::Vector3i node_grid
Struct holding all information for one particle.
constexpr auto const & pos() const
constexpr auto & pos_at_last_verlet_update()
constexpr auto const & image_box() const
Utils::Vector3i ghost_cell_grid
linked cell grid with ghost frame.
Utils::Vector3d max_cutoff() const override
Utils::Vector3d inv_cell_size
inverse cell_size.
std::vector< Cell * > m_ghost_cells
BoxGeometry const & m_box
Cell * particle_to_cell(Particle const &p) override
auto fully_connected_boundary() const
void resort(bool global, std::vector< ParticleChange > &diff) override
std::vector< Cell > cells
RegularDecomposition(boost::mpi::communicator comm, double range, BoxGeometry const &box_geo, LocalBox const &local_geo, std::optional< std::pair< int, int > > fully_connected)
Utils::Vector3d cell_size
Cell size.
std::span< Cell *const > local_cells() const override
GhostComm::HaloPlan m_halo_plan
Topology-agnostic direct-neighbor halo plan (see make_halo_plan).
std::vector< Cell * > m_local_cells
Utils::Vector3d max_range() const override
std::span< Cell *const > ghost_cells() const override
Utils::Vector3i cell_grid
Grid dimensions per node.
Utils::Vector3i cell_offset
Offset in global grid.
boost::mpi::communicator m_comm