openCARP
Doxygen code documentation for the open cardiac electrophysiology simulator openCARP
async_io.cc
Go to the documentation of this file.
1 // SPDX-FileCopyrightText: Copyright (c) NumeriCor GmbH
2 // SPDX-License-Identifier: LicenseRef-APL-1.1
3 
12 #include "basics.h"
13 #include "sim_utils.h"
14 #include "fem.h"
15 #include "physics.h"
16 #include "async_io.h"
17 
18 namespace opencarp {
19 namespace async {
20 
22 {
23  int rank = get_rank();
24 
25 #if 0
26  int size = get_size();
27 
28  for(int pid = 0; pid < size; pid++) {
29  if(rank == pid)
30  printf("IO rank %d / %d: polling ..\n", rank+1, size);
31 
32  MPI_Barrier(PETSC_COMM_WORLD);
33  }
34 #endif
35 
36  bool do_continue = true;
37  int cmd_buff = 0;
38  MPI_Status status;
39 
40  while(do_continue) {
41  if(!rank)
42  MPI_Recv(&cmd_buff, 1, MPI_INT, 0, ASYNC_TAG, user_globals::IO_Intercomm, &status);
43 
44  MPI_Bcast(&cmd_buff, 1, MPI_INT, 0, PETSC_COMM_WORLD);
45 
46  switch(cmd_buff) {
47  case ASYNC_CMD_EXIT:
48  do_continue = false;
49  break;
50 
52  IO_register_output(io_queue);
53  break;
54 
55  case ASYNC_CMD_OUTPUT:
56  IO_do_output(io_queue);
57  break;
58 
59  default: break;
60  }
61  }
62 
63  for(IGBheader* igb : io_queue.IGBs) {
64  if(igb->fileptr()) fclose((FILE*)igb->fileptr());
65  delete igb;
66  }
67 
68 #if 0
69  for(int pid = 0; pid < size; pid++) {
70  if(rank == pid)
71  printf("IO rank %d / %d: exiting ..\n", rank+1, size);
72 
73  MPI_Barrier(PETSC_COMM_WORLD);
74  }
75 #endif
76 }
77 
80 {
81  int rank = get_rank();
82 
83  if(!rank) {
84  int msg = ASYNC_CMD_EXIT;
85  MPI_Send(&msg, 1, MPI_INT, 0, ASYNC_TAG, user_globals::IO_Intercomm);
86  }
87 }
88 
90  const int dpn,
91  const char* name,
92  const char* units)
93 {
96 
97  long int loc_size = idx.size();
98  SF::vector<long int> layout;
99  layout_from_count(loc_size, layout, PETSC_COMM_WORLD);
100 
101  if(il.loc_rank == 0) {
102  char header[2048];
103 
105  const int num_io = tm.timers[iotm_spacedt]->numIOs;
106  const double dimt = tm.end - tm.start;
107 
108  snprintf(header, sizeof header, "%d %d %lf %s %s", dpn, num_io, dimt, name, units);
109 
110  int msg = ASYNC_CMD_REGISTER_OUTPUT;
111  MPI_Send(&msg, 1, MPI_INT, 0, ASYNC_TAG, user_globals::IO_Intercomm);
112 
113  MPI_Send(header, 2048, MPI_CHAR, 0, ASYNC_TAG, user_globals::IO_Intercomm);
114  MPI_Send(layout.data(), layout.size(), MPI_LONG, 0, ASYNC_TAG, user_globals::IO_Intercomm);
115  }
116 
117  MPI_Barrier(PETSC_COMM_WORLD);
118 
119  int receive_rank = COMPUTE_get_receive_rank(il);
120  MPI_Send(idx.data(), idx.size()*sizeof(mesh_int_t), MPI_BYTE, receive_rank,
122 
123  int id = -1;
124 
125  if(il.loc_rank == 0) {
126  MPI_Status status;
127  MPI_Recv(&id, 1, MPI_INT, 0, ASYNC_TAG, user_globals::IO_Intercomm, &status);
128  }
129 
130  MPI_Bcast(&id, 1, MPI_INT, 0, PETSC_COMM_WORLD);
131  return id;
132 }
133 
135  SF::commgraph<size_t> & grph,
136  SF::vector<mesh_int_t> & perm_before_comm,
137  SF::vector<mesh_int_t> & perm_after_comm)
138 {
139  int size = get_size(), rank = get_rank();
140  SF::vector<mesh_int_t> idx(inp_idx);
141 
142  // determine global min and max indices
143  mesh_int_t gmax = SF::global_max(idx, PETSC_COMM_WORLD);
144  mesh_int_t gmin = SF::global_min(idx, PETSC_COMM_WORLD);
145 
146  // block size
147  mesh_int_t bsize = (gmax - gmin) / size + 1;
148 
149  // distribute tuples uniquely and linearly ascending across the ranks ----------------
150  SF::vector<mesh_int_t> dest(idx.size());
151  SF::interval(perm_before_comm, 0, idx.size());
152 
153  // find a destination for every tuple
154  for(size_t i=0; i<dest.size(); i++)
155  dest[i] = (idx[i] - gmin) / bsize;
156 
157  // find permutation to sort tuples in the send buffer
158  binary_sort_copy(dest, perm_before_comm);
159 
160  // fill send buffer
161  SF::vector<mesh_int_t> snd_idx(idx.size());
162  for(size_t i=0; i<perm_before_comm.size(); i++)
163  snd_idx[i] = idx[perm_before_comm[i]];
164 
165  // communicate
166  grph.configure(dest, PETSC_COMM_WORLD);
167 
168  size_t rsize = sum(grph.rcnt);
169  SF::vector<mesh_int_t> recv_idx(rsize);
170 
171  MPI_Exchange(grph, snd_idx, recv_idx, PETSC_COMM_WORLD);
172 
173  // sort the received values locally
174  SF::interval(perm_after_comm, 0, recv_idx.size());
175  binary_sort_copy(recv_idx, perm_after_comm);
176 }
177 
178 
179 IGBheader* IO_open_igb(const int numIOs, const double dimt,
180  const size_t gsize, const int dpn,
181  const char* name, const char* units)
182 {
183  IGBheader* pigb = new IGBheader();
184  IGBheader & igb = *pigb;
185  int err = 0;
186 
187  igb.x(gsize / dpn);
188  igb.dim_x(igb.x()-1);
189  igb.inc_x(1);
190 
191  igb.y(1); igb.z(1);
192  igb.t(numIOs);
193  igb.dim_t(dimt);
194 
195  switch(dpn) {
196  default:
197  case 1: igb.type(IGB_FLOAT); break;
198  case 3: igb.type(IGB_VEC3_f); break;
199  case 4: igb.type(IGB_VEC4_f); break;
200  case 9: igb.type(IGB_VEC9_f); break;
201  }
202 
203  igb.unites_x("um"); igb.unites_y("um"); igb.unites_z("um");
204  igb.unites_t("ms");
205  igb.unites(units);
206 
207  igb.inc_t(param_globals::spacedt);
208 
209  if(get_rank() == 0) {
210  FILE_SPEC file = f_open(name, "wb");
211  if(file != NULL) {
212  igb.fileptr(file->fd);
213  igb.write();
214  delete file;
215  }
216  else err++;
217  }
218 
219  err = get_global(err, MPI_SUM);
220  if(err) {
221  log_msg(0,5,0, "%s error: Could not set up data output! Aborting!", __func__);
222  EXIT(1);
223  }
224 
225  return pigb;
226 }
227 
229 {
230  set_dir(OUTPUT);
231 
232  intercomm_layout il;
234 
235  char header[2048];
236  SF::vector<long int> data_layout(il.rem_size+1);
237  MPI_Status status;
238 
239  if(il.loc_rank == 0) {
240  MPI_Recv(header, 2048, MPI_CHAR, 0, ASYNC_TAG, user_globals::IO_Intercomm, &status);
241  MPI_Recv(data_layout.data(), data_layout.size(), MPI_LONG, 0, ASYNC_TAG, user_globals::IO_Intercomm, &status);
242  }
243 
244  MPI_Bcast(header, 2048, MPI_CHAR, 0, PETSC_COMM_WORLD);
245  MPI_Bcast(data_layout.data(), data_layout.size(), MPI_LONG, 0, PETSC_COMM_WORLD);
246 
247  int dpn = 0, numIOs = 0;
248  double dimt = 0.0;
249 
250  char name_str[2048], units_str[2048];
251  sscanf(header, "%d %d %lf %s %s", &dpn, &numIOs, &dimt, name_str, units_str);
252 
253  size_t gsize = data_layout[data_layout.size()-1];
254  IGBheader* igb = IO_open_igb(numIOs, dimt, gsize, dpn, name_str, units_str);
255 
256  SF::vector<int> senders;
257  IO_get_sender_ranks(il, senders);
258 
259  size_t num_recv = 0;
260  for(int s : senders)
261  num_recv += data_layout[s+1] - data_layout[s];
262 
263  SF::vector<mesh_int_t> idx_buff(num_recv);
264 
265  SF::vector<MPI_Request> req (senders.size());
266  SF::vector<MPI_Status> stat(senders.size());
267 
268  for(size_t i=0, dsp=0; i<senders.size(); i++) {
269  int send_rank = senders[i];
270  size_t send_size = data_layout[send_rank+1] - data_layout[send_rank];
271  MPI_Irecv(idx_buff.data() + dsp, send_size*sizeof(mesh_int_t), MPI_BYTE, send_rank,
273  dsp += send_size;
274  }
275 
276  MPI_Waitall(senders.size(), req.data(), stat.data());
277 
279  SF::vector<mesh_int_t> pafter, pbefore;
280  IO_prepare_sort(idx_buff, cg, pbefore, pafter);
281 
282  int id = io_queue.add(igb, data_layout, cg, pbefore, pafter);
283 
284  if(il.loc_rank == 0)
285  MPI_Send(&id, 1, MPI_INT, 0, ASYNC_TAG, user_globals::IO_Intercomm);
286 }
287 
290 {
291  SF::vector<int> remote_dist;
292  SF::divide(il.loc_size, il.rem_size, remote_dist);
293 
294  int remote_idx = 0;
295  int rank_cnt = remote_dist[remote_idx];
296 
297  while(remote_idx < int(remote_dist.size()) && il.loc_rank >= rank_cnt) {
298  remote_idx++;
299  rank_cnt += remote_dist[remote_idx];
300  }
301 
302  return remote_idx;
303 }
306 {
307  SF::vector<int> recv_cnt, recv_dsp, recv_ranks;
308  SF::divide(il.rem_size, il.loc_size, recv_cnt);
309  SF::dsp_from_cnt(recv_cnt, recv_dsp);
310 
311  sender.resize(0);
312  sender.reserve(recv_cnt[il.loc_rank]);
313 
314  int start = recv_dsp[il.loc_rank], stop = recv_dsp[il.loc_rank+1];
315  for(int i=start; i<stop; i++)
316  sender.push_back(i);
317 }
318 
320  const SF::vector<mesh_int_t> & perm_b,
321  const SF::vector<mesh_int_t> & perm_a,
323 {
324  SF::vector<float> snd_data(data.size());
325  for(size_t i=0; i<perm_b.size(); i++)
326  snd_data[i] = data[perm_b[i]];
327 
328  SF::vector<float> recv_data(perm_a.size());
329  SF::MPI_Exchange(cg, snd_data, recv_data, PETSC_COMM_WORLD);
330 
331  data.resize(perm_a.size());
332  for(size_t i=0; i<perm_a.size(); i++)
333  data[i] = recv_data[perm_a[i]];
334 }
335 
336 void IO_do_output(async_IO_queue & io_queue)
337 {
338  intercomm_layout il;
340 
341  MPI_Status status;
342  int id = -1;
343 
344  if(il.loc_rank == 0)
345  MPI_Recv(&id, 1, MPI_INT, 0, ASYNC_TAG, user_globals::IO_Intercomm, &status);
346  MPI_Bcast(&id, 1, MPI_INT, 0, PETSC_COMM_WORLD);
347 
348  assert(id > -1 && id < int(io_queue.IGBs.size()));
349 
350  IGBheader* igb = io_queue.IGBs[id];
351  SF::vector<long int> & layout = io_queue.layouts[id];
352  SF::commgraph<size_t> & cg = io_queue.cg[id];
353  SF::vector<mesh_int_t> & perm_b = io_queue.perm_b[id];
354  SF::vector<mesh_int_t> & perm_a = io_queue.perm_a[id];
355 
356  SF::vector<int> senders;
357  IO_get_sender_ranks(il, senders);
358 
359  size_t num_recv = 0;
360  for(int s : senders)
361  num_recv += layout[s+1] - layout[s];
362 
363  SF::vector<float> buff(num_recv);
364  SF::vector<MPI_Request> req (senders.size());
365  SF::vector<MPI_Status> stat(senders.size());
366 
367  for(size_t i=0, dsp=0; i<senders.size(); i++) {
368  int send_rank = senders[i];
369  size_t send_size = layout[send_rank+1] - layout[send_rank];
370 
371  MPI_Irecv(buff.data() + dsp, send_size, MPI_FLOAT, send_rank,
373  dsp += send_size;
374  }
375 
376  MPI_Waitall(senders.size(), req.data(), stat.data());
377  IO_sort_data(buff, perm_b, perm_a, cg);
378  SF::root_write((FILE*)igb->fileptr(), buff, PETSC_COMM_WORLD);
379 }
380 
381 void COMPUTE_do_output(SF_real* dat, const int lsize, const int IO_id)
382 {
383  intercomm_layout il;
385 
386  if(il.loc_rank == 0) {
387  int msg = ASYNC_CMD_OUTPUT;
388  MPI_Send(&msg, 1, MPI_INT, 0, ASYNC_TAG, user_globals::IO_Intercomm);
389 
390  msg = IO_id;
391  MPI_Send(&msg, 1, MPI_INT, 0, ASYNC_TAG, user_globals::IO_Intercomm);
392  }
393 
394  int receive_rank = COMPUTE_get_receive_rank(il);
395  SF::vector<float> data;
396  data.assign(dat, dat+lsize);
397 
398  MPI_Send(data.data(), data.size(), MPI_FLOAT, receive_rank, ASYNC_TAG, user_globals::IO_Intercomm);
399 }
400 
401 void COMPUTE_do_output(SF_real* dat, const SF::vector<mesh_int_t> & idx, const int IO_id)
402 {
403  intercomm_layout il;
405 
406  if(il.loc_rank == 0) {
407  int msg = ASYNC_CMD_OUTPUT;
408  MPI_Send(&msg, 1, MPI_INT, 0, ASYNC_TAG, user_globals::IO_Intercomm);
409 
410  msg = IO_id;
411  MPI_Send(&msg, 1, MPI_INT, 0, ASYNC_TAG, user_globals::IO_Intercomm);
412  }
413 
414  int receive_rank = COMPUTE_get_receive_rank(il);
415 
416  SF::vector<float> data;
417  data.resize(idx.size()); data.resize(0);
418 
419  for(mesh_int_t ii : idx)
420  data.push_back(dat[ii]);
421 
422  MPI_Send(data.data(), data.size(), MPI_FLOAT, receive_rank, ASYNC_TAG, user_globals::IO_Intercomm);
423 }
424 
425 
426 }}
opencarp::local_index_t mesh_int_t
Definition: SF_container.h:31
opencarp::real_t SF_real
Global scalar type.
Definition: SF_globals.h:18
Async IO functions.
#define ASYNC_CMD_REGISTER_OUTPUT
Definition: async_io.h:21
#define ASYNC_TAG
Definition: async_io.h:18
#define ASYNC_CMD_OUTPUT
Definition: async_io.h:22
#define ASYNC_CMD_EXIT
Definition: async_io.h:20
Basic utility structs and functions, mostly IO related.
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
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
void reserve(size_t n)
Definition: SF_vector.h:226
T * data()
Pointer to the vector's start.
Definition: SF_vector.h:76
T & push_back(T val)
Definition: SF_vector.h:268
void dim_t(float a)
Definition: IGBheader.h:314
void unites_x(const char *a)
Definition: IGBheader.h:326
void unites_z(const char *a)
Definition: IGBheader.h:332
void unites(const char *a)
Definition: IGBheader.h:338
void unites_y(const char *a)
Definition: IGBheader.h:329
void unites_t(const char *a)
Definition: IGBheader.h:335
void dim_x(float a)
Definition: IGBheader.h:305
void fileptr(FILE *f)
Definition: IGBheader.cc:312
void inc_t(float a)
Definition: IGBheader.h:302
void inc_x(float a)
Definition: IGBheader.h:293
centralize time managment and output triggering
Definition: timer_utils.h:58
double end
final time
Definition: timer_utils.h:66
double start
initial time (nonzero when restarting)
Definition: timer_utils.h:64
std::vector< base_timer * > timers
vector containing individual timers
Definition: timer_utils.h:69
Top-level header of FEM module.
void dsp_from_cnt(const vector< T > &cnt, vector< T > &dsp)
Compute displacements from counts.
Definition: SF_vector.h:295
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 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
T global_min(const vector< T > &vec, MPI_Comm comm)
Compute the global minimum of a distributed vector.
Definition: SF_network.h:111
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
void layout_from_count(const T count, vector< T > &layout, MPI_Comm comm)
Definition: SF_network.h:186
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_write(FILE *fd, const vector< V > &vec, MPI_Comm comm)
Write vector data binary to disk.
void IO_prepare_sort(const SF::vector< mesh_int_t > &inp_idx, SF::commgraph< size_t > &grph, SF::vector< mesh_int_t > &perm_before_comm, SF::vector< mesh_int_t > &perm_after_comm)
Definition: async_io.cc:134
void IO_get_sender_ranks(const intercomm_layout &il, SF::vector< int > &sender)
get the compute node ranks that will send their data chunk to us
Definition: async_io.cc:305
void IO_do_output(async_IO_queue &io_queue)
Definition: async_io.cc:336
void IO_sort_data(SF::vector< float > &data, const SF::vector< mesh_int_t > &perm_b, const SF::vector< mesh_int_t > &perm_a, SF::commgraph< size_t > &cg)
Definition: async_io.cc:319
IGBheader * IO_open_igb(const int numIOs, const double dimt, const size_t gsize, const int dpn, const char *name, const char *units)
Definition: async_io.cc:179
void COMPUTE_send_exit_flag()
this function sends the exit flag from a compute node to an io node.
Definition: async_io.cc:79
void COMPUTE_do_output(SF_real *dat, const int lsize, const int IO_id)
Definition: async_io.cc:381
int COMPUTE_get_receive_rank(const intercomm_layout &il)
get the IO node rank that will receive our data chunk
Definition: async_io.cc:289
void IO_poll_for_output(async_IO_queue &io_queue)
Definition: async_io.cc:21
int COMPUTE_register_output(const SF::vector< mesh_int_t > &idx, const int dpn, const char *name, const char *units)
Definition: async_io.cc:89
void IO_register_output(async_IO_queue &io_queue)
Definition: async_io.cc:228
std::map< int, std::string > units
Definition: stimulate.cc:26
MPI_Comm IO_Intercomm
Communicator between IO and compute worlds.
Definition: main.cc:48
timer_manager * tm_manager
a manager for the various physics timers
Definition: main.cc:40
@ iotm_spacedt
Definition: timer_utils.h:29
int set_dir(IO_t dest)
Definition: sim_utils.cc:1615
int get_rank(MPI_Comm comm=PETSC_COMM_WORLD)
Definition: basics.h:269
T get_global(T in, MPI_Op OP, MPI_Comm comm=PETSC_COMM_WORLD)
Do a global reduction on a variable.
Definition: basics.h:218
FILE_SPEC f_open(const char *fname, const char *mode)
Open a FILE_SPEC.
Definition: basics.cc:123
@ OUTPUT
Definition: sim_utils.h:39
void log_msg(FILE_SPEC out, int level, unsigned char flag, const char *fmt,...)
Definition: basics.cc:57
int get_size(MPI_Comm comm=PETSC_COMM_WORLD)
Definition: basics.h:283
#define IGB_VEC9_f
Definition: IGBheader.h:60
#define IGB_VEC3_f
Definition: IGBheader.h:55
#define IGB_FLOAT
Definition: IGBheader.h:44
#define IGB_VEC4_f
Definition: IGBheader.h:57
Top-level header of physics module.
Simulator-level utility execution control functions.
queue with the data required for performing async IO writes to IGB
Definition: async_io.h:40
int add(IGBheader *igb, const SF::vector< long int > &lt, const SF::commgraph< size_t > &c, const SF::vector< mesh_int_t > &pb, const SF::vector< mesh_int_t > &pa)
add one slice of IO info to the queue
Definition: async_io.h:48
SF::vector< IGBheader * > IGBs
IGBs with open filehandles on rank 0.
Definition: async_io.h:41
SF::vector< SF::vector< long int > > layouts
data layouts
Definition: async_io.h:42
SF::vector< SF::vector< mesh_int_t > > perm_b
permutation before MPI_Exchange
Definition: async_io.h:44
SF::vector< SF::vector< mesh_int_t > > perm_a
permutation after MPI_Exchange
Definition: async_io.h:45
SF::vector< SF::commgraph< size_t > > cg
commgraphs for MPI_Exchange
Definition: async_io.h:43
minimal information needed for communication between MPI_Comms
Definition: async_io.h:25
int loc_size
the local communicator size
Definition: async_io.h:27
int rem_size
the remote communicator size
Definition: async_io.h:28
void setup(MPI_Comm ic)
setup routine for the members
Definition: async_io.h:31
int loc_rank
the local rank
Definition: async_io.h:26
File descriptor struct.
Definition: basics.h:120