DNDSR 0.1.0.dev1+gcd065ad
Distributed Numeric Data Structure for CFV
Loading...
Searching...
No Matches
ArrayTransformer.hpp
Go to the documentation of this file.
1#pragma once
2/// @file ArrayTransformer.hpp
3/// @brief ParArray (MPI-aware array) and ArrayTransformer (ghost/halo communication).
4/// @par Unit Test Coverage (test_ArrayTransformer.cpp, MPI np=1,2,4)
5/// - ParArray: setMPI, Resize, createGlobalMapping, globalSize, AssertConsistent
6/// - ArrayTransformer pull-based ghost: setFatherSon, createFatherGlobalMapping,
7/// createGhostMapping (pull), createMPITypes, pullOnce
8/// -- layouts: TABLE_StaticFixed, TABLE_Fixed, CSR, std::array compound type
9/// - Persistent pull: initPersistentPull, startPersistentPull, waitPersistentPull,
10/// clearPersistentPull (with father data update between pulls)
11/// - BorrowGGIndexing: second array shares ghost mapping of first
12/// - pushOnce: write to son, push back to father
13/// @par Not Yet Tested
14/// - Push-based createGhostMapping(pushingIndexLocal, pushStarts)
15/// - Persistent push (initPersistentPush, startPersistentPush, etc.)
16/// - clearMPITypes, clearGlobalMapping, clearGhostMapping (independent)
17/// - reInitPersistentPullPush
18/// - getFatherSonData(DeviceBackend), AssertDataType, setDataType
19
20#include "Array.hpp"
22#include "DNDS/Errors.hpp"
23#include "IndexMapping.hpp"
24#include "Profiling.hpp"
25#include <utility>
26#include "VectorUtils.hpp"
27
28namespace DNDS
29{
30 /// @brief Shared pointer to a @ref DNDS::GlobalOffsetsMapping "GlobalOffsetsMapping" (globally replicated).
32 /// @brief Shared pointer to an @ref DNDS::OffsetAscendIndexMapping "OffsetAscendIndexMapping" (per-rank ghost layout).
33 using t_pLGhostMapping = ssp<OffsetAscendIndexMapping>; // TODO: change to unique_ptr and modify corresponding copy constructor/assigner
34
35 /**
36 * @brief MPI-aware @ref DNDS::Array "Array": adds a communicator, rank, and global index mapping.
37 *
38 * @details Inherits everything from @ref DNDS::Array "Array" and layers on:
39 * - an @ref DNDS::MPIInfo "MPIInfo" `mpi` context;
40 * - a @ref DNDS::GlobalOffsetsMapping "GlobalOffsetsMapping" `pLGlobalMapping` that maps local row indices
41 * to the global index space (populated by #createGlobalMapping);
42 * - collective serialization I/O that coordinates writes / reads across ranks.
43 *
44 * Typical usage:
45 * ```cpp
46 * auto father = std::make_shared<ParArray<real, 5>>(mpi);
47 * father->Resize(nLocal);
48 * father->createGlobalMapping(); // collective
49 * index nGlobal = father->globalSize(); // total rows across ranks
50 * ```
51 *
52 * Ghost (halo) data is not managed here; pair with @ref DNDS::ArrayTransformer "ArrayTransformer" or
53 * wrap in an @ref DNDS::ArrayPair "ArrayPair" for that.
54 *
55 * @sa ArrayTransformer, ArrayPair, docs/architecture/array_infrastructure.md.
56 */
57 template <class T, rowsize _row_size = 1, rowsize _row_max = _row_size, rowsize _align = NoAlign>
58 class ParArray : public Array<T, _row_size, _row_max, _align>
59 {
60 public:
65
66 using TArray::Array;
67 // TODO: privatize these
68 /// @brief Shared pointer to the global-offsets table. Populated by
69 /// #createGlobalMapping; may be pointed at an existing table to skip
70 /// the collective setup.
72 /// @brief MPI context associated with this array (must be set before collectives).
75
76 public:
77 // default copy
78 ParArray(const t_self &R) = default;
79 t_self &operator=(const t_self &R) = default;
80
81 // operator= handled automatically
82
83 /// @brief Copy-assign from another ParArray. Shallow copy semantics
84 /// (mirrors @ref DNDS::Array "Array"::clone): shares structural/data buffers.
85 void clone(const t_self &R)
86 {
87 this->operator=(R);
88 }
89
90 public:
91 /// @brief Serialize (write) the parallel array with MPI-aware metadata.
92 ///
93 /// Delegates to Array::WriteSerializer for metadata, structure, and data.
94 /// Additionally for collective (H5) serializers:
95 /// - Writes `sizeGlobal` (sum of all ranks' _size) as a scalar attribute.
96 /// - For CSR: computes global data offsets via MPI_Scan and writes pRowStart
97 /// in global data coordinates as a contiguous (nRowsGlobal+1) dataset.
98 /// Non-last ranks write nRows entries (dropping the redundant tail),
99 /// last rank writes nRows+1 (including the global data total).
100 ///
101 /// Asserts MPI context consistency with the serializer.
102 ///
103 /// @param serializerP Serializer instance.
104 /// @param name Sub-path name for this array.
105 /// @param offset [in] Row-level partitioning (typically ArrayGlobalOffset_Parts).
107 {
108 if (!serializerP->IsPerRank())
109 {
111 mpi == serializerP->getMPI(),
112 fmt::format("ParArray MPI context (rank={}, size={}) doesn't match serializer (rank={}, size={})",
113 mpi.rank, mpi.size, serializerP->GetMPIRank(), serializerP->GetMPISize()));
114 }
115
116 // For collective CSR, compute global data offset and pass to Array
117 // so it skips its own pRowStart write.
118 Serializer::ArrayGlobalOffset dataOffset = Serializer::ArrayGlobalOffset_Unknown;
119 if constexpr (_dataLayout == CSR)
120 {
121 if (!this->IfCompressed())
122 this->Compress();
123 if (!serializerP->IsPerRank() && this->_pRowStart)
124 {
125 index localDataCount = this->_pRowStart->at(this->_size);
130 }
131 }
132
134
135 if (!serializerP->IsPerRank())
136 {
137 auto cwd = serializerP->GetCurrentPath();
138 serializerP->GoToPath(name);
139
140 // Write sizeGlobal
141 index sizeGlobal = 0;
142 MPI::Allreduce(&this->_size, &sizeGlobal, 1, DNDS_MPI_INDEX, MPI_SUM, mpi.comm);
143 serializerP->WriteIndex("sizeGlobal", sizeGlobal);
144
145 // For CSR, write pRowStart in global data coordinates.
146 // Non-last ranks write nRows entries; last rank writes nRows+1.
147 // Total = nRowsGlobal + 1 (no overlap, contiguous).
148 if constexpr (_dataLayout == CSR)
149 {
150 if (dataOffset.isDist())
151 {
153 index nWrite = (mpi.rank == mpi.size - 1) ? (this->_size + 1) : this->_size;
154 auto prsGlobal = std::make_shared<host_device_vector<index>>(nWrite);
155 for (index i = 0; i < nWrite; i++)
156 prsGlobal->at(i) = this->_pRowStart->at(i) + globalDataStart;
157 serializerP->WriteSharedIndexVector("pRowStart", prsGlobal,
158 Serializer::ArrayGlobalOffset_Parts);
159 }
160 }
161
162 serializerP->GoToPath(cwd);
163 }
164 }
165
166 /// @brief Deserialize (read) the parallel array with MPI-aware metadata.
167 ///
168 /// Resolves the input `offset` before delegating to Array::ReadSerializer:
169 /// - EvenSplit: reads `sizeGlobal`, computes even-split range, resolves to
170 /// isDist({localRows, globalRowStart}).
171 /// - CSR with collective serializer: reads per-rank size, computes row offset
172 /// via MPI_Scan, resolves to isDist. This is required because CSR pRowStart
173 /// is stored in global coordinates.
174 /// - Otherwise: passes offset through unchanged.
175 ///
176 /// Asserts MPI context consistency with the serializer.
177 ///
178 /// @param serializerP Serializer instance.
179 /// @param name Sub-path name for this array.
180 /// @param offset [in/out] Row-level offset. EvenSplit is resolved here.
181 /// After return, reflects the resolved row-level position.
183 {
184 if (!serializerP->IsPerRank())
185 {
187 mpi == serializerP->getMPI(),
188 fmt::format("ParArray MPI context (rank={}, size={}) doesn't match serializer (rank={}, size={})",
189 mpi.rank, mpi.size, serializerP->GetMPIRank(), serializerP->GetMPISize()));
190 }
191
192 if (!serializerP->IsPerRank() && !offset.isDist())
193 {
194 if (offset == Serializer::ArrayGlobalOffset_EvenSplit)
195 {
196 // Read sizeGlobal, compute even-split range
197 auto cwd = serializerP->GetCurrentPath();
198 serializerP->GoToPath(name);
199 index sizeGlobal = 0;
200 serializerP->ReadIndex("sizeGlobal", sizeGlobal);
201 serializerP->GoToPath(cwd);
202
205 }
206 else if constexpr (_dataLayout == CSR)
207 {
208 // For CSR with collective serializer, pRowStart is stored in global
209 // coordinates (nRowsGlobal+1 contiguous entries). We must always
210 // resolve to isDist offset so Array reads the correct slice.
211 // Read this rank's _size from the per-rank size dataset, compute
212 // row offset via MPI_Scan, then set isDist offset.
213 auto cwd = serializerP->GetCurrentPath();
214 serializerP->GoToPath(name);
215 std::vector<index> _size_vv;
216 Serializer::ArrayGlobalOffset offsetV = Serializer::ArrayGlobalOffset_Unknown;
217 serializerP->ReadIndexVector("size", _size_vv, offsetV);
218 DNDS_check_throw(_size_vv.size() == 1);
219 index localSize = _size_vv.front();
220 serializerP->GoToPath(cwd);
221
222 index globalEnd = 0;
226 }
227 }
228
229 TArray::ReadSerializer(serializerP, name, offset);
230 }
231
232 private:
233 MPI_Datatype dataType = BasicType_To_MPIIntType<T>().first;
234 MPI_int typeMult = BasicType_To_MPIIntType<T>().second;
235
236 public:
237 /// @brief MPI element datatype used for ghost exchange (deduced from `T`).
238 MPI_Datatype getDataType() { return dataType; }
239 /// @brief Per-element count multiplier that goes with #getDataType.
240 MPI_int getTypeMult() { return typeMult; }
241
242 public:
243 /**
244 * @brief Install the MPI context after default construction.
245 * @details Calls @ref AssertDataType to verify the deduced datatype / multiplier
246 * match `sizeof(T)`.
247 */
248 void setMPI(const MPIInfo &n_mpi)
249 {
250 mpi = n_mpi;
252 }
253
254 /// @brief Mutable MPI context accessor.
256 {
257 return mpi;
258 }
259
260 /// @brief Read-only MPI context accessor.
261 [[nodiscard]] const MPIInfo &getMPI() const
262 {
263 return mpi;
264 }
265
266 /// @brief Override the deduced MPI datatype and element multiplier
267 /// (advanced; needed for custom compound element types).
269 {
270 dataType = n_dType;
271 typeMult = n_TypeMult;
272 }
273
274 /// @brief Default-construct an uninitialised ParArray; call #setMPI and @ref Resize later.
275 ParArray() = default;
276
277 /// @brief Construct a ParArray bound to the given MPI context.
279 {
281 }
282 /// @brief Construct with a custom (MPI datatype, multiplier) pair.
283 /// @details Useful for element types whose in-memory layout differs from
284 /// the default `BasicType_To_MPIIntType<T>()` deduction.
286 : mpi(n_mpi), dataType(n_dType), typeMult(n_TypeMult)
287 {
289 }
290
291 /// @brief Named constructor: sets the object name for tracing/debugging.
292 /// All existing constructor overloads are supported via perfect forwarding.
293 /// Inherited by derived classes (ArrayAdjacency, ArrayEigenVector, etc.)
294 /// through `using t_base::t_base`.
295 ///
296 /// Usage:
297 /// ParArray<index> arr(ObjName{"cell2node"}, mpi);
298 /// ArrayAdjacency<> adj(ObjName{"cell2cell"}, mpi);
299 template <typename... Args>
301 : ParArray(std::forward<Args>(args)...)
302 {
303 this->setObjectName(std::move(objName.name));
304 }
305
306 /// @brief Assert the MPI datatype matches `sizeof(T)` exactly.
307 /// @details Called from constructors / #setMPI / #setDataType. Guards
308 /// against size mismatches that would silently corrupt comms.
310 {
312 MPI_Aint lb;
314 MPI_Type_get_extent(dataType, &lb, &extent);
315 DNDS_check_throw(lb == 0 && extent * typeMult == sizeof(T));
316 }
317
318 /**
319 * @brief Check array consistency across all ranks.
320 *
321 * @details Uses `MPI_Allgather` to verify that row sizes (for `TABLE_Fixed`
322 * and `TABLE_Max` layouts) and MPI type multipliers are the same on every
323 * rank. Intended as a post-setup sanity check before entering ghost exchange.
324 *
325 * @warning Must be called collectively. O(nRanks) memory and communication.
326 * @return true Always (failures are reported via @ref DNDS_check_throw_info).
327 */
329 {
331 MPI::Barrier(mpi.comm); // must be globally existent
332 if constexpr (_dataLayout == TABLE_Max ||
333 _dataLayout == TABLE_Fixed) // must have the same dynamic size
334 {
335 // checking if is uniform across all procs
337 MPI_int rowsizeC = this->RowSizeField();
338 static_assert(sizeof(MPI_int) == sizeof(rowsize));
340 for (auto i : uniformSizes)
341 DNDS_check_throw_info(i == rowsizeC, "sizes not uniform across procs");
342 }
343
344 std::vector<MPI_int> uniform_typeMult(mpi.size);
346 for (auto i : uniform_typeMult)
347 DNDS_check_throw_info(i == typeMult, "typeMults not uniform across procs");
348
349 return true; // currently all errors aborts inside
350 }
351
352 /**
353 * @brief Collective: build the global offsets table.
354 *
355 * @details Every rank broadcasts its local `Size()`; after the call,
356 * #pLGlobalMapping holds the full #GlobalOffsetsMapping::RLengths /
357 * #GlobalOffsetsMapping::ROffsets on every
358 * rank. Must be invoked before #globalSize, @ref DNDS::ArrayTransformer "ArrayTransformer"::createFatherGlobalMapping,
359 * or any CSR collective serialization.
360 *
361 * @warning Must be called collectively on `mpi.comm`.
362 */
363 void createGlobalMapping() // collective;
364 {
366 // phase1.1: create localGlobal mapping (broadcast)
367 pLGlobalMapping = std::make_shared<GlobalOffsetsMapping>();
368 pLGlobalMapping->setMPIAlignBcast(mpi, this->Size());
369 }
370
371 /**
372 * @brief Returns the total global size (sum of sizes across all ranks).
373 *
374 * @note This method was previously collective (using MPI_Allreduce) but is now
375 * non-collective. It requires that the global mapping has been created
376 * first (which is done via the collective createGlobalMapping() method
377 * on the underlying ParArray).
378 *
379 * @pre createGlobalMapping() must have been called on the underlying array.
380 * @return index The global size (cached from global mapping).
381 */
383 {
385 "globalSize() requires global mapping. "
386 "Ensure createGlobalMapping() was called first (typically via ArrayPair operations).");
387 return pLGlobalMapping->globalSize();
388 }
389 };
390 /********************************************************************************************************/
391
392 /********************************************************************************************************/
393
394 /**
395 * @brief Ghost-communication engine for a father / son @ref DNDS::ParArray "ParArray" pair.
396 *
397 * @details Distributed-mesh stencil schemes need data from cells owned by
398 * other ranks. @ref DNDS::ArrayTransformer "ArrayTransformer" stores two @ref DNDS::ParArray "ParArray" pointers -- the
399 * *father* (owned rows) and the *son* (incoming ghost rows) -- plus the
400 * MPI machinery to move data between them.
401 *
402 * ## Setup (done once)
403 * 1. #setFatherSon -- attach the two arrays.
404 * 2. #createFatherGlobalMapping -- collective; populate global offsets.
405 * 3. #createGhostMapping -- specify which global rows this rank needs as ghosts.
406 * 4. #createMPITypes -- build `MPI_Type_create_hindexed` derived
407 * types (or in-situ pack buffers) for send/recv.
408 *
409 * ## Communication
410 * - One-shot: #pullOnce / #pushOnce (short-lived `MPI_Isend`/`MPI_Irecv` pair).
411 * - Persistent: #initPersistentPull -> repeated #startPersistentPull /
412 * #waitPersistentPull cycles -> #clearPersistentPull when done.
413 * Persistent requests avoid re-posting sends and receives on every
414 * iteration, saving per-step overhead.
415 *
416 * ## Reuse
417 * When multiple arrays share the same ghost pattern (e.g. the DOF array
418 * and the gradient array both use the `cell2cell` partition), call
419 * @ref BorrowGGIndexing on the secondary transformer to copy the mapping
420 * without redoing collective setup -- only #createMPITypes must be redone,
421 * because the element size differs.
422 *
423 * @sa ArrayPair, ArrayDof, docs/architecture/array_infrastructure.md.
424 */
425 template <class T, rowsize _row_size = 1, rowsize _row_max = _row_size, rowsize _align = NoAlign>
426 // template <class TArray>
428 {
430
431 public:
434 // using T = TArray::value_type;
435 // static const rowsize _align = TArray::al;
436 // static const rowsize _row_size = TArray::rs;
437 // static const rowsize _row_max = TArray::rm;
438
441
442 /*********************************/
443 /* MEMBER */
444 /*********************************/
445
446 /// @brief MPI context; copied from the attached father array.
448 /// @brief Ghost index mapping (rank-local layout). Populated by #createGhostMapping.
450 /// @brief The "owned" side of the father/son pair.
452 /// @brief The "ghost" side of the father/son pair (receives from other ranks).
454
455 /// @brief Shared pointer to the global offsets table (shared with father).
456 t_pLGlobalMapping pLGlobalMapping; // reference from father
457
458 /// @brief Cached `(count, MPI_Datatype)` pairs for push (son -> father).
460 /// @brief Cached `(count, MPI_Datatype)` pairs for pull (father -> son).
462
463 // ** comm aux info: comm running structures **
464 // TODO: make these aux info (sized) shared and thread-safe
465 /// @brief Persistent request handles for push.
467 /// @brief Persistent request handles for pull.
469 /// @brief Device currently holding push buffers (@ref Unknown if not initialised).
471 /// @brief Device currently holding pull buffers (@ref Unknown if not initialised).
473 /// @brief Number of receive requests in #PushReqVec (the rest are sends).
475 /// @brief Number of receive requests in #PullReqVec.
477 /// @brief Status buffer for push completion.
479 /// @brief Status buffer for pull completion.
481 /// @brief Total bytes sent per push call (for buffer sizing).
482 MPI_Aint pushSendSize;
483 /// @brief Total bytes sent per pull call.
484 MPI_Aint pullSendSize;
485
486 tMPI_intVec pushingSizes; ///< temp: per-peer count for #createMPITypes.
487 tMPI_AintVec pushingDisps; ///< temp: per-peer byte displacements for #createMPITypes.
488 std::vector<index> pushingIndexLocal; ///< for InSituPack strategy
489 std::vector<std::vector<T>> inSituBuffer; ///< for InSituPack strategy
490
491 /*********************************/
492 /* MEMBER */
493 /*********************************/
494
495 /// @brief Copy-assign the transformer state. Persistent requests are
496 /// re-created rather than shared because they point to different
497 /// memory than the source object.
499 {
500 if (this == &R)
501 return *this;
502 // must have commTypeCurrent copied as a result of createMPITypes()
503 commTypeCurrent = R.commTypeCurrent;
504
505 mpi = R.mpi;
507 father = R.father;
508 son = R.son;
509
511
512 // these are shared as results of createMPITypes()
515
516 // ** comm aux info: comm running structures **
517 // PushReqVec;
518 // PullReqVec;
519 // pushDevice;
520 // pullDevice;
521 // nRecvPushReq{-1};
522 // nRecvPullReq{-1};
523 // PushStatVec;
524 // PullStatVec;
525 // pushSendSize;
526 // pullSendSize;
527 // ! check comm aux info status and correctly duplicate them
528 // ! cannot share because point to different data
529 if (R.PullReqVec)
530 this->initPersistentPull();
531 if (R.PushReqVec)
532 this->initPersistentPush();
533
534 // these are createMPITypes() temporaries,
535 // TODO: maybe remove from member?
536 // pushingSizes;
537 // inSituBuffer;
538
539 // comm aux info but created in createMPITypes()
540 // TODO (remove from createMPITypes() maybe?)
541 pushingIndexLocal = R.pushingIndexLocal;
543 return *this;
544 }
545
546 /// @brief Default-construct an empty transformer; attach arrays later via #setFatherSon.
547 ArrayTransformer() = default;
548
549 /// @brief Copy-construct via operator=.
551 {
552 // initial-safe operator= call
553 this->operator=(R);
554 }
555
556 /**
557 * @brief Attach father and son arrays. First setup step.
558 *
559 * @details Both arrays must share the same MPI context and element
560 * MPI datatype. The transformer cannot be used until the remaining
561 * setup calls (#createFatherGlobalMapping, #createGhostMapping,
562 * #createMPITypes) have run.
563 *
564 * @param n_father Owned-side array (must not be null).
565 * @param n_son Ghost-side array (must not be null).
566 */
567 void setFatherSon(const t_pArray &n_father, const t_pArray &n_son)
568 {
569 DNDS_check_throw(n_father && n_son);
570 father = n_father;
571 son = n_son;
572 mpi = father->getMPI();
573 DNDS_check_throw_info(son->getMPI() == father->getMPI(), "MPI inconsistent between father & son");
574 DNDS_check_throw_info(father->getDataType() == son->getDataType(), "MPI datatype inconsistent between father & son");
575 DNDS_check_throw_info(father->getTypeMult() == son->getTypeMult(), "MPI datatype multiplication inconsistent between father & son");
576 DNDS_check_throw_info(father->getDataType() != MPI_DATATYPE_NULL, "MPI datatype invalid");
577 DNDS_check_throw_info(father->getTypeMult() > 0, "MPI datatype multiplication invalid");
578 pLGhostMapping.reset();
579 pLGlobalMapping.reset();
580 pLGlobalMapping = father->pLGlobalMapping;
581 }
582
583 /**
584 * @brief Borrow the ghost and global mapping from another transformer.
585 *
586 * @details Intended for the common case where several arrays share the
587 * same partition (e.g., the DOF array and the gradient array both
588 * live on the same cell partitioning). Copies the shared pointers --
589 * no collective work is performed. After this call, #createMPITypes
590 * still needs to be invoked because the element size differs.
591 *
592 * @tparam TRArrayTrans A compatible transformer type; must expose
593 * `father`, `pLGhostMapping`, `pLGlobalMapping`.
594 */
595 template <class TRArrayTrans>
596 void BorrowGGIndexing(const TRArrayTrans &RArrayTrans)
597 {
598 // DNDS_check_throw(father && Rarray.father); // Rarray's father is not visible...
599 // DNDS_check_throw(father->obtainTotalSize() == Rarray.father->obtainTotalSize());
600 DNDS_check_throw(RArrayTrans.father && father);
601 DNDS_check_throw(RArrayTrans.pLGhostMapping && RArrayTrans.pLGlobalMapping);
602 DNDS_check_throw(RArrayTrans.father->Size() == father->Size());
603 pLGhostMapping = RArrayTrans.pLGhostMapping;
604 pLGlobalMapping = RArrayTrans.pLGlobalMapping;
605 father->pLGlobalMapping = RArrayTrans.pLGlobalMapping;
606 }
607
608 /// @brief Collective: build the global offsets table on the father array.
609 /// @details Thin wrapper over `father->createGlobalMapping()` that also
610 /// caches the pointer in this transformer. Second setup step.
612 {
613 father->createGlobalMapping();
614 pLGlobalMapping = father->pLGlobalMapping;
615 }
616
617 /** @brief create ghost by pulling data
618 * @details
619 * pulling data indicates the data put in son (received in pulling operation)
620 * pullingIndexGlobal is the global indices in son
621 * pullingIndexGlobal should be mutually different, otherwise behavior undefined
622 *
623 * @warning pullingIndexGlobal is **sorted and deduplicated in-place** by
624 * OffsetAscendIndexMapping. After this call the input vector's element order
625 * is destroyed. If you need to keep the original order (e.g., for a
626 * redistribution mapping), save a copy before calling this method.
627 * The son array after pullOnce() will contain data in the sorted order
628 * of pullingIndexGlobal, NOT in the original input order.
629 */
630 template <class TPullSet>
631 void createGhostMapping(TPullSet &&pullingIndexGlobal) // collective;
632 {
633 DNDS_check_throw(bool(father) && bool(son));
634 DNDS_check_throw_info(bool(father->pLGlobalMapping), "Father needs to createGlobalMapping");
635 pLGlobalMapping = father->pLGlobalMapping;
636 // phase1.2: count how many to pull and allocate the localGhost mapping, fill the mapping
637 // counting could overflow
638 // tMPI_intVec ghostSizes(mpi.size, 0); // == pulling sizes
639 pLGhostMapping = std::make_shared<OffsetAscendIndexMapping>(
640 (*pLGlobalMapping)(mpi.rank, 0), father->Size(),
641 std::forward<TPullSet>(pullingIndexGlobal),
643 mpi);
644 }
645
646 /**
647 * @brief Create the ghost mapping from a *push* specification. Collective.
648 *
649 * @details Each rank supplies, grouped per receiver, the local indices it
650 * will push to that receiver. Row `i` of this rank's father will be sent
651 * to every rank listed for `i` across the CSR `(pushingIndexLocal, pushStarts)`.
652 * The son array is resized to hold the incoming entries on return from
653 * #createMPITypes.
654 *
655 * @param pushingIndexLocal Flat vector of local indices to push, grouped
656 * by receiver in ascending rank order.
657 * @param pushStarts Prefix-sum offsets into `pushingIndexLocal`,
658 * size `mpi.size + 1`.
659 *
660 * @warning Each local index must appear at most once across the entire
661 * CSR, otherwise the resulting ghost layout is undefined.
662 */
663 template <class TPushSet, class TPushStart>
664 void createGhostMapping(TPushSet &&pushingIndexLocal, TPushStart &&pushStarts) // collective;
665 {
666 DNDS_check_throw(bool(father) && bool(son));
667 DNDS_check_throw_info(bool(father->pLGlobalMapping), "Father needs to createGlobalMapping");
668 pLGlobalMapping = father->pLGlobalMapping;
669 // phase1.2: calculate over pushing
670 // counting could overflow
671 pLGhostMapping = std::make_shared<OffsetAscendIndexMapping>(
672 (*pLGlobalMapping)(mpi.rank, 0), father->Size(),
673 std::forward<TPushSet>(pushingIndexLocal),
674 std::forward<TPushStart>(pushStarts),
676 mpi);
677 }
678 /**
679 * @brief Collective: build the MPI derived datatypes (or in-situ buffers)
680 * that describe the ghost send/recv layout. Resizes the son array.
681 *
682 * @details Fourth (and final) setup step. Consumes the per-rank push
683 * sizes and the ghost mapping produced by #createGhostMapping, then:
684 * - for @ref HIndexed: builds `MPI_Type_create_hindexed` types that
685 * describe the scattered memory layout of the rows being sent and
686 * received;
687 * - for @ref InSituPack: allocates contiguous pack buffers.
688 *
689 * Also resizes `son` to hold exactly the incoming ghost rows.
690 *
691 * @pre `father`, `son`, #pLGlobalMapping, #pLGhostMapping are set.
692 * @post `pPullTypeVec` and `pPushTypeVec` (or the in-situ buffers) are
693 * valid; son has been resized.
694 */
695 void createMPITypes() // collective;
696 {
697 DNDS_check_throw(bool(father) && bool(son));
699 commTypeCurrent = MPI::CommStrategy::Instance().GetArrayStrategy();
700 if (commTypeCurrent == MPI::CommStrategy::HIndexed)
701 father->Compress(); //! assure CSR is in compressed form
702 // TODO: support comm for uncompressed: add in-situ packaging working mode
703 // TODO: support actual MAX arrays' size communicating: append comm types: ? needed?
704 // TODO: add manual packaging mode
705
706 /*********************************************/ // starts to deal with actual byte sizes
707
708 //*phase2.1: build push sizes and push disps
709 index nSend = pLGhostMapping->pushingIndexGlobal.size();
710 pushingSizes.resize(nSend); // pushing sizes xx in bytes xx now in num of remove_all_extents_t<T>
711
712 pushingDisps.resize(nSend); // pushing disps in bytes
713
715 auto fatherDataStart = father->operator[](0);
716 if (commTypeCurrent == MPI::CommStrategy::InSituPack)
717 pushingIndexLocal.resize(nSend);
718 for (index i = 0; i < index(nSend); i++)
719 {
720 MPI_int rank = -1;
721 index loc = -1;
722 bool found = pLGhostMapping->search(pLGhostMapping->pushingIndexGlobal[i], rank, loc);
723 DNDS_check_throw_info(found && rank == -1, "must be at local main"); // must be at local main
724 pushingDisps[i] = (father->operator[](loc) - father->operator[](0)) * sizeof(T); //* in bytes
725 if constexpr (_dataLayout == CSR)
726 pushingSizes[i] = father->RowSizeField(loc) * father->getTypeMult();
727 if constexpr (isTABLE_Max(_dataLayout)) //! init sizes
728 pushingSizes[i] = father->RowSize(loc) * father->getTypeMult();
729 if constexpr (isTABLE_Fixed(_dataLayout))
730 pushingSizes[i] = father->RowSizeField() * father->getTypeMult();
731
732 if (commTypeCurrent == MPI::CommStrategy::InSituPack)
733 pushingIndexLocal[i] = loc;
734 }
735 // PrintVec(pushingSizes, std::cout);
736 // std::cout << std::endl;
737
738 //*phase2.2: be informed of pulled sub-indexer
739 // equals to: building pullingSizes and pullingDisps, bytes size and disps of ghost
740 // - legacy: indexer.buildAsGhostAlltoall(father->indexer, pushingSizes, *pLGhostMapping, mpi); // cascade from father
741 auto do_son_resizing = [&]()
742 {
743 auto &LGhostMapping = *pLGhostMapping;
744 index ghostArraySiz = LGhostMapping.ghostStart[LGhostMapping.ghostStart.size() - 1];
745 DNDS_check_throw(mpi.size == LGhostMapping.ghostStart.size() - 1);
746 if constexpr (_dataLayout == TABLE_StaticFixed)
747 {
748 son->Resize(ghostArraySiz);
749 return;
750 }
751 if constexpr (_dataLayout == TABLE_Fixed)
752 {
753 son->Resize(ghostArraySiz, father->RowSize()); // using father's row size
754 return;
755 }
756 if constexpr (_dataLayout == TABLE_Max)
757 {
758 son->Resize(ghostArraySiz, father->RowSizeMax());
759 // and go on for non-uniform resizing
760 }
761 if constexpr (_dataLayout == TABLE_StaticMax)
762 {
763 son->Resize(ghostArraySiz);
764 // and go on for non-uniform resizing
765 }
766
767 // obtain pulling sizes with pushing sizes
768 tMPI_intVec pullingSizes(ghostArraySiz);
769 MPI_Alltoallv(pushingSizes.data(), LGhostMapping.pushIndexSizes.data(), LGhostMapping.pushIndexStarts.data(), MPI_INT,
770 pullingSizes.data(), LGhostMapping.ghostSizes.data(), LGhostMapping.ghostStart.data(), MPI_INT,
771 mpi.comm);
772
773 // std::cout << LGhostMapping.gStarts().size() << std::endl;
774 if constexpr (_dataLayout == CSR)
775 son->Resize(ghostArraySiz, [&](index i)
776 { return pullingSizes[i] / father->getTypeMult(); });
777 if constexpr (_dataLayout == TABLE_Max)
778 {
779 son->Resize(ghostArraySiz, father->RowSizeMax());
780 for (index i = 0; i < son->Size(); i++)
781 son->ResizeRow(i, pullingSizes[i] / father->getTypeMult());
782 }
783 if constexpr (_dataLayout == TABLE_StaticMax)
784 {
785 son->Resize(ghostArraySiz);
786 for (index i = 0; i < son->Size(); i++)
787 son->ResizeRow(i, pullingSizes[i] / father->getTypeMult());
788 }
789 // is actually pulling disps, but is contiguous anyway
790
791 // DNDS_MPI_InsertCheck(mpi);
792 // std::cout << mpi.rank << " VEC ";
793 // PrintVec(pullingSizes, std::cout);
794 // std::cout << std::endl;
795 // DNDS_MPI_InsertCheck(mpi);
796
797 // note that Rowstart and pullingSizes are in bytes
798 // pullingSizes is actual but Rowstart is before indexModder(), use indexModder[] to invert
799 };
800 do_son_resizing();
801
802 // phase3: create and register MPI types of pushing and pulling
803 if constexpr (isTABLE_Max(_dataLayout)) // convert back to full pushing sizes
804 {
805 for (auto &i : pushingSizes)
806 i = son->RowSizeField() * father->getTypeMult();
807 }
808
809 if (commTypeCurrent == MPI::CommStrategy::HIndexed) // record types
810 {
813 for (MPI_int r = 0; r < mpi.size; r++)
814 {
815 /************************************************************/
816 // push
817 MPI_int pushNumber = pLGhostMapping->pushIndexSizes[r];
818 // std::cout << "PN" << pushNumber << std::endl;
819 MPI_Aint *pPushDisps = pushingDisps.data() + pLGhostMapping->pushIndexStarts[r];
820 MPI_int *pPushSizes = pushingSizes.data() + pLGhostMapping->pushIndexStarts[r];
821 index sumPushSizes = 0; // using upgraded integer
822 for (MPI_int i = 0; i < pushNumber; i++)
823 sumPushSizes += pPushSizes[i];
824 if (sumPushSizes > 0) // if no actuall data is to be sent
825 {
826 // std::cout <<mpi.rank<< " pushSlice " << pPushDisps[0] << outputDelim << pPushSizes[0] << std::endl;
827
828 // if (mpi.rank == 0)
829 // {
830 // std::cout << "pushing to " << r << " size" << pushNumber << "\n";
831 // for (int i = 0; i < pushNumber; i++)
832 // std::cout << "b[" << i << "] = " << pPushSizes[i] << std::endl;
833 // for (int i = 0; i < pushNumber; i++)
834 // std::cout << "d[" << i << "] = " << pPushDisps[i] << std::endl;
835 // }
836 // std::cout << "=== PUSH TYPE : " << mpi.rank << " from " << r << std::endl;
837
838 MPI_Datatype dtype;
839 int sizeof_T = MPI_UNDEFINED;
840 MPI_Type_size(father->getDataType(), &sizeof_T);
841 DNDS_check_throw(sizeof_T != MPI_UNDEFINED);
842 auto [n_number, new_Sizes, new_Disps] =
843 optimize_hindexed_layout(pushNumber, pPushSizes, pPushDisps, sizeof_T);
844 MPI_Type_create_hindexed(n_number, new_Sizes.data(), new_Disps.data(), father->getDataType(), &dtype);
845 // MPI_Type_create_hindexed(PushDispsMPI.size(), PushSizesMPI.data(), PushDispsMPI.data(), father->getDataType(), &dtype);
846
847 MPI_Type_commit(&dtype);
848 pPushTypeVec->push_back(std::make_pair(r, dtype));
849 // OPT: could use MPI_Type_create_hindexed_block to save some space
850 }
851 /************************************************************/
852 // pull
853 std::array<MPI_Aint, 1> pullDisp;
854
855 std::array<MPI_int, 1> pullSizes; // same as pushSizes
856 auto gRPtr = son->operator[](index(pLGhostMapping->ghostStart[r + 1]));
857 auto gLPtr = son->operator[](index(pLGhostMapping->ghostStart[r]));
858 auto gStartPtr = son->operator[](index(0));
859 auto ghostSpan = gRPtr - gLPtr;
860 auto ghostStart = gLPtr - gStartPtr;
861 DNDS_check_throw(ghostSpan < INT_MAX && ghostStart < INT_MAX);
862 pullSizes[0] = MPI_int(ghostSpan) * father->getTypeMult();
863 pullDisp[0] = ghostStart * sizeof(T);
864 if (pullSizes[0] > 0)
865 {
866 // std::cout << "=== PULL TYPE : " << mpi.rank << " from " << r << std::endl;
867 MPI_Datatype dtype;
868
869 MPI_Type_create_hindexed(1, pullSizes.data(), pullDisp.data(), father->getDataType(), &dtype);
870
871 // std::cout << mpi.rank << " pullSlice " << pullDisp[0] << outputDelim << pullBytes[0] << std::endl;
872 MPI_Type_commit(&dtype);
873 pPullTypeVec->push_back(std::make_pair(r, dtype));
874 }
875 }
876 pPullTypeVec->shrink_to_fit();
877 pPushTypeVec->shrink_to_fit();
878
879 pushingDisps.clear();
880 pushingSizes.clear(); // no need
881 }
882 else if (commTypeCurrent == MPI::CommStrategy::CommStrategy::InSituPack)
883 {
884 // could simplify some info on sparse comm?
885 pushingDisps.clear();
886 }
887 else
888 {
889 DNDS_check_throw(false);
890 }
891 }
892 /******************************************************************************************************************************/
893
895 {
896 T *fatherData = nullptr;
897 T *sonData = nullptr;
898 if (B == DeviceBackend::Unknown)
899 {
900 fatherData = father->data();
901 sonData = son->data();
902 }
903 else
904 {
905 DNDS_check_throw(B == father->device());
906 DNDS_check_throw(B == son->device());
907 switch (B)
908 {
910 {
911 fatherData = father->data(B);
912 sonData = son->data(B);
913 }
914 break;
915#ifdef DNDS_USE_CUDA
916 case DeviceBackend::CUDA:
917 {
918 //!
919 DNDS_check_throw_info(MPI::isCudaAware(), "we require CUDA-aware MPI here");
920 fatherData = father->data(B);
921 sonData = son->data(B);
922 }
923 break;
924#endif
925 default:
926 {
927 DNDS_check_throw(false);
928 }
929 }
930 }
931 return std::make_pair(fatherData, sonData);
932 }
933
934 /******************************************************************************************************************************/
935 /**
936 * @brief Initialise persistent, non-blocking, non-buffered MPI requests
937 * for the push direction (son -> father).
938 *
939 * @details Once persistent requests are created, many push cycles may
940 * be run via #startPersistentPush / #waitPersistentPush without
941 * re-posting sends and receives.
942 *
943 * @pre #createMPITypes has been called; #pPullTypeVec and #pPushTypeVec are valid.
944 * @post @ref PushReqVec is populated with `MPI_Send_init` / `MPI_Recv_init` requests.
945 *
946 * @param B Device backend for the send/recv buffers
947 * (`DeviceBackend::Unknown` to use host; requires CUDA-aware
948 * MPI for non-host backends).
949 * @warning After init, the raw data pointers of both father and son
950 * must remain stable until #clearPersistentPush is called.
951 */
953 {
954 if (commTypeCurrent == MPI::CommStrategy::HIndexed)
955 {
956 pushDevice = B;
957 auto [fatherData, sonData] = getFatherSonData(B);
958 // DNDS_check_throw(pPullTypeVec && pPushTypeVec);
959 DNDS_check_throw(pPullTypeVec.use_count() > 0 && pPushTypeVec.use_count() > 0);
960 pushSendSize = 0;
961 auto nReqs = pPullTypeVec->size() + pPushTypeVec->size();
962 // DNDS_check_throw(nReqs > 0);
964 PushReqVec->resize(nReqs, (MPI_REQUEST_NULL)), PushStatVec.resize(nReqs);
965 nRecvPushReq = 0;
966 for (auto ip = 0; ip < pPushTypeVec->size(); ip++)
967 {
968 auto dtypeInfo = (*pPushTypeVec)[ip];
969 MPI_int rankOther = dtypeInfo.first;
970 MPI_int tag = rankOther + mpi.rank;
971 MPI_Recv_init(fatherData, 1, dtypeInfo.second, rankOther, tag, mpi.comm, PushReqVec->data() + pPullTypeVec->size() + ip);
972 // cascade from father
973 nRecvPushReq++;
974 }
975 for (auto ip = 0; ip < pPullTypeVec->size(); ip++)
976 {
977 auto dtypeInfo = (*pPullTypeVec)[ip];
978 MPI_int rankOther = dtypeInfo.first;
979 MPI_int tag = rankOther + mpi.rank;
980
981#ifndef ARRAY_COMM_USE_BUFFERED_SEND
982 // MPI_Ssend_init
983 MPI_Send_init
984#else
985 MPI_Bsend_init
986#endif
987 (sonData, 1, dtypeInfo.second, rankOther, tag, mpi.comm, PushReqVec->data() + ip);
988
989 // cascade from father
990
991 // // buffer calculate //!deprecated because of size limit
992 // MPI_Aint csize;
993 // MPI_Pack_external_size(1, dtypeInfo.second, mpi.comm, &csize);
994 // csize += MPI_BSEND_OVERHEAD;
995 // DNDS_check_throw(MAX_MPI_Aint - pushSendSize >= csize && csize > 0);
996 // pushSendSize += csize * 2;
997 }
998#ifdef ARRAY_COMM_USE_BUFFERED_SEND
999 // MPIBufferHandler::Instance().claim(pushSendSize, mpi.rank);
1000#endif
1001 }
1002 else if (commTypeCurrent == MPI::CommStrategy::CommStrategy::InSituPack)
1003 {
1004 // could simplify some info on sparse comm?
1006 }
1007 else
1008 {
1009 DNDS_check_throw(false);
1010 }
1011 }
1012 /******************************************************************************************************************************/
1013
1014 /******************************************************************************************************************************/
1015 /**
1016 * @brief Initialise persistent, non-blocking MPI requests for the pull
1017 * direction (father -> son). Counterpart to #initPersistentPush.
1018 *
1019 * @pre #createMPITypes has been called; #pPullTypeVec and #pPushTypeVec are valid.
1020 * @post @ref PullReqVec is populated.
1021 *
1022 * @param B Device backend for the send/recv buffers.
1023 * @warning Raw data pointers for both father and son must remain stable
1024 * until #clearPersistentPull.
1025 */
1027 {
1028 if (commTypeCurrent == MPI::CommStrategy::HIndexed)
1029 {
1030 pullDevice = B;
1031 auto [fatherData, sonData] = getFatherSonData(B);
1032 // DNDS_check_throw(pPullTypeVec && pPushTypeVec);
1033 DNDS_check_throw(pPullTypeVec.use_count() > 0 && pPushTypeVec.use_count() > 0);
1034 auto nReqs = pPullTypeVec->size() + pPushTypeVec->size();
1035 pullSendSize = 0;
1036 // DNDS_check_throw(nReqs > 0);
1038 PullReqVec->resize(nReqs, (MPI_REQUEST_NULL)), PullStatVec.resize(nReqs);
1039 nRecvPullReq = 0;
1040 for (typename decltype(pPullTypeVec)::element_type::size_type ip = 0; ip < pPullTypeVec->size(); ip++)
1041 {
1042 auto dtypeInfo = (*pPullTypeVec)[ip];
1043 MPI_int rankOther = dtypeInfo.first;
1044 MPI_int tag = rankOther + mpi.rank; //! receives a lot of messages, this distinguishes them
1045 // std::cout << mpi.rank << " Recv " << rankOther << std::endl;
1046 MPI_Recv_init(sonData, 1, dtypeInfo.second, rankOther, tag, mpi.comm, PullReqVec->data() + ip);
1047 nRecvPullReq++;
1048 // std::cout << *(real *)(dataGhost.data() + 8 * 0) << std::endl;
1049 // cascade from father
1050 }
1051 for (typename decltype(pPullTypeVec)::element_type::size_type ip = 0; ip < pPushTypeVec->size(); ip++)
1052 {
1053 auto dtypeInfo = (*pPushTypeVec)[ip];
1054 MPI_int rankOther = dtypeInfo.first;
1055 MPI_int tag = rankOther + mpi.rank;
1056 // std::cout << mpi.rank << " Send " << rankOther << std::endl;
1057#ifndef ARRAY_COMM_USE_BUFFERED_SEND
1058 // MPI_Ssend_init
1059 MPI_Send_init
1060#else
1061 MPI_Bsend_init
1062#endif
1063 (fatherData, 1, dtypeInfo.second, rankOther, tag, mpi.comm, PullReqVec->data() + pPullTypeVec->size() + ip);
1064 // std::cout << *(real *)(data.data() + 8 * 1) << std::endl;
1065 // cascade from father
1066
1067 // // buffer calculate //!deprecated because of size limit
1068 // MPI_Aint csize;
1069 // MPI_Pack_external_size(1, dtypeInfo.second, mpi.comm, &csize);
1070 // csize += MPI_BSEND_OVERHEAD * 8;
1071 // DNDS_check_throw(MAX_MPI_Aint - pullSendSize >= csize && csize > 0);
1072 // pullSendSize += csize * 2;
1073 }
1074#ifdef ARRAY_COMM_USE_BUFFERED_SEND
1075 // MPIBufferHandler::Instance().claim(pullSendSize, mpi.rank);
1076#endif
1077 }
1078 else if (commTypeCurrent == MPI::CommStrategy::CommStrategy::InSituPack)
1079 {
1080 // could simplify some info on sparse comm?
1082 }
1083 else
1084 {
1085 DNDS_check_throw(false);
1086 }
1087 }
1088 /******************************************************************************************************************************/
1089
1091 {
1092 if (B != DeviceBackend::Unknown)
1093 DNDS_check_throw_info(false, "in-situ pack not yet implemented for device");
1094 nRecvPushReq = 0;
1095 for (MPI_int r = 0; r < mpi.size; r++)
1096 {
1097 // push
1098 MPI_int pushNumber = pLGhostMapping->pushIndexSizes[r];
1099 // std::cout << "PN" << pushNumber << std::endl;
1100 if (pushNumber > 0)
1101 {
1102 index nPushData{0};
1103 for (index i = 0; i < pushNumber; i++)
1104 {
1105 auto loc = pushingIndexLocal.at(pLGhostMapping->pushIndexStarts[r] + i);
1106 index nPush = 0;
1107 if constexpr (_dataLayout == CSR)
1108 nPush = father->RowSizeField(loc);
1109 if constexpr (isTABLE_Max(_dataLayout)) //! init sizes
1110 nPush = father->RowSize(loc);
1111 if constexpr (isTABLE_Fixed(_dataLayout))
1112 nPush = father->RowSizeField();
1113 nPushData += nPush;
1114 }
1115 inSituBuffer.emplace_back(nPushData);
1116 PushReqVec->emplace_back(MPI_REQUEST_NULL);
1117 MPI_Irecv(inSituBuffer.back().data(), nPushData * father->getTypeMult(), father->getDataType(),
1118 r, mpi.rank + r, mpi.comm, &PushReqVec->back());
1119 nRecvPushReq++;
1120 }
1121 }
1122 for (MPI_int r = 0; r < mpi.size; r++)
1123 {
1124 // pull
1125 MPI_Aint pullDisp;
1126 MPI_int pullSize; // same as pushSizes
1127 auto gRPtr = son->operator[](index(pLGhostMapping->ghostStart[r + 1]));
1128 auto gLPtr = son->operator[](index(pLGhostMapping->ghostStart[r]));
1129 auto ghostSpan = gRPtr - gLPtr;
1130 pullSize = MPI_int(ghostSpan);
1131
1132 if (pullSize > 0)
1133 {
1134 PushReqVec->emplace_back(MPI_REQUEST_NULL);
1135 MPI_Issend(gLPtr, pullSize * father->getTypeMult(), father->getDataType(), r, r + mpi.rank, mpi.comm, &PushReqVec->back());
1136 }
1137 }
1138 }
1139
1140 /// @brief Start all persistent push requests (@ref MPI_Startall).
1141 /// @param B Device backend; must match the one passed to #initPersistentPush.
1143 {
1144 if (commTypeCurrent == MPI::CommStrategy::HIndexed)
1145 {
1147 // req already ready
1148 DNDS_check_throw(nRecvPushReq <= PushReqVec->size());
1149 if (!PushReqVec->empty())
1150 {
1151 if (MPI::CommStrategy::Instance().GetUseAsyncOneByOne())
1152 {
1153 }
1154 else
1155 MPI_Startall(PushReqVec->size(), PushReqVec->data());
1156 }
1157 }
1158 else if (commTypeCurrent == MPI::CommStrategy::InSituPack)
1159 {
1161 }
1162 else
1163 {
1164 DNDS_check_throw(false);
1165 }
1167#ifdef ARRAY_COMM_USE_BUFFERED_SEND
1169#endif
1170
1172 }
1173
1175 {
1176 if (B != DeviceBackend::Unknown)
1177 DNDS_check_throw_info(false, "in-situ pack not yet implemented for device");
1178 nRecvPullReq = 0;
1179 for (MPI_int r = 0; r < mpi.size; r++)
1180 {
1181 // pull
1182 MPI_Aint pullDisp;
1183 MPI_int pullSize; // same as pushSizes
1184 auto gRPtr = son->operator[](index(pLGhostMapping->ghostStart[r + 1]));
1185 auto gLPtr = son->operator[](index(pLGhostMapping->ghostStart[r]));
1186 auto ghostSpan = gRPtr - gLPtr;
1187 pullSize = MPI_int(ghostSpan);
1188
1189 if (pullSize > 0)
1190 {
1191 PullReqVec->emplace_back(MPI_REQUEST_NULL);
1192 MPI_Irecv(gLPtr, pullSize * father->getTypeMult(), father->getDataType(), r, r + mpi.rank, mpi.comm, &PullReqVec->back());
1193 nRecvPullReq++;
1194 }
1195 }
1196 for (MPI_int r = 0; r < mpi.size; r++)
1197 {
1198 // push
1199 MPI_int pushNumber = pLGhostMapping->pushIndexSizes[r];
1200 // std::cout << "PN" << pushNumber << std::endl;
1201 if (pushNumber > 0)
1202 {
1203 index nPushData{0};
1204 for (index i = 0; i < pushNumber; i++)
1205 {
1206 auto loc = pushingIndexLocal.at(pLGhostMapping->pushIndexStarts[r] + i);
1207 index nPush = 0;
1208 if constexpr (_dataLayout == CSR)
1209 nPush = father->RowSizeField(loc);
1210 if constexpr (isTABLE_Max(_dataLayout)) //! init sizes
1211 nPush = father->RowSize(loc);
1212 if constexpr (isTABLE_Fixed(_dataLayout))
1213 nPush = father->RowSizeField();
1214 nPushData += nPush;
1215 }
1216 inSituBuffer.emplace_back(nPushData);
1217 nPushData = 0;
1218 for (index i = 0; i < pushNumber; i++)
1219 {
1220 auto loc = pushingIndexLocal.at(pLGhostMapping->pushIndexStarts[r] + i);
1221 index nPush = 0;
1222 if constexpr (_dataLayout == CSR)
1223 nPush = father->RowSizeField(loc);
1224 if constexpr (isTABLE_Max(_dataLayout)) //! init sizes
1225 nPush = father->RowSize(loc);
1226 if constexpr (isTABLE_Fixed(_dataLayout))
1227 nPush = father->RowSizeField();
1228 std::copy((*father)[loc], (*father)[loc] + nPush, inSituBuffer.back().begin() + nPushData);
1229 nPushData += nPush;
1230 }
1231 PullReqVec->emplace_back(MPI_REQUEST_NULL);
1232 MPI_Issend(inSituBuffer.back().data(), nPushData * father->getTypeMult(), father->getDataType(),
1233 r, mpi.rank + r, mpi.comm, &PullReqVec->back());
1234 }
1235 }
1236 }
1237
1238 /// @brief Start all persistent pull requests (@ref MPI_Startall).
1239 /// @details After this call the sends/recvs are in flight; call
1240 /// #waitPersistentPull to consume the incoming ghost data.
1241 /// @param B Device backend; must match the one passed to #initPersistentPull.
1243 {
1245 if (commTypeCurrent == MPI::CommStrategy::HIndexed)
1246 {
1248 DNDS_check_throw(nRecvPullReq <= PullReqVec->size());
1249 // req already ready
1250 if (!PullReqVec->empty())
1251 {
1252 if (MPI::CommStrategy::Instance().GetUseAsyncOneByOne())
1253 {
1254 }
1255 else
1256 MPI_Startall(int(PullReqVec->size()), PullReqVec->data());
1257 }
1258 }
1259 else if (commTypeCurrent == MPI::CommStrategy::InSituPack)
1260 {
1262 }
1263 else
1264 {
1265 DNDS_check_throw(false);
1266 }
1267#ifdef ARRAY_COMM_USE_BUFFERED_SEND
1269#endif
1270
1272 }
1273
1274 /// @brief Wait for all outstanding persistent push requests to complete.
1276 {
1277 if (MPI::CommStrategy::Instance().GetUseStrongSyncWait())
1280 PushStatVec.resize(PushReqVec->size());
1281#ifdef ARRAY_COMM_USE_BUFFERED_SEND
1283#endif
1284 if (commTypeCurrent == MPI::CommStrategy::HIndexed)
1285 {
1286 // data alright
1287 if (!PushReqVec->empty())
1288 {
1289 DNDS_check_throw(nRecvPushReq <= PushReqVec->size());
1290 if (MPI::CommStrategy::Instance().GetUseAsyncOneByOne())
1291 {
1292 MPI_Startall(nRecvPushReq, PushReqVec->data());
1293 for (int iReq = nRecvPushReq; iReq < PushReqVec->size(); iReq++)
1294 {
1295 MPI_Start(&PushReqVec->operator[](iReq));
1296 MPI_Wait(&PushReqVec->operator[](iReq), MPI_STATUS_IGNORE);
1297 }
1298 MPI::WaitallAuto(nRecvPushReq, PushReqVec->data(), MPI_STATUSES_IGNORE);
1299 }
1300 else
1301 MPI::WaitallAuto(PushReqVec->size(), PushReqVec->data(), MPI_STATUSES_IGNORE);
1302 }
1303 }
1304 else if (commTypeCurrent == MPI::CommStrategy::InSituPack)
1305 {
1306 if (B != DeviceBackend::Unknown)
1307 DNDS_check_throw_info(false, "in-situ pack not yet implemented for device");
1308 if (!PushReqVec->empty())
1309 MPI::WaitallAuto(PushReqVec->size(), PushReqVec->data(), PushStatVec.data());
1310 auto bufferVec = inSituBuffer.begin();
1311 for (MPI_int r = 0; r < mpi.size; r++)
1312 {
1313 // push
1314 DNDS_check_throw(bufferVec < inSituBuffer.end());
1315 MPI_int pushNumber = pLGhostMapping->pushIndexSizes[r];
1316 // std::cout << "PN" << pushNumber << std::endl;
1317 if (pushNumber > 0)
1318 {
1319 index nPushData = 0;
1320 for (index i = 0; i < pushNumber; i++)
1321 {
1322 auto loc = pushingIndexLocal.at(pLGhostMapping->pushIndexStarts[r] + i);
1323 index nPush = 0;
1324 if constexpr (_dataLayout == CSR)
1325 nPush = father->RowSizeField(loc);
1326 if constexpr (isTABLE_Max(_dataLayout)) //! init sizes
1327 nPush = father->RowSize(loc);
1328 if constexpr (isTABLE_Fixed(_dataLayout))
1329 nPush = father->RowSizeField();
1330 std::copy(bufferVec->begin() + nPushData, bufferVec->begin() + nPushData + nPush, (*father)[loc]);
1331 nPushData += nPush;
1332 }
1333 bufferVec++;
1334 }
1335 }
1336 inSituBuffer.clear();
1337 PushReqVec->clear();
1338 }
1339 else
1340 {
1341 DNDS_check_throw(false);
1342 }
1344 if (MPI::CommStrategy::Instance().GetUseStrongSyncWait())
1346 }
1347 /// @brief Wait for all outstanding persistent pull requests. After this
1348 /// returns, the son array holds fresh ghost data.
1350 {
1352 PullStatVec.resize(PullReqVec->size());
1353
1354#ifdef ARRAY_COMM_USE_BUFFERED_SEND
1356#endif
1357 if (commTypeCurrent == MPI::CommStrategy::HIndexed)
1358 {
1359 // data alright
1360 if (!PullReqVec->empty())
1361 {
1362 DNDS_check_throw(nRecvPullReq <= PullReqVec->size());
1363 if (MPI::CommStrategy::Instance().GetUseAsyncOneByOne())
1364 {
1365 MPI_Startall(nRecvPullReq, PullReqVec->data());
1366 for (int iReq = nRecvPullReq; iReq < PullReqVec->size(); iReq++)
1367 {
1368 MPI_Start(&PullReqVec->operator[](iReq));
1369 MPI_Wait(&PullReqVec->operator[](iReq), MPI_STATUS_IGNORE);
1370 // if (mpi.rank == 0)
1371 // log() << "waited a req" << std::endl;
1372 }
1373 MPI::WaitallAuto(nRecvPullReq, PullReqVec->data(), MPI_STATUSES_IGNORE);
1374 }
1375 else
1376 {
1377 MPI::WaitallAuto(PullReqVec->size(), PullReqVec->data(), MPI_STATUSES_IGNORE);
1378 }
1379 }
1380 }
1381 else if (commTypeCurrent == MPI::CommStrategy::InSituPack)
1382 {
1383 if (B != DeviceBackend::Unknown)
1384 DNDS_check_throw_info(false, "in-situ pack not yet implemented for device");
1385 if (!PullReqVec->empty())
1386 MPI::WaitallAuto(PullReqVec->size(), PullReqVec->data(), PullStatVec.data());
1387 // std::cout << "waiting DONE" << std::endl;
1388 inSituBuffer.clear();
1389 PullReqVec->clear();
1390 }
1391 else
1392 {
1393 DNDS_check_throw(false);
1394 }
1396 }
1397
1398 /// @brief Wait on outstanding push requests then free them.
1399 void clearPersistentPush() // collective;
1400 {
1402 PushReqVec->clear(); // stat vec is left untouched here
1403 }
1404 /// @brief Wait on outstanding pull requests then free them.
1405 void clearPersistentPull() // collective;
1406 {
1408 PullReqVec->clear();
1409 }
1410
1411 /// @brief Release the MPI derived datatypes built by #createMPITypes.
1412 /// Rebuild with another call if you wish to continue using the transformer.
1413 void clearMPITypes() // collective;
1414 {
1415 pPullTypeVec.reset();
1416 pPushTypeVec.reset();
1417 }
1418
1419 /// @brief Drop the shared pointer to the global offsets table.
1420 void clearGlobalMapping() // collective;
1421 {
1422 pLGlobalMapping.reset();
1423 }
1424
1425 /// @brief Drop the ghost mapping (#pLGhostMapping).
1426 void clearGhostMapping() // collective;
1427 {
1428 pLGhostMapping.reset();
1429 }
1430
1431 /// @brief Convenience: init + start + wait + clear a single pull.
1432 /// @details Suitable when ghosts are updated only once (e.g., post-restart);
1433 /// use the persistent API in hot loops.
1434 void pullOnce() // collective;
1435 {
1440 }
1441
1442 /// @brief Convenience: init + start + wait + clear a single push.
1443 void pushOnce() // collective;
1444 {
1449 }
1450
1451 /// @brief Re-initialise persistent requests; useful after rebuilding MPI
1452 /// types but wanting to resume persistent comms. Idempotent w.r.t.
1453 /// whichever direction(s) were previously initialised.
1455 {
1456 bool clearedPull{false}, clearedPush{false};
1457 if (!PullReqVec->empty())
1458 {
1459 clearedPull = true;
1462 }
1463 if (!PushReqVec->empty())
1464 {
1465 clearedPush = true;
1468 }
1469 if (clearedPull)
1471 if (clearedPush)
1473 }
1474 };
1475
1476 /// @brief Type trait computing the ArrayTransformer type for a given Array type.
1477 template <class TArray>
1482
1483 template <class TArray>
1485
1486}
Core 2D variable-length array container with five data layouts.
#define DNDS_INDEX_MAX
Definition Defines.hpp:116
Device memory abstraction layer with backend-specific storage and factory creation.
Assertion / error-handling macros and supporting helper functions.
#define DNDS_assert_info(expr, info)
Debug-only assertion with an extra std::string info message.
Definition Errors.hpp:113
#define DNDS_check_throw_info(expr, info)
Same as DNDS_check_throw but attaches a user-supplied info message to the thrown std::runtime_error.
Definition Errors.hpp:96
#define DNDS_check_throw(expr)
Runtime check active in both debug and release builds. Throws std::runtime_error if expr evaluates to...
Definition Errors.hpp:89
Global-to-local index mapping for distributed arrays.
Wall-clock performance timer and running scalar statistics utilities.
Small utilities for MPI-indexed type layouts (hindexed optimisation).
Non-owning device-callable view of an Array, specialised per DeviceBackend.
static const DataLayout _dataLayout
Ghost-communication engine for a father / son ParArray pair.
DeviceBackend pullDevice
Device currently holding pull buffers (Unknown if not initialised).
ssp< tMPI_typePairVec > pPushTypeVec
Cached (count, MPI_Datatype) pairs for push (son -> father).
tMPI_statVec PullStatVec
Status buffer for pull completion.
MPI_int nRecvPushReq
Number of receive requests in PushReqVec (the rest are sends).
ArrayTransformer()=default
Default-construct an empty transformer; attach arrays later via setFatherSon.
void createGhostMapping(TPushSet &&pushingIndexLocal, TPushStart &&pushStarts)
Create the ghost mapping from a push specification. Collective.
void startPersistentPull(DeviceBackend B=DeviceBackend::Unknown)
Start all persistent pull requests (MPI_Startall).
void clearPersistentPush()
Wait on outstanding push requests then free them.
t_pLGlobalMapping pLGlobalMapping
Shared pointer to the global offsets table (shared with father).
TSelf & operator=(const TSelf &R)
Copy-assign the transformer state. Persistent requests are re-created rather than shared because they...
t_pArray son
The "ghost" side of the father/son pair (receives from other ranks).
t_pLGhostMapping pLGhostMapping
Ghost index mapping (rank-local layout). Populated by createGhostMapping.
void createMPITypes()
Collective: build the MPI derived datatypes (or in-situ buffers) that describe the ghost send/recv la...
void pullOnce()
Convenience: init + start + wait + clear a single pull.
MPIInfo mpi
MPI context; copied from the attached father array.
void setFatherSon(const t_pArray &n_father, const t_pArray &n_son)
Attach father and son arrays. First setup step.
MPI_int nRecvPullReq
Number of receive requests in PullReqVec.
tMPI_statVec PushStatVec
Status buffer for push completion.
void initPersistentPull(DeviceBackend B=DeviceBackend::Unknown)
Initialise persistent, non-blocking MPI requests for the pull direction (father -> son)....
void __InSituPackStartPush(DeviceBackend B)
void reInitPersistentPullPush()
Re-initialise persistent requests; useful after rebuilding MPI types but wanting to resume persistent...
void startPersistentPush(DeviceBackend B=DeviceBackend::Unknown)
Start all persistent push requests (MPI_Startall).
void pushOnce()
Convenience: init + start + wait + clear a single push.
MPI_Aint pushSendSize
Total bytes sent per push call (for buffer sizing).
DeviceBackend pushDevice
Device currently holding push buffers (Unknown if not initialised).
tMPI_intVec pushingSizes
temp: per-peer count for createMPITypes.
ssp< MPIReqHolder > PushReqVec
Persistent request handles for push.
void clearPersistentPull()
Wait on outstanding pull requests then free them.
ssp< MPIReqHolder > PullReqVec
Persistent request handles for pull.
MPI_Aint pullSendSize
Total bytes sent per pull call.
void clearGlobalMapping()
Drop the shared pointer to the global offsets table.
static const DataLayout _dataLayout
void clearGhostMapping()
Drop the ghost mapping (pLGhostMapping).
void __InSituPackStartPull(DeviceBackend B)
void createGhostMapping(TPullSet &&pullingIndexGlobal)
create ghost by pulling data
std::vector< std::vector< T > > inSituBuffer
for InSituPack strategy
void waitPersistentPush(DeviceBackend B=DeviceBackend::Unknown)
Wait for all outstanding persistent push requests to complete.
void BorrowGGIndexing(const TRArrayTrans &RArrayTrans)
Borrow the ghost and global mapping from another transformer.
auto getFatherSonData(DeviceBackend B)
ssp< tMPI_typePairVec > pPullTypeVec
Cached (count, MPI_Datatype) pairs for pull (father -> son).
void initPersistentPush(DeviceBackend B=DeviceBackend::Unknown)
Initialise persistent, non-blocking, non-buffered MPI requests for the push direction (son -> father)...
t_pArray father
The "owned" side of the father/son pair.
void clearMPITypes()
Release the MPI derived datatypes built by createMPITypes. Rebuild with another call if you wish to c...
ArrayTransformer(const TSelf &R)
Copy-construct via operator=.
void createFatherGlobalMapping()
Collective: build the global offsets table on the father array.
void waitPersistentPull(DeviceBackend B=DeviceBackend::Unknown)
Wait for all outstanding persistent pull requests. After this returns, the son array holds fresh ghos...
tMPI_AintVec pushingDisps
temp: per-peer byte displacements for createMPITypes.
std::vector< index > pushingIndexLocal
for InSituPack strategy
const T & at(index iRow, rowsize iCol) const
Bounds-checked element read (not device-callable because CSR decompressed uses std::vector::at which ...
DNDS_DEVICE_CALLABLE T * data()
Raw pointer to the start of the flat data buffer.
Core 2D variable-length array container, the storage foundation of DNDSR.
Definition Array.hpp:97
index _size
Definition Array.hpp:143
t_pRowStart _pRowStart
Definition Array.hpp:136
void WriteSerializer(Serializer::SerializerBaseSSP serializerP, const std::string &name, Serializer::ArrayGlobalOffset offset, Serializer::ArrayGlobalOffset dataOffset=Serializer::ArrayGlobalOffset_Unknown)
Serialize (write) array data to a serializer.
Definition Array.hpp:939
bool IfCompressed() const
(CSR only) Whether the array is in packed / flat form.
Definition Array.hpp:294
ArrayDeviceView< B, T, _row_size, _row_max, _align > t_deviceView
Definition Array.hpp:1240
Array()=default
Default-constructed array: empty, no storage.
ssp< t_RowSizes > t_pRowSizes
Definition Array.hpp:133
void Compress()
Layout-polymorphic compress: no-op for non-CSR, calls CSRCompress for CSR.
Definition Array.hpp:355
index Size() const
Number of rows currently stored. O(1).
Definition Array.hpp:171
rowsize RowSizeField() const
"Logical" row-field width used by derived (Eigen) arrays: max for padded layouts, uniform width for f...
Definition Array.hpp:255
iterator< B > end()
Iterator one past the last row, viewed on device backend B.
Definition Array.hpp:1334
void ReadSerializer(Serializer::SerializerBaseSSP serializerP, const std::string &name, Serializer::ArrayGlobalOffset &offset)
Convenience overload that discards the dataOffset output.
Definition Array.hpp:992
void unclaim(MPI_int cs)
Release cs previously-claim ed bytes (only updates accounting; does not shrink the buffer).
Definition MPI.hpp:618
static MPIBufferHandler & Instance()
Access the process-wide singleton.
Definition MPI.cpp:107
void claim(MPI_Aint cs, int reportRank=0)
Reserve cs additional bytes, growing and re-attaching the MPI buffer if needed. reportRank is only us...
Definition MPI.hpp:595
static CommStrategy & Instance()
Access the process-wide singleton.
Definition MPI.cpp:415
ArrayCommType GetArrayStrategy()
Current array-pack strategy.
Definition MPI.cpp:421
ArrayCommType
Which derived-type strategy ArrayTransformer should use.
Definition MPI.hpp:733
@ InSituPack
Manually pack / unpack into contiguous buffers.
Definition MPI.hpp:736
@ UnknownArrayCommType
Sentinel / uninitialised.
Definition MPI.hpp:734
@ HIndexed
Use MPI_Type_create_hindexed derived types (default).
Definition MPI.hpp:735
void setObjectName(const std::string &name)
Definition Defines.hpp:232
MPI-aware Array: adds a communicator, rank, and global index mapping.
void setDataType(MPI_Datatype n_dType, MPI_int n_TypeMult)
Override the deduced MPI datatype and element multiplier (advanced; needed for custom compound elemen...
ParArray()=default
Default-construct an uninitialised ParArray; call setMPI and Resize later.
t_self & operator=(const t_self &R)=default
void setMPI(const MPIInfo &n_mpi)
Install the MPI context after default construction.
typename TArray::t_pRowSizes t_pRowSizes
void clone(const t_self &R)
Copy-assign from another ParArray. Shallow copy semantics (mirrors Arrayclone): shares structural/dat...
static const DataLayout _dataLayout
MPI_int getTypeMult()
Per-element count multiplier that goes with getDataType.
void AssertDataType()
Assert the MPI datatype matches sizeof(T) exactly.
index globalSize() const
Returns the total global size (sum of sizes across all ranks).
bool AssertConsistent()
Check array consistency across all ranks.
const MPIInfo & getMPI() const
Read-only MPI context accessor.
MPI_Datatype getDataType()
MPI element datatype used for ghost exchange (deduced from T).
t_pLGlobalMapping pLGlobalMapping
Shared pointer to the global-offsets table. Populated by createGlobalMapping; may be pointed at an ex...
void WriteSerializer(Serializer::SerializerBaseSSP serializerP, const std::string &name, Serializer::ArrayGlobalOffset offset)
Serialize (write) the parallel array with MPI-aware metadata.
void createGlobalMapping()
Collective: build the global offsets table.
ParArray(const t_self &R)=default
ParArray(ObjName objName, Args &&...args)
Named constructor: sets the object name for tracing/debugging. All existing constructor overloads are...
MPIInfo mpi
MPI context associated with this array (must be set before collectives).
ParArray(MPI_Datatype n_dType, MPI_int n_TypeMult, const MPIInfo &n_mpi)
Construct with a custom (MPI datatype, multiplier) pair.
ParArray(const MPIInfo &n_mpi)
Construct a ParArray bound to the given MPI context.
MPIInfo & getMPI()
Mutable MPI context accessor.
void ReadSerializer(Serializer::SerializerBaseSSP serializerP, const std::string &name, Serializer::ArrayGlobalOffset &offset)
Deserialize (read) the parallel array with MPI-aware metadata.
@ Comm
Catch-all MPI comm.
Definition Profiling.hpp:37
static PerformanceTimer & Instance()
Access the process-wide singleton.
Definition Profiling.cpp:9
void StartTimer(TimerType t)
Record the current wall time in the "start" slot for timer t.
Definition Profiling.cpp:15
void StopTimer(TimerType t)
Add (now - start) to the accumulated time for timer t.
Definition Profiling.cpp:25
Describes one rank's window into a globally-distributed dataset.
bool isDist() const
Whether this descriptor carries a real distributed offset (rather than a sentinel like Offset_Parts).
MPI_int Allgather(const void *sendbuf, MPI_int sendcount, MPI_Datatype sendtype, void *recvbuf, MPI_int recvcount, MPI_Datatype recvtype, MPI_Comm comm)
Wrapper over MPI_Allgather.
Definition MPI.cpp:230
MPI_int WaitallAuto(MPI_int count, MPI_Request *reqs, MPI_Status *statuses)
Wait on an array of requests, choosing between MPI_Waitall and the lazy-poll variant based on CommStr...
Definition MPI.cpp:283
MPI_int Scan(const void *sendbuf, void *recvbuf, MPI_int count, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm)
Wrapper over MPI_Scan (inclusive prefix reduction).
Definition MPI.cpp:220
MPI_int Allreduce(const void *sendbuf, void *recvbuf, MPI_int count, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm)
Wrapper over MPI_Allreduce.
Definition MPI.cpp:203
MPI_int Barrier(MPI_Comm comm)
Wrapper over MPI_Barrier.
Definition MPI.cpp:248
bool isCudaAware()
Runtime probe: is the current MPI implementation configured with CUDA-aware support?...
Definition MPI.cpp:298
ssp< SerializerBase > SerializerBaseSSP
the host side operators are provided as implemented
ssp< GlobalOffsetsMapping > t_pLGlobalMapping
Shared pointer to a GlobalOffsetsMapping (globally replicated).
std::vector< rowsize > t_RowsizeVec
Vector of row widths (one rowsize per row).
Definition Defines.hpp:159
ssp< OffsetAscendIndexMapping > t_pLGhostMapping
Shared pointer to an OffsetAscendIndexMapping (per-rank ghost layout).
const MPI_Datatype DNDS_MPI_INDEX
MPI datatype matching index (= MPI_INT64_T).
Definition MPI.hpp:90
constexpr bool isTABLE_Fixed(DataLayout lo)
Whether the layout has a uniform row width (no per-row size needed).
DeviceBackend
Enumerates the backends a DeviceStorage / Array can live on.
@ Unknown
Unset / sentinel.
@ Host
Plain CPU memory.
tMPI_indexVec tMPI_AintVec
Alias for tMPI_indexVec to match MPI_Aint terminology.
Definition MPI.hpp:58
int32_t rowsize
Row-width / per-row element-count type (signed 32-bit).
Definition Defines.hpp:109
DataLayout
Enumeration of the five concrete data layouts supported by Array.
@ TABLE_StaticFixed
Fixed row width, known at compile time.
@ TABLE_Max
Padded variable rows; max width set at runtime.
@ CSR
Compressed Sparse Row (flat buffer + row-start index).
@ TABLE_StaticMax
Padded variable rows; max width fixed at compile time.
@ TABLE_Fixed
Fixed row width, set at runtime (uniform across rows).
std::pair< index, index > EvenSplitRange(int rank, int nRanks, index nGlobal)
Split a global range [0, nGlobal) evenly among nRanks workers.
Definition Defines.hpp:129
constexpr bool isTABLE_Max(DataLayout lo)
Whether the layout is a padded-max variant (uses _pRowSizes).
int64_t index
Global row / DOF index type (signed 64-bit; handles multi-billion-cell meshes).
Definition Defines.hpp:107
tMPI_sizeVec tMPI_intVec
Alias for tMPI_sizeVec; used where the name "int vec" reads better.
Definition MPI.hpp:54
std::shared_ptr< T > ssp
Shortened alias for std::shared_ptr used pervasively in DNDSR.
Definition Defines.hpp:138
auto optimize_hindexed_layout(index o_size, TBlkSiz *blk_sizes, TDisp *disps, TSizeof sizeofElem)
Coalesce contiguous blocks in an MPI_Type_create_hindexed layout.
std::vector< MPI_Status > tMPI_statVec
Vector of MPI_Status, for MPI_Waitall / MPI_Testall.
Definition MPI.hpp:61
typename ArrayTransformerType< TArray >::Type ArrayTransformerType_t
int MPI_int
MPI counterpart type for MPI_int (= C int). Used for counts and ranks in MPI calls.
Definition MPI.hpp:43
Type trait computing the ArrayTransformer type for a given Array type.
Lightweight bundle of an MPI communicator and the calling rank's coordinates.
Definition MPI.hpp:215
int size
Number of ranks in comm (-1 until initialised).
Definition MPI.hpp:221
int rank
This rank's 0-based index within comm (-1 until initialised).
Definition MPI.hpp:219
MPI_Comm comm
The underlying MPI communicator handle.
Definition MPI.hpp:217
static ssp< MPIReqHolder > create(Args &&...args)
Only public path to construct an instance.
Definition MPI.hpp:410
static ssp< MPITypePairHolder > create(Args &&...args)
Only public path to construct an instance; forwards to the private constructor.
Definition MPI.hpp:358
Tag type for naming objects created via make_ssp.
Definition Defines.hpp:249
tVec r(NCells)