openCARP
Doxygen code documentation for the open cardiac electrophysiology simulator openCARP
MULTI_ION_IF.cc
Go to the documentation of this file.
1 // SPDX-FileCopyrightText: Copyright (c) NumeriCor GmbH
2 // SPDX-License-Identifier: LicenseRef-APL-1.1
3 
39 // TODO remove the "get_sv_address()" and "get_sv_size()" calls by moving
40 // the functions that use them to the IonIf class
41 
42 #include "MULTI_ION_IF.h"
43 #include "limpet_types.h"
44 #include "mpi_utils.h"
45 #include <stdio.h>
46 #ifdef HAS_CUDA_MODEL
47 #include <cuda_runtime.h>
48 #endif
49 #if defined HAS_ROCM_MODEL && defined __HIP__
50 #include <hip/hip_runtime.h>
51 #endif
52 
53 #include "SF_init.h" // for SF::init_xxx()
54 #include "petsc_utils.h" // TODO for EXIT
55 
56 namespace limpet {
57 
58 using ::opencarp::base_timer;
70 using ::opencarp::Salt_list;
75 #ifdef USE_FMEM_WRAPPER
77 #endif
78 
79 /*
80  PROTOTYPES
81  */
82 void initialize_params_MIIF(MULTI_IF *pMIIF);
83 void initialize_ionic_IF(MULTI_IF *pMIIF);
84 void CreateIIFLocalNodeLsts(MULTI_IF *pMIIF);
86 void alloc_MIIF(MULTI_IF *pMIIF);
87 void initializeIMPData(MULTI_IF *pMIIF);
88 void freeIMPData(MULTI_IF *pMIIF);
89 void allocate_shared_data(MULTI_IF *);
90 node_index_t getGlobalNodalIndex(IonIfBase& pIF, node_index_t relIdx);
92 bool isIMPdata(const char *);
93 
94 static MULTI_IF *gMIIF_Error_Recovery;
95 static int current_IIF_index;
96 static float current_time = 0;
97 static float start_time = 0;
98 
99 static void prepare_error_recovery(MULTI_IF *MIIF, int IIF_index, float time) {
100  gMIIF_Error_Recovery = MIIF;
101  current_IIF_index = IIF_index;
102  current_time = start_time+time;
103 }
104 
106  return gMIIF_Error_Recovery->NodeLists[current_IIF_index][local_node];
107 }
108 
110  return current_time;
111 }
112 
113 static int g_print_bounds_exceeded = 1;
114 
116  return g_print_bounds_exceeded;
117 }
118 
120  int oldval = g_print_bounds_exceeded;
121 
122  g_print_bounds_exceeded = newval;
123  return oldval;
124 }
125 
129  CreateIIFNodeLsts_(pMIIF->N_IIF, pMIIF->IIFmask, &pMIIF->N_Nodes,
130  &pMIIF->NodeLists, pMIIF->numNode);
131 }
132 
144 void CreateIIFNodeLsts_(int N_IIF, IIF_Mask_t *IIF_Mask, node_count_t **N_Nodes,
145  node_index_t ***NodeLists, node_count_t numNode)
146 {
147  /* create node lists for each IIF
148  NodeNum: store number of nodes for each IIF
149  NodeLst: store node indices of each IIF
150  */
151  node_count_t *NodeNum = static_cast<node_count_t *>(calloc(N_IIF, sizeof(node_count_t)));
152  node_index_t **NodeLst = static_cast<node_index_t **>(calloc(N_IIF, sizeof(node_index_t *)));
153 
154  // determine number of nodes of each IIF
155  for (node_index_t i = 0; i < numNode; i++)
156  NodeNum[static_cast<int>(IIF_Mask[i])]++;
157 
158  /* now we know the number of nodes per IIF and store the indices of
159  nodes belonging to a particular IIF into a list.
160  */
161  for (int i = 0; i < N_IIF; i++) {
162  if (NodeNum[i] > 0)
163  NodeLst[i] = static_cast<node_index_t *>(malloc(sizeof(node_index_t) * NodeNum[i]));
164  else
165  NodeLst[i] = NULL;
166  }
167 
168  // just store indices relative to low
169  for (int j = 0; j < N_IIF; j++) {
170  node_count_t hcount = 0;
171  for (node_index_t i = 0; i < numNode; i++)
172  if (IIF_Mask[i] == j)
173  NodeLst[j][hcount++] = i;
174  }
175 
176  for(int j=0; j<N_IIF; j++)
177  std::sort(NodeLst[j], NodeLst[j]+NodeNum[j]);
178 
179  *N_Nodes = NodeNum;
180  *NodeLists = NodeLst;
181 
182 } // CreateIIFNodeLsts_
183 
184 
193  // get pointer to parent MIIF
194  MULTI_IF *pMIIF = (MULTI_IF *) pIF.parent(); // TODO check?!?!?!?!?!
195 
196  if (pIF.get_type().is_plugin())
197  pMIIF = (MULTI_IF *) pIF.parent()->parent();
198 
199  return pMIIF->NodeLists[pIF.miifIdx][rIdx];
200 }
201 
208 void alloc_MIIF(MULTI_IF *pMIIF) {
209  int N_IIF = pMIIF->N_IIF;
210 
211  pMIIF->contiguous = static_cast<bool *>(calloc(N_IIF, sizeof(bool)));
212 #if 0
213  pMIIF->ldata = (GlobalData_t***) build_matrix_ns<GlobalData_t>(N_IIF, NUM_IMP_DATA_TYPES, sizeof(GlobalData_t **), Target::CPU);
214 #else
215  pMIIF->ldata = allocate_on_target<GlobalData_t**>(Target::CPU, N_IIF);
216  for (std::size_t i = 0; i < pMIIF->N_IIF; i++) {
217  pMIIF->ldata[i] = allocate_on_target<GlobalData_t*>(pMIIF->iontypes[i].get().select_target(pMIIF->targets[i]), NUM_IMP_DATA_TYPES);
218  }
219 #endif
220 
221  for (int i = 0; i < pMIIF->N_IIF; i++) {
222  // Create an IonIf object on the chosen target
223  pMIIF->IIF.push_back(pMIIF->iontypes[i].get().make_ion_if(pMIIF->targets[i], pMIIF->N_Nodes[i], pMIIF->plugtypes[i]));
224  // this is questionable. A IIF does not have a parent, parent should be NULL
225  // It might make sense in some situtation to be able to refer to the MIIF
226  // structure, but pIF->parent is of type IonIf and not MULTI_IF!!!!
227  pMIIF->IIF[i]->set_parent((IonIfBase *)pMIIF);
228 
229  // set unique miifIdx for imp and plugins to enable refering back
230  // to global entities such as global node index from within imp
231  pMIIF->IIF[i]->for_each([&](IonIfBase& IF) { IF.miifIdx = i; });
232  }
233 } // alloc_MIIF
234 
240  for (auto& imp : pMIIF->IIF) {
241  imp->initialize_params();
242  }
243 }
244 
249  pMIIF->getRealData(); // needed for initializing Vm, etc.
250  for (int i = 0; i < pMIIF->IIF.size(); i++)
251  pMIIF->IIF[i]->initialize(pMIIF->dt, pMIIF->ldata[i]);
252 
253  pMIIF->releaseRealDataDuringInit();
254 }
255 
262  // we need to copy all global data which has been set, even if it is not
263  // listed as moddat. This is the case with Vm which may not be modified by
264  // LIMPET but is initialized by LIMPET
265  unsigned int *moddat = static_cast<unsigned int *>(calloc(this->N_IIF, sizeof(unsigned int)));
266 
267  for (int i = 0; i < this->N_IIF; i++) {
268  moddat[i] = this->IIF[i]->get_moddat();
269  this->IIF[i]->set_moddat(moddat[i] | this->IIF[i]->get_reqdat()); // add in the reqdat
270  }
271  this->releaseRealData();
272  for (int i = 0; i < this->N_IIF; i++) {
273  this->IIF[i]->set_moddat(moddat[i]); // remove the reqdat
274  }
275  free(moddat);
276 }
277 
283 {
285  alloc_MIIF(this);
287 
288  memset(&svd, 0, sizeof(SV_DUMP) );
289  svd.active = 0;
290  svd.intv = 1.0; // 1. ms by default
291 }
292 
293 #define FILENAME_BUF 1024
294 
296 int node_index_cmp(const void *a, const void *b) {
297  const node_index_t lhs = *static_cast<const node_index_t *>(a);
298  const node_index_t rhs = *static_cast<const node_index_t *>(b);
299  return (lhs > rhs) - (lhs < rhs);
300 }
301 
329 void open_trace(MULTI_IF *MIIF, int n_traceNodes, int *traceNodes, int *label, opencarp::sf_mesh* imesh)
330 {
331  if (!n_traceNodes) return;
332 
333  if (n_traceNodes > 1000)
334  log_msg(0, 4, 0, "%s warning: %d trace nodes may impact performance", __func__, n_traceNodes);
335 
336  MIIF->trace_info = (Trace_Info *)calloc(n_traceNodes+1, sizeof(Trace_Info));
337  MIIF->trace_info[n_traceNodes].region = -1; // end of list marker
338 
339  // bijective index mapping between set A (local petsc indexing) and set B (local nodal indexing)
341  if(imesh)
342  SF::local_petsc_to_nodal_mapping(*imesh, petsc2nod);
343 
344  for (int iTrace = 0; iTrace < n_traceNodes; iTrace++) {
345  mesh_int_t lnode = imesh ? imesh->pl.localize(traceNodes[iTrace]) : traceNodes[iTrace];
346 
347  // here we check if lnode is in set B (i.e. local nodal indexing)
348  if(imesh) {
349  // here we check if lnode is in set B (i.e. local nodal indexing)
350  if(petsc2nod.in_b(lnode) == false) continue;
351 
352  // lnode is local nodal, we map it to local petsc
353  lnode = petsc2nod.backward_map(lnode);
354  }
355 
356  for (int iRegion = 0; iRegion < MIIF->N_IIF; iRegion++)
357  {
358  node_index_t local_node = static_cast<node_index_t>(lnode);
359  auto target = static_cast<node_index_t *>(bsearch(&local_node, MIIF->NodeLists[iRegion],
360  MIIF->N_Nodes[iRegion], sizeof(node_index_t), node_index_cmp));
361 
362  if (target != NULL ) {
363  node_index_t idx = target - MIIF->NodeLists[iRegion];
364  Trace_Info *trace_info = MIIF->trace_info+iTrace;
365  trace_info->found = true;
366  trace_info->region = iRegion;
367  trace_info->node_idx = idx;
368  }
369  }
370  }
371 
372  for (int iTrace = 0; iTrace < n_traceNodes; iTrace++) {
373  if(get_global(int(MIIF->trace_info[iTrace].found), MPI_SUM) == 0) {
374  MIIF->trace_info[iTrace].ignored = true;
375  log_msg(0,4,0, "trace node %d not found", traceNodes[iTrace]);
376  continue;
377  }
378 
379  char traceName[FILENAME_BUF];
380  snprintf(traceName, sizeof traceName, "Trace_%d.dat", label ? label[iTrace] : traceNodes[iTrace]);
381  MIIF->trace_info[iTrace].file = f_open(traceName, "w");
382  }
383 } // open_trace
384 
399 void dump_trace(MULTI_IF *MIIF, limpet::Real time) {
400  if (MIIF->trace_info == NULL)
401  return;
402 
403 // struct exception_type e;
404 
405 #define MAX_TRACE_LINE_LEN 8196
406 
407  std::vector<IonIfBase*>& IIF = MIIF->IIF;
408  FILE *fs;
409 
410  for (int iTrace = 0; MIIF->trace_info[iTrace].region >= 0; iTrace++) {
411  Trace_Info *ctrace = MIIF->trace_info+iTrace;
412 
413  if (ctrace->ignored)
414  continue;
415 
416  if (ctrace->found) {
417  // The buffer is printed with %s, so it has to be zeroed for every line: the
418  // BSD fmemopen_ fallback does not append a terminating null byte on close.
419  char trace_buf[MAX_TRACE_LINE_LEN] = {0};
420 
421  fs = fmemopen_(trace_buf, MAX_TRACE_LINE_LEN, "w");
422  fprintf(fs, "%4.10f\t", time);
423  if (IIF[ctrace->region]->get_type().has_trace()) {
424  IIF[ctrace->region]->get_type().trace(*IIF[ctrace->region], ctrace->node_idx,
425  fs, MIIF->ldata[ctrace->region]);
426  }
427  for (auto& plugin : IIF[ctrace->region]->plugins()) {
428  if (plugin->get_type().has_trace()) {
429  fprintf(fs, "\t");
430  plugin->get_type().trace(
431  *plugin, ctrace->node_idx,
432  fs, MIIF->ldata[ctrace->region]);
433  }
434  }
435  fprintf(fs, "\n");
436  fclose(fs);
437 
438  fprintf(ctrace->file->fd, "%s", trace_buf);
439  }
440  fflush(ctrace->file->fd);
441  }
442 } // dump_trace
443 
444 void close_trace(MULTI_IF *MIIF) {
445  if (!MIIF->trace_info) return;
446 
447  for (int iTrace = 0; MIIF->trace_info[iTrace].region >= 0; iTrace++)
448  f_close(MIIF->trace_info[iTrace].file);
449 
450  free(MIIF->trace_info);
451  MIIF->trace_info = NULL;
452 }
453 
460 void MULTI_IF::initialize_currents(double idt, int subDt) {
461  numSubDt = subDt;
462  dt = idt/this->numSubDt;
463 
464  allocate_shared_data(this);
465  initializeIMPData(this);
466  initialize_ionic_IF(this);
467 }
468 
475 void MULTI_IF::dump_luts_MIIF(bool zipped) {
476  // if one of the IFs in MIIF does not live in partion 0
477  // we won't get a LUT dumped.
478  if (get_rank()) return;
479 
480  std::vector<IonIfBase*>& pIF = this->IIF;
481  for (auto& IF : pIF) {
482  int ndmps = IF->dump_luts(zipped);
483  if (ndmps < IF->tables().size()) {
484  log_msg(logger, 4, 0, "LUT dump error %s: only %d out of %d LUTs dumped.\n",
485  IF->get_type().get_name().c_str(), ndmps, IF->tables().size());
486  }
487  for (auto& plugin : IF->plugins()) {
488  ndmps = plugin->dump_luts(zipped);
489  if (ndmps < plugin->tables().size()) {
490  log_msg(logger, 4, 0, "LUT dump error %s: only %d out of %d LUTs dumped.\n",
491  plugin->get_type().get_name().c_str(), ndmps, IF->tables().size());
492  }
493  }
494  }
495 }
496 
502  if (get_rank()) return;
503 
504  // close files
505  for (int i = 0; i < svd.n; i++)
506 #ifndef USE_HDF5
507  if (svd.hdls[i] != NULL)
508 #endif // ifndef USE_HDF5
509  f_close(svd.hdls[i]);
510 }
511 
522 char *get_sv(void *tab, int offset, node_count_t n, int svSize, int size, int dlo_vector_size) {
523  char *buf = static_cast<char *>(malloc(static_cast<size_t>(n)*size));
524  char *bp = buf;
525  char *p = static_cast<char *>(tab) + offset;
526 
527  for (node_index_t i = 0; i < n; i += dlo_vector_size) {
528  node_count_t dlo_array_size = min(static_cast<node_count_t>(dlo_vector_size), n - i);
529  memcpy(bp, p, size * dlo_array_size);
530  bp += size * dlo_array_size;
531  p += svSize;
532  }
533  return buf;
534 }
535 
543 size_t MULTI_IF::dump_svs(base_timer *iot) {
544  size_t nwr = 0;
545 
546  if (iot->triggered) {
547  for (int i = 0; i < svd.n; i++) {
548  nwr = 0;
549 
550  FILE* fd = svd.hdls[i] ? svd.hdls[i]->fd : NULL;
551  char *buf = get_sv(svd.svtab[i], svd.offset[i], svd.num[i], svd.svsize[i], svd.size[i], svd.dlo_vs[i]);
552  nwr += SF::root_write<char>(fd, (char*) buf,
553  static_cast<size_t>(svd.size[i]) * static_cast<size_t>(svd.num[i]),
554  PETSC_COMM_WORLD);
555  free(buf);
556  }
557  svd.nwr += static_cast<long>(nwr);
558  svd.n_dumps++;
559  }
560 
561  return nwr;
562 }
563 
569 #ifdef HAS_GPU_MODEL
570 __global__
571 void update_vm(node_index_t start, node_index_t end, double *vm, double *ion, double dt)
572 {
573  node_index_t i = blockIdx.x*blockDim.x + threadIdx.x;
574  if (i < end)
575  vm[i] = vm[i] + (ion[i] * (-dt));
576 }
577 #endif
578 
579 // * compute what has to be computed, current or otherwise
580 void MULTI_IF::compute_ionic_current(bool flag_send, bool flag_receive)
581 {
582  gdata[Iion]->set(0.0);
583  if (flag_send == 1) {
584  this->getRealData();
585  }
586 
587  for (int j = 0; j < this->numSubDt; j++)
588  {
589  for (int i = 0; i < this->N_IIF; i++)
590  {
591  IonIfBase* pIF = this->IIF[i];
592  if (!this->N_Nodes[i]) continue;
593 
594  prepare_error_recovery(this, i, pIF->get_tstp().cnt * this->dt);
595  update_ts(&pIF->get_tstp());
596 
597  node_index_t current = 0;
598  do {
599  try {
600  pIF->compute(current, this->N_Nodes[i], this->ldata[i]);
601  current = this->N_Nodes[i];
602  }
603  catch(int e) { //FIXME
604  if(e==-1) {
605  // CHECK this->NodeLists[i][e.node], e.node);
606  fprintf(stderr, "LIMPET compute fail in %s at node %jd (local %jd)! Aborting!\n",
607  this->name.c_str(), printable_int(this->NodeLists[i][j]), printable_int(j));
608  exit(1);
609  }
610  current++;
611  }
612  } while (current < this->N_Nodes[i]);
613 
614  for (auto& plugin : pIF->plugins()) {
615  current = 0;
616  update_ts(&plugin->get_tstp());
617 
618  do {
619  try {
620  plugin->compute(current, this->N_Nodes[i], this->ldata[i]);
621  current = this->N_Nodes[i];
622 
623  if (plugin == nullptr)
624  throw -1;
625  }
626  catch(int e) { //FIXME
627  if(e==-1) {
628  // CHECK this->NodeLists[i][e.node], e.node);
629  fprintf(stderr, "LIMPET plugin fail in %s at node %jd (local %jd)! Aborting!\n",
630  this->name.c_str(), printable_int(this->NodeLists[i][j]), printable_int(j));
631  exit(1);
632  }
633  current++;
634  }
635  } while (current < this->N_Nodes[i]);
636  }
637  }
638 
639  #ifdef HAS_GPU_MODEL
640  //TODO: extUpdateVm can be true outside of the bench executable ! But this should
641  // only run in bench! This should be done for each model according to its target
642  if(!extUpdateVm && is_gpu(this->targets[0])) {
643 
644  #if defined __CUDA__ || defined __HIP__
645  update_vm<<<(this->N_Nodes[0]/64)+1,64>>>(0, this->N_Nodes[0], this->ldata[0][Vm], this->ldata[0][Iion], this->dt);
646  #ifdef __CUDA__
647  cudaDeviceSynchronize();
648  #elif defined __HIP__
649  hipDeviceSynchronize();
650 #endif
651  #else
652  fprintf(stderr, "GPU/CUDA not found");
653  #endif
654  }
655  #endif
656 
657  if (flag_receive == 1) {
658  this->releaseRealData();
659  }
660 
661  // TODO: Target should be checked for each region
662  if(!extUpdateVm && !is_gpu(this->targets[0]))
663  gdata[Vm]->add_scaled(*gdata[Iion], SF_real(-dt));
664  }
665 }
666 
668  assert(!this->doppel);
669 
670  // Free data first since this function needs to access memory in the IIFs
671  freeIMPData(this);
672  for (auto& pIF : this->IIF) {
673  pIF->get_type().destroy_ion_if(pIF);
674  }
675 }
676 
684  for (int i = 0; i < pMIIF->N_IIF; i++) {
685  if (!pMIIF->N_Nodes[i])
686  continue;
687 
688  // if memory contiguous, we don't need to copy data, merely pass pointers
689  pMIIF->contiguous[i] = pMIIF->N_Nodes[i]-1 ==
690  pMIIF->NodeLists[i][pMIIF->N_Nodes[i]-1]-pMIIF->NodeLists[i][0];
691 
692  for (int j = 0; j < NUM_IMP_DATA_TYPES; j++) {
693  // check if it is used by the IMPs
694  if (!USED_DAT(pMIIF->IIF[i], imp_data_flag[j]) )
695  continue;
696 
697  // allocate mem buffer only if not contiguous or if data is on GPU
698  if (!pMIIF->contiguous[i] || is_gpu(pMIIF->IIF[i]->get_target()))
699  pMIIF->ldata[i][j] =
700  allocate_on_target<GlobalData_t>(pMIIF->IIF[i]->get_target(),
701  pMIIF->N_Nodes[i]);
702  // check if data supplied
703  if (pMIIF->gdata[j] == NULL) {
704  log_msg(pMIIF->logger, 5, LOCAL, "IMP data type %s not supplied for region %d", imp_data_names[j], i);
705  exit(1);
706  }
707  }
708  }
709 } // initializeIMPData
710 
720  // set rdata in the parent vector to point to the
721  // local data to be computed on the local processor
722  for (int j = 0; j < NUM_IMP_DATA_TYPES; j++) {
723  SF_real* rdata;
724  if (this->gdata[j] != NULL) {
725  rdata = this->gdata[j]->ptr();
726  this->procdata[j] = rdata;
727  }
728  else continue;
729 
730  // now map local data so that each ionic model can use it
731  // if noncontiguous in the parent vector, copy to contiguous local vector
732  for (int i = 0; i < this->N_IIF; i++) {
733  if (!this->N_Nodes[i] || !USED_DAT(this->IIF[i], imp_data_flag[j]) )
734  continue;
735 
736  if (this->contiguous[i] && !is_gpu(this->IIF[i]->get_target()))
737  this->ldata[i][j] = static_cast<GlobalData_t *>(rdata) + this->NodeLists[i][0];
738  else {
739  const node_index_t* ip = this->NodeLists[i];
740  for (node_index_t k = 0; k < this->N_Nodes[i]; k++)
741  this->ldata[i][j][k] = rdata[ip[k]];
742  }
743  }
744  }
745 } // getRealData
746 
756 
757  for (int i = 0; i < this->N_IIF; i++) {
758  if (!this->N_Nodes[i]) continue;
759 
760  // Every external variable used by the model must be copied on GPU
761  if (is_gpu(this->IIF[i]->get_target())) {
762  for (int j = 0; j < NUM_IMP_DATA_TYPES; j++) {
763  if (!USED_DAT(this->IIF[i], imp_data_flag[j]) )
764  continue;
765  if (this->contiguous[i])
766  memcpy(&this->procdata[j][this->NodeLists[i][0]], this->ldata[i][j], sizeof(this->ldata[i][j][0])*this->N_Nodes[i]);
767  else {
768  for (node_index_t k = 0; k < this->N_Nodes[i]; k++)
769  this->procdata[j][this->NodeLists[i][k]] = this->ldata[i][j][k];
770  }
771  }
772  }
773  else {
774  for (int j = 0; j < NUM_IMP_DATA_TYPES; j++) {
775  if (this->IIF[i]->get_moddat() & imp_data_flag[j]) {
776  if (!this->contiguous[i]) {
777  for (node_index_t k = 0; k < this->N_Nodes[i]; k++)
778  this->procdata[j][this->NodeLists[i][k]] = this->ldata[i][j][k];
779  }
780  }
781  }
782  }
783  }
784 
785  for (int j = 0; j < NUM_IMP_DATA_TYPES; j++)
786  if (this->gdata[j] != NULL)
787  this->gdata[j]->release_ptr(this->procdata[j]);
788 }
789 
790 // * free the local storage vectors
791 void freeIMPData(MULTI_IF *pMIIF) {
792  for (int j = 0; j < NUM_IMP_DATA_TYPES; j++) {
793  if (pMIIF->gdata[j] != NULL) {
794  for (int i = 0; i < pMIIF->N_IIF; i++)
795  if ((!pMIIF->contiguous[i] || is_gpu(pMIIF->IIF[i]->get_target())) && USED_DAT(pMIIF->IIF[i], imp_data_flag[j]) && pMIIF->N_Nodes[i] > 0) {
796  deallocate_on_target<GlobalData_t>(pMIIF->IIF[i]->get_target(),
797  pMIIF->ldata[i][j]);
798  }
799  delete pMIIF->gdata[j];
800  }
801  }
802  free(pMIIF->contiguous);
804 }
805 
815 int get_plug_flag(char *plgstr, int *out_num_plugins, IonTypeList& out_plugins) {
816  if (plgstr == NULL) {
817  *out_num_plugins = 0;
818  out_plugins.clear();
819  return 1;
820  }
821 
822  // maximum number of plugins is 200 apparently
823  char *plugspec = dupstr(plgstr);
824  char *saveptr;
825  char *token = tokstr_r(plugspec, ":", &saveptr);
826 
827  *out_num_plugins = 0;
828  while (token) {
829  IonType* type = get_ion_type(std::string(token));
830  if (type == NULL) {
831  free(plugspec);
832  return 0;
833  } else {
834  out_plugins.push_back(*type);
835  }
836  token = tokstr_r(NULL, ":", &saveptr);
837  }
838 
839  *out_num_plugins = out_plugins.size();
840 
841  free(plugspec);
842  return 1;
843 } // get_plug_flag
844 
863 int MULTI_IF::adjust_MIIF_variables(const char* variable,
864  const SF::vector<SF_int> & indices,
865  const SF::vector<SF_real> & values)
866 {
867  int num_changed = 0;
868 
869  // determine if we're dealing with an external variable or a state variable.
870  if (index(variable, '.') == NULL) {
871  // external variable, the easy case.
872  // which external variable?
873  int data_id = -1;
874  for (int ii = 0; ii < NUM_IMP_DATA_TYPES; ii++) {
875  if (strcmp(variable, imp_data_names[ii]) == 0) {
876  data_id = ii;
877  break;
878  }
879  }
880  if (data_id == -1) {
881  log_msg(logger, 5, FLUSH, "Error! No external variable named %s in this build of openCARP.", variable);
882  exit(EXIT_FAILURE);
883  }
884 
885  // make sure that external variable is being used.
886  if (this->gdata[data_id] == NULL) {
887  log_msg(logger, 4, 0,
888  "External variable %s is not being used this processor.\n"
889  "Is this really what you meant to do?\n"
890  "Perhaps you don't have the correct Ionic models selected.",
891  imp_data_names[data_id]);
892  }
893  else {
894  // fill in the external variable with the data from the file.
895  SF_real *raw_data = this->gdata[data_id]->ptr();
896 
897  for (size_t i = 0; i < indices.size(); i++) {
898  raw_data[indices[i]] = values[i];
899  num_changed++;
900  }
901 
902  this->gdata[data_id]->release_ptr(raw_data);
903  }
904  }
905  else {
906  // state variable.
907  // extract the IMP name and state variable name from the string.
908  char *saveptr;
909  char *my_variable = dupstr(variable);
910  char *IIF_name = tokstr_r(my_variable, ".", &saveptr);
911  char *sv_name = tokstr_r(NULL, ".", &saveptr);
912 
913  IonType* type = get_ion_type(std::string(IIF_name));
914  if (type == NULL) {
915  log_msg(logger, 5, 0, "%s error: %s is not a valid IMP name.", __func__, IIF_name);
916  exit(EXIT_FAILURE);
917  }
918 
919  int sv_offset;
920  int sv_size;
921  SVgetfcn sv_get = type->get_sv_offset(sv_name, &sv_offset, &sv_size);
922  if (sv_get == NULL) {
923  log_msg(logger, 5, 0, "%s error: %s is not a valid state variable for the %s model.",
924  __func__, sv_name, IIF_name);
925  exit(EXIT_FAILURE);
926  }
927  SVputfcn sv_put = getPutSV(sv_get);
928 
929  // Ok, go through the ionic models
930  for (int i_iif = 0; i_iif < this->N_IIF; i_iif++) {
931  IonIfBase *lIIF = NULL;
932  if (this->IIF[i_iif]->get_type() == *type) {
933  lIIF = this->IIF[i_iif];
934  }
935  else {
936  // Go through the plugins
937  for (auto& plugin : this->IIF[i_iif]->plugins()) {
938  if (plugin->get_type() == *type) {
939  lIIF = plugin;
940  break;
941  }
942  }
943  }
944 
945  if (lIIF == NULL) {
946  continue;
947  }
948 
949  for (size_t ii = 0; ii < indices.size(); ii++) {
950  GlobalData_t file_value = values[ii];
951 
952  node_index_t local_node = static_cast<node_index_t>(indices[ii]);
953  node_index_t *target = static_cast<node_index_t*>(bsearch(&local_node, this->NodeLists[i_iif],
954  this->N_Nodes[i_iif], sizeof(node_index_t), node_index_cmp));
955  if (target) {
956  // We found a point to adjust! Do the adjustment.
957  num_changed++;
958  sv_put(*lIIF, target - this->NodeLists[i_iif], sv_offset, file_value);
959  }
960  }
961 
962  }
963  free(my_variable);
964  }
965 
966  MPI_Allreduce(MPI_IN_PLACE, &num_changed, 1, MPI_INT, MPI_SUM, PETSC_COMM_WORLD);
967 
968  return num_changed;
969 } // adjust_MIIF_variables
970 
988 int determine_write_ranges(int N, size_t *offset, size_t bufsize, int **ranges) {
989  int nitems;
990 
991  if (!get_rank() ) {
992  Salt_list r; STRUCT_ZERO(r); r.chunk = 10;
993  int temp = 0;
994  SLIST_APPEND(&r, temp);
995  long loff = offset[0];
996  for (int i = 0; i < N; i++)
997  if (offset[i]-loff > bufsize) {
998  SLIST_APPEND(&r, i);
999  loff = offset[i];
1000  }
1001 
1002  nitems = r.nitems;
1003  SLIST_APPEND(&r, N);
1004  *ranges = (int *)r.data;
1005  }
1006 
1007  MPI_Bcast(&nitems, 1, MPI_INT, 0, PETSC_COMM_WORLD);
1008  if (get_rank() )
1009  *ranges = static_cast<int *>(malloc( (nitems+1)*sizeof(int) ));
1010  MPI_Bcast(*ranges, nitems+1, MPI_INT, 0, PETSC_COMM_WORLD);
1011  return nitems;
1012 } // determine_write_ranges
1013 
1052 void MULTI_IF::dump_state(char *fname, float simtime, mesh_t gid,
1053  bool append, unsigned int revision)
1054 {
1055  float t0, t1;
1056  t0 = get_time();
1057 
1058  FILE_SPEC out = NULL;
1059  int rank = get_rank();
1060  global_node_index_t miif_node_gsize = get_global(static_cast<global_node_index_t>(this->numNode), MPI_SUM);
1061  int error = 0;
1062 
1063  // EMI ionic state lives on mesh elements (membrane faces), electrics on nodes.
1064  // Pick the matching canonical permutation and numbering from the data layout.
1065  const bool elemwise = this->gdata[Vm] && this->gdata[Vm]->layout == sf_vec::elemwise;
1066  const int canon_perm = elemwise ? ELEM_PETSC_TO_CANONICAL : PETSC_TO_CANONICAL;
1067 
1068  log_msg(logger, 0, 0, "Saving state at time %f in file: %s", simtime, fname);
1069 
1070  if (rank == 0) {
1071  out = f_open(fname, append ? "a" : "w");
1072 
1073  if(out) {
1074  fseek(out->fd, 0, SEEK_END); // NOP call to sync disk data for switch to write mode
1075  write_bin_string(out, Magic_MIIF_ID);
1076 
1077  fwrite(&MIIF_Format, sizeof(unsigned int), 1, out->fd);
1078  fwrite(&revision, sizeof(unsigned int), 1, out->fd);
1079  time_t tm = time(NULL);
1080  fwrite(&tm, sizeof(time_t), 1, out->fd);
1081  fwrite(&simtime, sizeof(float), 1, out->fd);
1082  fwrite(&miif_node_gsize, sizeof(global_node_index_t), 1, out->fd);
1083 
1084  int num_gdata = 0;
1085  for (int i = 0; i < NUM_IMP_DATA_TYPES; i++)
1086  if (this->gdata[i]) num_gdata++;
1087 
1088  fwrite(&num_gdata, sizeof(int), 1, out->fd);
1089  }
1090  else
1091  error++;
1092  }
1093 
1094 // #define CHATTY
1095 
1096  if(get_global(error, MPI_SUM))
1097  EXIT(EXIT_FAILURE);
1098 
1099  FILE* fd = rank == 0 ? out->fd : NULL;
1100  sf_vec* outVec;
1101 
1102  for (int i = 0; i < NUM_IMP_DATA_TYPES; i++) {
1103  if (this->gdata[i]) {
1104  if(rank == 0) {
1105  write_bin_string(out, imp_data_names[i]);
1106 #ifdef CHATTY
1107  log_msg(NULL, 0, 0, "\tDumping %s at %d", imp_data_names[i], ftell(fd) );
1108 #endif
1109  }
1110 
1111  // If we have a proper set up parallel context, we pass a gid != unset_msh. Then
1112  // we can query petsc-to-canonical scattering. If we are in a simpler context
1113  // like bench, we assume that no scattering is needed and just shallow-copy the vector.
1114  if(gid != unset_msh) {
1115  SF::init_vector(&outVec, this->gdata[Vm]);
1116  SF::scattering* sc = get_permutation(gid, canon_perm, 1);
1117  assert(sc != NULL);
1118 
1119  sc->forward(*this->gdata[i], *outVec);
1120  outVec->write_binary<SF_real>(fd);
1121  delete outVec;
1122  }
1123  else {
1124  this->gdata[i]->write_binary<SF_real>(fd);
1125  }
1126  }
1127  }
1128 
1129  // output IMP region info
1130  // #regions and the IM and plugins for each region
1131  // determine memory requirements for each region
1132  int* imp_mem = new int[this->N_IIF];
1133  size_t *offset = NULL;
1134  long filepos = 0.;
1135 
1136  if (rank == 0) {
1137 #ifdef CHATTY
1138  log_msg(NULL, 0, 0, "\tDumping IMP sizes at %d", ftell(fd) );
1139 #endif
1140 
1141  int max = 1;
1142  fwrite(&this->N_IIF, sizeof(int), 1, fd); // #IIF's
1143 
1144  for (int i = 0; i < this->N_IIF; i++) {
1145  IonIfBase *imp = this->IIF[i];
1146  write_bin_string(out, imp->get_type().get_name().c_str());
1147 
1148  // record the de-interleaved per-node size (format 3), not the whole SoA block
1149  int sv_size = (int) imp->get_sv_per_node_size();
1150  fwrite(&sv_size, sizeof(int), 1, fd);
1151  // fingerprint of the SV layout, so a restart against a changed model revision
1152  // is detected instead of silently misread field-for-field
1153  uint64_t sv_fp = imp->sv_fingerprint();
1154  fwrite(&sv_fp, sizeof(uint64_t), 1, fd);
1155  unsigned long n_plugins = (unsigned long) imp->plugins().size();
1156  fwrite(&n_plugins, sizeof(int), 1, fd);
1157  imp_mem[i] = sv_size;
1158 
1159  for (auto& plug : imp->plugins()) {
1160  write_bin_string(out, plug->get_type().get_name().c_str());
1161 
1162  int plug_size = (int) plug->get_sv_per_node_size();
1163  fwrite(&plug_size, sizeof(int), 1, fd);
1164  uint64_t plug_fp = plug->sv_fingerprint();
1165  fwrite(&plug_fp, sizeof(uint64_t), 1, fd);
1166  imp_mem[i] += plug_size;
1167  }
1168  }
1169  filepos = ftell(fd);
1170  }
1171 
1172  MPI_Bcast(imp_mem, this->N_IIF, MPI_INT, 0, PETSC_COMM_WORLD);
1173 
1174  // If we have a proper set up parallel context, we pass a gid != unset_msh. Then
1175  // we can query a mesh and its parallel numbering. If we are in a simpler context
1176  // like bench, we build the numbering ourselves. Then, we assume that no
1177  // renumbering has taken place.
1179  if(gid != unset_msh) {
1180  const sf_mesh & mesh = get_mesh(gid);
1181  if(elemwise) {
1182  const SF::vector<mesh_int_t> & canon_nbr = mesh.get_numbering(SF::NBR_ELEM_SUBMESH);
1183  loc2canon.resize(canon_nbr.size());
1184 
1185  for(size_t i=0; i<canon_nbr.size(); i++) loc2canon[i] = canon_nbr[i];
1186  }
1187  else {
1188  const SF::vector<mesh_int_t> & canon_nbr = mesh.get_numbering(SF::NBR_SUBMESH);
1189  const SF::vector<mesh_int_t> & alg_idx = mesh.pl.algebraic_nodes();
1190  loc2canon.resize(alg_idx.size());
1191 
1192  for(size_t i=0; i<alg_idx.size(); i++) loc2canon[i] = canon_nbr[alg_idx[i]];
1193  }
1194  }
1195  else {
1196  int rank = get_rank();
1198  SF::layout_from_count<global_node_index_t>(static_cast<global_node_index_t>(this->numNode),
1199  layout, PETSC_COMM_WORLD);
1200  loc2canon.resize(this->numNode);
1201 
1202  for (node_index_t i = 0; i < this->numNode; i++) loc2canon[i] = layout[rank] + i;
1203  }
1204 
1205  // loc2canon must hold one canonical index per local node; a shorter array
1206  // would read out of bounds below and write a corrupt checkpoint
1207  assert(loc2canon.size() == static_cast<size_t>(this->numNode));
1208 
1209  // write out IIF_Mask canonically ordered
1210  global_node_index_t *canord = new global_node_index_t[this->numNode];
1211  for (node_index_t i = 0; i < this->numNode; i++)
1212  canord[i] = loc2canon[i];
1213 
1214 #ifdef CHATTY
1215  log_msg(NULL, 0, 0, "\tDumping IMP masks at %d", filepos);
1216 #endif // ifdef CHATTY
1217 
1218  SF::root_write_ordered<global_node_index_t, IIF_Mask_t>(fd, canord, IIFmask, numNode, PETSC_COMM_WORLD);
1219 
1220  // for every local node, we compute the size of its ionic model
1221  // and plugins, and the associated displacments between the nodes
1222  SF::vector<global_node_index_t> impsize(numNode), impdsp;
1223  for(node_index_t i=0; i<numNode; i++)
1224  impsize[i] = imp_mem[(int)IIFmask[i]];
1225  SF::dsp_from_cnt(impsize, impdsp);
1226 
1227  // this also gives us the size of the total buffer
1228  size_t num_entr = static_cast<size_t>(SF::sum(impsize));
1229  SF::vector<char> impdata(num_entr);
1230 
1231  // De-interleave each node's state into a contiguous per-node record (format 3).
1232  // The state lives as data-layout-optimized SoA blocks: node imp_nod_idx is lane
1233  // (imp_nod_idx % vec_size) of block (imp_nod_idx / vec_size). We gather that lane
1234  // of every state-variable field, keyed by the node's own canonical id (via loc).
1235  // This makes the dump independent of the partitioning. Without DLO vec_size is 1
1236  // and this reduces to copying each node's contiguous state.
1237  for (int imp_idx = 0; imp_idx < this->N_IIF; imp_idx++)
1238  {
1239  IonIfBase* iif = this->IIF[imp_idx];
1240 
1241  // field layouts are model properties: gather them once, not per node
1242  std::vector<std::pair<int, int>> main_fields;
1243  iif->get_sv_layout(main_fields);
1244 
1245  std::vector<std::vector<std::pair<int, int>>> plug_fields;
1246  for (auto& plug : iif->plugins()) {
1247  plug_fields.emplace_back();
1248  plug->get_sv_layout(plug_fields.back());
1249  }
1250 
1251  for (node_index_t imp_nod_idx = 0; imp_nod_idx < this->N_Nodes[imp_idx]; imp_nod_idx++) {
1252  node_index_t loc = this->NodeLists[imp_idx][imp_nod_idx];
1253 
1254  char* write = impdata.data() + impdsp[loc];
1255  auto gather = [&](char* src, std::size_t sz) { memcpy(write, src, sz); write += sz; };
1256 
1257  iif->for_each_sv_field(imp_nod_idx, main_fields, gather);
1258  for (size_t p = 0; p < plug_fields.size(); p++)
1259  iif->plugins()[p]->for_each_sv_field(imp_nod_idx, plug_fields[p], gather);
1260  }
1261  }
1262 
1263  // finally we can write out the IonIf data to disk
1264  SF::root_write_ordered<global_node_index_t, char>(fd, canord, impsize.data(), impdata.data(),
1265  numNode, num_entr, PETSC_COMM_WORLD);
1266  delete [] canord;
1267  delete [] imp_mem;
1268 
1269  if(fd) fclose(fd);
1270 
1271  double dump_time = timing(t1, t0);
1272  log_msg(logger, 0, 0, " in %.3f seconds.\n", dump_time);
1273 }
1274 
1297 float MULTI_IF::restore_state(const char *fname, mesh_t gid, bool close)
1298 {
1299  static FILE_SPEC in = NULL;
1300  static char *last_fn = NULL;
1301  double t0, t1;
1302 
1303  t0 = get_time();
1304  int error = 0;
1305  int my_rank = get_rank();
1306  int mpi_size = get_size();
1307 
1308  // EMI ionic state lives on mesh elements (membrane faces), electrics on nodes.
1309  // Pick the matching canonical permutation and numbering from the data layout.
1310  const bool elemwise = this->gdata[Vm] && this->gdata[Vm]->layout == sf_vec::elemwise;
1311  const int canon_perm = elemwise ? ELEM_PETSC_TO_CANONICAL : PETSC_TO_CANONICAL;
1312 
1313  if (my_rank == 0) {
1314  if (fname) in = f_open(fname, "r");
1315 
1316  if (!in) {
1317  if (fname) log_msg(logger, 5, 0, "Error: cannot open file: %s\n", fname);
1318  else log_msg(logger, 5, 0, "Error: file stream not open yet");
1319  error++;
1320  }
1321  else {
1322  if (strcmp(read_bin_string(in), Magic_MIIF_ID) ) {
1323  log_msg(logger, 5, 0, "%s is not a recognized MIIF dump file", fname);
1324  error++;
1325  }
1326  }
1327  }
1328 
1329  if (get_global(error, MPI_SUM))
1330  EXIT(1);
1331 
1332  if (fname) {
1333  free(last_fn);
1334  last_fn = strdup(fname);
1335  }
1336  else {
1337  fname = last_fn;
1338  }
1339 
1340  unsigned int format, version;
1341  float time = 0.0f;
1342  time_t save_date;
1343  f_read_par(&format, sizeof(unsigned int), 1, in);
1344  f_read_par(&version, sizeof(unsigned int), 1, in);
1345  f_read_par(&save_date, sizeof(time_t), 1, in);
1346  f_read_par(&time, sizeof(float), 1, in);
1347  log_msg(logger, 0, 0, "Restoring time %f from %s (format v%d) generated\n\tby calling "
1348  "program r%d on %s", time, fname, format, version, ctime(&save_date) );
1349 
1350  global_node_index_t savedNum = 0;
1351  global_node_index_t glob_numNode = get_global(static_cast<global_node_index_t>(this->numNode),
1352  MPI_SUM, PETSC_COMM_WORLD);
1353 
1354  if (format >= 2) {
1355  f_read_par(&savedNum, sizeof(global_node_index_t), 1, in);
1356  } else {
1357  int savedNum32 = 0;
1358  f_read_par(&savedNum32, sizeof(int), 1, in);
1359  savedNum = savedNum32;
1360  }
1361  if (savedNum != glob_numNode) {
1362  log_msg(logger, 5, 0, "expecting %jd nodes but read %jd nodes",
1363  printable_int(glob_numNode), printable_int(savedNum));
1364  EXIT(1);
1365  }
1366 
1367  int num_gdata;
1368  f_read_par(&num_gdata, sizeof(int), 1, in);
1369  SF::scattering & petsc2canon = *get_permutation(gid, canon_perm, 1);
1370 
1371  for (int g = 0; g < num_gdata; g++) {
1372  char *datatype = read_bin_string_par(in);
1373  int i;
1374  for (i = 0; i < NUM_IMP_DATA_TYPES; i++) {
1375  if (!strcmp(datatype, imp_data_names[i]) ) {
1376  if (this->gdata[i]) {
1377  log_msg(logger, 0, 0, "\tRestoring global data %s", imp_data_names[i], datatype);
1378 
1379  FILE* fd = my_rank == 0 ? in->fd : NULL;
1380  this->gdata[i]->read_binary<SF_real>(fd);
1381  bool fwd = false;
1382  petsc2canon(*this->gdata[i], fwd);
1383  } else {
1384  log_msg(logger, 0, 0, "\tGlobal data %s not used", datatype);
1385  }
1386  break;
1387  }
1388  }
1389  if (i == NUM_IMP_DATA_TYPES)
1390  log_msg(logger, 3, 0, "\tSaved global data %s not recognized", datatype);
1391 
1392  // skip the saved block for a global-data type we cannot restore. The skip
1393  // length must come from the saved size (one SF_real per global node), not
1394  // from this->gdata[i], which is exactly what is NULL / out of range here.
1395  if ( (my_rank == 0) && ((i == NUM_IMP_DATA_TYPES) || !this->gdata[i]) )
1396  fseek(in->fd, static_cast<long>(glob_numNode) * sizeof(SF_real), SEEK_CUR);
1397 
1398  free(datatype);
1399  }
1400 
1401  int N_IIF;
1402  f_read_par(&N_IIF, sizeof(int), 1, in);
1403 
1404  // A checkpoint is restorable only into the configuration it was written from. The
1405  // region a node belongs to is stored per node, so a different set of regions means the
1406  // saved state does not describe this run and cannot be mapped onto it. Every check
1407  // below reads header data that f_read_par has broadcast, so it holds identically on
1408  // every rank and the abort is collective without further communication.
1409  if (N_IIF != this->N_IIF) {
1410  log_msg(logger, 5, ECHO, "Checkpoint was written with %d ionic region(s), this simulation has %d.",
1411  N_IIF, this->N_IIF);
1412  EXIT(EXIT_FAILURE);
1413  }
1414 
1415  // read in the IMPs for each region
1416  IMPinfo *imps = static_cast<IMPinfo *>(calloc(N_IIF, sizeof(IMPinfo)));
1417  int *IMPsz = static_cast<int *>(malloc(N_IIF * sizeof(int)));
1418 
1419  for (int i = 0; i < N_IIF; i++) {
1420  imps[i].name = read_bin_string_par(in);
1421  f_read_par(&imps[i].sz, sizeof(int), 1, in);
1422  if (format >= 3) f_read_par(&imps[i].fingerprint, sizeof(uint64_t), 1, in);
1423  f_read_par(&imps[i].nplug, sizeof(int), 1, in);
1424 
1425  imps[i].offset = 0;
1426  IMPsz[i] = imps[i].sz;
1427  imps[i].plug = static_cast<IMPinfo *>(calloc(sizeof(IMPinfo), imps[i].nplug));
1428 
1429  for (int j = 0; j < imps[i].nplug; j++) {
1430  imps[i].plug[j].offset = IMPsz[i];
1431  imps[i].plug[j].name = read_bin_string_par(in);
1432  f_read_par(&imps[i].plug[j].sz, sizeof(int), 1, in);
1433  if (format >= 3) f_read_par(&imps[i].plug[j].fingerprint, sizeof(uint64_t), 1, in);
1434  IMPsz[i] += imps[i].plug[j].sz;
1435  }
1436  }
1437 
1438  // every saved region must describe the region it is restored into, down to the plugins
1439  for (int i = 0; i < N_IIF; i++) {
1440  if (strcmp(imps[i].name, this->IIF[i]->get_type().get_name().c_str()) ) {
1441  log_msg(logger, 5, ECHO, "Checkpoint holds ionic model %s for region %d, this simulation uses %s.",
1442  imps[i].name, i, this->IIF[i]->get_type().get_name().c_str());
1443  EXIT(EXIT_FAILURE);
1444  }
1445  // format >= 3 stores the de-interleaved per-node size; format 2 the whole SoA block
1446  const bool per_node_fmt = format >= 3;
1447  const int exp_sz = per_node_fmt ? (int) this->IIF[i]->get_sv_per_node_size()
1448  : (int) this->IIF[i]->get_sv_size();
1449  if (imps[i].sz != exp_sz) {
1450  log_msg(logger, 5, ECHO, "Checkpoint holds %d byte(s) of state for region %d (%s), this simulation needs %d.",
1451  imps[i].sz, i, imps[i].name, exp_sz);
1452  EXIT(EXIT_FAILURE);
1453  }
1454  // a matching size but different SV-layout fingerprint means the model's state
1455  // variables were reordered or retyped since the checkpoint, so the saved bytes
1456  // would be read field-for-field into the wrong variables
1457  if (per_node_fmt && imps[i].fingerprint != this->IIF[i]->sv_fingerprint()) {
1458  log_msg(logger, 5, ECHO, "Checkpoint state-variable layout of region %d (%s) does not match the model in this build.",
1459  i, imps[i].name);
1460  EXIT(EXIT_FAILURE);
1461  }
1462 
1463  // The plugin sets must match exactly, in both directions. A plugin missing from the
1464  // checkpoint is as invalid as one missing from this run: its own state would start
1465  // from initial conditions while the model it is coupled to resumes an equilibrated
1466  // state, and the coupling also alters the main model's trajectory.
1467  const int cur_nplug = (int) this->IIF[i]->plugins().size();
1468  if (imps[i].nplug != cur_nplug) {
1469  log_msg(logger, 5, ECHO, "Checkpoint holds %d plugin(s) for region %d (%s), this simulation has %d.",
1470  imps[i].nplug, i, imps[i].name, cur_nplug);
1471  EXIT(EXIT_FAILURE);
1472  }
1473 
1474  // The same plugin may be listed more than once for a region, so the matching has to be
1475  // one-to-one: without this, both saved copies of a duplicated plugin would map onto the
1476  // same current plugin and the unmatched one would silently keep its initial conditions.
1477  std::vector<bool> matched(cur_nplug, false);
1478 
1479  imps[i].compatible = true;
1480  for (int j = 0; j < imps[i].nplug; j++) {
1481  for (int k = 0; k < cur_nplug; k++) {
1482  if (matched[k])
1483  continue;
1484 
1485  const int exp_plug_sz = per_node_fmt ? (int) this->IIF[i]->plugins()[k]->get_sv_per_node_size()
1486  : (int) this->IIF[i]->plugins()[k]->get_sv_size();
1487  const bool plug_fp_ok = !per_node_fmt ||
1488  imps[i].plug[j].fingerprint == this->IIF[i]->plugins()[k]->sv_fingerprint();
1489  if (!strcmp(imps[i].plug[j].name, this->IIF[i]->plugins()[k]->get_type().get_name().c_str()) &&
1490  (imps[i].plug[j].sz == exp_plug_sz) && plug_fp_ok) {
1491  log_msg(logger, 0, 0, "Saved IMP region %d plugin %s compatible", i, imps[i].plug[j].name);
1492  imps[i].plug[j].map = k;
1493  imps[i].plug[j].compatible = true;
1494  matched[k] = true;
1495  break;
1496  }
1497  }
1498  if (!imps[i].plug[j].compatible) {
1499  log_msg(logger, 5, ECHO, "Checkpoint plugin %s of region %d (%s) has no counterpart in this simulation.",
1500  imps[i].plug[j].name, i, imps[i].name);
1501  EXIT(EXIT_FAILURE);
1502  }
1503  }
1504  }
1505 
1506  // read in region mask
1507  const size_t global_node_count = static_cast<size_t>(glob_numNode);
1508  IIF_Mask_t *canMask = static_cast<char *>(malloc(global_node_count*sizeof(this->IIFmask[0]) ));
1509  f_read_par(canMask, sizeof(IIF_Mask_t), global_node_count, in);
1510 
1511  // determine relative IMP data offsets based on canonical ordering
1512  size_t *offset = static_cast<size_t *>(calloc(global_node_count+1, sizeof(size_t)));
1513  for (size_t i = 1; i <= global_node_count; i++)
1514  offset[i] = offset[i-1] + IMPsz[static_cast<int>(canMask[i-1])];
1515 
1516  // read in the state variable data
1517  // TODO: Aurel: Ideally rank0 would read in the data and communicate it, but right now I dont have
1518  // time to code this up. Thus, at least the ranks will read their data one by one so
1519  // we dont saturate the file system.
1520  long SVstart;
1521  if (my_rank == 0) {
1522  SVstart = ftell(in->fd);
1523  f_close(in);
1524  }
1525 
1526  MPI_Bcast(&SVstart, sizeof(long), MPI_BYTE, 0, PETSC_COMM_WORLD);
1527  global_node_index_t mismatch = 0;
1528 
1529  // we need to map from a local algebraic index to a global canonical index
1530  const sf_mesh & mesh = get_mesh(gid);
1532  if(elemwise) {
1533  const SF::vector<mesh_int_t> & canon_nbr = mesh.get_numbering(SF::NBR_ELEM_SUBMESH);
1534  loc2canon.resize(canon_nbr.size());
1535 
1536  for(size_t i=0; i<canon_nbr.size(); i++) loc2canon[i] = canon_nbr[i];
1537  }
1538  else {
1539  const SF::vector<mesh_int_t> & canon_nbr = mesh.get_numbering(SF::NBR_SUBMESH);
1540  const SF::vector<mesh_int_t> & alg_idx = mesh.pl.algebraic_nodes();
1541  loc2canon.resize(alg_idx.size());
1542 
1543  for(size_t i=0; i<alg_idx.size(); i++) loc2canon[i] = canon_nbr[alg_idx[i]];
1544  }
1545 
1546  // loc2canon must hold one canonical index per local node; a shorter array
1547  // would be indexed out of bounds by the per-node restore below
1548  assert(loc2canon.size() == static_cast<size_t>(this->numNode));
1549 
1550  // the ranks read the file one-by-one
1551  for (int pid = 0; pid < mpi_size; pid++) {
1552  if (my_rank == pid) {
1553  if(in) delete in;
1554  in = f_open(fname, "r");
1555 
1556  for (int i = 0; i < N_IIF; i++) {
1557  fseek(in->fd, SVstart, SEEK_SET);
1558  if (format >= 3)
1559  mismatch += this->IIF[i]->restore_per_node(in, this->N_Nodes[i], this->NodeLists[i],
1560  canMask, offset, imps+i, loc2canon.data());
1561  else
1562  mismatch += this->IIF[i]->restore(in, this->N_Nodes[i], this->NodeLists[i],
1563  canMask, offset, imps+i, loc2canon.data());
1564  }
1565 
1566  f_close(in);
1567  }
1568  MPI_Barrier(PETSC_COMM_WORLD);
1569  }
1570  free(canMask);
1571  free(IMPsz);
1572 
1573  // Nodes whose saved region differs from the region they are being restored into keep
1574  // the initial conditions of their ionic model while the rest of the mesh resumes the
1575  // checkpointed state, which is not a restart of the saved simulation. The count is
1576  // all-reduced rather than reduced onto rank 0, so that every rank aborts together
1577  // instead of the others blocking in the barrier above.
1578  mismatch = get_global(mismatch, MPI_SUM);
1579  if (mismatch) {
1580  log_msg(logger, 5, ECHO, "Checkpoint assigns a different ionic region than this simulation does at %jd node(s). "
1581  "The region layout must be identical to restore this checkpoint.",
1582  printable_int(mismatch));
1583  EXIT(EXIT_FAILURE);
1584  }
1585 
1586  if ( (my_rank == 0) && (!close) ) {
1587  if(in) delete in;
1588  in = f_open(fname, "r");
1589 
1590  // position at end of current MIIF data
1591  fseek(in->fd, SVstart+offset[global_node_count], SEEK_SET);
1592  }
1593 
1594  free(offset);
1595  for (int i = 0; i < N_IIF; i++)
1596  free(imps[i].plug);
1597  free(imps);
1598 
1599  double restore_time = timing(t1, t0);
1600  log_msg(logger, 0, 0, "State restored from file %s in %.3f seconds.\n", fname, restore_time);
1601 
1602  return time;
1603 }
1604 
1616 void MULTI_IF::sv_dump_add_by_name_list( int region, char *imp_name,
1617  char *reg_name, char *sv_lst, char *plg_lst,
1618  char *plg_sv_lst, double t, double dump_dt) {
1619  char file[8000], svs[1024], plgs[1024], plgsvs[1024];
1620  char *e, *l, *p, *svnames, *plgnames, *plgsvnames;
1621 
1622  strcpy(svs, sv_lst);
1623  strcpy(plgs, plg_lst);
1624  strcpy(plgsvs, plg_sv_lst);
1625  svnames = &svs[0];
1626  plgnames = &plgs[0];
1627  plgsvnames = &plgsvs[0];
1628 
1629  // override default setting for dump interval
1630  this->svd.intv = dump_dt;
1631 
1632  // override default start time for dumping (in case of a restart)
1633  this->svd.t_dump = t;
1634 
1635  // parse ionic model sv list
1636  while ( (e = get_next_list(svnames, ',') ) ) {
1637  snprintf(file, sizeof file, "%s.%s.bin", reg_name, svnames);
1638  this->sv_dump_add_by_name(region, imp_name, svnames, reg_name, file);
1639  svnames = e;
1640  }
1641 
1642  // Information is provided as follows:
1643  // region[X].plugin = "PLG_A:PLG_B"
1644  // region[X].plug_sv_dumps = "m,h,n:x,y"
1645 
1646  // parse plugin list
1647  while ( (p = get_next_list(plgnames, ':') ) ) {
1648  // get sv list corresponding to current plugin
1649  l = get_next_list(plgsvnames, ':');
1650  while ( (e = get_next_list(plgsvnames, ',') ) ) {
1651  snprintf(file, sizeof file, "%s_%s.%s.bin", reg_name, plgnames, plgsvnames);
1652  this->sv_dump_add_by_name(region, plgnames, plgsvnames, reg_name, file);
1653  plgsvnames = e;
1654  }
1655  plgnames = p;
1656  plgsvnames = l;
1657  }
1658 } // sv_dump_add_by_name_list
1659 
1675 void MULTI_IF::sv_dump_add(int region, const IonType& type, int offset, int size, int dtype,
1676  const char *filename, const char *regname) {
1677  IonIfBase *IF = this->IIF[region];
1678  int n = this->svd.n;
1679 
1680  // find the proper IMP
1681  if (IF->get_type() != type) {
1682  int i;
1683  for (i = 0; i < IF->plugins().size(); i++)
1684  if (IF->plugins()[i]->get_type() == type)
1685  break;
1686  if (i == IF->plugins().size()) {
1687  log_msg(logger, 2, 0, "Warning: IMP %s not found in Region %s\n",
1688  type.get_name().c_str(), regname);
1689  return;
1690  } else {
1691  IF = IF->plugins()[i];
1692  }
1693  }
1694 
1695  this->svd.active = 1;
1696  this->svd.n++;
1697  this->svd.hdls = static_cast<FILE_SPEC *>(realloc(this->svd.hdls, this->svd.n*sizeof(this->svd.hdls[0])));
1698  this->svd.fn = static_cast<char **>(realloc(this->svd.fn, this->svd.n*sizeof(char *)));
1699  this->svd.reg = static_cast<int *>(realloc(this->svd.reg, this->svd.n*sizeof(int)));
1700  this->svd.svnames = static_cast<char **>(realloc(this->svd.svnames, this->svd.n*sizeof(char *)));
1701  this->svd.n_dumps = 0;
1702  this->svd.nwr = 0;
1703  this->svd.offset = static_cast<int *>(realloc(this->svd.offset, this->svd.n*sizeof(int)));
1704  this->svd.size = static_cast<int *>(realloc(this->svd.size, this->svd.n*sizeof(int)));
1705  this->svd.dlo_vs = static_cast<int *>(realloc(this->svd.dlo_vs, this->svd.n*sizeof(int)));
1706  this->svd.dtype = static_cast<int *>(realloc(this->svd.dtype, this->svd.n*sizeof(int)));
1707  this->svd.svtab = static_cast<void **>(realloc(this->svd.svtab, this->svd.n*sizeof(void *)));
1708  this->svd.svsize = static_cast<size_t *>(realloc(this->svd.svsize, this->svd.n*sizeof(size_t)));
1709  this->svd.num = static_cast<node_count_t *>(realloc(this->svd.num, this->svd.n*sizeof(node_count_t)));
1710  this->svd.reg = static_cast<int *>(realloc(this->svd.reg, this->svd.n*sizeof(int)));
1711 
1712  if (!get_rank() ) {
1713 #ifdef HDF5
1714  assert(0);
1715 #else // ifdef HDF5
1716  this->svd.hdls[n] = f_open(filename, "w+");
1717 #endif // ifdef HDF5
1718  } else {
1719  this->svd.hdls[n] = NULL;
1720  }
1721 
1722  this->svd.fn[n] = dupstr(filename);
1723  this->svd.reg[n] = region;
1724  this->svd.svnames[n] = NULL; // has to be filled in in calling routine
1725  this->svd.offset[n] = offset;
1726  this->svd.size[n] = size;
1727  this->svd.dtype[n] = dtype;
1728  this->svd.svtab[n] = IF->get_sv_address();
1729  this->svd.svsize[n] = IF->get_sv_size();
1730  this->svd.num[n] = IF->get_num_node();
1731  this->svd.dlo_vs[n] = IF->get_type().dlo_vector_size();
1732 } // sv_dump_add
1733 
1744 int MULTI_IF::sv_dump_add_by_name(int region, char *impname,
1745  char *svname, char *regname, char *filename) {
1746  int offset, size, dtype, added = 0;
1747  char *svtypename = NULL;
1748  char *filename_bin;
1749 
1750  IonType* type = get_ion_type(std::string(impname));
1751  filename_bin = strcat(filename, ".bin");
1752 
1753  assert(type != NULL);
1754  if (type->get_sv_offset(svname, &offset, &size) ) {
1755  type->get_sv_type(svname, &dtype, &svtypename);
1756  this->sv_dump_add(region, *type, offset, size, dtype, filename_bin, regname);
1757  this->svd.svnames[this->svd.n-1] = dupstr(svname);
1758  added = 1;
1759  } else {
1760  log_msg(NULL, 1, 0, "No state variable added to dump list");
1761  }
1762 
1763  return added;
1764 }
1765 
1778 {
1779  assert(miif->gdata[Vm] != NULL);
1780 
1781  for (int i = 0; i < NUM_IMP_DATA_TYPES; i++) {
1782  for (int n = 0; n < miif->N_IIF; n++)
1783  if (USED_DAT(miif->IIF[n], imp_data_flag[i]) && (miif->gdata[i] == NULL)) {
1784  SF::init_vector(&miif->gdata[i], miif->gdata[Vm]);
1785  break;
1786  }
1787  }
1788 }
1789 
1798  int num_mech_data = 0;
1799 
1800  for (int i = 0; i < NUM_IMP_DATA_TYPES; i++) {
1801  if ( (Lambda_DATA_FLAG == i) || (delLambda_DATA_FLAG == i) ||
1802  (Tension_DATA_FLAG == i) || (tension_component_DATA_FLAG == i) ) {
1803  for (int n = 0; n < this->N_IIF; n++)
1804  if (USED_DAT(this->IIF[n], imp_data_flag[i]) )
1805  num_mech_data++;
1806  }
1807  }
1808  return static_cast<bool>(num_mech_data);
1809 }
1810 
1828 void MULTI_IF::transmem_stim_species(float charge, const char *species,
1829  float beta, int *node, int numnode) {
1830  float z;
1831  if (strstr(species, "Ca") != NULL) {
1832  charge *= 1000; // Ca uses micromolar instead of millimolar
1833  z = 2;
1834  } else if (strstr(species, "Na") != NULL) {
1835  z = 1;
1836  } else if (strstr(species, "Cl") != NULL) {
1837  z = -1;
1838  } else if (strstr(species, "K") != NULL) {
1839  z = 1;
1840  } else {
1841  log_msg(logger, 5, 0, "Unimplemented ion species: %s\n", species);
1842  exit(1);
1843  }
1844  int offset, sz;
1845  SVgetfcn ion_get;
1846  SVputfcn ion_put;
1847  static int warned = 0;
1848 
1849  int rank = get_rank();
1850  SF::vector<int> layout;
1851  layout_from_count(numnode, layout, PETSC_COMM_WORLD);
1852  int my_low_idx = layout[rank], my_high_idx = layout[rank+1];
1853 
1854  for (int i = 0; i < numnode; i++) {
1855  if ((node[i] < my_low_idx) || (node[i] >= my_high_idx)) continue; // not on processor
1856 
1857  for (int j = 0; j < this->N_IIF; j++) {
1858  if (this->NodeLists[j] == NULL) continue; // no stim nodes on processor
1859 
1860  if ((node[i] < this->NodeLists[j][0]) ||
1861  (node[i] > this->NodeLists[j][this->N_Nodes[j]-1])) {
1862  continue; // not in this IMP
1863  } else {
1864  ion_get = this->IIF[j]->get_type().get_sv_offset(species, &offset, &sz);
1865  if (ion_get == NULL) {
1866  if (!warned) {
1867  warned = 1;
1868  log_msg(logger, 2, 0, "Ion species not present in ionic model: %s\n",
1869  species);
1870  }
1871  return;
1872  }
1873  ion_put = getPutSV(ion_get);
1874  }
1875 
1876  // does this model provide a correct conversion factor?
1877  double delta_conc;
1878  cell_geom g = this->IIF[j]->cgeom();
1879  if (g.sl_i2c != NDEF) {
1880  delta_conc = charge*g.sl_i2c/z;
1881  } else {
1882  // conversion with generic factor
1883  delta_conc = 10*charge*beta/(FARADAY*z); // convert to millimolar/L
1884  }
1885 
1886  for (node_index_t k = 0; k < this->N_Nodes[j]; k++)
1887  if (node[i] == this->NodeLists[j][k])
1888  ion_put(*this->IIF[j], k, offset, ion_get(*this->IIF[j], k, offset)-delta_conc);
1889  }
1890  }
1891 } // transmem_stim_species
1892 
1899 void MULTI_IF::MIIF_change_dt( double Dt) {
1900  this->dt = Dt;
1901  for (int i = 0; i < this->N_IIF; i++) {
1902  this->IIF[i]->set_dt((float) Dt);
1903  this->IIF[i]->rebuild_lookup_tables();
1904 
1905  for (int j = 0; j < this->IIF[i]->plugins().size(); j++) {
1906  auto& plugin = this->IIF[i]->plugins()[j];
1907  plugin->set_dt((float) Dt);
1908  plugin->rebuild_lookup_tables();
1909  }
1910  }
1911 }
1912 
1913 #define MEMFREE(A) free(A)
1914 
1922  assert(m->doppel);
1923 
1924  if (m->ldata) {
1925  for (size_t i = 0; i < m->IIF.size(); i++) {
1926  if (!m->ldata[i])
1927  continue;
1928 
1929  Target target = m->IIF[i]->get_target();
1930  for (int j = 0; j < NUM_IMP_DATA_TYPES; j++)
1931  if ((!m->contiguous[i] || is_gpu(target)) &&
1932  USED_DAT(m->IIF[i], imp_data_flag[j]) && m->N_Nodes[i] > 0 && m->ldata[i][j])
1933  deallocate_on_target<GlobalData_t>(target, m->ldata[i][j]);
1934  deallocate_on_target<GlobalData_t*>(target, m->ldata[i]);
1935  }
1936  deallocate_on_target<GlobalData_t**>(Target::CPU, m->ldata);
1937  }
1938 
1939  for (auto& iif : m->IIF)
1940  iif->get_type().destroy_ion_if(iif);
1941 
1942  for (int j = 0; j < NUM_IMP_DATA_TYPES; j++) {
1943  if (m->gdata[j])
1944  delete m->gdata[j];
1945  }
1946 
1947  m->zero_data();
1948  m->doppel = false;
1949 }
1950 
1951 #undef MEMFREE
1952 
1953 static void copy_IIF_configuration_tree(IonIfBase& dst, IonIfBase& src) {
1954  dst.copy_configuration_from(src);
1955 
1956  auto& dst_plugins = dst.plugins();
1957  auto& src_plugins = src.plugins();
1958  if (dst_plugins.size() != src_plugins.size())
1959  throw std::logic_error("cannot copy IMP configuration between different plugin layouts");
1960 
1961  for (size_t i = 0; i < src_plugins.size(); i++)
1962  copy_IIF_configuration_tree(*dst_plugins[i], *src_plugins[i]);
1963 }
1964 
1965 static void copy_IIF_state_tree(IonIfBase& dst, IonIfBase& src) {
1966  dst.copy_SVs_from(src, false);
1967 
1968  auto& dst_plugins = dst.plugins();
1969  auto& src_plugins = src.plugins();
1970  if (dst_plugins.size() != src_plugins.size())
1971  throw std::logic_error("cannot copy IMP state between different plugin layouts");
1972 
1973  for (size_t i = 0; i < src_plugins.size(); i++)
1974  copy_IIF_state_tree(*dst_plugins[i], *src_plugins[i]);
1975 }
1976 
1977 static void share_IIF_lookup_tables_tree(IonIfBase& dst, IonIfBase& src) {
1978  dst.share_lookup_tables_from(src);
1979 
1980  auto& dst_plugins = dst.plugins();
1981  auto& src_plugins = src.plugins();
1982  if (dst_plugins.size() != src_plugins.size())
1983  throw std::logic_error("cannot share LUTs between different plugin layouts");
1984 
1985  for (size_t i = 0; i < src_plugins.size(); i++)
1986  share_IIF_lookup_tables_tree(*dst_plugins[i], *src_plugins[i]);
1987 }
1988 
1989 static void copy_global_data_values(MULTI_IF& dst, MULTI_IF& src) {
1990  for (int i = 0; i < NUM_IMP_DATA_TYPES; i++) {
1991  if (!src.gdata[i])
1992  continue;
1993  if (!dst.gdata[i])
1994  throw std::logic_error("cannot copy missing IMP global data vector");
1995  *dst.gdata[i] = *src.gdata[i];
1996  }
1997 }
1998 
1999 static void allocate_doppel_local_data(MULTI_IF& doppel) {
2000  doppel.ldata = allocate_on_target<GlobalData_t**>(Target::CPU, doppel.N_IIF);
2001  for (int i = 0; i < doppel.N_IIF; i++) {
2002  Target target = doppel.IIF[i]->get_target();
2003  doppel.ldata[i] = allocate_on_target<GlobalData_t*>(target, NUM_IMP_DATA_TYPES);
2004 
2005  for (int j = 0; j < NUM_IMP_DATA_TYPES; j++)
2006  if ((!doppel.contiguous[i] || is_gpu(target)) &&
2007  USED_DAT(doppel.IIF[i], imp_data_flag[j]) && doppel.N_Nodes[i] > 0)
2008  doppel.ldata[i][j] = allocate_on_target<GlobalData_t>(target, doppel.N_Nodes[i]);
2009  }
2010 }
2011 
2019 void doppel_update(MULTI_IF *orig, MULTI_IF *miif_doppel) {
2020  // This operation is intentionally bidirectional: normal doppel execution
2021  // updates the clone, while RRC resets the working MIIF from its baseline.
2022  copy_global_data_values(*miif_doppel, *orig);
2023 
2024  for (int i = 0; i < orig->N_IIF; i++)
2025  copy_IIF_state_tree(*miif_doppel->IIF[i], *orig->IIF[i]);
2026 }
2027 
2037 void doppel_MIIF(MULTI_IF *orig, MULTI_IF *miif_doppel) {
2038  *miif_doppel = *orig;
2039  miif_doppel->doppel = true;
2040  miif_doppel->IIF.clear();
2041  miif_doppel->ldata = nullptr;
2042  for (int i = 0; i < NUM_IMP_DATA_TYPES; i++)
2043  miif_doppel->gdata[i] = nullptr;
2044 
2045  try {
2046  for (int i = 0; i < NUM_IMP_DATA_TYPES; i++)
2047  if (orig->gdata[i])
2048  SF::init_vector(&miif_doppel->gdata[i], orig->gdata[i]);
2049  copy_global_data_values(*miif_doppel, *orig);
2050 
2051  miif_doppel->IIF.reserve(orig->N_IIF);
2052  for (int i = 0; i < orig->N_IIF; i++) {
2053  IonIfBase* doppel_iif = orig->IIF[i]->get_type().make_ion_if(
2054  orig->IIF[i]->get_target(), orig->IIF[i]->get_num_node(), orig->plugtypes[i]);
2055  miif_doppel->IIF.push_back(doppel_iif);
2056  }
2057 
2058  for (int i = 0; i < orig->N_IIF; i++) {
2059  IonIfBase* doppel_iif = miif_doppel->IIF[i];
2060  doppel_iif->set_parent((IonIfBase *)miif_doppel);
2061  doppel_iif->for_each([&](IonIfBase& IF) { IF.miifIdx = i; });
2062  copy_IIF_configuration_tree(*doppel_iif, *orig->IIF[i]);
2063  share_IIF_lookup_tables_tree(*doppel_iif, *orig->IIF[i]);
2064  }
2065 
2066  allocate_doppel_local_data(*miif_doppel);
2067  miif_doppel->getRealData();
2068  try {
2069  for (int i = 0; i < orig->N_IIF; i++)
2070  miif_doppel->IIF[i]->initialize_state(miif_doppel->dt, miif_doppel->ldata[i]);
2071  }
2072  catch (...) {
2073  miif_doppel->releaseRealDataDuringInit();
2074  throw;
2075  }
2076  miif_doppel->releaseRealDataDuringInit();
2077 
2078  // Initialization may overwrite external state such as Vm. Restore the
2079  // source values before copying state/private workspace into the clone.
2080  copy_global_data_values(*miif_doppel, *orig);
2081  for (int i = 0; i < orig->N_IIF; i++)
2082  copy_IIF_state_tree(*miif_doppel->IIF[i], *orig->IIF[i]);
2083  }
2084  catch (...) {
2085  free_doppel(miif_doppel);
2086  throw;
2087  }
2088 }
2089 
2097 bool isIMPdata(const char *sv) {
2098  return IMPdataLabel2Index(sv) != -1;
2099 }
2100 
2108 int IMPdataLabel2Index(const char *sv) {
2109  int imp_data_id = -1;
2110 
2111  for (int i = 0; i < NUM_IMP_DATA_TYPES; i++) {
2112  if (strcmp(sv, imp_data_names[i]) == 0) {
2113  imp_data_id = i;
2114  break;
2115  }
2116  }
2117  return imp_data_id;
2118 }
2119 
2120 /* copy all of the IMP data from one node to another
2121  *
2122  * \param IF ionic model
2123  * \param from local IMP number
2124  * \param to local IMP number
2125  */
2127  for (int i = 0; i < NUM_IMP_DATA_TYPES; i++)
2128  if (localdata && localdata[i])
2129  localdata[i][to] = localdata[i][from];
2130 
2131  IF.for_each([&](IonIfBase& imp) {
2132  char **sv_list;
2133  int sv_list_size = imp.get_type().get_sv_list(&sv_list);
2134 
2135  for (int i = 0; i < sv_list_size; i++) {
2136  char *sv_name = sv_list[i];
2137  int sv_offset;
2138  int sv_size;
2139  SVgetfcn sv_get = imp.get_type().get_sv_offset(sv_name, &sv_offset, &sv_size);
2140  if (sv_get == NULL) {
2141  throw std::runtime_error(std::string(__func__) + " error: " + sv_name + " is not a valid state variable for the " + imp.get_type().get_name() + " model.");
2142  }
2143  SVputfcn sv_put = getPutSV(sv_get);
2144 
2145  GlobalData_t sv_val = sv_get(imp, from, sv_offset);
2146  sv_put(imp, to, sv_offset, sv_val);
2147  }
2148  free(sv_list);
2149  });
2150 }
2151 
2152 } // namespace limpet
#define NDEF
definition of cell geometry
Definition: ION_IF.h:118
#define FILENAME_BUF
#define MAX_TRACE_LINE_LEN
Define multiple ionic models to be used in different regions.
#define FARADAY
Faraday's constant.
Definition: MULTI_ION_IF.h:132
#define USED_DAT(I, F)
Definition: MULTI_ION_IF.h:297
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
#define SLIST_APPEND(S, P)
Definition: basics.h:39
#define FLUSH
Definition: basics.h:304
#define LOCAL
Definition: basics.h:302
#define ECHO
Definition: basics.h:301
#define fmemopen_
Definition: basics.h:426
#define STRUCT_ZERO(S)
Definition: basics.h:38
virtual S * ptr()=0
virtual void release_ptr(S *&p)=0
size_t read_binary(FILE *fd)
ltype layout
used vector layout (nodal, algebraic, unset)
virtual void add_scaled(const abstract_vector< T, S > &vec, S k)=0
size_t write_binary(FILE *fd)
Write a vector to HD in binary. File descriptor is already set up.
virtual void set(const vector< T > &idx, const vector< S > &vals, const bool additive=false, const bool local=false)=0
bool in_b(const T idx)
return whether idx is in set B
Definition: SF_container.h:351
T backward_map(T idx) const
Map one index from b to a.
Definition: SF_container.h:259
overlapping_layout< T > pl
nodal parallel layout
Definition: SF_container.h:414
Container for a PETSc VecScatter.
void forward(abstract_vector< T, S > &in, abstract_vector< T, S > &out, bool add=false)
Forward scattering.
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
T * data()
Pointer to the vector's start.
Definition: SF_vector.h:76
Represents the ionic model and plug-in (IMP) data structure.
Definition: ION_IF.h:168
void set_parent(IonIfBase *parent)
Definition: ION_IF.cc:170
const IonType & get_type() const
Gets this IMP's model type.
Definition: ION_IF.cc:134
void for_each_sv_field(node_index_t node, const std::vector< std::pair< int, int >> &fields, Fn &&fn)
Visits each state-variable field of one node, in storage order.
Definition: ION_IF.h:505
std::vector< IonIfBase * > & plugins()
Returns a vector containing the plugins of this IMP.
Definition: ION_IF.cc:174
void get_sv_layout(std::vector< std::pair< int, int >> &fields) const
Describes the per-node memory layout of this IMP's state variables.
Definition: ION_IF.cc:326
void compute(node_index_t start, node_index_t end, GlobalData_t **data)
Perform ionic model computation for 1 time step.
Definition: ION_IF.cc:258
size_t get_sv_per_node_size() const
Size in bytes of one node's de-interleaved state-variable record.
Definition: ION_IF.cc:334
uint64_t sv_fingerprint() const
Fingerprint of this IMP's state-variable layout.
Definition: ION_IF.cc:341
ts & get_tstp()
Gets the time stepper.
Definition: ION_IF.cc:198
void for_each(const std::function< void(IonIfBase &)> &consumer)
Executes the consumer functions on this IMP and each of its plugins.
Definition: ION_IF.cc:526
IonIfBase * parent() const
Gets the parent IMP.
Definition: ION_IF.cc:166
int miifIdx
imp index within miif
Definition: ION_IF.h:180
virtual std::size_t get_sv_size() const =0
Gets the size of the structure this IMP uses for state variables.
virtual void * get_sv_address()=0
Gets the raw address of the state variables for this IMP.
node_count_t get_num_node() const
Gets the number of nodes handled by this IMP.
Definition: ION_IF.cc:138
Abstract class representing an ionic model type.
Definition: ion_type.h:44
bool is_plugin() const
Returns whether this model is a plugin or not.
Definition: ion_type.cc:12
const std::string & get_name() const
Gets the model name.
Definition: ion_type.cc:8
virtual SVgetfcn get_sv_offset(const char *svname, int *off, int *sz) const =0
Get the offset and size of a state variable of the model, as well as an access function.
virtual int get_sv_type(const char *svname, int *type, char **type_name) const =0
Determines the type of a SV.
virtual size_t dlo_vector_size() const =0
Gets the vector size when using data layout optimization (DLO).
bool extUpdateVm
flag indicating update function for Vm
Definition: MULTI_ION_IF.h:204
std::vector< IonIfBase * > IIF
array of IIF's
Definition: MULTI_ION_IF.h:198
opencarp::sf_vec * gdata[NUM_IMP_DATA_TYPES]
data used by all IMPs
Definition: MULTI_ION_IF.h:212
void sv_dump_add_by_name_list(int, char *, char *, char *, char *, char *, double, double)
node_count_t numNode
local number of nodes
Definition: MULTI_ION_IF.h:206
size_t dump_svs(opencarp::base_timer *)
std::vector< Target > targets
target for each region
Definition: MULTI_ION_IF.h:209
std::vector< IonTypeList > plugtypes
plugins types for each region
Definition: MULTI_ION_IF.h:211
IonTypeList iontypes
type for each region
Definition: MULTI_ION_IF.h:208
void dump_state(char *, float, opencarp::mesh_t gid, bool, unsigned int)
SV_DUMP svd
state variable dump
Definition: MULTI_ION_IF.h:199
void sv_dump_add(int, const IonType &, int, int, int, const char *, const char *)
void transmem_stim_species(float, const char *, float, int *, int)
void initialize_currents(double, int)
GlobalData_t *** ldata
data local to each IMP
Definition: MULTI_ION_IF.h:201
float restore_state(const char *, opencarp::mesh_t gid, bool)
int N_IIF
how many different IIF's
Definition: MULTI_ION_IF.h:207
void compute_ionic_current(bool flag_send=1, bool flag_receive=1)
GPU kernel to emulate the add_scaled call made to adjust the Vm values when the update to Vm is not m...
int sv_dump_add_by_name(int, char *, char *, char *, char *)
Trace_Info * trace_info
Information about traces.
Definition: MULTI_ION_IF.h:203
double dt
time step (ms)
Definition: MULTI_ION_IF.h:215
node_count_t * N_Nodes
#nodes for each IMP
Definition: MULTI_ION_IF.h:196
GlobalData_t * procdata[NUM_IMP_DATA_TYPES]
data for this processor
Definition: MULTI_ION_IF.h:200
void dump_luts_MIIF(bool)
bool * contiguous
whether a region is contiguously numbered
Definition: MULTI_ION_IF.h:202
opencarp::FILE_SPEC logger
Definition: MULTI_ION_IF.h:213
int numSubDt
number of sub-dt time steps
Definition: MULTI_ION_IF.h:216
node_index_t ** NodeLists
local partitioned node lists for each IMP stored
Definition: MULTI_ION_IF.h:197
void MIIF_change_dt(double)
IIF_Mask_t * IIFmask
region for each node
Definition: MULTI_ION_IF.h:210
int adjust_MIIF_variables(const char *variable, const SF::vector< SF_int > &indices, const SF::vector< SF_real > &values)
std::string name
name for MIIF region
Definition: MULTI_ION_IF.h:194
bool doppel
is this a shallow clone?
Definition: MULTI_ION_IF.h:195
void releaseRealDataDuringInit()
#define log_msg(F, L, O,...)
Definition: filament.h:8
void dsp_from_cnt(const vector< T > &cnt, vector< T > &dsp)
Compute displacements from counts.
Definition: SF_vector.h:295
T sum(const vector< T > &vec)
Compute sum of a vector's entries.
Definition: SF_vector.h:325
void local_petsc_to_nodal_mapping(const meshdata< T, S > &mesh, index_mapping< T > &petsc_to_nodal)
void layout_from_count(const T count, vector< T > &layout, MPI_Comm comm)
Definition: SF_network.h:186
void init_vector(SF::abstract_vector< T, S > **vec)
Definition: SF_init.h:110
@ NBR_SUBMESH
Submesh nodal numbering: The globally ascending sorted reference indices are reindexed.
Definition: SF_container.h:187
@ NBR_ELEM_SUBMESH
Submesh element numbering: The globally ascending sorted reference indices are reindexed.
Definition: SF_container.h:190
void(* SVputfcn)(IonIfBase &, node_index_t, int, GlobalData_t)
Definition: ion_type.h:34
void CreateIIFNodeLsts_(int, IIF_Mask_t *, node_count_t **, node_index_t ***, node_count_t)
void doppel_MIIF(MULTI_IF *orig, MULTI_IF *miif_doppel)
void initializeIMPData(MULTI_IF *pMIIF)
int determine_write_ranges(int N, size_t *offset, size_t bufsize, int **ranges)
int get_plug_flag(char *plgstr, int *out_num_plugins, IonTypeList &out_plugins)
SVputfcn getPutSV(SVgetfcn)
void doppel_update(MULTI_IF *orig, MULTI_IF *miif_doppel)
double Real
Definition: MULTI_ION_IF.h:136
node_index_t getGlobalNodalIndex(IonIfBase &pIF, node_index_t relIdx)
int should_print_bounds_exceeded_messages()
Target
enum that represents different targets to run ionic models on.
Definition: target.h:30
@ CPU
baseline CPU model generated with the original opencarp code generator
Definition: target.h:33
IonType * get_ion_type(const std::string &name)
int IMPdataLabel2Index(const char *sv)
void CreateIIFGlobalNodeLsts(MULTI_IF *pMIIF)
SF_real GlobalData_t
Definition: limpet_types.h:12
void allocate_shared_data(MULTI_IF *)
bool isIMPdata(const char *)
bool is_gpu(Target const target)
Checks if this is a GPU target.
Definition: target.cc:52
void initialize_params_MIIF(MULTI_IF *pMIIF)
constexpr T min(T a, T b)
Definition: ion_type.h:18
void alloc_MIIF(MULTI_IF *pMIIF)
void freeIMPData(MULTI_IF *pMIIF)
GlobalData_t(* SVgetfcn)(IonIfBase &, node_index_t, int)
Definition: ion_type.h:33
std::vector< std::reference_wrapper< IonType > > IonTypeList
Definition: ion_type.h:276
void dup_IMP_node_state(IonIfBase &IF, node_index_t from, node_index_t to, GlobalData_t **localdata)
constexpr T max(T a, T b)
Definition: ion_type.h:16
char * get_sv(void *tab, int offset, node_count_t n, int svSize, int size, int dlo_vector_size)
void deallocate_on_target(Target target, T *ptr)
Utility function for deallocating memory on a target. See TargetAllocator.
Definition: target.h:303
void CreateIIFLocalNodeLsts(MULTI_IF *pMIIF)
void initialize_ionic_IF(MULTI_IF *pMIIF)
void update_ts(ts *ptstp)
Definition: ION_IF.cc:575
float current_global_time()
opencarp::local_index_t node_count_t
Definition: limpet_types.h:14
void close_trace(MULTI_IF *MIIF)
int node_index_cmp(const void *a, const void *b)
void dump_trace(MULTI_IF *MIIF, limpet::Real time)
char * get_next_list(char *lst, char delimiter)
Definition: ION_IF.cc:621
void open_trace(MULTI_IF *MIIF, int n_traceNodes, int *traceNodes, int *label, opencarp::sf_mesh *imesh)
Set up ionic model traces at some global node numbers.
char IIF_Mask_t
Definition: ion_type.h:35
void free_doppel(MULTI_IF *m)
opencarp::global_index_t global_node_index_t
Definition: limpet_types.h:15
node_index_t current_global_node(node_index_t local_node)
opencarp::local_index_t node_index_t
Definition: limpet_types.h:13
char * tokstr_r(char *s1, const char *s2, char **lasts)
Definition: ION_IF.cc:74
int set_print_bounds_exceeded_messages(int newval)
std::intmax_t printable_int(T value)
Definition: mpi_utils.h:115
sf_mesh & get_mesh(const mesh_t gt)
Get a mesh by specifying the gridID.
Definition: sf_interface.cc:18
SF::scattering * get_permutation(const int mesh_id, const int perm_id, const int dpn)
Get the PETSC to canonical permutation scattering for a given mesh and number of dpn.
SF::meshdata< mesh_int_t, mesh_real_t > sf_mesh
Definition: sf_interface.h:33
char * read_bin_string(FILE_SPEC in)
Definition: basics.cc:216
void f_read_par(void *ptr, size_t size, size_t nmemb, FILE_SPEC stream, MPI_Comm comm)
Parallel fread. Root reads, then broadcasts.
Definition: basics.cc:159
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
void write_bin_string(FILE_SPEC out, const char *s)
Definition: basics.cc:207
FILE_SPEC f_open(const char *fname, const char *mode)
Open a FILE_SPEC.
Definition: basics.cc:123
char * read_bin_string_par(FILE_SPEC in)
Definition: basics.cc:227
char * dupstr(const char *old_str)
Definition: basics.cc:29
mesh_t
The enum identifying the different meshes we might want to load.
Definition: sf_interface.h:44
void get_time(double &tm)
Definition: basics.h:429
SF::abstract_vector< SF_int, SF_real > sf_vec
Definition: sf_interface.h:35
int get_size(MPI_Comm comm=PETSC_COMM_WORLD)
Definition: basics.h:283
V timing(V &t2, const V &t1)
Definition: basics.h:441
void f_close(FILE_SPEC &f)
Close a FILE_SPEC.
Definition: basics.cc:150
file_desc * FILE_SPEC
Definition: basics.h:125
#define PETSC_TO_CANONICAL
Permute algebraic data from PETSC to canonical ordering.
Definition: sf_interface.h:64
#define ELEM_PETSC_TO_CANONICAL
Permute algebraic element data from PETSC to canonical ordering.
Definition: sf_interface.h:66
int offset
offset into node data
Definition: ION_IF.h:138
int sz
storage required
Definition: ION_IF.h:133
char * name
IMP name.
Definition: ION_IF.h:132
int map
which plugin does this IMO match
Definition: ION_IF.h:137
IMPinfo * plug
plugins
Definition: ION_IF.h:135
bool compatible
does IM match stored IM
Definition: ION_IF.h:136
int nplug
number of plugins
Definition: ION_IF.h:134
uint64_t fingerprint
saved SV-layout fingerprint (format >= 3; 0 otherwise)
Definition: ION_IF.h:139
data structure to manage state variable file dumps
Definition: MULTI_ION_IF.h:156
size_t * svsize
state variable sizes
Definition: MULTI_ION_IF.h:172
char ** fn
array to store file names
Definition: MULTI_ION_IF.h:160
double intv
time interval for sv dumps
Definition: MULTI_ION_IF.h:163
int * dtype
data type
Definition: MULTI_ION_IF.h:169
int n_dumps
keep track of number of dumped time slices
Definition: MULTI_ION_IF.h:165
node_count_t * num
number of nodes
Definition: MULTI_ION_IF.h:170
long nwr
keep track of number of written tokens
Definition: MULTI_ION_IF.h:166
int * size
sizes of SV to dump
Definition: MULTI_ION_IF.h:168
void ** svtab
state variable tables
Definition: MULTI_ION_IF.h:171
int * reg
array to store region ids
Definition: MULTI_ION_IF.h:161
double t_dump
next instant for sv dump
Definition: MULTI_ION_IF.h:164
char ** svnames
array to store sv names
Definition: MULTI_ION_IF.h:162
int n
#state variables we want to dump
Definition: MULTI_ION_IF.h:158
int * offset
offsets into structure for SV
Definition: MULTI_ION_IF.h:167
opencarp::FILE_SPEC * hdls
array of file handles to sv output files
Definition: MULTI_ION_IF.h:159
data structure to manage trace dumps. Should eventually be combined with the state variable dumps,...
Definition: MULTI_ION_IF.h:180
bool ignored
globally not found
Definition: MULTI_ION_IF.h:182
node_index_t node_idx
local node number
Definition: MULTI_ION_IF.h:183
bool found
found on this node
Definition: MULTI_ION_IF.h:181
opencarp::FILE_SPEC file
Definition: MULTI_ION_IF.h:185
float sl_i2c
convert sl-currents in uA/cm^2 to mM/L without valence
Definition: ION_IF.h:124
int cnt
Definition: ION_IF.h:92