SENSEI
A frame work for generic in situ analytics
MPIUtils.h
Go to the documentation of this file.
1 #ifndef MPIUtils_h
2 #define MPIUtils_h
3 
4 /// @file
5 
6 #include <algorithm>
7 #include <limits>
8 
9 namespace sensei
10 {
11 
12 /// A collection of communication routines
13 namespace MPIUtils
14 {
15 
16 
17 /// @cond
18 
19 /// type traits to help convert a C++ type to an MPI enumeration for that type
20 template<typename cpp_t> struct mpi_tt {};
21 
22 #define define_mpi_tt(CT, ME) \
23 template<> struct mpi_tt<CT> { static MPI_Datatype datatype(){ return ME; } };
24 
25 define_mpi_tt(float, MPI_FLOAT)
26 define_mpi_tt(double, MPI_DOUBLE)
27 define_mpi_tt(char, MPI_CHAR)
28 define_mpi_tt(signed char, MPI_CHAR)
29 define_mpi_tt(short, MPI_SHORT)
30 define_mpi_tt(int, MPI_INT)
31 define_mpi_tt(long, MPI_LONG)
32 define_mpi_tt(long long, MPI_LONG_LONG)
33 define_mpi_tt(unsigned char, MPI_UNSIGNED_CHAR)
34 define_mpi_tt(unsigned short, MPI_UNSIGNED_SHORT)
35 define_mpi_tt(unsigned int, MPI_UNSIGNED)
36 define_mpi_tt(unsigned long, MPI_UNSIGNED_LONG)
37 define_mpi_tt(unsigned long long, MPI_UNSIGNED_LONG_LONG)
38 
39 /// @endcond
40 
41 
42 /** helper to recuce by summation elements in a vector it's assumed that the
43  * vector is the same size on all ranks.
44  */
45 template<typename cpp_t>
46 void GlobalCounts(MPI_Comm comm, std::vector<cpp_t> &vec)
47 {
48  MPI_Allreduce(MPI_IN_PLACE, vec.data(), vec.size(),
49  mpi_tt<cpp_t>::datatype(), MPI_SUM, comm);
50 }
51 
52 /** helper function to compute an axis aligned bounding box that bounds a
53  * collection of distrubted axis aligned bounding boxes
54  *
55  * these can be integer index space bounds (ie SVTK extents) or floating point
56  * world cooridnate system bounds, but for index space bounds a signed integer
57  * type is required.
58  *
59  * local bounds are expected in the layout:
60  *
61  * bx_0_0, bx_1_0, by_0_0, by_1_0, bz_0_0, bz_1_0,
62  * ...
63  * bx_0_n, bx_1_n, by_0_n, by_1_n, bz_0_n, bz_1_n
64  *
65  * where n is the number of blocks minus 1
66  *
67  * global bounds are returned in the same layout:
68  *
69  * bx_0, bx_1, by_0, by_1, bz_0, bz_1
70  */
71 template <typename cpp_t>
72 void GlobalBounds(MPI_Comm comm, const std::vector<std::array<cpp_t,6>> &lbounds,
73  std::array<cpp_t,6> &gbounds)
74 {
75  int nLocal = lbounds.size();
76 
77  gbounds = {std::numeric_limits<cpp_t>::max(), std::numeric_limits<cpp_t>::lowest(),
78  std::numeric_limits<cpp_t>::max(), std::numeric_limits<cpp_t>::lowest(),
79  std::numeric_limits<cpp_t>::max(), std::numeric_limits<cpp_t>::lowest()};
80 
81  // find the smallest bounding covering all local
82  for (int q = 0; q < nLocal; ++q)
83  {
84  const cpp_t *plbounds = lbounds[q].data();
85  for (int i = 0; i < 6; ++i)
86  {
87  int useMax = i % 2;
88  cpp_t lval = plbounds[i];
89  cpp_t gval = gbounds[i];
90  gbounds[i] = useMax ? std::max(gval, lval) : std::min(gval, lval);
91  }
92  }
93 
94  // so we can use MPI_MAX
95  for (size_t i = 0; i < 6; i += 2)
96  gbounds[i] = -gbounds[i];
97 
98  // find the smallest bounding covering all distributed
99  MPI_Allreduce(MPI_IN_PLACE, gbounds.data(), 6,
100  mpi_tt<cpp_t>::datatype(), MPI_MAX, comm);
101 
102  // because we used MPI_MAX
103  for (size_t i = 0; i < 6; i += 2)
104  gbounds[i] = -gbounds[i];
105 }
106 
107 /// helper function to compute glpbal array range
108 template <typename cpp_t>
109 void GlobalRange(MPI_Comm comm, const std::vector<std::array<cpp_t,2>> &lrange,
110  std::array<cpp_t,2> &grange)
111 {
112  int nLocal = lrange.size();
113 
114  grange = {std::numeric_limits<cpp_t>::max(),
115  std::numeric_limits<cpp_t>::lowest()};
116 
117  // find the range over local blocks
118  for (int q = 0; q < nLocal; ++q)
119  {
120  const cpp_t *plrange = lrange[q].data();
121  grange[0] = std::min(grange[0], plrange[0]);
122  grange[1] = std::max(grange[1], plrange[1]);
123  }
124 
125  // so we can use MPI_MAX
126  grange[0] = -grange[0];
127 
128  // find the smallest bounding covering all distributed
129  MPI_Allreduce(MPI_IN_PLACE, grange.data(), 2,
130  mpi_tt<cpp_t>::datatype(), MPI_MAX, comm);
131 
132  // because we used MPI_MAX
133  grange[0] = -grange[0];
134 }
135 
136 /* helper function to generate a global view from a local view. here it is
137  * assumed that all ranks have the number of items in local data. If that is
138  * not the case see GlobalViewV
139  */
140 template <typename cpp_t>
141 void GlobalView(MPI_Comm comm, const std::vector<cpp_t> &ldata,
142  std::vector<cpp_t> &gdata)
143 {
144  int rank = 0;
145  int nRanks = 1;
146 
147  MPI_Comm_rank(comm, &rank);
148  MPI_Comm_size(comm, &nRanks);
149 
150  int nLocal = ldata.size();
151 
152  gdata.resize(nRanks*nLocal);
153  for (int i = 0; i < nLocal; ++i)
154  gdata[nLocal*rank+i] = ldata[i];
155 
156  MPI_Allgather(MPI_IN_PLACE, 0, MPI_DATATYPE_NULL,
157  gdata.data(), nLocal, mpi_tt<cpp_t>::datatype(), comm);
158 }
159 
160 /* helper function to generate a global view from a local view. A vector of
161  * local data items is passed in, this vector could be a different length on
162  * each rank. a vector of the global data items is returned along with an array
163  * of counts, and offsets that are used to index into the global data. counts
164  * is indexed by rank and contains the number of items contributed by each
165  * rank. offsets contains an offset of each ranks data.
166  */
167 template <typename cpp_t>
168 void GlobalViewV(MPI_Comm comm, const std::vector<cpp_t> &ldata,
169  std::vector<int> &gcounts, std::vector<int> &goffset,
170  std::vector<cpp_t> &gdata)
171 {
172  int rank = 0;
173  int nRanks = 1;
174 
175  MPI_Comm_rank(comm, &rank);
176  MPI_Comm_size(comm, &nRanks);
177 
178  gcounts.clear();
179  gcounts.resize(nRanks);
180 
181  int nLocal = ldata.size();
182  gcounts[rank] = nLocal;
183 
184  MPI_Allgather(MPI_IN_PLACE, 0, MPI_DATATYPE_NULL,
185  gcounts.data(), 1, MPI_INT, comm);
186 
187  goffset.clear();
188  goffset.resize(nRanks);
189 
190  int q = 0;
191  int nTotal = 0;
192  std::for_each(gcounts.begin(), gcounts.end(),
193  [&nTotal,&goffset,&q](const int &n)
194  {
195  goffset[q] = nTotal;
196  nTotal += n;
197  q += 1;
198  });
199 
200  gdata.resize(nTotal);
201 
202  const cpp_t *ld = ldata.data();
203  cpp_t *gd = gdata.data() + goffset[rank];
204  for (int i = 0; i < nLocal; ++i)
205  gd[i] = ld[i];
206 
207  MPI_Allgatherv(MPI_IN_PLACE, 0, MPI_DATATYPE_NULL, gdata.data(),
208  gcounts.data(), goffset.data(), mpi_tt<cpp_t>::datatype(), comm);
209 }
210 
211 /// use this if you don't need counts & offsets
212 template <typename cpp_t>
213 void GlobalViewV(MPI_Comm comm, const std::vector<cpp_t> &ldata,
214  std::vector<cpp_t> &gdata)
215 {
216  std::vector<int> counts, offsets;
217  GlobalViewV(comm, ldata, counts, offsets, gdata);
218 }
219 
220 /** use this if you don't need counts & offsets and want the result to replace
221  * the input.
222  */
223 template <typename cpp_t>
224 void GlobalViewV(MPI_Comm comm, std::vector<cpp_t> &ldata)
225 {
226  std::vector<int> counts, offsets;
227  std::vector<cpp_t> gdata;
228  GlobalViewV(comm, ldata, counts, offsets, gdata);
229  ldata = std::move(gdata);
230 }
231 
232 /** helper function to generate a global view from a local view. A vector of
233  * local data items is passed in, this vector could be a different length on
234  * each rank. a vector of the global data items is returned along with an array
235  * of counts, and offsets that are used to index into the global data. counts
236  * is indexed by rank and contains the number of items contributed by each
237  * rank. offsets contains an offset of each ranks data.
238  */
239 template <typename cpp_t, std::size_t N>
240 void GlobalViewV(MPI_Comm comm, const std::vector<std::array<cpp_t,N>> &ldata,
241  std::vector<std::array<cpp_t,N>> &gdata)
242 {
243  // serialize the data
244  size_t n = ldata.size();
245  std::vector<cpp_t> ld(n*N);
246  for (size_t i = 0; i < n; ++i)
247  {
248  const std::array<cpp_t,N> &lda = ldata[i];
249  for (size_t j = 0; j < N; ++j)
250  {
251  ld[i*N + j] = lda[j];
252  }
253  }
254 
255  // send it
256  std::vector<cpp_t> gd;
257  std::vector<int> counts, offsets;
258  GlobalViewV(comm, ld, counts, offsets, gd);
259 
260  // deserialize
261  n = gd.size()/N;
262  gdata.resize(n);
263  for (size_t i = 0; i < n; ++i)
264  {
265  std::array<cpp_t,N> &gda = gdata[i];
266  for (size_t j = 0; j < N; ++j)
267  {
268  gda[j] = gd[i*N + j];
269  }
270  }
271 }
272 
273 /** use this if you don't need counts & offsets and want the result to replace
274  * the input.
275  */
276 template <typename cpp_t, std::size_t N>
277 void GlobalViewV(MPI_Comm comm, std::vector<std::array<cpp_t,N>> &ldata)
278 {
279  std::vector<std::array<cpp_t,N>> gdata;
280  GlobalViewV(comm, ldata, gdata);
281  ldata = std::move(gdata);
282 }
283 
284 /** use this if you don't need counts & offsets and want the result to replace
285  * the input.
286  */
287 template <typename cpp_t>
288 void GlobalViewV(MPI_Comm comm, std::vector<std::vector<cpp_t>> &ldata)
289 {
290  // gather local sizes
291  std::vector<long> lsizes;
292  int nLocal = ldata.size();
293  for (int i = 0; i < nLocal; ++i)
294  lsizes.push_back(ldata[i].size());
295 
296  std::vector<int> scounts, soffsets;
297  std::vector<long> gsizes;
298 
299  GlobalViewV(comm, lsizes, scounts, soffsets, gsizes);
300 
301  // flatten local data
302  std::vector<cpp_t> lfdata;
303  for (int i = 0; i < nLocal; ++i)
304  {
305  const std::vector<cpp_t> &elem = ldata[i];
306  long nElem = elem.size();
307  for (int j = 0; j < nElem; ++j)
308  lfdata.push_back(elem[j]);
309  }
310 
311  // gather flattened data
312  std::vector<cpp_t> gfdata;
313  GlobalViewV(comm, lfdata, gfdata);
314 
315  // un-flatten
316  std::vector<std::vector<cpp_t>> gdata;
317  unsigned int nranks = scounts.size();
318  for (unsigned int i = 0, q = 0; i < nranks; ++i)
319  {
320  int nVec = scounts[i];
321  int vecOffs = soffsets[i];
322  for (int j = 0; j < nVec; ++j)
323  {
324  long nElem = gsizes[vecOffs + j];
325  std::vector<cpp_t> vec;
326  for (long k = 0; k < nElem; ++k,++q)
327  {
328  vec.push_back(gfdata[q]);
329  }
330  gdata.push_back(vec);
331  }
332  }
333 
334  // return global view
335  ldata.swap(gdata);
336 }
337 
338 }
339 }
340 
341 #endif
void GlobalBounds(MPI_Comm comm, const std::vector< std::array< cpp_t, 6 >> &lbounds, std::array< cpp_t, 6 > &gbounds)
helper function to compute an axis aligned bounding box that bounds a collection of distrubted axis a...
Definition: MPIUtils.h:72
void GlobalCounts(MPI_Comm comm, std::vector< cpp_t > &vec)
helper to recuce by summation elements in a vector it&#39;s assumed that the vector is the same size on a...
Definition: MPIUtils.h:46
SENSEI.
Definition: ADIOS2AnalysisAdaptor.h:27
void GlobalRange(MPI_Comm comm, const std::vector< std::array< cpp_t, 2 >> &lrange, std::array< cpp_t, 2 > &grange)
helper function to compute glpbal array range
Definition: MPIUtils.h:109