openCARP
Doxygen code documentation for the open cardiac electrophysiology simulator openCARP
SF_parallel_utils.h
Go to the documentation of this file.
1 // SPDX-FileCopyrightText: Copyright (c) NumeriCor GmbH
2 // SPDX-License-Identifier: Apache-2.0
3 
11 #ifndef _SF_PARALLEL_UTILS_H
12 #define _SF_PARALLEL_UTILS_H
13 
14 
15 #include <mpi.h>
16 
17 
18 #include "SF_container.h"
19 #include "SF_globals.h"
20 #include "SF_mesh_io.h"
21 #include "SF_vector.h"
22 #if WITH_EMI_MODEL
23  #include "SF_mesh_io_emi.h"
24 #endif
25 
26 namespace SF {
27 
38 template<class T>
39 void sort_parallel(MPI_Comm comm, const vector<T> & idx, vector<T> & out_idx)
40 {
41  int size, rank;
42  MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank);
43 
44  // determine global min and max indices
45  T gmax = global_max(idx, comm);
46  T gmin = global_min(idx, comm);
47 
48  // block size
49  T bsize = (gmax - gmin) / size + 1;
50 
51  // distribute tuples uniquely and linearly ascending across the ranks ----------------
52  vector<T> dest(idx.size()), perm;
53  interval(perm, 0, idx.size());
54 
55  // find a destination for every tuple
56  for(size_t i=0; i<dest.size(); i++)
57  dest[i] = (idx[i] - gmin) / bsize;
58 
59  // find permutation to sort tuples in the send buffer
60  binary_sort_copy(dest, perm);
61 
62  // fill send buffer
63  vector<T> snd_idx(idx.size());
64  for(size_t i=0; i<perm.size(); i++)
65  snd_idx[i] = idx[perm[i]];
66 
67  // communicate
68  commgraph<size_t> grph;
69  grph.configure(dest, comm);
70 
71  size_t rsize = sum(grph.rcnt);
72  out_idx.resize(rsize);
73 
74  MPI_Exchange(grph, snd_idx, out_idx, comm);
75 
76  // sort the received values locally
77  binary_sort(out_idx);
78 }
79 
92 template<class T, class V>
93 void sort_parallel(MPI_Comm comm, const vector<T> & idx, const vector<V> & val,
94  vector<T> & out_idx, vector<V> & out_val)
95 {
96  int size, rank;
97  MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank);
98 
99  // determine global min and max indices
100  T gmax = global_max(idx, comm);
101  T gmin = global_min(idx, comm);
102 
103  // block size
104  T bsize = (gmax - gmin) / size + 1;
105 
106  // distribute tuples uniquely and linearly ascending across the ranks ----------------
107  vector<T> dest(idx.size()), perm;
108  interval(perm, 0, idx.size());
109 
110  // find a destination for every tuple
111  for(size_t i=0; i<dest.size(); i++)
112  dest[i] = (idx[i] - gmin) / bsize;
113 
114  // find permutation to sort tuples in the send buffer
115  binary_sort_copy(dest, perm);
116 
117  // fill send buffer
118  vector<T> snd_idx(idx.size());
119  vector<V> snd_val(idx.size());
120  for(size_t i=0; i<perm.size(); i++) {
121  snd_idx[i] = idx[perm[i]];
122  snd_val[i] = val[perm[i]];
123  }
124 
125  // communicate
126  commgraph<size_t> grph;
127  grph.configure(dest, comm);
128 
129  size_t rsize = sum(grph.rcnt);
130  out_idx.resize(rsize);
131  out_val.resize(rsize);
132 
133  MPI_Exchange(grph, snd_idx, out_idx, comm);
134  MPI_Exchange(grph, snd_val, out_val, comm);
135 
136  // sort the received values locally
137  binary_sort_copy(out_idx, out_val);
138 }
139 
140 template<class T, class V>
141 void sort_parallel(MPI_Comm comm, const vector<T> & idx, const vector<T> & cnt, const vector<V> & val,
142  vector<T> & out_idx, vector<T> & out_cnt, vector<V> & out_val)
143 {
144  int size, rank;
145  MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank);
146 
147  // determine global min and max indices
148  T gmax = global_max(idx, comm);
149  T gmin = global_min(idx, comm);
150 
151  // block size
152  T bsize = (gmax - gmin) / size + 1;
153 
154  // distribute tuples uniquely and linearly ascending across the ranks ----------------
155  vector<T> dest(idx.size()), perm, dsp;
156  interval(perm, 0, idx.size());
157  dsp_from_cnt(cnt, dsp);
158 
159  // find a destination for every tuple
160  for(size_t i=0; i<dest.size(); i++)
161  dest[i] = (idx[i] - gmin) / bsize;
162 
163  // find permutation to sort tuples in the send buffer
164  binary_sort_copy(dest, perm);
165 
166  // fill send buffer. this has to happen in two steps since
167  // each element has a different size
168  vector<T> snd_idx(idx.size()), snd_cnt(idx.size()), snd_dsp;
169  vector<V> snd_val(val.size());
170 
171  for(size_t i=0; i<perm.size(); i++) {
172  snd_idx[i] = idx[perm[i]];
173  snd_cnt[i] = cnt[perm[i]];
174  }
175  dsp_from_cnt(snd_cnt, snd_dsp);
176 
177  for(size_t i=0; i<perm.size(); i++) {
178  const V* read = val.data() + dsp[perm[i]];
179  V* write = snd_val.data() + snd_dsp[i];
180 
181  for(T j=0; j<snd_cnt[i]; j++)
182  write[j] = read[j];
183  }
184 
185  // set up two communication graphs, one for one entry per element and one
186  // for multiple entries
187  commgraph<T> grph, grph_entr;
188  grph.configure(dest, comm);
189  grph_entr.configure(dest, snd_cnt, comm);
190 
191  size_t rsize = sum(grph.rcnt), rsize_entr = sum(grph_entr.rcnt);
192  vector<T> rec_cnt(rsize), rec_dsp, out_dsp;
193  vector<V> rec_val(rsize_entr);
194  out_idx.resize(rsize); out_cnt.resize(rsize);
195  out_val.resize(rsize_entr);
196 
197  // communicate
198  MPI_Exchange(grph, snd_idx, out_idx, comm);
199  MPI_Exchange(grph, snd_cnt, rec_cnt, comm);
200  MPI_Exchange(grph_entr, snd_val, rec_val, comm);
201 
202  dsp_from_cnt(rec_cnt, rec_dsp);
203 
204  // sort the received values locally, again in two steps
205  interval(perm, 0, rsize);
206  binary_sort_copy(out_idx, perm);
207 
208  for(size_t i=0; i<perm.size(); i++)
209  out_cnt[i] = rec_cnt[perm[i]];
210 
211  dsp_from_cnt(out_cnt, out_dsp);
212 
213  for(size_t i=0; i<perm.size(); i++) {
214  const V* read = rec_val.data() + rec_dsp[perm[i]];
215  V* write = out_val.data() + out_dsp[i];
216 
217  for(T j=0; j<out_cnt[i]; j++)
218  write[j] = read[j];
219  }
220 }
221 
222 
236 template<class V>
237 size_t root_write(FILE* fd, const vector<V> & vec, MPI_Comm comm)
238 {
239  int size, rank;
240  MPI_Comm_rank(comm, &rank); MPI_Comm_size(comm, &size);
241  long int lsize = vec.size();
242  long int nwr = 0;
243 
244  if(rank == 0) {
245  // file descriptor on root must be valid
246  assert(fd != NULL);
247  // write own chunk
248  nwr += fwrite(vec.data(), sizeof(V), vec.size(), fd);
249  }
250 
251  vector<V> wbuff;
252 
253  // iterate over other ranks and write their chunks
254  for(int pid=1; pid < size; pid++)
255  {
256  if(rank == pid) {
257  MPI_Send(&lsize, 1, MPI_LONG, 0, SF_MPITAG, comm);
258  MPI_Send(vec.data(), lsize*sizeof(V), MPI_BYTE, 0, SF_MPITAG, comm);
259  }
260  else if (rank == 0) {
261  long int rsize;
262  MPI_Status stat;
263 
264  MPI_Recv(&rsize, 1, MPI_LONG, pid, SF_MPITAG, comm, &stat);
265  wbuff.resize(rsize);
266 
267  MPI_Recv(wbuff.data(), rsize*sizeof(V), MPI_BYTE, pid, SF_MPITAG, comm, &stat);
268 
269  nwr += fwrite(wbuff.data(), sizeof(V), rsize, fd);
270  }
271 
272  MPI_Barrier(comm);
273  }
274 
275  MPI_Bcast(&nwr, 1, MPI_LONG, 0, comm);
276  return nwr;
277 }
278 
282 template<class V>
283 size_t root_write(FILE* fd, V* vec, const size_t vec_size, MPI_Comm comm)
284 {
285  vector<V> vecbuff;
286  vecbuff.assign(vec_size, vec, false);
287 
288  size_t nwr = root_write(fd, vecbuff, comm);
289 
290  vecbuff.assign(0, NULL, false);
291 
292  return nwr;
293 }
294 
308 template<class V>
309 size_t root_read(FILE* fd, vector<V> & vec, MPI_Comm comm)
310 {
311  int size, rank;
312  MPI_Comm_rank(comm, &rank); MPI_Comm_size(comm, &size);
313  long int lsize = vec.size();
314  long int nrd = 0;
315 
316  if(rank == 0) {
317  // file descriptor on root must be valid
318  assert(fd != NULL);
319 
320  // read own chunk
321  nrd += fread(vec.data(), sizeof(V), vec.size(), fd);
322  vector<V> rbuff;
323  // iterate over other ranks and write their chunks
324  for(int pid=1; pid < size; pid++)
325  {
326  long int rsize;
327  MPI_Status stat;
328  MPI_Recv(&rsize, 1, MPI_LONG, pid, SF_MPITAG, comm, &stat);
329 
330  rbuff.resize(rsize);
331  nrd += fread(rbuff.data(), sizeof(V), rsize, fd);
332 
333  MPI_Send(rbuff.data(), rsize*sizeof(V), MPI_BYTE, pid, SF_MPITAG, comm);
334  }
335  }
336  else {
337  MPI_Send(&lsize, 1, MPI_LONG, 0, SF_MPITAG, comm);
338 
339  MPI_Status stat;
340  MPI_Recv(vec.data(), lsize*sizeof(V), MPI_BYTE, 0, SF_MPITAG, comm, &stat);
341  }
342 
343  MPI_Bcast(&nrd, 1, MPI_LONG, 0, comm);
344  return nrd;
345 }
346 
361 template<class V>
362 size_t root_read_ascii(FILE* fd, vector<V> & vec, MPI_Comm comm, bool int_data)
363 {
364  int size, rank;
365  MPI_Comm_rank(comm, &rank); MPI_Comm_size(comm, &size);
366  long int lsize = vec.size();
367  long int nrd = 0;
368 
369  double fbuff;
370  long int ibuff;
371 
372  if(rank == 0) {
373  // file descriptor on root must be valid
374  assert(fd != NULL);
375 
376  // read own chunk
377  if(int_data) {
378  for(size_t i=0; i<vec.size(); i++) {
379  nrd += fscanf(fd, "%ld", &ibuff);
380  vec[i] = V(ibuff);
381  }
382  }
383  else {
384  for(size_t i=0; i<vec.size(); i++) {
385  nrd += fscanf(fd, "%lf", &fbuff);
386  vec[i] = V(fbuff);
387  }
388  }
389 
390  vector<V> rbuff;
391 
392  // iterate over other ranks and write their chunks
393  for(int pid=1; pid < size; pid++)
394  {
395  long int rsize;
396  MPI_Status stat;
397  MPI_Recv(&rsize, 1, MPI_LONG, pid, SF_MPITAG, comm, &stat);
398 
399  rbuff.resize(rsize);
400  for(long int i=0; i<rsize; i++) {
401  if(int_data) {
402  nrd += fscanf(fd, "%ld", &ibuff);
403  rbuff[i] = V(ibuff);
404  }
405  else {
406  nrd += fscanf(fd, "%lf", &fbuff);
407  rbuff[i] = V(fbuff);
408  }
409  }
410 
411  MPI_Send(rbuff.data(), rsize*sizeof(V), MPI_BYTE, pid, SF_MPITAG, comm);
412  }
413  }
414  else {
415  MPI_Send(&lsize, 1, MPI_LONG, 0, SF_MPITAG, comm);
416 
417  MPI_Status stat;
418  MPI_Recv(vec.data(), lsize*sizeof(V), MPI_BYTE, 0, SF_MPITAG, comm, &stat);
419  }
420 
421  MPI_Bcast(&nrd, 1, MPI_LONG, 0, comm);
422  return nrd;
423 }
424 
426 inline size_t root_count_ascii_lines(std::string file, MPI_Comm comm)
427 {
428  int size, rank;
429  MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank);
430 
431  size_t line_count = 0;
432  if(rank == 0) {
433  FILE* fd = fopen(file.c_str(), "r");
434 
435  if(fd) {
436  size_t fsize = file_size(fd);
437 
438  vector<int> chunk_sizes;
439  divide(fsize, size, chunk_sizes);
440 
441  vector<unsigned char> chunk;
442  for(int chunk_size : chunk_sizes) {
443  chunk.resize(chunk_size);
444  fread(chunk.data(), chunk_size, 1, fd);
445 
446  for(unsigned char c : chunk)
447  if(c == '\n') line_count++;
448  }
449 
450  fclose(fd);
451  } else {
452  fprintf(stderr, "%s error: Cannot open file %s!\n", __func__, file.c_str());
453  }
454  }
455 
456  MPI_Bcast(&line_count, sizeof(size_t), MPI_BYTE, 0, comm);
457  return line_count;
458 }
459 
463 template<class V>
464 size_t root_read(FILE* fd, V* vec, const size_t vec_size, MPI_Comm comm)
465 {
466  vector<V> vecbuff;
467  vecbuff.assign(vec_size, vec, false);
468 
469  size_t nrd = root_read(fd, vecbuff, comm);
470 
471  vecbuff.assign(0, NULL, false);
472 
473  return nrd;
474 }
475 
479 template<class V>
480 size_t root_read_ascii(FILE* fd, V* vec, const size_t vec_size, MPI_Comm comm, bool int_type)
481 {
482  vector<V> vecbuff;
483  vecbuff.assign(vec_size, vec, false);
484 
485  size_t nrd = root_read_ascii(fd, vecbuff, comm, int_type);
486 
487  vecbuff.assign(0, NULL, false);
488 
489  return nrd;
490 }
491 
507 template<class T, class V>
508 size_t root_write_ordered(FILE* fd, const vector<T> & idx, const vector<V> & vec, MPI_Comm comm)
509 {
510  vector<T> srt_idx;
511  vector<V> srt_vec;
512 
513  sort_parallel(comm, idx, vec, srt_idx, srt_vec);
514  return root_write(fd, srt_vec, comm);
515 }
516 
533 template<class T, class V>
534 size_t root_write_ordered(FILE* fd, const vector<T> & idx, const vector<T> & cnt,
535  const vector<V> & vec, MPI_Comm comm)
536 {
537  vector<T> srt_idx, srt_cnt;
538  vector<V> srt_vec;
539 
540  sort_parallel(comm, idx, cnt, vec, srt_idx, srt_cnt, srt_vec);
541  return root_write(fd, srt_vec, comm);
542 }
543 
544 
548 template<class T, class V>
549 size_t root_write_ordered(FILE* fd, T* idx, V* vec, const size_t vec_size, MPI_Comm comm)
550 {
551  vector<T> idxbuff;
552  vector<V> vecbuff;
553 
554  idxbuff.assign(vec_size, idx, false);
555  vecbuff.assign(vec_size, vec, false);
556 
557  size_t nwr = root_write_ordered(fd, idxbuff, vecbuff, comm);
558 
559  idxbuff.assign(0, NULL, false);
560  vecbuff.assign(0, NULL, false);
561  return nwr;
562 }
563 
567 template<class T, class V>
568 size_t root_write_ordered(FILE* fd, T* idx, T* cnt, V* vec,
569  const size_t idx_size, const size_t vec_size, MPI_Comm comm)
570 {
571  vector<T> idxbuff, cntbuff;
572  vector<V> vecbuff;
573 
574  idxbuff.assign(idx_size, idx, false);
575  cntbuff.assign(idx_size, cnt, false);
576  vecbuff.assign(vec_size, vec, false);
577 
578  size_t nwr = root_write_ordered(fd, idxbuff, cntbuff, vecbuff, comm);
579 
580  idxbuff.assign(0, NULL, false);
581  cntbuff.assign(0, NULL, false);
582  vecbuff.assign(0, NULL, false);
583 
584  return nwr;
585 }
586 
587 
588 template<class T>
589 void print_vector(MPI_Comm comm, const vector<T> & vec, const short dpn, FILE* fd)
590 {
591  int size, rank;
592  MPI_Comm_size(comm, &size), MPI_Comm_rank(comm, &rank);
593 
594  if(rank == 0) {
595  for (size_t i=0; i<vec.size() / dpn; i++ ) {
596  for(short j=0; j<dpn-1; j++)
597  fprintf(fd, "%g ", double(vec[i*dpn + j]) );
598  fprintf(fd, "%g\n", double(vec[i*dpn + (dpn-1)]) );
599  }
600 
601  // iterate over other ranks and write their chunks
602  vector<T> wbuff;
603  for(int pid=1; pid < size; pid++)
604  {
605  long int rsize;
606  MPI_Status stat;
607  MPI_Recv(&rsize, 1, MPI_LONG, pid, SF_MPITAG, comm, &stat);
608  wbuff.resize(rsize);
609  MPI_Recv(wbuff.data(), rsize*sizeof(T), MPI_BYTE, pid, SF_MPITAG, comm, &stat);
610 
611  for (size_t i=0; i<wbuff.size() / dpn; i++ ) {
612  for(short j=0; j<dpn-1; j++)
613  fprintf(fd, "%g ", double(wbuff[i*dpn + j]) );
614 
615  fprintf(fd, "%g\n", double(wbuff[i*dpn + (dpn-1)]) );
616  }
617  }
618  } else {
619  long int lsize = vec.size();
620  MPI_Send(&lsize, 1, MPI_LONG, 0, SF_MPITAG, comm);
621  MPI_Send(vec.data(), lsize*sizeof(T), MPI_BYTE, 0, SF_MPITAG, comm);
622  }
623 }
624 
625 
626 template<class T, class S>
627 void write_data_ascii(const MPI_Comm comm, const vector<T> & idx, const vector<S> & data,
628  std::string file, short dpn = 1)
629 {
630  assert(idx.size() == data.size());
631 
632  int rank;
633  MPI_Comm_rank(comm, &rank);
634 
635  vector<T> srt_idx;
636  vector<S> srt_data;
637  sort_parallel(comm, idx, data, srt_idx, srt_data);
638 
639  FILE* fd = NULL;
640  if(rank == 0) {
641  fd = fopen(file.c_str(), "w");
642  if(fd == NULL) {
643  fprintf(stderr, "%s error: Cannot open file %s for writing! Aborting!\n", __func__, file.c_str());
644  exit(1);
645  }
646  }
647 
648  print_vector(comm, srt_data, dpn, fd);
649 
650  if(fd) fclose(fd);
651 }
652 
653 }
654 #endif
Basic containers.
#define SF_MPITAG
the MPI tag when communicating
Definition: SF_globals.h:15
Functions related to mesh IO.
Functions related to EMI mesh IO.
The vector class and related algorithms.
The class holds the communication graph for a MPI_Exchange() call.
Definition: SF_container.h:623
vector< T > rcnt
Number of elements received from each rank.
Definition: SF_container.h:627
void configure(const vector< V > &dest, MPI_Comm comm)
Set up the communication graph.
Definition: SF_container.h:664
A vector storing arbitrary data.
Definition: SF_vector.h:28
size_t size() const
The current size of the vector.
Definition: SF_vector.h:89
void resize(size_t n)
Resize a vector.
Definition: SF_vector.h:194
void assign(InputIterator s, InputIterator e)
Assign a memory range.
Definition: SF_vector.h:146
T * data()
Pointer to the vector's start.
Definition: SF_vector.h:76
Definition: dense_mat.hpp:19
void write_data_ascii(const MPI_Comm comm, const vector< T > &idx, const vector< S > &data, std::string file, short dpn=1)
void dsp_from_cnt(const vector< T > &cnt, vector< T > &dsp)
Compute displacements from counts.
Definition: SF_vector.h:295
size_t file_size(FILE *fd)
return file size from a file descriptor
Definition: SF_io_base.h:75
void print_vector(MPI_Comm comm, const vector< T > &vec, const short dpn, FILE *fd)
void interval(vector< T > &vec, size_t start, size_t end)
Create an integer interval between start and end.
Definition: SF_vector.h:335
void sort_parallel(MPI_Comm comm, const vector< T > &idx, vector< T > &out_idx)
Sort index values parallel ascending across the ranks.
void binary_sort_copy(vector< T > &_V, vector< S > &_W)
Definition: SF_sort.h:286
T sum(const vector< T > &vec)
Compute sum of a vector's entries.
Definition: SF_vector.h:325
void divide(const size_t gsize, const size_t num_parts, vector< T > &loc_sizes)
divide gsize into num_parts local parts with even distribution of the remainder
Definition: SF_vector.h:343
size_t root_write_ordered(FILE *fd, const vector< T > &idx, const vector< V > &vec, MPI_Comm comm)
Write index value pairs to disk in ordered permutation.
T global_min(const vector< T > &vec, MPI_Comm comm)
Compute the global minimum of a distributed vector.
Definition: SF_network.h:111
size_t root_count_ascii_lines(std::string file, MPI_Comm comm)
count the lines in a ascii file
void MPI_Exchange(commgraph< T > &grph, vector< S > &send, vector< S > &recv, MPI_Comm comm)
Exchange data in parallel over MPI.
Definition: SF_network.h:32
size_t root_read(FILE *fd, vector< V > &vec, MPI_Comm comm)
Read binary data into a vector.
void binary_sort(vector< T > &_V)
Definition: SF_sort.h:274
T global_max(const vector< T > &vec, MPI_Comm comm)
Compute the global maximum of a distributed vector.
Definition: SF_network.h:141
size_t root_read_ascii(FILE *fd, vector< V > &vec, MPI_Comm comm, bool int_data)
Read binary data into a vector.
size_t root_write(FILE *fd, const vector< V > &vec, MPI_Comm comm)
Write vector data binary to disk.