Documentation 6b54b78464
Loading...
Searching...
No Matches
ndflattener.hpp
Go to the documentation of this file.
1/*!
2 * \file ndflattener.hpp
3 * \brief Flatten pointer-to-pointer-... arrays for MPI communication
4 * \author M. Aehle
5 * \version 8.5.0 "Harrier"
6 *
7 * SU2 Project Website: https://su2code.github.io
8 *
9 * The SU2 Project is maintained by the SU2 Foundation
10 * (http://su2foundation.org)
11 *
12 * Copyright 2012-2026, SU2 Contributors (cf. AUTHORS.md)
13 *
14 * SU2 is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU Lesser General Public
16 * License as published by the Free Software Foundation; either
17 * version 2.1 of the License, or (at your option) any later version.
18 *
19 * SU2 is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 * Lesser General Public License for more details.
23 *
24 * You should have received a copy of the GNU Lesser General Public
25 * License along with SU2. If not, see <http://www.gnu.org/licenses/>.
26 */
27
28#pragma once
29
30#include <cassert>
31#include <iostream>
32#include <sstream>
33#include <utility>
34#include <vector>
35
36#include "../containers/C2DContainer.hpp"
37#include "../parallelization/mpi_structure.hpp"
38
39/// \addtogroup Containers
40/// @{
41
42// --- Usage
43/*! \page ndflattener_usage NdFlattener usage
44 *
45 * # What is an NdFlattener?
46 *
47 * NdFlattener is a container to store a deep copy of multidimensional data in a single object that provides a
48 * generic []...[] interface. The data and their structure are stored in few contiguous arrays, allowing for an
49 * efficient MPI gathering operation.
50 *
51 * # Which problem does it solve?
52 * In SU2, there is a lot of multidimensional data: data depending on K>1 indices, like a wall property
53 * depending on the rank, zone index, and marker index.
54 *
55 * The data is often stored non-contiguously as a "tree":
56 * - an array of pointers, each one of which points to
57 * - (repeat the previous line another (K-2) times)
58 * - an array of data.
59 *
60 * The individual arrays' sizes are usually unrelated to each other. Therefore in addition to this tree of
61 * pointers-to-...-to-data, usually there is a tree of pointers-to-...-to-size for each lower level.
62 * Instead of arrays, there might be getter functions for the data and the sizes.
63 *
64 * In order to use multidimensional data behind such a non-generic interface as an argument in a function call,
65 * we usually make the callee aware of the interface and expose all the trees: Either as separate pointers to arrays
66 * (or functions), which is tedious, or by handing an object in which they naturally live, which exposes much more
67 * information.
68 *
69 * If the callee is an MPI communication operation, this is not possible. Here, a frequent pattern is to copy the data
70 * into a single contiguous array, which is then communicated alongside with the structure (lengths of the arrays).
71 *
72 * The NdFlattener container is a systematic way to store multidimensional data in a single contiguous array, and
73 * the structure in few additional contiguous arrays. It exposes them through a generic []...[] interface and allows
74 * for an efficient MPI_Allgatherv-like operation.
75 *
76 * To demonstrate the usage of NdFlattener, let us store a property of type su2double that depends on
77 * the rank, zone index, and marker index.
78 *
79 * # Form the local NdFlattener
80 * by a "recursive function" like this:
81 *
82 * auto f_local =
83 * make_pair( nZone, [=](unsigned long iZone){
84 * return make_pair( (Geometry of iZone)->GetnMarker() , [=](unsigned long iMarker){
85 * return YOUR_PROPERTY(iZone, iMarker);
86 * });
87 * });
88 * NdFlattener<2> nd_local(f_local);
89 *
90 * It might be safer to explicitly capture what you need in the lambda expressions.
91 *
92 * You can also construct an NdFlattener without any arguments, and use NdFlattener::initialize(f_local).
93 *
94 * f_local is
95 * - a pair of an (exclusive) upper bound for the first index and a function that maps the first index to
96 * - a pair of an (exclusive) upper bound for the second index and a function that maps the second index to
97 * - (iterate this if there are more layers)
98 * - the value corresponding to these indices.
99 * The template parameter "2" is the number of indices. The data type (default: su2double) and the index type
100 * (default: unsigned long) are further optional template parameters.
101 *
102 * # Form the global NdFlattener
103 * by "collective communication" like this:
104 *
105 * NdFlattener<3> nd_global(Nd_MPI_Environment(), &nd_local);
106 *
107 * nd_global's first index is the rank, then the indices of nd_local follow.
108 *
109 * The struct Nd_MPI_Environment contains the relevant information about the parallel environment. The default
110 * values are suitable to communicate su2double data. In order to communicate data of another MPI datatype,
111 * use e.g. Nd_MPI_Environment(MPI_UNSIGNED_LONG).
112 *
113 * You can also construct an NdFlattener without any arguments, and use NdFlattener::initialize(mpi_env, &nd_local).
114 *
115 * # Refreshing
116 * If only the data but not the indices (i.e. the structure of sublists' lengths) have changed, you can refresh
117 * them with the call
118 *
119 * nd_local.refresh(f_local); // , or
120 * nd_global(Nd_MPI_Environment(), &nd_local); // respectively.
121 *
122 * An NdFlattener constructed or initialized from a "recursive function" or "collective communication" must be
123 * refreshed in the same way.
124 *
125 * We also provide a function NdFlattener::initialize_or_refresh, that initializes if not initialized, and refreshes
126 * otherwise.
127 *
128 * # Look-up
129 * You can access the NdFlattener via a "[]...[]" interface that resembles multidimensional arrays
130 * or pointer-to-pointer-... arrays. If you provide all of the K indices, this returns a reference
131 * to the data element. If you provide less than K indices, you obtain an IndexAccumulator object
132 * to which you can later pass more indices. With respect to the above example:
133 *
134 * std::cout << nd_global[rank][zone][marker]; // e.g. 4.0
135 * auto nd_g_rank = nd_global[rank];
136 * nd_g_rank[zone][marker] *= 2.0;
137 * std::cout << nd_g_rank[zone][marker]; // e.g. 8.0
138 *
139 * You may use the IndexAccumulator repeatedly as long as the accessed NdFlattener exists.
140 * As NdFlatteners are always deep copies, the above block would not change nd_local or the non-contiguous
141 * data from which it was copied.
142 *
143 * Each index is checked to lie in the correct range dictated by the NdFlattener and the
144 * previous indices. You can obtain the minimal overflowing index with `size`:
145 *
146 * auto nd_g_rank = nd_global[rank];
147 * std::cout << nd_g_rank[zone].size();
148 *
149 * When all indices except the last one are fixed, the corresponding data is stored contiguously and a pointer
150 * to this 1D array can be retrieved by
151 *
152 * nd_global[rank][zone].data()
153 *
154 * # Constness
155 * The reference returned by the last `operator[]`, and the pointer returned by `data()`, are const if
156 * - the accessed NdFlattener object was const, or
157 * - one of the intermediate IndexAccumulator objects was qualified as const.
158 *
159 * This reflects the semantic meaning of const:
160 *
161 * NdFlattener<2> nd2(...);
162 *
163 * template<class T> set(T& t) { t[0] = 1.0; }
164 * set( nd2[0] ); // OK
165 *
166 * template<class T> set_const(T const& t) { t[0] = 1.0; }
167 * set_const( nd2[0] ); // does not compile
168 *
169 * # Unit tests
170 * The interface described here is tested in UnitTests/Common/toolboxes/ndflattener_tests.cpp, which may also
171 * serve as an addition example of how to use the NdFlattener.
172 */
173
174// --- Implementation details---
175/*! \page ndflattener_implementationdetails Implementation details of NdFlattener
176 * If your array has K indices, instantiate this class with template parameter K,
177 * which is derived recursively from this class with template parameter (K-1).
178 * In each layer, there is an array: Of type Data for K=1, and of type Index for K>1.
179 *
180 * The data array of K=1 contains the values of the array A in lexicographic ordering:
181 * [0]...[0][0][0], [0]...[0][0][1], ..., [0]...[0][0][something],
182 * [0]...[0][1][0], [0]...[0][1][1], ..., [0]...[0][1][something],
183 * ...,
184 * [0]...[1][0][0], [0]...[1][0][1], ..., [0]...[1][0][something],
185 * ... ...,
186 * [1]...[0][0][0], [1]...[0][0][1], ..., [1]...[0][0][something],
187 * ... ... ...
188 * Let us call each row in this representation a "last-index list".
189 * Note that they might have different lengths, so "something" might stand for different
190 * values here. Last-index lists can also be empty.
191 *
192 * The indices array of K=2 contains the indices of the (K=1)-data array at which a new
193 * last-index list starts. If a last-index list is empty, the are repetitive entries in the
194 * indices array.
195 *
196 * The indices array of K=3 contains the indices of the (K=2)-indices array at which the
197 * last-but-two index increases by one or drops to zero. If the last-but-two index is increased
198 * by more than one, there are repetitive entries.
199 *
200 * Etc. etc, up to the indices array at layer K.
201 *
202 * To form such a structure, we typically iterate twice through the pointer-to-pointer-...
203 * array: The first time we get to know how much space to reserve in each layer, then we
204 * allocate it and during the second iteration we copy the data.
205 */
206
207template <size_t K, typename Data_t_ = su2double, typename Index_t_ = unsigned long>
208class NdFlattener;
209
210/*! Allgatherv wrapper defaulting to a copy operation if there is only a single MPI rank.
211 *
212 * Introducing this was necessary because MPICH's Allgatherv behaved unexpectedly if there
213 * is only one MPI rank (seemingly ignoring displs[0] != 0).
214 */
215static inline void SU2_MPI_Allgatherv_safe(const void* sendbuf, int sendcount, SU2_MPI::Datatype sendtype,
216 void* recvbuf, const int* recvcounts, const int* displs,
217 SU2_MPI::Datatype recvtype, SU2_MPI::Comm comm) {
218 if (SU2_MPI::GetSize() == 1) {
219 SU2_MPI::CopyData(sendbuf, recvbuf, sendcount, sendtype, displs[0]);
220 } else {
221 SU2_MPI::Allgatherv(sendbuf, sendcount, sendtype, recvbuf, recvcounts, displs, recvtype, comm);
222 }
223}
224
225/*! \struct Nd_MPI_Environment
226 * \brief Contains information for the collective communication of NdFlatteners.
227 *
228 * The default arguments in the constructor are chosen in a sensible way:
229 * - To communicate su2double data, just call Nd_MPI_Environment().
230 * - To communicate unsigned long data, call Nd_MPI_Environment(MPI_UNSIGNED_LONG).
231 */
258
259namespace helpers {
260/*! \class IndexAccumulator
261 * \brief Data structure holding an offset for the NdFlattener, to provide a []...[]-interface.
262 * \details Derived from IndexAccumulator_Base, specifying the operator[] method:
263 * - For N==1, the structure has already read all indices but one. So after this method has read the last
264 * index, return a (possibly const) reference to the data. An additional function data() should be provided.
265 * - For N>1, more indices have to be read, return an IndexAccumulator<N-1,Nd_t_lower>. Nd_t_lower is the
266 * NdFlattener type with one index less. Nd_t_lower is const-qualified if Nd_t is const-qualified, or if the
267 * index look-up is performed in a const IndexAccumulator.
268 * \tparam N - Number of missing parameters
269 * \tparam Nd_t - Type of the accessed NdFlattener, should be NdFlattener<N,...>
270 * \tparam Check - if true, raise error if index to operator[] is not in range
271 */
272/*! \class IndexAccumulator_Base
273 * \brief Parent class of IndexAccumulator.
274 * \details IndexAccumulator provides the operator[] method.
275 */
276template <size_t N_, typename Nd_t_, bool Check>
278 public:
279 static constexpr size_t N = N_;
280 using Nd_t = Nd_t_;
281 using Index_t = typename Nd_t::Index_t;
282
283 /*! \brief Return exclusive upper bound for next index.
284 */
285 Index_t size() const { return size_; }
286
287 protected:
288 Nd_t& nd; /*!< \brief The accessed NdFlattener. */
289 const Index_t offset; /*!< \brief Index in the currently accessed layer. */
290 const Index_t size_; /*!< \brief Exclusive upper bound for the next index. */
291
293
295 assert(i < size());
296 if (Check) {
297 if (i >= size()) SU2_MPI::Error("NdFlattener: Index out of range.", CURRENT_FUNCTION);
298 }
299 }
300};
301
302template <size_t N_, typename Nd_t_, bool Check = false>
303class IndexAccumulator : public IndexAccumulator_Base<N_, Nd_t_, Check> {
304 public:
306 static constexpr size_t N = N_;
307 using Nd_t = Nd_t_;
308 using Index_t = typename Nd_t::Index_t;
309
311
312 /*! The Base of NdFlattener<K> is NdFlattener<K-1>, but do also preserve constness.
313 */
314 using Nd_Base_t = su2conditional_t<std::is_const<Nd_t>::value, const typename Nd_t::Base, typename Nd_t::Base>;
315 /*! Return type of operator[]. */
317 /*! Return type of operator[] const. */
318 using LookupType_const = IndexAccumulator<N - 1, const typename Nd_t::Base>;
319 using Base::CheckBound;
320 using Base::nd;
321 using Base::offset;
322 using Base::size;
323
324 /*! \brief Read one more index, checking whether it is in the range dictated by the NdFlattener and
325 * previous indices. Non-const version.
326 * \param[in] i - Index.
327 */
329 CheckBound(i);
330 const Index_t new_offset = nd.GetIndices()[offset + i];
331 const Index_t new_size = nd.GetIndices()[offset + i + 1] - new_offset;
332 return LookupType(nd, new_offset, new_size);
333 }
334 /*! \brief Read one more index, const version.
335 * \param[in] i - Index.
336 */
338 CheckBound(i);
339 const Index_t new_offset = nd.GetIndices()[offset + i];
340 const Index_t new_size = nd.GetIndices()[offset + i + 1] - new_offset;
341 return LookupType_const(nd, new_offset, new_size);
342 }
343};
344
345template <typename Nd_t_, bool Check>
346class IndexAccumulator<1, Nd_t_, Check> : public IndexAccumulator_Base<1, Nd_t_, Check> {
347 public:
349 static constexpr size_t N = 1;
350 using Nd_t = Nd_t_;
351 using Index_t = typename Nd_t::Index_t;
352
354
355 /*! Return type of operator[].
356 * \details Data type of NdFlattener, but do also preserve constness.
357 */
358 using LookupType = su2conditional_t<std::is_const<Nd_t>::value, const typename Nd_t::Data_t, typename Nd_t::Data_t>;
359 using LookupType_const = const typename Nd_t::Data_t;
360 using Base::CheckBound;
361 using Base::nd;
362 using Base::offset;
363 using Base::size;
364
365 /*! \brief Return (possibly const) reference to the corresponding data element, checking if the index is in its range.
366 * Non-const version.
367 * \details The returned reference is const if and only if the type of the accessed NdFlattener is const-qualified.
368 * Even though the original NdFlattener might be non-const, marking any IndexAccumulator as const during index look-up
369 * will invoke the const version of operator[], which turns the NdFlattener type to const.
370 * \param[in] i - Last index.
371 */
373 CheckBound(i);
374 return nd.data()[offset + i];
375 }
376 /*! \brief Return const reference to the corresponding data element, checking if the index is in its range.
377 * \param[in] i - Last index.
378 */
380 CheckBound(i);
381 return nd.data()[offset + i];
382 }
383
384 /*! \brief Return (possibly const) pointer to data, non-const version.
385 * \details If all indices except the last one are fixed, the corresponding data
386 * is stored contiguously. Return a pointer to the beginning of the
387 * block. If this IndexAccumulator was generated from a non-const NdFlattener, the
388 * pointer is non-const, otherwise it is const.
389 */
390 LookupType* data() { return &(this->operator[](0)); }
391 /*! \brief Return const pointer to data.
392 */
393 LookupType_const* data() const { return &(this->operator[](0)); }
394};
395
396} // namespace helpers
397
398/*!
399 * \class NdFlattener
400 * \brief Serialize pointer-to-pointer-... array into one 1D array, keeping track
401 * of the offsets in few additional 1D arrays.
402 *
403 * The pointer-to-pointer-... array can be provided by a nested lambda function
404 * ('recursive function') or by gathering such arrays from MPI processes ('collective
405 * communication'). After initializing an NdFlattener with either of these data,
406 * it can be refreshed in the same way after the the pointer-to-pointer-... array's
407 * values (but not its structure) have changed.
408 *
409 * \tparam K - number of indices
410 * \tparam Data_t - Type of stored array data
411 * \tparam Index - Type of index
412 */
413
414template <size_t K_, typename Data_t_, typename Index_t_>
415class NdFlattener : public NdFlattener<K_ - 1, Data_t_, Index_t_> {
416 public:
417 static constexpr size_t K = K_;
418 using Data_t = Data_t_;
419 using Index_t = Index_t_;
420
423 using LowestLayer = typename Base::LowestLayer; // the K=1 class
424
425 private:
426 /*! \brief Number of nodes in this layer.
427 *
428 * For the layer K=1, nNodes will be the number of data points.
429 * For a layer K>1, nNodes will be the number of sublists.
430 */
431 Index_t nNodes = 0;
432
433 /*! \brief Iterator used at construction, runs from 0 to (nNodes-1). */
434 Index_t iNode = 0;
435
436 /*! \brief Indices in the lower layer's indices or data array */
437 std::vector<Index_t> indices;
438
439 /*=== Getters ===*/
440 public:
441 Index_t* GetIndices() { return indices.data(); }
442 const Index_t* GetIndices() const { return indices.data(); }
443
444 /*=== Outputting ===*/
445
446 /*! \brief Write in Python-list style.
447 *
448 * Like this: [[1, 2], [10, 20, 30]]
449 */
450 friend std::ostream& operator<<(std::ostream& output, NdFlattener const& nd) {
451 nd.toPythonString_fromto(output, 0, nd.size());
452 return output;
453 }
454
455 protected:
456 /*! \brief Write to stream in Python-list style, using the data of the
457 * indices array between 'from' (inclusive) and 'to' (exclusive).
458 *
459 * Like this: [[1, 2], [10, 20, 30]]
460 * \param[in] output - Stream
461 * \param[in] from - Beginning of the representation in the indices array.
462 * \param[in] to - Ending of the representation in the indices array.
463 */
464 void toPythonString_fromto(std::ostream& output, Index_t from, Index_t to) const {
465 output << "[";
466 for (Index_t i = from; i < to;) {
467 Base::toPythonString_fromto(output, indices[i], indices[i + 1]);
468 if (++i < to) output << ", ";
469 }
470 output << "]";
471 }
472
473 public:
474 /*! \brief Basic constructor. Afterwards, initialization can be done with initialize_or_refresh.
475 *
476 * Called recursively when a derived class (higher K) is constructed.
477 */
479
480 /*! \brief Constructor which calls initialize_or_refresh.
481 *
482 */
483 template <class... ARGS>
484 NdFlattener(ARGS const&... args) {
485 initialize_or_refresh(args...);
486 }
487
488 /*! \brief Initialize or refresh the NdFlattener.
489 * \details Either a 'recursive function' or 'collective communication'
490 * may be used. When the NdFlattener does not hold data yet, it is
491 * initialized, meaning that the data are collected and the indices arrays
492 * are allocated and filled. Otherwise it is refreshed, meaning that the data are
493 * recollected under the assumption that the indices arrays did not change.
494 */
495 template <class... ARGS>
496 void initialize_or_refresh(ARGS const&... args) {
497 if (initialized()) {
498 refresh(args...);
499 } else {
500 initialize(args...);
501 }
502 }
503
504 /*! \brief Initialization status of the NdFlattener.
505 * \returns true if the NdFlattener has been initialized
506 */
507 bool initialized() { return nNodes > 0; }
508
509 protected:
510 /*! \brief Allocate the indices array after \a nNodes has been determined.
511 */
512 void allocate() {
513 indices.resize(nNodes + 1);
514 indices[0] = 0;
516 }
517
518 /*! \brief Set \a iNode to 0 in all layers.
519 */
520 void reset_iNode() {
521 iNode = 0;
523 }
524
525 /*=== Construct from 'recursive function' ===*/
526 public:
527 /*! \brief Initialize from a 'recursive function'.
528 *
529 * The function should return a pair. Its first entry is the number of children.
530 * Its second entry is a function with the same meaning, recursively
531 * one layer down.
532 * \param f - the 'recursive function'
533 */
534 template <class f_type>
535 void initialize(f_type f) {
536 count_f(f);
537 allocate();
538 set_f(f, false);
539 }
540
541 /*! \brief Refresh the data according to the 'recursive function'
542 *
543 * The NdFlattener must have been constructed with a 'recursive function'.
544 * Now refresh the values with another 'recursive function'. The subarray lengths
545 * resulting from both 'recursive functions' must coincide, as the indices arrays
546 * are not changed.
547 *
548 * \param f - the 'recursive function'
549 * \tparam f_type - to allow for any type of the 'recursive function'
550 */
551 template <class f_type>
552 void refresh(f_type f) {
553 reset_iNode();
554 set_f(f, true);
555 }
556
557 protected:
558 /*! \brief Determine the space required for reading the 'recursive function'.
559 *
560 * \param f - the 'recursive function'
561 */
562 template <class f_type>
563 void count_f(f_type f) {
564 Index_t nChild = f.first;
565 for (Index_t iChild = 0; iChild < nChild; iChild++) {
566 nNodes++;
567 Base::count_f(f.second(iChild));
568 }
569 }
570
571 /*! \brief Read the 'recursive function' into the allocated arrays.
572 *
573 * \param f - the 'recursive function'
574 * \param refresh - if true, the object is already initialized and only the data
575 * in layer 1 have to be overwritten
576 * \tparam f_type - to allow for any type of the 'recursive function'
577 */
578 template <class f_type>
579 void set_f(f_type f, bool refresh) {
580 Index_t nChild = f.first;
581 for (Index_t iChild = 0; iChild < nChild; iChild++) {
582 Base::set_f(f.second(iChild), refresh);
583 if (!refresh) {
584 indices[iNode + 1] = indices[iNode] + f.second(iChild).first;
585 } else {
586 if (indices[iNode + 1] != indices[iNode] + f.second(iChild).first) {
587 SU2_MPI::Error("NdFlattener: Structure has changed, cannot refresh.", CURRENT_FUNCTION);
588 }
589 }
590 iNode++;
591 }
592 }
593
594 /*=== Construct with Allgatherv ===*/
595
596 public:
597 /*! \brief Initialize a flattener with K indices by combining distributed flatteners with (K-1) indices each.
598 *
599 * The new first index will encode the rank of the process. Data is exchanged in MPI::Allgatherv-style
600 * collective communication.
601 * \param[in] mpi_env - The MPI environment used for communication.
602 * \param[in] local_version - The local NdFlattener structure with (K-1) indices.
603 */
604 template <typename MPI_Environment_type>
605 void initialize(MPI_Environment_type const& mpi_env, Base const& local_version) {
606 // [k][r] is number of all nodes in layer (k+1), rank r in the new structure
607 su2matrix<Index_t> Nodes_all(K, mpi_env.size);
608 // the first index decides on the rank, so there is exactly one node per rank
609 for (int r = 0; r < mpi_env.size; r++) {
610 nNodes += Nodes_all[K - 1][r] = 1;
611 }
612 // set the lower layers' nNodes and Nodes_all[k]
613 Base::count_g(mpi_env, Nodes_all, local_version);
614
615 allocate();
616
617 indices[0] = 0;
618 for (int r = 0; r < mpi_env.size; r++) {
619 indices[r + 1] = indices[r] + Nodes_all[K - 2][r];
620 }
621 Base::set_g(mpi_env, Nodes_all, local_version);
622 }
623
624 /*! \brief Refresh the data by MPI collective communication.
625 *
626 * The NdFlattener must have been constructed by MPI collective communication.
627 * Now refresh the values with another collective communication. The subarray lengths
628 * resulting from both collective communications must coincide, as the indices arrays
629 * are not changed.
630 * \param[in] mpi_env - The MPI environment used for communication.
631 * \param[in] local_version - The local NdFlattener structure.
632 */
633 template <typename MPI_Environment_type>
634 void refresh(MPI_Environment_type const& mpi_env, Base const& local_version) {
635 su2matrix<Index_t> Nodes_all_0(1, mpi_env.size);
636 LowestLayer::count_g(mpi_env, Nodes_all_0, local_version);
637 LowestLayer::set_g(mpi_env, Nodes_all_0, local_version);
638 }
639
640 protected:
641 /*! \brief Count the distributed flatteners' numbers of nodes, and set nNodes.
642 *
643 * \param[in] mpi_env - MPI environment for communication
644 * \param[out] Nodes_all - [k][r] is set to number of nodes in layer (k+1), rank r.
645 * \param[in] local_version - local instance to be send to the other processes
646 */
647 void count_g(Nd_MPI_Environment const& mpi_env, su2matrix<Index_t>& Nodes_all, CurrentLayer const& local_version) {
648 nNodes = 0;
649 // gather numbers of nodes in the current layer from all processes
650 mpi_env.MPI_Allgather_fun(&(local_version.nNodes), 1, mpi_env.mpi_index, Nodes_all[K - 1], 1, mpi_env.mpi_index,
651 mpi_env.comm);
652 for (int r = 0; r < mpi_env.size; r++) {
653 nNodes += Nodes_all[K - 1][r];
654 }
655 Base::count_g(mpi_env, Nodes_all, local_version);
656 }
657
658 /*! \brief Gather the distributed flatteners' data and index arrays into the allocated arrays.
659 *
660 * \param[in] mpi_env - MPI environment for communication
661 * \param[in] Nodes_all - [k][r] is the number of nodes in layer (k+1), rank r.
662 * \param[in] local_version - local instance to be sent to the other processes
663 */
664 void set_g(Nd_MPI_Environment const& mpi_env, su2matrix<Index_t> const& Nodes_all,
665 CurrentLayer const& local_version) {
666 std::vector<int> Nodes_all_K_as_int(mpi_env.size);
667 std::vector<int> Nodes_all_k_cumulated(mpi_env.size + 1);
668 //< [r] is the number of nodes in the current layer, summed over all processes with rank below r, **plus one**.
669 // Used as displacements in Allgatherv, as we do not want to transfer the initial zeros, but we want to transfer the
670 // last element of indices, which is the local nNodes of the layer below. Note that MPI needs indices of type 'int'.
671 Nodes_all_k_cumulated[0] = 1;
672 for (int r = 0; r < mpi_env.size; r++) {
673 Nodes_all_k_cumulated[r + 1] = Nodes_all_k_cumulated[r] + Nodes_all[K - 1][r];
674 Nodes_all_K_as_int[r] = Nodes_all[K - 1][r];
675 }
676 mpi_env.MPI_Allgatherv_fun(local_version.indices.data() + 1, Nodes_all[K - 1][mpi_env.rank], mpi_env.mpi_index,
677 indices.data(), Nodes_all_K_as_int.data(), Nodes_all_k_cumulated.data(),
678 mpi_env.mpi_index, mpi_env.comm);
679 // shift indices
680 for (int r = 1; r < mpi_env.size; r++) {
681 Index_t first_entry_to_be_shifted = Nodes_all_k_cumulated[r];
682 Index_t last_entry_to_be_shifted = Nodes_all_k_cumulated[r + 1] - 1;
683 Index_t shift = indices[first_entry_to_be_shifted - 1];
684 for (Index_t i = first_entry_to_be_shifted; i <= last_entry_to_be_shifted; i++) {
685 indices[i] += shift;
686 }
687 }
688
689 Base::set_g(mpi_env, Nodes_all, local_version);
690 }
691
692 /*== Data access ==*/
693 public:
694 Index_t size() const { // should not be called by recursion, is incorrect in lower layers!
695 return nNodes;
696 }
697
698 /*! \brief Look-up with IndexAccumulator, non-const version.
699 */
703 /*! \brief Look-up with IndexAccumulator, const version.
704 */
708};
709
710template <typename Data_t_, typename Index_t_>
711class NdFlattener<1, Data_t_, Index_t_> {
712 public:
713 static constexpr size_t K = 1;
714 using Data_t = Data_t_;
715 using Index_t = Index_t_;
716
719
720 private:
721 Index_t nNodes = 0;
722 Index_t iNode = 0;
723 std::vector<Data_t> data_;
724
725 /*=== Outputting ===*/
726 protected:
727 void toPythonString_fromto(std::ostream& output, Index_t from, Index_t to) const {
728 output << "[";
729 for (Index_t i = from; i < to;) {
730 output << data_[i];
731 if (++i < to) output << ", ";
732 }
733 output << "]";
734 }
735
736 public:
737 NdFlattener(void) {}
738
739 template <class... ARGS>
740 NdFlattener(ARGS const&... args) {
741 initialize_or_refresh(args...);
742 }
743
744 template <class... ARGS>
745 void initialize_or_refresh(ARGS const&... args) {
746 if (initialized()) {
747 refresh(args...);
748 } else {
749 initialize(args...);
750 }
751 }
752
753 bool initialized() { return nNodes > 0; }
754
755 // Functionality to initialize/refresh from a recursive function
756 // could be desirable also for N=1, in order to gather from such
757 // NdFlatteners an NdFlattener with N=2.
758 // Gathering an NdFlattener with N=1 is not meaningful however.
759
760 template <class f_type>
761 void initialize(f_type f) {
762 count_f(f);
763 allocate();
764 set_f(f, false);
765 }
766
767 template <class f_type>
768 void refresh(f_type f) {
769 reset_iNode();
770 set_f(f, true);
771 }
772
773 protected:
774 void allocate() { data_.resize(nNodes); }
775
776 void reset_iNode() { iNode = 0; }
777
778 /*=== Construct from 'recursive function' ===*/
779 protected:
780 template <typename f_type>
781 void count_f(f_type f) {
782 nNodes += f.first;
783 }
784
785 template <typename f_type>
786 void set_f(f_type f, bool refresh) {
787 Index_t nChild = f.first;
788 for (Index_t iChild = 0; iChild < nChild; iChild++) {
789 data_[iNode] = f.second(iChild);
790 iNode++;
791 }
792 }
793
794 /*=== Construct with Allgatherv ===*/
795 protected:
796 void count_g(Nd_MPI_Environment const& mpi_env, su2matrix<Index_t>& Nodes_all, CurrentLayer const& local_version) {
797 nNodes = 0;
798 // gather numbers of nodes in the current layer from all processes
799 mpi_env.MPI_Allgather_fun(&(local_version.nNodes), 1, mpi_env.mpi_index, Nodes_all[0], 1, mpi_env.mpi_index,
800 mpi_env.comm);
801 for (int r = 0; r < mpi_env.size; r++) {
802 nNodes += Nodes_all[0][r];
803 }
804 }
805
806 void set_g(Nd_MPI_Environment const& mpi_env, su2matrix<Index_t> const& Nodes_all,
807 CurrentLayer const& local_version) {
808 std::vector<int> Nodes_all_0_as_int(mpi_env.size);
809 std::vector<int> Nodes_all_0_cumulated(mpi_env.size + 1);
810 Nodes_all_0_cumulated[0] = 0;
811 for (int r = 0; r < mpi_env.size; r++) {
812 Nodes_all_0_cumulated[r + 1] = Nodes_all_0_cumulated[r] + Nodes_all[0][r];
813 Nodes_all_0_as_int[r] = Nodes_all[0][r];
814 }
815
816 mpi_env.MPI_Allgatherv_fun(local_version.data_.data(), Nodes_all[0][mpi_env.rank], mpi_env.mpi_data, data_.data(),
817 Nodes_all_0_as_int.data(), Nodes_all_0_cumulated.data(), mpi_env.mpi_data, mpi_env.comm);
818 }
819
820 /*== Access to data ==*/
821 // Calling the following functions is a bit strange, because if you have only one level,
822 // you do not really benefit from NdFlattener's functionality.
823 public:
824 Index_t size() const { // should not be called by recursion, is incorrect in lower layers!
825 return nNodes;
826 }
827 /*! \brief Data look-up, non-const version.
828 */
829 Data_t& operator[](Index_t i0) { return data_[i0]; }
830 /*! \brief Data look-up, const version.
831 */
832 const Data_t& operator[](Index_t i0) const { return data_[i0]; }
833
834 Data_t* data() { return data_.data(); }
835
836 const Data_t* data() const { return data_.data(); }
837};
838
839/// @}
A templated matrix/vector-like object.
Definition C2DContainer.hpp:374
Definition mpi_structure.hpp:501
static void Allgatherv(const void *sendbuf, int sendcnt, Datatype sendtype, void *recvbuf, const int *recvcnt, const int *displs, Datatype recvtype, Comm comm)
Definition mpi_structure.hpp:584
static Comm GetComm()
Definition mpi_structure.hpp:530
int Datatype
Definition mpi_structure.hpp:504
static void Error(const std::string &ErrorMsg, const std::string &FunctionName)
Definition mpi_structure.cpp:131
int Comm
Definition mpi_structure.hpp:503
static int GetSize()
Definition mpi_structure.hpp:526
static void CopyData(const void *sendbuf, void *recvbuf, int size, Datatype datatype, int recvshift=0, int sendshift=0)
Definition mpi_structure.cpp:144
static void Allgather(const void *sendbuf, int sendcnt, Datatype sendtype, void *recvbuf, int recvcnt, Datatype recvtype, Comm comm)
Definition mpi_structure.hpp:589
void refresh(f_type f)
Definition ndflattener.hpp:768
void initialize_or_refresh(ARGS const &... args)
Definition ndflattener.hpp:745
const Data_t & operator[](Index_t i0) const
Data look-up, const version.
Definition ndflattener.hpp:832
Data_t_ Data_t
Definition ndflattener.hpp:714
Index_t_ Index_t
Definition ndflattener.hpp:715
Data_t & operator[](Index_t i0)
Data look-up, non-const version.
Definition ndflattener.hpp:829
const Data_t * data() const
Definition ndflattener.hpp:836
void set_f(f_type f, bool refresh)
Definition ndflattener.hpp:786
bool initialized()
Definition ndflattener.hpp:753
void toPythonString_fromto(std::ostream &output, Index_t from, Index_t to) const
Definition ndflattener.hpp:727
Index_t size() const
Definition ndflattener.hpp:824
void count_g(Nd_MPI_Environment const &mpi_env, su2matrix< Index_t > &Nodes_all, CurrentLayer const &local_version)
Definition ndflattener.hpp:796
NdFlattener(ARGS const &... args)
Definition ndflattener.hpp:740
void count_f(f_type f)
Definition ndflattener.hpp:781
void set_g(Nd_MPI_Environment const &mpi_env, su2matrix< Index_t > const &Nodes_all, CurrentLayer const &local_version)
Definition ndflattener.hpp:806
Data_t * data()
Definition ndflattener.hpp:834
void reset_iNode()
Definition ndflattener.hpp:776
void allocate()
Definition ndflattener.hpp:774
void initialize(f_type f)
Definition ndflattener.hpp:761
NdFlattener(void)
Definition ndflattener.hpp:737
Serialize pointer-to-pointer-... array into one 1D array, keeping track of the offsets in few additio...
Definition ndflattener.hpp:415
NdFlattener< K, Data_t, Index_t > CurrentLayer
Definition ndflattener.hpp:422
const Index_t * GetIndices() const
Definition ndflattener.hpp:442
NdFlattener()
Basic constructor. Afterwards, initialization can be done with initialize_or_refresh.
Definition ndflattener.hpp:478
void toPythonString_fromto(std::ostream &output, Index_t from, Index_t to) const
Write to stream in Python-list style, using the data of the indices array between 'from' (inclusive) ...
Definition ndflattener.hpp:464
Index_t * GetIndices()
Definition ndflattener.hpp:441
void refresh(f_type f)
Refresh the data according to the 'recursive function'.
Definition ndflattener.hpp:552
helpers::IndexAccumulator< K - 1, NdFlattener< K - 1, Data_t, Index_t > > operator[](Index_t i0)
Look-up with IndexAccumulator, non-const version.
Definition ndflattener.hpp:700
bool initialized()
Initialization status of the NdFlattener.
Definition ndflattener.hpp:507
friend std::ostream & operator<<(std::ostream &output, NdFlattener const &nd)
Write in Python-list style.
Definition ndflattener.hpp:450
void initialize(MPI_Environment_type const &mpi_env, Base const &local_version)
Initialize a flattener with K indices by combining distributed flatteners with (K-1) indices each.
Definition ndflattener.hpp:605
void allocate()
Allocate the indices array after nNodes has been determined.
Definition ndflattener.hpp:512
static constexpr size_t K
Definition ndflattener.hpp:417
void count_g(Nd_MPI_Environment const &mpi_env, su2matrix< Index_t > &Nodes_all, CurrentLayer const &local_version)
Count the distributed flatteners' numbers of nodes, and set nNodes.
Definition ndflattener.hpp:647
typename Base::LowestLayer LowestLayer
Definition ndflattener.hpp:423
void initialize(f_type f)
Initialize from a 'recursive function'.
Definition ndflattener.hpp:535
void initialize_or_refresh(ARGS const &... args)
Initialize or refresh the NdFlattener.
Definition ndflattener.hpp:496
Data_t_ Data_t
Definition ndflattener.hpp:418
helpers::IndexAccumulator< K - 1, const NdFlattener< K - 1, Data_t, Index_t > > operator[](Index_t i0) const
Look-up with IndexAccumulator, const version.
Definition ndflattener.hpp:705
NdFlattener(ARGS const &... args)
Constructor which calls initialize_or_refresh.
Definition ndflattener.hpp:484
Index_t size() const
Definition ndflattener.hpp:694
void count_f(f_type f)
Determine the space required for reading the 'recursive function'.
Definition ndflattener.hpp:563
void reset_iNode()
Set iNode to 0 in all layers.
Definition ndflattener.hpp:520
Index_t_ Index_t
Definition ndflattener.hpp:419
void set_f(f_type f, bool refresh)
Read the 'recursive function' into the allocated arrays.
Definition ndflattener.hpp:579
void refresh(MPI_Environment_type const &mpi_env, Base const &local_version)
Refresh the data by MPI collective communication.
Definition ndflattener.hpp:634
void set_g(Nd_MPI_Environment const &mpi_env, su2matrix< Index_t > const &Nodes_all, CurrentLayer const &local_version)
Gather the distributed flatteners' data and index arrays into the allocated arrays.
Definition ndflattener.hpp:664
LookupType_const & operator[](Index_t i) const
Return const reference to the corresponding data element, checking if the index is in its range.
Definition ndflattener.hpp:379
LookupType_const * data() const
Return const pointer to data.
Definition ndflattener.hpp:393
IndexAccumulator(Nd_t &nd, Index_t offset, Index_t size)
Definition ndflattener.hpp:353
LookupType * data()
Return (possibly const) pointer to data, non-const version.
Definition ndflattener.hpp:390
Nd_t_ Nd_t
Definition ndflattener.hpp:350
typename Nd_t::Index_t Index_t
Definition ndflattener.hpp:351
LookupType & operator[](Index_t i)
Return (possibly const) reference to the corresponding data element, checking if the index is in its ...
Definition ndflattener.hpp:372
const typename Nd_t::Data_t LookupType_const
Definition ndflattener.hpp:359
su2conditional_t< std::is_const< Nd_t >::value, const typename Nd_t::Data_t, typename Nd_t::Data_t > LookupType
Definition ndflattener.hpp:358
Parent class of IndexAccumulator.
Definition ndflattener.hpp:277
Nd_t & nd
The accessed NdFlattener.
Definition ndflattener.hpp:288
void CheckBound(Index_t i) const
Definition ndflattener.hpp:294
static constexpr size_t N
Definition ndflattener.hpp:279
const Index_t size_
Exclusive upper bound for the next index.
Definition ndflattener.hpp:290
IndexAccumulator_Base(Nd_t &nd, Index_t offset, Index_t size)
Definition ndflattener.hpp:292
Nd_t_ Nd_t
Definition ndflattener.hpp:280
Index_t size() const
Return exclusive upper bound for next index.
Definition ndflattener.hpp:285
const Index_t offset
Index in the currently accessed layer.
Definition ndflattener.hpp:289
typename Nd_t::Index_t Index_t
Definition ndflattener.hpp:281
Data structure holding an offset for the NdFlattener, to provide a []...[]-interface.
Definition ndflattener.hpp:303
Nd_t_ Nd_t
Definition ndflattener.hpp:307
IndexAccumulator< N - 1, const typename Nd_t::Base > LookupType_const
Definition ndflattener.hpp:318
static constexpr size_t N
Definition ndflattener.hpp:306
Nd_t & nd
The accessed NdFlattener.
Definition ndflattener.hpp:288
void CheckBound(Index_t i) const
Definition ndflattener.hpp:294
su2conditional_t< std::is_const< Nd_t >::value, const typename Nd_t::Base, typename Nd_t::Base > Nd_Base_t
Definition ndflattener.hpp:314
LookupType_const operator[](Index_t i) const
Read one more index, const version.
Definition ndflattener.hpp:337
IndexAccumulator(Nd_t &nd, Index_t offset, Index_t size)
Definition ndflattener.hpp:310
IndexAccumulator< N - 1, Nd_Base_t > LookupType
Definition ndflattener.hpp:316
Index_t size() const
Return exclusive upper bound for next index.
Definition ndflattener.hpp:285
const Index_t offset
Index in the currently accessed layer.
Definition ndflattener.hpp:289
typename Nd_t::Index_t Index_t
Definition ndflattener.hpp:308
LookupType operator[](Index_t i)
Read one more index, checking whether it is in the range dictated by the NdFlattener and previous ind...
Definition ndflattener.hpp:328
typename su2conditional< B, T, F >::type su2conditional_t
Definition code_config.hpp:88
#define FORCEINLINE
Definition code_config.hpp:44
#define CURRENT_FUNCTION
Definition mpi_structure.hpp:59
#define MPI_UNSIGNED_LONG
Definition mpi_structure.hpp:480
#define MPI_DOUBLE
Definition mpi_structure.hpp:484
Definition ndflattener.hpp:259
Contains information for the collective communication of NdFlatteners.
Definition ndflattener.hpp:232
decltype(&(SU2_MPI::Allgather)) MPI_Allgather_t
Definition ndflattener.hpp:233
MPI_Communicator_t comm
Definition ndflattener.hpp:240
typename SU2_MPI::Datatype MPI_Datatype_t
Definition ndflattener.hpp:235
const int size
Definition ndflattener.hpp:244
decltype(&(SU2_MPI::Allgatherv)) MPI_Allgatherv_t
Definition ndflattener.hpp:234
typename SU2_MPI::Comm MPI_Communicator_t
Definition ndflattener.hpp:236
MPI_Allgather_t MPI_Allgather_fun
Definition ndflattener.hpp:241
MPI_Allgatherv_t MPI_Allgatherv_fun
Definition ndflattener.hpp:242
const MPI_Datatype_t mpi_data
Definition ndflattener.hpp:238
const MPI_Datatype_t mpi_index
Definition ndflattener.hpp:239
const int rank
Definition ndflattener.hpp:243
Nd_MPI_Environment(MPI_Datatype_t mpi_data=4, MPI_Datatype_t mpi_index=1, MPI_Communicator_t comm=SU2_MPI::GetComm(), MPI_Allgather_t MPI_Allgather_fun=&(SU2_MPI::Allgather), MPI_Allgatherv_t MPI_Allgatherv_fun=&(SU2_MPI_Allgatherv_safe))
Definition ndflattener.hpp:246