27#ifdef ESPRESSO_CALIPER
37#include <boost/mpi/collectives.hpp>
38#include <boost/mpi/nonblocking.hpp>
83std::span<ParticleList *const>
as_span(std::vector<ParticleList *>
const &v) {
84 return {v.data(), v.size()};
88std::vector<ParticleList *>
region_cells(std::vector<SendRegion>
const &send) {
89 std::vector<ParticleList *> cells;
90 cells.reserve(send.size());
91 for (
auto const &r : send)
92 cells.emplace_back(r.cell);
119#if defined(ESPRESSO_ADDITIONAL_CHECKS) and not defined(NDEBUG)
122 if (not regions.empty()) {
123 auto const &ref = regions.front().shift;
124 for (
auto const &r : regions) {
125 assert(r.shift == ref &&
126 "pack_regions: per-region shifts differ within one NeighborComm "
127 "-- packer must be generalized for per-region shift support");
168 auto const &comm = plan.
comm;
169 int const comm_size = comm.size();
170 int const my_rank = comm.rank();
180 assert(
static_cast<int>(cs.cells.size()) == comm_size);
187 alignas(double)
static char empty_sentinel[
sizeof(
double)];
188 auto const safe_data = [](
CommBuf &b) {
189 return b.
size() != 0u ? b.data() : &empty_sentinel[0];
192 for (
int root = 0; root < comm_size; ++root) {
193 ParticleList *cell = cs.cells[
static_cast<std::size_t
>(root)];
194 auto const cell_span = std::span<ParticleList *const>{&cell, 1};
198 if (my_rank == root) {
199 pack_cells(buf, cell_span, {}, box, data_parts);
200 boost::mpi::broadcast(comm, safe_data(buf),
201 static_cast<int>(buf.
size()), root);
202 boost::mpi::broadcast(comm, buf.
bonds(), root);
206 boost::mpi::broadcast(comm, safe_data(buf),
207 static_cast<int>(buf.
size()), root);
208 boost::mpi::broadcast(comm, buf.
bonds(), root);
215 pack_cells(buf, cell_span, {}, box, data_parts);
216 auto *raw =
reinterpret_cast<double *
>(safe_data(buf));
217 int const count =
static_cast<int>(buf.
size() /
sizeof(double));
218 if (my_rank == root) {
221 auto *recv_raw =
reinterpret_cast<double *
>(safe_data(recv_buf));
222 boost::mpi::reduce(comm, raw, count, recv_raw, std::plus<double>{},
226 boost::mpi::reduce(comm, raw, count, std::plus<double>{}, root);
246#ifdef ESPRESSO_ADDITIONAL_CHECKS
266 "halo_exchange first use"));
276#ifdef ESPRESSO_ROTATION
277 [[maybe_unused]]
bool const force_with_torque =
280 [[maybe_unused]]
bool const force_with_torque =
false;
282 [[maybe_unused]]
bool reducible = (force_only or force_with_torque);
283#ifdef ESPRESSO_BOND_CONSTRAINT
286#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
290 "Combine::Add only valid for reducible parts (FORCE, FORCE|TORQUE, "
294 auto const &comm = plan.
comm;
310 int const tag = main_tag(data_parts);
316 for (std::size_t i = 0; i < n; ++i) {
329#ifdef ESPRESSO_CALIPER
331 CALI_MARK_BEGIN(
"ghost/pack");
333 for (std::size_t i = 0; i < n; ++i) {
336 pack_regions(bufs.
send[i], nc.send, box, data_parts);
343#ifdef ESPRESSO_CALIPER
345 CALI_MARK_END(
"ghost/pack");
353#ifdef ESPRESSO_CALIPER
355 CALI_MARK_BEGIN(
"ghost/post");
357 for (std::size_t i = 0; i < n; ++i) {
361 bufs.
requests.push_back(comm.irecv(nc.peer, tag, bufs.
recv[i].data(),
362 static_cast<int>(bufs.
recv[i].size())));
364 for (std::size_t i = 0; i < n; ++i) {
366 bufs.
requests.push_back(comm.isend(nc.peer, tag, bufs.
send[i].data(),
367 static_cast<int>(bufs.
send[i].size())));
375 for (std::size_t i = 0; i < n; ++i) {
378 comm.irecv(nc.peer, TAG_BONDS, bufs.
recv[i].bonds()));
380 for (std::size_t i = 0; i < n; ++i) {
383 comm.isend(nc.peer, TAG_BONDS, bufs.
send[i].bonds()));
386#ifdef ESPRESSO_CALIPER
388 CALI_MARK_END(
"ghost/post");
413 for (
auto const &lc : plan.
local) {
430 auto pool = std::make_unique<ExchangeBuffers>();
433 st.owned = std::move(pool);
441 auto &bufs = *st.
bufs;
463#ifdef ESPRESSO_CALIPER
465 CALI_MARK_BEGIN(
"ghost/wait");
467 boost::mpi::wait_all(bufs.requests.begin(), bufs.requests.end());
468#ifdef ESPRESSO_CALIPER
470 CALI_MARK_END(
"ghost/wait");
472 CALI_MARK_BEGIN(
"ghost/unpack");
474 for (std::size_t i = 0; i < n; ++i) {
475 auto dst = as_span(bufs.recv_cells[i]);
478#ifdef ESPRESSO_CALIPER
480 CALI_MARK_END(
"ghost/unpack");
492#ifdef ESPRESSO_CALIPER
494 CALI_MARK_BEGIN(
"ghost/wait");
496 for (std::size_t i = 0; i < n; ++i) {
497 bufs.requests[i].wait();
498#ifdef ESPRESSO_CALIPER
500 CALI_MARK_END(
"ghost/wait");
502 CALI_MARK_BEGIN(
"ghost/unpack");
504 auto dst = as_span(bufs.recv_cells[i]);
508#ifdef ESPRESSO_BOND_CONSTRAINT
513#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
518#ifdef ESPRESSO_CALIPER
520 CALI_MARK_END(
"ghost/unpack");
522 CALI_MARK_BEGIN(
"ghost/wait");
528 boost::mpi::wait_all(bufs.requests.begin() +
static_cast<std::ptrdiff_t
>(n),
529 bufs.requests.begin() +
530 static_cast<std::ptrdiff_t
>(2 * n));
531#ifdef ESPRESSO_CALIPER
533 CALI_MARK_END(
"ghost/wait");
535 CALI_MARK_BEGIN(
"ghost/unpack");
537 CALI_MARK_END(
"ghost/unpack");
553#ifdef ESPRESSO_CALIPER
555 CALI_MARK_BEGIN(
"ghost/wait");
559 for (std::size_t i = 0; i < n; ++i)
560 bufs.slot_to_neighbor[i] = i;
563 std::size_t active_end = n;
565 while (active_end > 0) {
566 auto first = bufs.requests.begin();
568 bufs.requests.begin() +
static_cast<std::ptrdiff_t
>(active_end);
570 auto [status, done_it] = boost::mpi::wait_any(first, last);
572#ifdef ESPRESSO_CALIPER
574 CALI_MARK_END(
"ghost/wait");
576 CALI_MARK_BEGIN(
"ghost/unpack");
579 std::size_t
const done_slot =
580 static_cast<std::size_t
>(done_it - bufs.requests.begin());
581 std::size_t
const neighbor_idx = bufs.slot_to_neighbor[done_slot];
584 auto dst = as_span(bufs.recv_cells[neighbor_idx]);
591 if (done_slot != active_end) {
592 std::iter_swap(done_it, bufs.requests.begin() +
593 static_cast<std::ptrdiff_t
>(active_end));
594 std::swap(bufs.slot_to_neighbor[done_slot],
595 bufs.slot_to_neighbor[active_end]);
597#ifdef ESPRESSO_CALIPER
599 CALI_MARK_END(
"ghost/unpack");
601 CALI_MARK_BEGIN(
"ghost/wait");
605#ifdef ESPRESSO_CALIPER
615 CALI_MARK_BEGIN(
"ghost/wait");
617 boost::mpi::wait_all(bufs.requests.begin() +
static_cast<std::ptrdiff_t
>(n),
618 bufs.requests.begin() +
619 static_cast<std::ptrdiff_t
>(2 * n));
620#ifdef ESPRESSO_CALIPER
622 CALI_MARK_END(
"ghost/wait");
624 CALI_MARK_BEGIN(
"ghost/unpack");
626 CALI_MARK_END(
"ghost/unpack");
Asynchronous, split-phase ghost-communication engine.
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.
Class that stores marshalled data for ghost communications.
std::size_t size() const
Returns the number of elements in the non-bond storage.
auto & bonds()
Returns a reference to the bond storage.
void resize(std::size_t new_size)
Resizes the underlying storage s.t.
DEVICE_QUALIFIER constexpr bool empty() const noexcept
DEVICE_QUALIFIER constexpr reference front()
Ghost particles and particle exchange.
@ GHOSTTRANS_RATTLE
transfer ParticleRattle
@ GHOSTTRANS_DIPFLD
transfer dipole field tracking data
@ GHOSTTRANS_PARTNUM
resize the receiver particle arrays to the size of the senders
@ GHOSTTRANS_POSITION
transfer ParticlePosition
@ GHOSTTRANS_FORCE
transfer ParticleForce
@ GHOSTTRANS_TORQUE
transfer torque (reduced with force; runtime-conditional)
int main_tag(unsigned data_parts)
std::span< ParticleList *const > as_span(std::vector< ParticleList * > const &v)
View a list of cell pointers as a span for the packing routines.
void run_collective(HaloPlan const &plan, BoxGeometry const &box, unsigned data_parts, ExchangeOp op)
Collective (broadcast/reduce-sum) section.
void pack_regions(CommBuf &buf, std::vector< SendRegion > const ®ions, BoxGeometry const &box, unsigned data_parts)
Pack all send regions of one NeighborComm into a single buffer.
GhostTag
MPI tags for ghost communications.
std::vector< ParticleList * > region_cells(std::vector< SendRegion > const &send)
Extract the plain cell pointers from a list of send regions.
void unpack_cells(CommBuf &buf, std::span< ParticleList *const > cells, BoxGeometry const &box_geo, unsigned data_parts)
Unpack particle data from a communication buffer into cells.
void pack_cells(CommBuf &buf, std::span< ParticleList *const > cells, Utils::Vector3d const &shift, BoxGeometry const &box_geo, unsigned data_parts)
Pack particle data from cells into a communication buffer.
void local_cell_copy(ParticleList &src, ParticleList &dst, Utils::Vector3d const &shift, BoxGeometry const &box_geo, unsigned data_parts)
Copy particle data from src to dst applying a ghost shift.
std::size_t calc_transmit_size(BoxGeometry const &box_geo, unsigned data_parts)
Calculate the per-particle transmit size for the given data parts.
void add_forces(CommBuf &buf, std::span< ParticleList *const > cells, unsigned data_parts)
Add forces (and optionally torques) from a communication buffer to particles in cells.
void add_dip_fld(CommBuf &buf, std::span< ParticleList *const > cells)
Add dipole fields from a communication buffer to particles.
void halo_exchange_finish(GhostExchange &st)
Complete a halo exchange: run same-rank copies (overlapping the in-flight messages),...
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).
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.
void add_rattle(CommBuf &buf, std::span< ParticleList *const > cells)
Add rattle corrections from a communication buffer to particles.
std::vector< std::string > validate_halo_plan_symmetry(HaloPlan const &plan)
Cross-rank symmetry check for a HaloPlan.
Reusable particle packing/unpacking for ghost communications.
Persistent per-neighbor buffer pool for halo exchanges.
std::vector< CommBuf > send
Per-neighbor packed send buffers (index-aligned with plan->neighbors).
std::vector< std::size_t > slot_to_neighbor
Scratch index map for the Overwrite (wait_any) path in halo_exchange_finish: maps active request slot...
std::vector< CommBuf > recv
Per-neighbor recv buffers (index-aligned with plan->neighbors).
std::vector< std::vector< ParticleList * > > send_cells
Scratch cell-pointer arrays for the packing routines.
std::vector< std::vector< ParticleList * > > recv_cells
std::vector< boost::mpi::request > requests
Outstanding non-blocking send/recv requests (cleared before each use).
Opaque handle for one in-flight halo exchange.
ExchangeBuffers * bufs
Non-owning pointer to the active buffer pool (caller's or owned).
bool symmetry_validated
Lazily-set flag for the once-per-plan symmetry check.
std::optional< CollectiveSection > collective
boost::mpi::communicator comm
std::vector< NeighborComm > neighbors
std::vector< LocalComm > local