openCARP
Doxygen code documentation for the open cardiac electrophysiology simulator openCARP
electrics.cc
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // openCARP is an open cardiac electrophysiology simulator.
3 //
4 // Copyright (C) 2020 openCARP project
5 //
6 // This program is licensed under the openCARP Academic Public License (APL)
7 // v1.0: You can use and redistribute it and/or modify it in non-commercial
8 // academic environments under the terms of APL as published by the openCARP
9 // project v1.0, or (at your option) any later version. Commercial use requires
10 // a commercial license (info@opencarp.org).
11 //
12 // This program is distributed without any warranty; see the openCARP APL for
13 // more details.
14 //
15 // You should have received a copy of the openCARP APL along with this program
16 // and can find it online: http://www.opencarp.org/license
17 // ----------------------------------------------------------------------------
18 
26 #include "electrics.h"
27 #include "petsc_utils.h"
28 #include "timers.h"
29 #include "stimulate.h"
30 #include "electric_integrators.h"
31 
32 #include "SF_init.h" // for SF::init_xxx()
33 
34 namespace opencarp {
35 
37 {
38  double t1, t2;
39  get_time(t1);
40 
41  set_dir(OUTPUT);
42 
43  // open logger
44  logger = f_open("electrics.log", param_globals::experiment != 4 ? "w" : "r");
45 
46  // setup mappings between extra and intra grids, algebraic and nodal,
47  // and between PETSc and canonical orderings
48  setup_mappings();
49 
50  // the ionic physics is currently triggered from inside the Electrics to have tighter
51  // control over it
52  ion.logger = logger;
53  ion.initialize();
54 
55  // set up Intracellular tissue
57  region_mask(intra_elec_msh, mtype[intra_grid].regions, mtype[intra_grid].regionIDs, true, "gregion_i");
58 
59  if (param_globals::bidomain || param_globals::extracell_monodomain_stim) {
60  // set up Extracellular tissue
62  region_mask(extra_elec_msh, mtype[extra_grid].regions, mtype[extra_grid].regionIDs, true, "gregion_e");
63  }
64 
65  // add electrics timer for time stepping, add to time stepper tool (TS)
66  double global_time = user_globals::tm_manager->time;
67  timer_idx = user_globals::tm_manager->add_eq_timer(global_time, param_globals::tend, 0,
68  param_globals::dt, 0, "elec::ref_dt", "TS");
69 
70  // electrics stimuli setup
71  setup_stimuli();
72 
73  // set up the linear equation systems. this needs to happen after the stimuli have been
74  // set up, since we need boundary condition info
75  setup_solvers();
76 
77  // the next setup steps require the solvers to be set up, since they use the matrices
78  // generated by those
79 
80  // balance electrodes, we may need the extracellular mass matrix
81  balance_electrodes();
82  // total current scaling
84  // initialize the LATs detector
86 
87  // initialize phie recovery data
88  if(strlen(param_globals::phie_rec_ptf) > 0)
90 
91  // prepare the electrics output. we skip it if we do post-processing
92  if(param_globals::experiment != EXP_POSTPROCESS)
93  setup_output();
94 
95  if (param_globals::prepacing_bcl > 0)
96  prepace();
97  this->initialize_time += timing(t2, t1);
98 }
99 
101 {
102  MaterialType *m = mtype+g;
103 
104  // initialize random conductivity fluctuation structure with PrM values
105  m->regions.resize(param_globals::num_gregions);
106 
107  const char* grid_name = g == Electrics::intra_grid ? "intracellular" : "extracellular";
108  log_msg(logger, 0, 0, "Setting up %s tissue properties for %d regions ..", grid_name,
109  param_globals::num_gregions);
110 
111  char buf[64];
112  RegionSpecs* reg = m->regions.data();
113 
114  for (size_t i=0; i<m->regions.size(); i++, reg++) {
115  if(!strcmp(param_globals::gregion[i].name, "")) {
116  snprintf(buf, sizeof buf, ", gregion_%d", int(i));
117  param_globals::gregion[i].name = dupstr(buf);
118  }
119 
120  reg->regname = strdup(param_globals::gregion[i].name);
121  reg->regID = i;
122  reg->nsubregs = param_globals::gregion[i].num_IDs;
123  if(!reg->nsubregs)
124  reg->subregtags = NULL;
125  else {
126  reg->subregtags = new int[reg->nsubregs];
127  for (int j=0;j<reg->nsubregs;j++) {
128  reg->subregtags[j] = param_globals::gregion[i].ID[j];
129  if(reg->subregtags[j]==-1)
130  log_msg(NULL,3,ECHO, "Warning: not all %u IDs provided for gregion[%u]!\n", reg->nsubregs, i);
131  }
132  }
133 
134  // describe material in given region
135  elecMaterial *emat = new elecMaterial();
136  emat->material_type = ElecMat;
137 
138  emat->InVal[0] = param_globals::gregion[i].g_il;
139  emat->InVal[1] = param_globals::gregion[i].g_it;
140  emat->InVal[2] = param_globals::gregion[i].g_in;
141 
142  emat->ExVal[0] = param_globals::gregion[i].g_el;
143  emat->ExVal[1] = param_globals::gregion[i].g_et;
144  emat->ExVal[2] = param_globals::gregion[i].g_en;
145 
146  emat->BathVal[0] = param_globals::gregion[i].g_bath;
147  emat->BathVal[1] = param_globals::gregion[i].g_bath;
148  emat->BathVal[2] = param_globals::gregion[i].g_bath;
149 
150  // convert units from S/m -> mS/um
151  for (int j=0; j<3; j++) {
152  emat->InVal[j] *= 1e-3 * param_globals::gregion[i].g_mult;
153  emat->ExVal[j] *= 1e-3 * param_globals::gregion[i].g_mult;
154  emat->BathVal[j] *= 1e-3 * param_globals::gregion[i].g_mult;
155  }
156  reg->material = emat;
157  }
158 
159  if((g == Electrics::intra_grid && strlen(param_globals::gi_scale_vec)) ||
160  (g == Electrics::extra_grid && strlen(param_globals::ge_scale_vec)) )
161  {
163  sf_mesh & mesh = get_mesh(mt);
164  const char* file = g == Electrics::intra_grid ? param_globals::gi_scale_vec : param_globals::ge_scale_vec;
165 
166  size_t num_file_entries = SF::root_count_ascii_lines(file, mesh.comm);
167 
168  if(num_file_entries != mesh.g_numelem)
169  log_msg(0,4,0, "%s warning: number of %s conductivity scaling entries does not match number of elements!",
170  __func__, get_mesh_type_name(mt));
171 
172  // set up parallel element vector and read data
173  sf_vec *escale;
174  SF::init_vector(&escale, get_mesh(mt), 1, sf_vec::elemwise);
175  escale->read_ascii(g == Electrics::intra_grid ? param_globals::gi_scale_vec : param_globals::ge_scale_vec);
176 
177  if(get_size() > 1) {
178  // set up element vector permutation and permute
179  if(get_permutation(mt, ELEM_PETSC_TO_CANONICAL, 1) == NULL) {
181  }
183  sc(*escale, false);
184  }
185 
186  // copy data into SF::vector
187  SF_real* p = escale->ptr();
188  m->el_scale.assign(p, p + escale->lsize());
189  escale->release_ptr(p);
190  }
191 }
192 
193 void Electrics::setup_mappings()
194 {
195  bool intra_exits = mesh_is_registered(intra_elec_msh), extra_exists = mesh_is_registered(extra_elec_msh);
196  assert(intra_exits);
197  const int dpn = 1;
198 
199  // It may be that another physic (e.g. ionic models) has already computed the intracellular mappings,
200  // thus we first test their existence
201  if(get_scattering(intra_elec_msh, ALG_TO_NODAL, dpn) == NULL) {
202  log_msg(logger, 0, 0, "%s: Setting up intracellular algebraic-to-nodal scattering.", __func__);
204  }
206  log_msg(logger, 0, 0, "%s: Setting up intracellular PETSc to canonical permutation.", __func__);
208  }
209 
210  // extracellular mappings
211  if(extra_exists) {
212  log_msg(logger, 0, 0, "%s: Setting up extracellular algebraic-to-nodal scattering.", __func__);
214  log_msg(logger, 0, 0, "%s: Setting up extracellular PETSc to canonical permutation.", __func__);
216  log_msg(logger, 0, 0, "%s: Setting up intra-to-extra scattering.", __func__);
218  }
219 
220  bool check_i2e = false;
221  if(check_i2e && extra_exists) {
222  sf_mesh & intra_mesh = get_mesh(intra_elec_msh);
223  sf_mesh & extra_mesh = get_mesh(extra_elec_msh);
224  int rank = get_rank();
225 
227 
228  const SF::vector<mesh_int_t> & intra_alg_nod = intra_mesh.pl.algebraic_nodes();
229  const SF::vector<mesh_int_t> & extra_alg_nod = extra_mesh.pl.algebraic_nodes();
230  const SF::vector<mesh_int_t> & extra_petsc_nbr = extra_mesh.get_numbering(SF::NBR_PETSC);
231  const SF::vector<mesh_int_t> & intra_ref_nbr = intra_mesh.get_numbering(SF::NBR_REF);
232  const SF::vector<mesh_int_t> & extra_ref_nbr = extra_mesh.get_numbering(SF::NBR_REF);
233 
234  // TODO(init) : delete these three at the end of this section?
235  sf_vec *intra_testvec; SF::init_vector(&intra_testvec, intra_mesh, 1, sf_vec::algebraic);
236  sf_vec *extra_testvec; SF::init_vector(&extra_testvec, extra_mesh, 1, sf_vec::algebraic);
237  sf_vec *i2e_testvec; SF::init_vector(&i2e_testvec, extra_mesh, 1, sf_vec::algebraic);
238 
239  SF_real* id = intra_testvec->ptr();
240  for(size_t i=0; i<intra_alg_nod.size(); i++) {
241  int lpidx = local_nodal_to_local_petsc(intra_mesh, rank, intra_alg_nod[i]);
242  id[lpidx] = intra_ref_nbr[intra_alg_nod[i]];
243  }
244  intra_testvec->release_ptr(id);
245 
246  SF_real* ed = extra_testvec->ptr();
247  for(size_t i=0; i<extra_alg_nod.size(); i++) {
248  int lpidx = local_nodal_to_local_petsc(extra_mesh, rank, extra_alg_nod[i]);
249  ed[lpidx] = extra_ref_nbr[extra_alg_nod[i]];
250  }
251  extra_testvec->release_ptr(ed);
252 
253  i2e_testvec->set(-1.0);
254  i2e.forward(*intra_testvec, *i2e_testvec);
255 
256  int err = 0;
257  for(size_t i=0; i<extra_alg_nod.size(); i++) {
258  auto id = i2e_testvec->get(i);
259  auto ed = extra_testvec->get(i);
260  if(id > -1 && id != ed)
261  err++;
262  }
263 
264  if(get_global(err, MPI_SUM))
265  log_msg(0,5,0, "Electrics mapping test failed!");
266  else
267  log_msg(0,5,0, "Electrics mapping test succeeded!");
268  }
269 }
270 
272 {
273  double t1, t2;
274  get_time(t1);
275 
276  // if requested, we checkpoint the current state
277  checkpointing();
278 
279  // activation checking
280  const double time = user_globals::tm_manager->time,
281  time_step = user_globals::tm_manager->time_step;
282  lat.check_acts(time);
283  lat.check_quiescence(time, time_step);
284 
285  // I believe that we need to treat the stimuli in two ways:
286  // - Extracellular potential stimuli (this includes ground) affect the
287  // elliptic solver in a more delicate way, as such, there is a dbc_manager
288  // to take care of that.
289  // - Extracellular and Intracellular current stimuli are applied to the rhs vectors
290  // and can be managed by the stimulate() code directly.
291  stimulate_extracellular();
292 
293  if(param_globals::bidomain == BIDOMAIN)
295 
296  clamp_Vm();
297 
298  // compute ionics update
299  ion.compute_step();
300 
301  stimulate_intracellular();
302 
303  // store Vm before parabolic step, the full Ic we compute in the output step
304  if(param_globals::dump_data & DUMP_IC)
306 
307  // solver parabolic system
309 
310  clamp_Vm();
311 
312  if(user_globals::tm_manager->trigger(iotm_console)) {
313  // output lin solver stats
315  if(param_globals::bidomain == BIDOMAIN)
317  }
318  this->compute_time += timing(t2, t1);
319 
320  // since the traces have their own timing, we check for trace dumps in the compute step loop
323 
324 }
325 
327 {
328  double t1, t2;
329  get_time(t1);
330 
331  const double time = user_globals::tm_manager->time,
332  time_step = user_globals::tm_manager->time_step;
333 
334  // for pseudo-bidomain we compute extracellular potential only for output
335  if(param_globals::bidomain == PSEUDO_BIDM) {
338  ellip_solver.stats.log_stats(time, false);
339  }
340 
341  if(param_globals::dump_data & DUMP_IVOL)
343 
344  if(param_globals::bidomain && (param_globals::dump_data & DUMP_IACT)) {
346  }
347 
348  if(param_globals::dump_data & DUMP_IC) {
349  PetscReal *Ic = parab_solver.Ic->ptr(), *Vmv = parab_solver.Vmv->ptr();
350 
351  for(PetscInt i=0; i < parab_solver.Ic->lsize(); i++)
352  Ic[i] = (Ic[i] - Vmv[i]) / (-time_step);
353 
355  }
356 
357  // recover phie
358  if(phie_rcv.pts.size()) {
360  }
361 
364 
365  double curtime = timing(t2, t1);
366  this->output_time += curtime;
367 
368  IO_stats.calls++;
369  IO_stats.tot_time += curtime;
370 
372  IO_stats.log_stats(time, false);
373 }
374 
379 {
380  // output LAT data
382 
383  // close logger
384  f_close(logger);
385 
386  // close output files
388 
389  // destroy ionics
390  ion.destroy();
391 }
392 
393 void balance_electrode(elliptic_solver & ellip, SF::vector<stimulus> & stimuli, int balance_from, int balance_to)
394 {
395  log_msg( NULL, 0, 0, "Balancing stimulus %d with %d %s-wise.",balance_from, balance_to,
396  is_current(stimuli[balance_from].phys.type) ? "current" : "voltage" );
397 
398  stimulus & from = stimuli[balance_from];
399  stimulus & to = stimuli[balance_to];
400 
401  to.pulse = from.pulse;
402  to.ptcl = from.ptcl;
403  to.phys = from.phys;
404  to.pulse.strength *= -1.0;
405 
406  if (from.phys.type == I_ex)
407  {
408  // if from is total current, skip volume based adjustment of strength
409  // otherwise, calling constant_total_stimulus_current() will undo the balanced scaling of to.pulse.strength
410  // constant_total_stimulus_current() will do the scaling based on the volume
411  if (!from.phys.total_current) {
412  sf_mat& mass = *ellip.mass_e;
413  SF_real vol0 = get_volume_from_nodes(mass, from.electrode.vertices);
415 
416  to.pulse.strength *= fabs(vol0 / vol1);
417  }
418  }
419 }
420 
421 void Electrics::balance_electrodes()
422 {
423  for(int i=0; i<param_globals::num_stim; i++) {
424  if(param_globals::stim[i].crct.balance != -1) {
425  int from = param_globals::stim[i].crct.balance;
426  int to = i;
427 
428  balance_electrode(this->ellip_solver, stimuli, from, to);
429  }
430  }
431 }
432 
433 void Electrics::setup_stimuli()
434 {
435  // initialize basic stim info data (used units, supported types, etc)
436  init_stim_info();
437  bool dumpTrace = true;
438 
439  if(dumpTrace) set_dir(OUTPUT);
440 
441  stimuli.resize(param_globals::num_stim);
442  for(int i=0; i<param_globals::num_stim; i++)
443  {
444  // construct new stimulus
445  stimulus & s = stimuli[i];
446 
448  s.translate(i);
449 
450  s.setup(i);
451 
452  if(dumpTrace && get_rank() == 0)
453  s.pulse.wave.write_trace(s.name+".trc");
454  }
455 }
456 
457 void apply_stim_to_vector(const stimulus & s, sf_vec & vec, bool add)
458 {
459  double val; s.value(val);
460  const SF::vector<mesh_int_t> & idx = s.electrode.vertices;
461  const int rank = get_rank();
462  SF::vector<mesh_int_t> local_idx = idx;
463  for (size_t i = 0; i < idx.size(); i++) {
464  local_idx[i] = local_nodal_to_local_petsc(*vec.mesh, rank, idx[i]);
465  }
466  vec.set(local_idx, val, add, true);
467 }
468 
469 void Electrics::stimulate_intracellular()
470 {
472 
473  // iterate over stimuli
474  for(stimulus & s : stimuli) {
475  if(s.is_active()) {
476  // for active stimuli, deal with the stimuli-type specific stimulus application
477  switch(s.phys.type)
478  {
479  case I_tm: {
480  if(param_globals::operator_splitting) {
481  apply_stim_to_vector(s, *ps.Vmv, true);
482  }
483  else {
484  SF_real Cm = 1.0;
486  SF_real sc = tm.time_step / Cm;
487 
488  ps.Irhs->set(0.0);
489  apply_stim_to_vector(s, *ps.Irhs, true);
490 
491  *ps.tmp_i1 = *ps.IIon;
492  *ps.tmp_i1 -= *ps.Irhs;
493  *ps.tmp_i1 *= sc; // tmp_i1 = sc * (IIon - Irhs)
494 
495  // add ionic, transmembrane and intracellular currents to rhs
496  if(param_globals::parab_solve != parabolic_solver::EXPLICIT)
497  ps.mass_i->mult(*ps.tmp_i1, *ps.Irhs);
498  else
499  *ps.Irhs = *ps.tmp_i1;
500  }
501  break;
502  }
503 
504  case Illum: {
505  sf_vec* illum_vec = ion.miif->gdata[limpet::illum];
506 
507  if(illum_vec == NULL) {
508  log_msg(0,5,0, "Cannot apply illumination stim: global vector not present!");
509  EXIT(EXIT_FAILURE);
510  } else {
511  apply_stim_to_vector(s, *illum_vec, false);
512  }
513 
514  break;
515  }
516 
517  default: break;
518  }
519  }
520  }
521 }
522 
523 void Electrics::clamp_Vm() {
524  for(stimulus & s : stimuli) {
525  if(s.phys.type == Vm_clmp && s.is_active())
527  }
528 }
529 
530 void Electrics::stimulate_extracellular()
531 {
532  if(param_globals::bidomain) {
533  // we check if the DBC layout changed, if so we recompute the matrix and the dbc_manager
534  bool dbcs_have_updated = ellip_solver.dbc != nullptr && ellip_solver.dbc->dbc_update();
536 
537  if(dbcs_have_updated && time_not_final)
539 
540  ellip_solver.phiesrc->set(0.0);
541 
542  for(const stimulus & s : stimuli) {
543  if(s.is_active() && s.phys.type == I_ex)
545  }
546  }
547 }
548 
550  SF::vector<mesh_int_t> & inp_idx,
552 {
553  int mpi_rank = get_rank(), mpi_size = get_size();
554  const SF::vector<mesh_int_t> & layout = mesh.pl.algebraic_layout();
555 
556  SF::vector<mesh_int_t> sndbuff;
557 
558  size_t buffsize = 0;
559  idx.resize(0);
560 
561  for(int pid=0; pid < mpi_size; pid++) {
562  if(mpi_rank == pid) {
563  sndbuff = inp_idx;
564  buffsize = sndbuff.size();
565  }
566 
567  MPI_Bcast(&buffsize, sizeof(size_t), MPI_BYTE, pid, PETSC_COMM_WORLD);
568  sndbuff.resize(buffsize);
569  MPI_Bcast(sndbuff.data(), buffsize*sizeof(mesh_int_t), MPI_BYTE, pid, PETSC_COMM_WORLD);
570 
571  mesh_int_t start = layout[mpi_rank], stop = layout[mpi_rank+1];
572 
573  for(mesh_int_t i : sndbuff) {
574  if(i >= start && i < stop)
575  idx.push_back(i - start);
576  }
577  }
578 
579  binary_sort(idx); unique_resize(idx);
580 }
581 
583  SF::vector<mesh_int_t> & inp_idx,
585 {
586  int mpi_rank = get_rank(), mpi_size = get_size();
587  const SF::vector<mesh_int_t> & alg_nod = mesh.pl.algebraic_nodes();
589 
591  for(mesh_int_t ii : alg_nod)
592  amap[nbr[ii]] = ii;
593 
594  SF::vector<mesh_int_t> sndbuff;
595  size_t buffsize = 0;
596  idx.resize(0);
597 
598  for(int pid=0; pid < mpi_size; pid++) {
599  if(mpi_rank == pid) {
600  sndbuff = inp_idx;
601  buffsize = sndbuff.size();
602  }
603 
604  MPI_Bcast(&buffsize, sizeof(size_t), MPI_BYTE, pid, PETSC_COMM_WORLD);
605  sndbuff.resize(buffsize);
606  MPI_Bcast(sndbuff.data(), buffsize*sizeof(mesh_int_t), MPI_BYTE, pid, PETSC_COMM_WORLD);
607 
608  for(mesh_int_t i : sndbuff) {
609  if(amap.count(i))
610  idx.push_back(amap[i]);
611  }
612  }
613 
614  binary_sort(idx); unique_resize(idx);
615 }
616 
617 void setup_dataout(const int dataout, std::string dataout_vtx, mesh_t grid,
618  SF::vector<mesh_int_t>* & restr, bool async)
619 {
620  sf_mesh & mesh = get_mesh(grid);
621 
622  switch(dataout) {
623 
624  case DATAOUT_SURF: {
625  sf_mesh surfmesh;
626  compute_surface_mesh(mesh, SF::NBR_SUBMESH, surfmesh);
627 
628  SF::vector<mesh_int_t> idxbuff(surfmesh.con);
629  binary_sort(idxbuff); unique_resize(idxbuff);
630 
631  restr = new SF::vector<mesh_int_t>();
632 
633  // for sync output, we need restr to hold the local indices in the petsc vectors
634  // that have been permuted to canonical numbering. For async, we need the
635  // non-overlapping decomposition of indices in NBR_SUBMESH numbering. The petsc indices will be
636  // computed at a later stage. The only reason we need to call compute_restr_idx_async,
637  // is that surface nodes in NBR_SUBMESH, may
638  // reside on partitions where they are not part of the algebraic nodes. thus we need to
639  // recommunicate to make sure the data layout is correct. We do not have this problem for
640  // DATAOUT_VTX.
641  if(!async)
642  compute_restr_idx(mesh, idxbuff, *restr);
643  else
644  compute_restr_idx_async(mesh, idxbuff, *restr);
645 
646  break;
647  }
648 
649  case DATAOUT_VTX: {
650  SF::vector<mesh_int_t> idxbuff;
651 
652  update_cwd();
653 
654  set_dir(INPUT);
655  read_indices(idxbuff, dataout_vtx, mesh, SF::NBR_REF, true, PETSC_COMM_WORLD);
656  set_dir(CURDIR);
657 
658  restr = new SF::vector<mesh_int_t>();
659 
660  if(!async) {
662  for(mesh_int_t & i : idxbuff) i = nbr[i];
663 
664  compute_restr_idx(mesh, idxbuff, *restr);
665  } else {
666  *restr = idxbuff;
667  }
668 
669  break;
670  }
671 
672  case DATAOUT_NONE:
673  case DATAOUT_VOL:
674  default: break;
675  }
676 }
677 
678 void Electrics::setup_output()
679 {
680  int rank = get_rank();
681  SF::vector<mesh_int_t>* restr_i = NULL;
682  SF::vector<mesh_int_t>* restr_e = NULL;
683  set_dir(OUTPUT);
684 
685  setup_dataout(param_globals::dataout_i, param_globals::dataout_i_vtx, intra_elec_msh,
686  restr_i, param_globals::num_io_nodes > 0);
687 
688  if(param_globals::dataout_i)
689  output_manager.register_output(parab_solver.Vmv, intra_elec_msh, 1, param_globals::vofile, "mV", restr_i);
690 
691  if(param_globals::bidomain) {
692  setup_dataout(param_globals::dataout_e, param_globals::dataout_e_vtx, extra_elec_msh,
693  restr_e, param_globals::num_io_nodes > 0);
694 
695  if(param_globals::dataout_i)
696  output_manager.register_output(ellip_solver.phie_i, intra_elec_msh, 1, param_globals::phieifile, "mV", restr_i);
697  if(param_globals::dataout_e)
698  output_manager.register_output(ellip_solver.phie, extra_elec_msh, 1, param_globals::phiefile, "mV", restr_e);
699  }
700 
701  if(param_globals::dump_data & DUMP_IC) {
702  output_manager.register_output(parab_solver.Ic, intra_elec_msh, 1, "Ic.igb", "uA/cm^2", restr_i);
703  output_manager.register_output(parab_solver.IIon, intra_elec_msh, 1, "Iion.igb","uA/cm^2", restr_i);
704  }
705 
706  if(param_globals::dump_data & DUMP_IVOL)
707  output_manager.register_output(parab_solver.Ivol, intra_elec_msh, 1, "Ivol.igb", "uA", restr_i);
708  if(param_globals::dump_data & DUMP_IACT)
709  output_manager.register_output(parab_solver.Iact, intra_elec_msh, 1, "Iact.igb", "uA", restr_i);
710 
711  if(phie_rcv.pts.size())
712  output_manager.register_output_sync(phie_rcv.phie_rec, phie_recv_msh, 1, param_globals::phie_recovery_file, "mV");
713 
715 
716  if(param_globals::num_trace) {
717  sf_mesh & imesh = get_mesh(intra_elec_msh);
718  open_trace(ion.miif, param_globals::num_trace, param_globals::trace_node, NULL, &imesh);
719  }
720 
721  // initialize generic logger for IO timings per time_dt
722  IO_stats.init_logger("IO_stats.dat");
723 }
724 
725 void Electrics::dump_matrices()
726 {
727  std::string bsname = param_globals::dump_basename;
728  std::string fn;
729 
730  set_dir(OUTPUT);
731 
732  // dump monodomain matrices
733  if ( param_globals::parab_solve==1 ) {
734  // using Crank-Nicolson
735  fn = bsname + "_Ki_CN.bin";
736  parab_solver.lhs_parab->write(fn.c_str());
737  }
738  fn = bsname + "_Ki.bin";
739  parab_solver.rhs_parab->write(fn.c_str());
740 
741  fn = bsname + "_Mi.bin";
742  parab_solver.mass_i->write(fn.c_str());
743 
744  if ( param_globals::bidomain ) {
745  fn = bsname + "_Kie.bin";
746  ellip_solver.phie_mat->write(fn.c_str());
747 
748  fn = bsname + "_Me.bin";
749  ellip_solver.mass_e->write(fn.c_str());
750  }
751 }
752 
753 
756 double Electrics::timer_val(const int timer_id)
757 {
758  // determine
759  int sidx = stimidx_from_timeridx(stimuli, timer_id);
760  double val = 0.0;
761  if(sidx != -1) {
762  stimuli[sidx].value(val);
763  }
764  else
765  val = std::nan("NaN");
766 
767  return val;
768 }
769 
772 std::string Electrics::timer_unit(const int timer_id)
773 {
774  int sidx = stimidx_from_timeridx(stimuli, timer_id);
775  std::string s_unit;
776 
777  if(sidx != -1)
778  // found a timer-linked stimulus
779  s_unit = stimuli[sidx].pulse.wave.f_unit;
780 
781  return s_unit;
782 }
783 
786 int stimidx_from_timeridx(const SF::vector<stimulus> & stimuli, const int timer_id)
787 {
788  // the only electrical quantities linked to a timer are stimuli
789  // thus we search for timer links only among stimuli for now
790 
791  // iterate over stimuli
792  for(size_t i = 0; i<stimuli.size(); i++)
793  {
794  const stimulus & s = stimuli[i];
795 
796  if(s.ptcl.timer_id == timer_id)
797  return s.idx;
798  }
799 
800  // invalid timer index not linked to any stimulus
801  return -1;
802 }
803 
814 void get_kappa(sf_vec & kappa, IMPregion *ir, limpet::MULTI_IF & miif, double k)
815 {
816  double* reg_kappa = new double[miif.N_IIF];
817 
818  for(int i=0; i<miif.N_IIF; i++)
819  reg_kappa[i] = k * miif.IIF[i]->cgeom().SVratio * ir[i].volFrac;
820 
821  double *kd = kappa.ptr();
822 
823  for(int i = 0; i < miif.numNode; i++)
824  kd[i] = reg_kappa[(int) miif.IIFmask[i]];
825 
826  kappa.release_ptr(kd);
827  delete [] reg_kappa;
828 }
829 
830 
839 {
840  for(size_t i=0; i < m.regions.size(); i++) {
841  elecMaterial *emat = static_cast<elecMaterial*>(m.regions[i].material);
842  emat->g = type;
843  }
844 }
845 
846 void Electrics::setup_solvers()
847 {
848  set_dir(OUTPUT);
849  parab_solver.init();
851 
852  if (param_globals::bidomain) {
853  ellip_solver.init();
855  }
856 
857  if(param_globals::dump2MatLab)
858  dump_matrices();
859 }
860 
862 {
863  for(const stimulus & s : stimuli) {
864  if(is_dbc(s.phys.type))
865  return true;
866  }
867  return false;
868 }
869 
870 const char* get_tsav_ext(double time)
871 {
872  int min_idx = -1;
873  double min_diff = 1e100;
874 
875  for(int i=0; i<param_globals::num_tsav; i++)
876  {
877  double diff = fabs(param_globals::tsav[i] - time);
878  if(min_diff > diff) {
879  min_diff = diff;
880  min_idx = i;
881  }
882  }
883 
884  if(min_idx == -1)
885  min_idx = 0;
886 
887  return param_globals::tsav_ext[min_idx];
888 }
889 
890 void Electrics::checkpointing()
891 {
893 
894  // regular user selected state save
895  if (tm.trigger(iotm_chkpt_list)) {
896  char save_fnm[1024];
897  const char* tsav_ext = get_tsav_ext(tm.time);
898 
899  snprintf(save_fnm, sizeof save_fnm, "%s.%s.roe", param_globals::write_statef, tsav_ext);
900 
901  ion.miif->dump_state(save_fnm, tm.time, intra_elec_msh, false, GIT_COMMIT_COUNT);
902  }
903 
904  // checkpointing based on interval
905  if (tm.trigger(iotm_chkpt_intv)) {
906  char save_fnm[1024];
907  snprintf(save_fnm, sizeof save_fnm, "checkpoint.%.1f.roe", tm.time);
908  ion.miif->dump_state(save_fnm, tm.time, intra_elec_msh, false, GIT_COMMIT_COUNT);
909  }
910 }
911 
913 {
914  double t0, t1, dur;
915  get_time(t0);
916  stats.init_logger("ell_stats.dat");
917 
918  // here we can differentiate the solvers
919  SF::init_solver(&lin_solver);
920  sf_mesh & extra_mesh = get_mesh(extra_elec_msh);
921  sf_vec::ltype alg_type = sf_vec::algebraic;
922  const int dpn = 1;
923 
924  SF::init_vector(&phie, extra_mesh, dpn, alg_type);
925  SF::init_vector(&phiesrc, extra_mesh, dpn, alg_type);
926  SF::init_vector(&currtmp, extra_mesh, dpn, alg_type);
927 
929  sf_mesh & intra_mesh = get_mesh(intra_elec_msh);
930  SF::init_vector(&phie_i, intra_mesh, dpn, alg_type);
931  }
932 
933  int max_row_entries = max_nodal_edgecount(extra_mesh);
934 
935  SF::init_matrix(&phie_mat);
936  SF::init_matrix(&mass_e);
937 
938  // alloc stiffness matrix
939  phie_mat->init(extra_mesh, dpn, dpn, max_row_entries);
940  // alloc mass matrix
941  mass_e ->init(extra_mesh, dpn, dpn, param_globals::mass_lumping ? 1 : max_row_entries);
942  dur = timing(t1, t0);
943 }
944 
948 {
949  double t0, t1, dur;
950  get_time(t0);
951  rebuild_stiffness(mtype, stimuli, logger);
952  rebuild_mass(logger);
953  dur = timing(t1, t0);
954 }
955 
959 {
960  double t0, t1, dur;
961  int log_flag = param_globals::output_level > 1 ? ECHO : 0;
962 
963  MaterialType & mt = mtype[Electrics::extra_grid];
964  const bool have_dbc = have_dbc_stims(stimuli);
965 
966  cond_t condType = sum_cond;
967  set_cond_type(mt, condType);
968 
969  // get mesh reference
970  sf_mesh & mesh = get_mesh(extra_elec_msh);
971 
972  get_time(t0);
973 
974  // fill the system
975  elec_stiffness_integrator stfn_integ(mt);
976 
977  phie_mat->zero();
978  SF::assemble_matrix(*phie_mat, mesh, stfn_integ);
979  phie_mat->scale(-1.0);
980 
981  dur = timing(t1,t0);
982  log_msg(logger,0,log_flag, "Computed ellipitc stiffness matrix in %.3f seconds.", dur);
983 
984  // set boundary conditions
985  if(have_dbc) {
986  log_msg(logger,0,log_flag, "Elliptic lhs matrix enforcing Dirichlet boundaries.");
987  get_time(t0);
988 
989  if(dbc == nullptr)
990  dbc = new dbc_manager(*phie_mat, stimuli);
991  else
992  dbc->recompute_dbcs();
993 
994  dbc->enforce_dbc_lhs();
995 
996  dur = timing(t1,t0);
997  log_msg(logger,0,log_flag, "Elliptic lhs matrix Dirichlet enforcing done in %.3f seconds.", dur);
998  }
999  else {
1000  log_msg(logger,1,ECHO, "Elliptic lhs matrix is singular!");
1001  // we are dealing with a singular system
1002  phie_mat_has_nullspace = true;
1003  }
1004 
1005  // solver has not been initialized yet
1006  set_dir(INPUT);
1007  get_time(t0);
1008 
1009  setup_linear_solver(logger);
1010 
1011  dur = timing(t1,t0);
1012  log_msg(logger,0,log_flag, "Initializing elliptic solver in %.5f seconds.", dur);
1013  set_dir(OUTPUT);
1014 }
1015 
1017 {
1018  int log_flag = param_globals::output_level > 1 ? ECHO : 0;
1019  double t0, t1, dur;
1020  mass_integrator mass_integ;
1021 
1022  // get mesh reference
1023  sf_mesh & mesh = get_mesh(extra_elec_msh);
1024  get_time(t0);
1025  mass_e->zero();
1026 
1027  if(param_globals::mass_lumping) {
1028  SF::assemble_lumped_matrix(*mass_e, mesh, mass_integ);
1029  } else {
1030  SF::assemble_matrix(*mass_e, mesh, mass_integ);
1031  }
1032 
1033  dur = timing(t1,t0);
1034  log_msg(logger,0,log_flag, "Computed elliptic mass matrix in %.3f seconds.", dur);
1035 }
1036 
1037 void elliptic_solver::setup_linear_solver(FILE_SPEC logger)
1038 {
1039 
1040  tol = param_globals::cg_tol_ellip;
1041  max_it = param_globals::cg_maxit_ellip;
1042 
1043  std::string solver_file;
1044  solver_file = param_globals::ellip_options_file;
1045  lin_solver->setup_solver(*phie_mat, tol, max_it, param_globals::cg_norm_ellip,
1046  "elliptic PDE", phie_mat_has_nullspace,
1047  logger, solver_file.c_str(),
1048  "-ksp_type cg -pc_type hypre -pc_hypre_type boomeramg "
1049  "-pc_hypre_boomeramg_max_iter 1 "
1050  "-pc_hypre_boomeramg_strong_threshold 0.0 -options_left");
1051 }
1052 
1053 void elliptic_solver::solve(sf_mat & Ki, sf_vec & Vmv, sf_vec & tmp_i)
1054 {
1055  double t0,t1;
1057  // assembly of rhs for FE
1058  if (phiesrc->mag() > 0.0) {
1059  mass_e->mult(*phiesrc, *currtmp);
1060  phiesrc->deep_copy(*currtmp);
1061  }
1062 
1063  Ki.mult(Vmv, tmp_i);
1064 
1065  bool add = true;
1066  i2e->forward(tmp_i, *phiesrc, add);
1067 
1068  if(dbc != nullptr)
1069  dbc->enforce_dbc_rhs(*phiesrc);
1070 
1071  get_time(t0);
1072  (*lin_solver)(*phie, *phiesrc);
1073 
1074  // treat solver statistics
1075  auto dur = timing(t1, t0);
1076  lin_solver->time += dur;
1077  stats.slvtime += dur;
1078  stats.update_iter(lin_solver->niter);
1079  if(lin_solver->reason < 0) {
1080  log_msg(0, 5, 0,"%s solver diverged. Reason: %s.", lin_solver->name.c_str(),
1081  petsc_get_converged_reason_str(lin_solver->reason));
1082  EXIT(1);
1083  }
1084 
1085  add = false;
1086  i2e->backward(*phie, *phie_i, add);
1087 }
1088 
1090 {
1091  double t0,t1;
1092 
1093  if(dbc != nullptr)
1094  dbc->enforce_dbc_rhs(*phiesrc);
1095 
1096  get_time(t0);
1097  (*lin_solver)(*phie, *phiesrc);
1098 
1099  // treat solver statistics
1100  auto dur = timing(t1, t0);
1101  lin_solver->time += dur;
1102  stats.slvtime += dur;
1103  stats.update_iter(lin_solver->niter);
1104 
1105  if(lin_solver->reason < 0) {
1106  log_msg(0, 5, 0,"%s solver diverged. Reason: %s.", lin_solver->name.c_str(),
1107  petsc_get_converged_reason_str(lin_solver->reason));
1108  EXIT(1);
1109  }
1110 
1111  // phie_i is only set up when we have an IntraMesh registered
1112  if(is_init(phie_i)) {
1113  bool add = false;
1115  i2e->backward(*phie, *phie_i, add);
1116  }
1117 }
1118 
1120 {
1121  double t0, t1, dur;
1122  get_time(t0);
1123  stats.init_logger("par_stats.dat");
1124 
1125  // here we can differentiate the solvers
1126  SF::init_solver(&lin_solver);
1127 
1128  sf_vec* vm_ptr = get_data(vm_vec);
1129  sf_vec* iion_ptr = get_data(iion_vec);
1130 
1131  if(!(vm_ptr != NULL && iion_ptr != NULL)) {
1132  log_msg(0,5,0, "%s error: global Vm and Iion vectors not properly set up! Ionics seem invalid! Aborting!",
1133  __func__);
1134  EXIT(1);
1135  }
1136 
1137  SF::init_vector(&Vmv);
1138  SF::init_vector(&IIon);
1139  Vmv-> shallow_copy(*vm_ptr);
1140  IIon->shallow_copy(*iion_ptr);
1141 
1142  if(param_globals::dump_data & DUMP_IC) SF::init_vector(&Ic , Vmv);
1143  if(param_globals::dump_data & DUMP_IVOL) SF::init_vector(&Ivol, Vmv);
1144  if(param_globals::dump_data & DUMP_IACT) SF::init_vector(&Iact, Vmv);
1145 
1146  SF::init_vector(&Diff_term, Vmv);
1147  SF::init_vector(&old_vm, Vmv);
1148 
1149  sf_mesh & intra_mesh = get_mesh(intra_elec_msh);
1150  sf_vec::ltype alg_type = sf_vec::algebraic;
1151 
1152  int dpn = 1;
1153  SF::init_vector(&kappa_i, intra_mesh, dpn, alg_type);
1154  SF::init_vector(&tmp_i1, intra_mesh, dpn, alg_type);
1155  SF::init_vector(&tmp_i2, intra_mesh, dpn, alg_type);
1156  SF::init_vector(&old_vm, intra_mesh, dpn, alg_type);
1157 
1158  if(!param_globals::operator_splitting)
1159  SF::init_vector(&Irhs, intra_mesh, dpn, alg_type);
1160 
1161  // alloc matrices
1162  int max_row_entries = max_nodal_edgecount(intra_mesh);
1163 
1164  SF::init_matrix(&lhs_parab);
1165  SF::init_matrix(&rhs_parab);
1166  SF::init_matrix(&mass_i);
1167 
1168  rhs_parab->init(intra_mesh, dpn, dpn, max_row_entries);
1169  mass_i ->init(intra_mesh, dpn, dpn, param_globals::mass_lumping ? 1 : max_row_entries);
1170 
1171  parab_tech = static_cast<parabolic_solver::parabolic_t>(param_globals::parab_solve);
1172  dur = timing(t1, t0);
1173 }
1174 
1176 {
1177  double start, end, period;
1178  get_time(start);
1179  double t0, t1, dur;
1180  mass_integrator mass_integ;
1181  int dpn = 1;
1182 
1183  int log_flag = param_globals::output_level > 1 ? ECHO : 0;
1184  MaterialType & mt = mtype[Electrics::intra_grid];
1185 
1186  double Dt = user_globals::tm_manager->time_step;
1187  get_kappa(*kappa_i, param_globals::imp_region, miif, UM2_to_CM2 / Dt);
1188 
1189  cond_t condType = intra_cond;
1190  sf_mesh & mesh = get_mesh(intra_elec_msh);
1191 
1192  if( (param_globals::bidomain == MONODOMAIN && param_globals::bidm_eqv_mono) ||
1193  (param_globals::bidomain == PSEUDO_BIDM) )
1194  condType = para_cond;
1195 
1196  // set material and conductivity type
1197  set_cond_type(mt, condType);
1198 
1199  // fill the system
1200  {
1201  get_time(t0);
1202 
1203  elec_stiffness_integrator stfn_integ(mt);
1204  SF::assemble_matrix(*rhs_parab, mesh, stfn_integ);
1205 
1206  dur = timing(t1,t0);
1207  log_msg(logger,0,log_flag, "Computed parabolic stiffness matrix in %.3f seconds.", dur);
1208  get_time(t0);
1209 
1210  if(param_globals::mass_lumping)
1211  SF::assemble_lumped_matrix(*mass_i, mesh, mass_integ);
1212  else
1213  SF::assemble_matrix(*mass_i, mesh, mass_integ);
1214 
1215  sf_vec* empty; SF::init_vector(&empty);
1216  mass_i->mult_LR(*kappa_i, *empty);
1217 
1218  dur = timing(t1,t0);
1219  log_msg(logger,0,log_flag, "Computed parabolic mass matrix in %.3f seconds.", dur);
1220  delete empty;
1221  }
1222 
1223  // initialize parab lhs
1224  if(parab_tech != EXPLICIT) {
1225  lhs_parab->duplicate(*rhs_parab);
1226  // if we have mass lumping, then the nonzero pattern between Mi and Ki is different
1227  bool same_nonzero = param_globals::mass_lumping == false;
1228 
1229  if (parab_tech==CN) {
1230  lhs_parab->scale(-param_globals::theta);
1231  lhs_parab->add_scaled_matrix(*mass_i, 1.0, same_nonzero);
1232  }
1233  else if (parab_tech==O2dT) {
1234  lhs_parab->scale(-0.5);
1235  mass_i->scale(0.5);
1236  lhs_parab->add_scaled_matrix(*mass_i, 1.0, same_nonzero);
1237  lhs_parab->add_scaled_matrix(*mass_i, 1.0, same_nonzero);
1238  lhs_parab->add_scaled_matrix(*mass_i, 1.0, same_nonzero);
1239  }
1240  }
1241  else {
1242  SF::init_vector(&inv_mass_diag, Vmv);
1243  mass_i->get_diagonal(*inv_mass_diag);
1244 
1245  SF_real* p = inv_mass_diag->ptr();
1246 
1247  for(int i=0; i<inv_mass_diag->lsize(); i++)
1248  p[i] = 1.0 / p[i];
1249 
1250  inv_mass_diag->release_ptr(p);
1251  }
1252 
1253  if(parab_tech == CN || parab_tech == O2dT) {
1254  set_dir(INPUT);
1255  get_time(t0);
1256 
1257  setup_linear_solver(logger);
1258 
1259  dur = timing(t1,t0);
1260  log_msg(logger,0,log_flag, "Initializing parabolic solver in %.5f seconds.", dur);
1261  set_dir(OUTPUT);
1262  }
1263  period = timing(end, start);
1264 }
1265 
1266 void parabolic_solver::setup_linear_solver(FILE_SPEC logger)
1267 {
1268  tol = param_globals::cg_tol_parab;
1269  max_it = param_globals::cg_maxit_parab;
1270 
1271  std::string solver_file;
1272  solver_file = param_globals::parab_options_file;
1273  lin_solver->setup_solver(*lhs_parab, tol, max_it, param_globals::cg_norm_parab,
1274  "parabolic PDE", false, logger, solver_file.c_str(),
1275  "-pc_type bjacobi -sub_pc_type ilu -ksp_type cg");
1276 }
1277 
1279 {
1280  switch (parab_tech) {
1281  case CN: solve_CN(phie_i); break;
1282  case O2dT: solve_O2dT(phie_i); break;
1283  default: solve_EF(phie_i); break;
1284  }
1285 }
1286 
1287 void parabolic_solver::solve_CN(sf_vec & phie_i)
1288 {
1289  double t0,t1;
1290  // assembly of rhs for CN
1291  if (param_globals::bidomain == BIDOMAIN) {
1292  tmp_i1->deep_copy(phie_i);
1293  tmp_i1->add_scaled(*Vmv, 1.0 - param_globals::theta);
1294  rhs_parab->mult(*tmp_i1, *tmp_i2);
1295  }
1296  else {
1297  rhs_parab->mult(*Vmv, *tmp_i2);
1298  *tmp_i2 *= 1.0 - param_globals::theta;
1299  }
1300 
1301  mass_i->mult(*Vmv, *tmp_i1);
1302  *tmp_i1 += *tmp_i2;
1303 
1304  // add current contributions to rhs
1305  if(!param_globals::operator_splitting)
1306  tmp_i1->add_scaled(*Irhs, -1.0);
1307 
1308  get_time(t0);
1309 
1310  (*lin_solver)(*Vmv, *tmp_i1);
1311 
1312  if(lin_solver->reason < 0) {
1313  log_msg(0, 5, 0,"%s solver diverged. Reason: %s.", lin_solver->name.c_str(),
1314  petsc_get_converged_reason_str(lin_solver->reason));
1315  EXIT(1);
1316  }
1317 
1318  // treat solver statistics
1319  auto dur = timing(t1, t0);
1320  lin_solver->time += dur;
1321  stats.slvtime += dur;
1322  stats.update_iter(lin_solver->niter);
1323 }
1324 
1325 void parabolic_solver::solve_O2dT(sf_vec & phie_i)
1326 {
1327  double t0,t1;
1328  // assembly of rhs for FE
1329  if (param_globals::bidomain == BIDOMAIN) {
1330  tmp_i2->deep_copy(phie_i);
1331  tmp_i2->add_scaled(*Vmv, 0.5);
1332  rhs_parab->mult(*tmp_i2, *tmp_i1); // tmp_i1 = K_i(Vm^t * 0.5 + phi_e)
1333  }
1334  else {
1335  rhs_parab->mult(*Vmv, *tmp_i1);
1336  *tmp_i1 *= 0.5; // tmp_i1 = 0.5 * K_i Vm^t
1337  }
1338 
1339  mass_i->mult(*Vmv, *tmp_i2); // tmp_i2 = M/2 Vm^t
1340  tmp_i1->add_scaled(*tmp_i2, 4.0); // tmp_i1 = (2M+K_i/2)Vm^t
1341  mass_i->mult(*old_vm, *tmp_i2); // tmp_i2 = M/2 Vm^{t-1}
1342 
1343  tmp_i1->add_scaled(*tmp_i2, -1.0); // tmp_i1 = (2M+K_i/2)Vm^t-M/2 Vm^{t-1}
1344  *old_vm = *Vmv;
1345 
1346  get_time(t0);
1347 
1348  // solve
1349  (*lin_solver)(*Vmv, *tmp_i1);
1350 
1351  // treat solver statistics
1352  stats.slvtime += timing(t1, t0);
1353  stats.update_iter(lin_solver->niter);
1354 }
1355 
1356 void parabolic_solver::solve_EF(sf_vec & phie_i)
1357 {
1358  double t0,t1,t2;
1359  get_time(t0);
1360 
1361  // assembly of rhs for FE
1362  if (param_globals::bidomain == BIDOMAIN) {
1363  tmp_i2->deep_copy(phie_i);
1364  *tmp_i2 += *Vmv;
1365  rhs_parab->mult(*tmp_i2, *tmp_i1);
1366  }
1367  else {
1368  rhs_parab->mult(*Vmv, *tmp_i1);
1369  }
1370 
1371  *tmp_i1 *= *inv_mass_diag;
1372  Vmv->add_scaled(*tmp_i1, 1.0);
1373 
1374  if(param_globals::operator_splitting == false)
1375  Vmv->add_scaled(*Irhs, -1.0);
1376 
1377  // record rhs timing
1378  stats.slvtime += timing(t1, t0);
1379 }
1380 
1382 {
1383  char* prvSimDir = strlen(param_globals::start_statef) ?
1384  get_file_dir(param_globals::start_statef) : NULL;
1385 
1386  const char* extn = ".dat";
1387 
1388  // if compute_APD we need an extra 2 acts
1389  int addLATs = param_globals::compute_APD ? 2 : 0;
1390 
1391  bool have_sentinel = param_globals::t_sentinel > 0.0;
1392  bool need_to_add_sentinel = have_sentinel && (param_globals::sentinel_ID < 0);
1393 
1394  addLATs += need_to_add_sentinel ? 1 : 0;
1395  acts.resize(param_globals::num_LATs + addLATs);
1396 
1397  int j=0;
1398  for (int i = 0; i < param_globals::num_LATs; i++ )
1399  {
1400  // using Ph only with bidomain runs
1401  if (param_globals::lats[i].method <= 0 || (param_globals::lats[i].measurand == PHIE && !param_globals::bidomain)) {
1402  log_msg(NULL, 3, 0, "Phie-based LAT measurement requires bidomain >=1 Ignoring lats[%d].", i);
1403  continue;
1404  }
1405 
1406  acts[j].method = (ActMethod)param_globals::lats[i].method;
1407  acts[j].threshold = param_globals::lats[i].threshold;
1408  acts[j].mode = param_globals::lats[i].mode;
1409  acts[j].all = param_globals::lats[i].all;
1410  acts[j].measurand = (PotType)param_globals::lats[i].measurand;
1411  acts[j].ID = param_globals::lats[i].ID;
1412  acts[j].fout = NULL;
1413 
1414  if(param_globals::lats[i].all) {
1415  acts[j].fname = (char*) malloc((strlen(param_globals::lats[i].ID)+strlen(extn)+1)*sizeof(char));
1416  snprintf(acts[j].fname, strlen(param_globals::lats[i].ID)+strlen(extn)+1, "%s%s", param_globals::lats[i].ID, extn);
1417  }
1418  else {
1419  char prfx[] = "init_acts_";
1420  int max_len = strlen(prfx) + strlen(param_globals::lats[i].ID) + strlen(extn) + 1;
1421 
1422  acts[j].fname = (char*) malloc(max_len*sizeof(char));
1423  snprintf(acts[j].fname, max_len, "%s%s%s", prfx, param_globals::lats[i].ID, extn);
1424  }
1425 
1426  // restarting
1427  if(prvSimDir != NULL) {
1428  int len_fname = strlen(prvSimDir)+strlen(acts[j].fname)+2;
1429  acts[j].prv_fname = (char*) malloc(len_fname*sizeof(char));
1430  snprintf(acts[j].prv_fname, len_fname, "%s/%s", prvSimDir, acts[j].fname);
1431  }
1432 
1433  j++;
1434  }
1435 
1436  if(param_globals::compute_APD) {
1437  acts[j].method = ACT_THRESH; // threshold crossing
1438  acts[j].threshold = param_globals::actthresh;
1439  acts[j].mode = 0; // upstroke
1440  acts[j].all = true;
1441  acts[j].measurand = VM; // Vm
1442  //acts[j].ID = dupstr("Vm_Activation");
1443  acts[j].fout = NULL;
1444  acts[j].fname = dupstr("vm_activation.dat");
1445 
1446  j++;
1447  acts[j].method = ACT_THRESH; // threshold crossing
1448  acts[j].threshold = param_globals::recovery_thresh;
1449  acts[j].mode = 1; // repol
1450  acts[j].all = true;
1451  acts[j].measurand = VM; // Vm
1452  //(*acts)[j+1].ID = param_globals::lats[i].ID;
1453  acts[j].fout = NULL;
1454  acts[j].fname = dupstr("vm_repolarisation.dat");
1455 
1456  j++;
1457  }
1458 
1459  // set up sentinel for activity checking
1460  sntl.activated = have_sentinel;
1461  sntl.t_start = param_globals::t_sentinel_start;
1462  sntl.t_window = param_globals::t_sentinel;
1463  sntl.t_quiesc =-1.;
1464  sntl.ID = param_globals::sentinel_ID;
1465 
1466  if(need_to_add_sentinel) {
1467  // add a default LAT detector as sentinel
1468  acts[j].method = ACT_THRESH; // threshold crossing
1469  acts[j].threshold = param_globals::actthresh;
1470  acts[j].mode = 0; // upstroke
1471  acts[j].all = true;
1472  acts[j].measurand = VM; // Vm
1473  //(*acts)[j].ID = dupstr("Vm_Activation");
1474  acts[j].fout = NULL;
1475  acts[j].fname = dupstr("vm_sentinel.dat");
1476  // set sentinel index
1477  sntl.ID = j;
1478  j++;
1479  }
1480 
1481  if(prvSimDir) free(prvSimDir);
1482 }
1483 
1484 void print_act_log(FILE_SPEC logger, const SF::vector<Activation> & acts, int idx)
1485 {
1486  const Activation & act = acts[idx];
1487 
1488  log_msg(logger, 0, 0, "\n");
1489  log_msg(logger, 0, 0, "LAT detector [%2d]", idx);
1490  log_msg(logger, 0, 0, "-----------------\n");
1491 
1492  log_msg(logger, 0, 0, "Measurand: %s", act.measurand ? "Phie" : "Vm");
1493  log_msg(logger, 0, 0, "All: %s", act.all ? "All" : "Only first");
1494  log_msg(logger, 0, 0, "Method: %s", act.method==ACT_DT ? "Derivative" : "Threshold crossing");
1495 
1496  char buf[64], gt[2], sgn[2];
1497  snprintf(sgn, sizeof sgn, "%s", act.mode?"-":"+");
1498  snprintf(gt, sizeof gt, "%s", act.mode?"<":">");
1499 
1500  if(act.method==ACT_DT)
1501  snprintf(buf, sizeof buf, "Maximum %sdf/dt %s %.2f mV", sgn, gt, act.threshold);
1502  else
1503  snprintf(buf, sizeof buf, "Intersection %sdf/dt with %.2f", sgn, act.threshold);
1504 
1505  log_msg(logger, 0, 0, "Mode: %s", buf);
1506  log_msg(logger, 0, 0, "Threshold: %.2f mV\n", act.threshold);
1507 }
1508 
1509 void LAT_detector::init(sf_vec & vm, sf_vec & phie, int offset, enum physic_t phys_t)
1510 {
1511  if(!get_physics(phys_t)) {
1512  log_msg(0,0,5, "There seems to be no EP is defined. LAT detector requires active EP! Aborting LAT setup!");
1513  return;
1514  }
1515 
1516  // we use the electrics logger for output
1517  FILE_SPEC logger = get_physics(phys_t)->logger;
1518 
1519  // TODO(init): except for the shallow copies, shouldn't these be deleted?
1520  // When to delete them?
1521  for(size_t i = 0; i < acts.size(); ++i) {
1522  acts[i].init = 1;
1523  SF::init_vector(&(acts[i].phi));
1524  acts[i].phi->shallow_copy(!acts[i].measurand ? vm : phie);
1525  acts[i].offset = offset;
1526 
1527  SF::init_vector(&(acts[i].phip), acts[i].phi);
1528  *acts[i].phip = *acts[i].phi;
1529 
1530  // derivative based detector
1531  if (acts[i].method == ACT_DT) {
1532  SF::init_vector(&(acts[i].dvp0), acts[i].phi);
1533  SF::init_vector(&(acts[i].dvp1), acts[i].phi);
1534  if(acts[i].mode)
1535  log_msg(NULL,2,0, "Detection of -df/dt|max not implemented, +df/dt|max will be detected.");
1536  }
1537 
1538  // allocate additional local buffers
1539  acts[i].ibuf = (int *)malloc(acts[i].phi->lsize()*sizeof(int));
1540  acts[i].actbuf = (double *)malloc(acts[i].phi->lsize()*sizeof(double));
1541 
1542  if (!acts[i].all) {
1543  SF::init_vector(&acts[i].tm, acts[i].phi->gsize(), acts[i].phi->lsize());
1544  acts[i].tm->set(-1.);
1545 
1546  // initialize with previous initial activations
1547  if(acts[i].prv_fname != NULL) {
1548  set_dir(INPUT);
1549  size_t nread = acts[i].tm->read_ascii(acts[i].prv_fname);
1550 
1551  if(nread == 0)
1552  log_msg(NULL,2,ECHO,"Warning: Initialization of LAT[%2d] failed.", i);
1553 
1554  set_dir(OUTPUT);
1555  }
1556  }
1557  else {
1558  if ( !get_rank() ) {
1559  // here we should copy over previous file and open in append mode
1560  if(acts[i].prv_fname!=NULL) {
1561  set_dir(INPUT);
1562  FILE_SPEC in = f_open( acts[i].prv_fname, "r" );
1563  if(in) {
1564  log_msg(NULL,2,0, "Copying over of previous activation file not implemented.\n"); f_close(in);
1565  }
1566  else
1567  log_msg(NULL,3,0,"Warning: Initialization in %s - \n"
1568  "Failed to read activation file %s.\n", __func__, acts[i].prv_fname);
1569 
1570  set_dir(OUTPUT);
1571  }
1572  acts[i].fout = f_open( acts[i].fname, acts[i].prv_fname==NULL?"w":"a" );
1573  }
1574  }
1575  print_act_log(logger, acts, i);
1576  }
1577 
1578  sf_mesh & intra_mesh = get_mesh(intra_elec_msh);
1579  SF::local_petsc_to_nodal_mapping(intra_mesh, this->petsc_to_nodal);
1580 }
1581 
1582 
1583 int output_all_activations(FILE_SPEC fp, int *ibuf, double *act_tbuf, int nlacts)
1584 {
1585  int rank = get_rank(), gacts = 0, numProc = get_size();
1586 
1587  if (rank == 0) {
1588  // rank 0 writes directly to the table
1589  for (int i=0; i<nlacts; i++)
1590  fprintf(fp->fd, "%d\t%.6f\n", ibuf[i], act_tbuf[i]);
1591 
1592  gacts += nlacts;
1593 
1594  SF::vector<int> buf_inds;
1595  SF::vector<double> buf_acts;
1596 
1597  for (int j=1; j<numProc; j++) {
1598  int acts = 0;
1599  MPI_Status status;
1600  MPI_Recv(&acts, 1, MPI_INT, j, 110, PETSC_COMM_WORLD, &status);
1601 
1602  if (acts) {
1603  buf_inds.resize(acts);
1604  buf_acts.resize(acts);
1605 
1606  MPI_Recv(buf_inds.data(), acts, MPI_INT, j, 110, PETSC_COMM_WORLD, &status);
1607  MPI_Recv(buf_acts.data(), acts, MPI_DOUBLE, j, 110, PETSC_COMM_WORLD, &status);
1608 
1609  for(int ii=0; ii<acts; ii++)
1610  fprintf(fp->fd, "%d\t%.6f\n", buf_inds[ii], buf_acts[ii]);
1611 
1612  gacts += acts;
1613  }
1614  }
1615  fflush(fp->fd);
1616  }
1617  else {
1618  MPI_Send(&nlacts, 1, MPI_INT, 0, 110, PETSC_COMM_WORLD);
1619  if (nlacts) {
1620  MPI_Send(ibuf, nlacts, MPI_INT, 0, 110, PETSC_COMM_WORLD);
1621  MPI_Send(act_tbuf, nlacts, MPI_DOUBLE, 0, 110, PETSC_COMM_WORLD);
1622  }
1623  }
1624 
1625  MPI_Bcast(&gacts, 1, MPI_INT, 0, PETSC_COMM_WORLD);
1626  return gacts;
1627 }
1628 
1630 {
1631  int nacts = 0;
1632  double *a;
1633 
1634  for(Activation* aptr = acts.data(); aptr != acts.end(); aptr++)
1635  {
1636  int lacts = 0;
1637  switch (aptr->method) {
1638  case ACT_THRESH:
1639  lacts = check_cross_threshold(*aptr->phi, *aptr->phip, tm,
1640  aptr->ibuf, aptr->actbuf, aptr->threshold, aptr->mode);
1641  break;
1642 
1643  case ACT_DT:
1644  lacts = check_mx_derivative (*aptr->phi, *aptr->phip, tm,
1645  aptr->ibuf, aptr->actbuf, *aptr->dvp0, *aptr->dvp1,
1646  aptr->threshold, aptr->mode);
1647  break;
1648 
1649  default:
1650  break;
1651  }
1652 
1653  if (!aptr->all)
1654  a = aptr->tm->ptr();
1655 
1657 
1658  for(int j=0; j<lacts; j++) {
1659  if(aptr->all) {
1660  int nodal_idx = this->petsc_to_nodal.forward_map(aptr->ibuf[j]);
1661  aptr->ibuf[j] = canon_nbr[nodal_idx] + aptr->offset;
1662  }
1663  else {
1664  if(a[aptr->ibuf[j]] == -1)
1665  a[aptr->ibuf[j]] = aptr->actbuf[j];
1666  }
1667  }
1668 
1669  if(aptr->all)
1670  output_all_activations(aptr->fout, aptr->ibuf, aptr->actbuf, lacts);
1671  else
1672  aptr->tm->release_ptr(a);
1673 
1674  MPI_Allreduce(MPI_IN_PLACE, &lacts, 1, MPI_INT, MPI_SUM, PETSC_COMM_WORLD);
1675  nacts += lacts;
1676 
1677  aptr->nacts = nacts;
1678  }
1679 
1680  return nacts > 0;
1681 }
1682 
1683 
1684 int LAT_detector::check_quiescence(double tm, double dt)
1685 {
1686  static int savequitFlag = 0;
1687  int numNodesActivated = -1;
1688 
1689  if(sntl.activated) {
1690  // initialization
1691  if(sntl.t_quiesc < 0. && sntl.t_window >= 0.0 ) {
1692  log_msg(0,0,ECHO | NONL, "================================================================================================\n");
1693  log_msg(0,0,ECHO | NONL, "%s() WARNING: simulation is configured to savequit() after %.2f ms of quiescence\n", __func__, sntl.t_window);
1694  log_msg(0,0,ECHO | NONL, "================================================================================================\n");
1695  sntl.t_quiesc = 0.0;
1696  }
1697 
1698  if(tm >= sntl.t_start && !savequitFlag)
1699  {
1700  numNodesActivated = acts[sntl.ID].nacts;
1701 
1702  if(numNodesActivated) sntl.t_quiesc = 0.0;
1703  else sntl.t_quiesc += dt;
1704 
1705  if(sntl.t_window >= 0.0 && sntl.t_quiesc > sntl.t_window && !savequitFlag) {
1706  savequitFlag++;
1707  savequit();
1708  }
1709  }
1710  }
1711 
1712  return numNodesActivated;
1713 }
1714 
1715 
1716 
1717 
1718 int LAT_detector::check_cross_threshold(sf_vec & vm, sf_vec & vmp, double tm,
1719  int *ibuf, double *actbuf, float threshold, int mode)
1720 {
1721  SF_real *c = vm.ptr();
1722  SF_real *p = vmp.ptr();
1723  int lsize = vm.lsize();
1724  int nacts = 0, gnacts = 0;
1725 
1726  for (int i=0; i<lsize; i++) {
1727  int sgn = 1;
1728  bool triggered = false;
1729  if(mode==0) {// detect +slope crossing
1730  triggered = p[i] <= threshold && c[i] > threshold; }
1731  else { // detect -slope crossing
1732  triggered = p[i] >= threshold && c[i] < threshold;
1733  sgn = -1;
1734  }
1735 
1736  if (triggered) {
1737  double tact = tm - param_globals::dt + (threshold-p[i])/(c[i]-p[i])*sgn*param_globals::dt;
1738  ibuf [nacts] = i;
1739  actbuf[nacts] = tact;
1740  nacts++;
1741  }
1742  p[i] = c[i];
1743  }
1744 
1745  vm.release_ptr(c);
1746  vmp.release_ptr(p);
1747  return nacts;
1748 }
1749 
1750 int LAT_detector::check_mx_derivative(sf_vec & vm, sf_vec & vmp, double tm,
1751  int *ibuf, double *actbuf, sf_vec & dvp0, sf_vec & dvp1,
1752  float threshold, int mode)
1753 {
1754  int nacts = 0, gnacts = 0;
1755  double tact, dt2 = 2 * param_globals::dt;
1756  int lsize = vm.lsize();
1757  SF_real ddv0, ddv1, dv, dvdt;
1758  SF_real *c, *p, *pd0, *pd1;
1759 
1760  c = vm.ptr();
1761  p = vmp.ptr();
1762  pd0 = dvp0.ptr();
1763  pd1 = dvp1.ptr();
1764 
1765  for (int i=0; i<lsize; i++ ) {
1766  dv = (c[i]-p[i]);
1767  dvdt = dv/param_globals::dt;
1768  ddv0 = pd1[i]-pd0[i];
1769  ddv1 = dv -pd1[i];
1770 
1771  if (dvdt>=threshold && ddv0>0 && ddv1<0) {
1772  tact = tm-dt2+(ddv0/(ddv0-ddv1))*param_globals::dt;
1773  ibuf [nacts] = i;
1774  actbuf[nacts] = tact;
1775  nacts++;
1776  }
1777  p [i] = c[i];
1778  pd0[i] = pd1[i];
1779  pd1[i] = dv;
1780  }
1781 
1782  vm .release_ptr(c);
1783  vmp .release_ptr(p);
1784  dvp0.release_ptr(pd0);
1785  dvp1.release_ptr(pd1);
1786 
1787  return nacts;
1788 }
1789 
1794 {
1796  assert(sc != NULL);
1797 
1798  bool forward = true;
1799 
1800  for (size_t i = 0; i < acts.size(); i++) {
1801  if (is_init(acts[i].tm)) {
1802  (*sc)(*acts[i].tm, forward);
1803  acts[i].tm->write_ascii(acts[i].fname, false);
1804  }
1805  }
1806 }
1807 
1808 void Electrics::prepace() {
1809  log_msg(NULL, 0, 0, "Using activation times from file %s to distribute prepacing states\n",
1810  param_globals::prepacing_lats);
1811  log_msg(NULL, 0, 0, "Assuming stimulus strength %f uA/uF with duration %f ms for prepacing\n",
1812  param_globals::prepacing_stimstr, param_globals::prepacing_stimdur);
1813 
1814  limpet::MULTI_IF* miif = this->ion.miif;
1815 
1816  const sf_mesh & mesh = get_mesh(intra_elec_msh);
1817  sf_vec* read_lats; SF::init_vector(&read_lats, mesh, 1, sf_vec::algebraic);
1818 
1819  // read in the global distributed vector of all activation times
1820  set_dir(INPUT);
1821  size_t numread = read_lats->read_ascii(param_globals::prepacing_lats);
1822  if (numread == 0) {
1823  log_msg(NULL, 5, 0, "Failed reading required LATs! Skipping prepacing!");
1824  return;
1825  }
1826  set_dir(OUTPUT);
1827 
1829  assert(sc != NULL);
1830 
1831  // permute in-place to petsc permutation
1832  bool forward = false;
1833  (*sc)(*read_lats, forward);
1834 
1835  // take care of negative LAT values
1836  {
1837  PetscReal* lp = read_lats->ptr();
1838  for(int i=0; i<read_lats->lsize(); i++)
1839  if(lp[i] < 0.0) lp[i] = param_globals::tend + 10.0;
1840 
1841  read_lats->release_ptr(lp);
1842  }
1843 
1844  // make LATs relative and figure out the first LAT
1845  // so we know when to save state of each point
1846  SF_real LATmin = read_lats->min();
1847 
1848  if(LATmin < 0.0) {
1849  log_msg(0,3,0, "LAT data is not complete. Skipping prepacing.");
1850  return;
1851  }
1852 
1853  SF_real offset = floor(LATmin / param_globals::prepacing_bcl) * param_globals::prepacing_bcl;
1854  SF_real last_tm = param_globals::prepacing_bcl * param_globals::prepacing_beats;
1855 
1856  // compute read_lats[i] = last_tm - (read_lats[i] - offset)
1857  *read_lats += -offset;
1858  *read_lats *= -1.;
1859  *read_lats += last_tm;
1860 
1861  miif->getRealData();
1862  SF_real *save_tm = read_lats->ptr();
1863  SF_real *vm = miif->gdata[limpet::Vm]->ptr();
1864 
1865  for (int ii = 0; ii < miif->N_IIF; ii++) {
1866  if (!miif->N_Nodes[ii]) continue;
1867 
1868  // create sorted array of save times.
1869  SF::vector<SF::mixed_tuple<double,int>> sorted_save(miif->N_Nodes[ii]); // v1 = time, v2 = index
1870  for (int kk = 0; kk < miif->N_Nodes[ii]; kk++) {
1871  sorted_save[kk].v1 = save_tm[miif->NodeLists[ii][kk]];
1872  sorted_save[kk].v2 = kk;
1873  }
1874  std::sort(sorted_save.begin(), sorted_save.end());
1875 
1876  size_t lastidx = sorted_save.size() - 1;
1877  int paced = sorted_save[lastidx].v2; // IMP index of latest node
1878  int csav = 0;
1879 
1880  for (double t = 0; t < sorted_save[lastidx].v1; t += param_globals::dt) {
1881  if (fmod(t, param_globals::prepacing_bcl) < param_globals::prepacing_stimdur &&
1882  t < param_globals::prepacing_bcl * param_globals::prepacing_beats - 1)
1883  miif->ldata[ii][limpet::Vm][paced] += param_globals::prepacing_stimstr * param_globals::dt;
1884 
1885  compute_IIF(*miif->IIF[ii], miif->ldata[ii], paced);
1886 
1887  // Vm update always happens now outside of the imp
1888  miif->ldata[ii][limpet::Vm][paced] -= miif->ldata[ii][limpet::Iion][paced] * param_globals::dt;
1889  vm[miif->NodeLists[ii][paced]] = miif->ldata[ii][limpet::Vm][paced];
1890 
1891  while (csav < miif->N_Nodes[ii] - 1 && t >= sorted_save[csav].v1)
1892  dup_IMP_node_state(*miif->IIF[ii], paced, sorted_save[csav++].v2, miif->ldata[ii]);
1893  }
1894 
1895  // get nodes which may be tied for last
1896  while (csav < miif->N_Nodes[ii] - 1)
1897  dup_IMP_node_state(*miif->IIF[ii], paced, sorted_save[csav++].v2, miif->ldata[ii]);
1898  // ipdate global Vm vector
1899  for (int k = 0; k < miif->N_Nodes[ii]; k++) vm[miif->NodeLists[ii][k]] = miif->ldata[ii][limpet::Vm][k];
1900  }
1901 
1902  read_lats->release_ptr(save_tm);
1903  miif->gdata[limpet::Vm]->release_ptr(vm);
1904  miif->releaseRealData();
1905 }
1906 
1907 
1909 {
1910  if (!rcv.pts.size())
1911  return;
1912 
1913  int rank = get_rank();
1914 
1915  if(!get_physics(elec_phys)) {
1916  log_msg(0,0,5, "There seems to be no EP is defined. Phie recovery requires active EP! Aborting!");
1917  return;
1918  }
1919 
1920  Electrics* elec = static_cast<Electrics*>(get_physics(elec_phys));
1921  sf_mat & Ki = *elec->parab_solver.rhs_parab;
1922 
1923  const sf_mesh & imesh = get_mesh(intra_elec_msh);
1924  const SF::vector<mesh_int_t> & alg_nod = imesh.pl.algebraic_nodes();
1925 
1926  SF_int start, end;
1927  vm.get_ownership_range(start, end);
1928 
1929  if(!rcv.Im) {
1930  SF::init_vector(&rcv.Im, &vm);
1931  SF::init_vector(&rcv.dphi, &vm);
1932  }
1933 
1934  SF_int r_start, r_end;
1935  rcv.phie_rec->get_ownership_range(r_start, r_end);
1936 
1937  SF_real *ph_r = rcv.phie_rec->ptr();
1938 
1939  // use minimum distance to ensure r>0
1940  // consistent with the line source approximation, the "cable radius"
1941  // is used as a lower limit for the source-field point distance
1942  float minDist = 2. / param_globals::imp_region[0].cellSurfVolRatio; // radius in um
1943 
1944  Ki.mult(vm, *rcv.Im);
1945  int numpts = rcv.pts.size() / 3;
1946  Point fpt, cpt;
1947 
1948  for (int j=0; j<numpts; j++) {
1949  fpt = rcv.pts.data() + j*3;
1950 
1951  *rcv.dphi = *rcv.Im;
1952  SF_real* dp = rcv.dphi->ptr();
1953 
1954  for (size_t i = 0; i<alg_nod.size(); i++)
1955  {
1956  mesh_int_t loc_nodal_idx = alg_nod[i];
1957  mesh_int_t loc_petsc_idx = local_nodal_to_local_petsc(imesh, rank, loc_nodal_idx);
1958  cpt = imesh.xyz.data()+loc_nodal_idx*3;
1959 
1960  double r = dist(fpt, cpt) + minDist;
1961  dp[loc_petsc_idx] /= r;
1962  }
1963 
1964  rcv.dphi->release_ptr(dp);
1965 
1966  SF_real phi = rcv.dphi->sum() / 4. / M_PI / rcv.gBath;
1967  if ( (j>=r_start) && (j<r_end) )
1968  ph_r[j-r_start] = phi;
1969  }
1970 
1971  rcv.phie_rec->release_ptr(ph_r);
1972 }
1973 
1975 {
1976  int err = 0, rank = get_rank();
1977 
1979  log_msg(0,0,5, "There seems to be no EP is defined. Phie recovery requires active EP! Aborting!");
1980  return 1;
1981  }
1982 
1983  sf_mesh & imesh = get_mesh(intra_elec_msh);
1984  Electrics* elec = static_cast<Electrics*>(get_physics(elec_phys));
1986 
1987  // we close the files of the default electrics if there are any open
1989 
1990  // register output
1991  set_dir(POSTPROC);
1992  igb_output_manager phie_rec_out;
1993  phie_rec_out.register_output(phie_rcv.phie_rec, phie_recv_msh, 1,
1994  param_globals::phie_recovery_file, "mV");
1995 
1996  // Buffer for Vm data
1997  sf_vec* vm = get_data(vm_vec); assert(vm);
1998 
1999  // set up igb header and point fd to start of Vm file
2000  set_dir(OUTPUT);
2001  IGBheader vm_igb;
2002  if(rank == 0) {
2003  FILE_SPEC file = f_open(param_globals::vofile, "r");
2004  if(file != NULL) {
2005  vm_igb.fileptr(file->fd);
2006  vm_igb.read();
2007 
2008  if(vm_igb.x() != vm->gsize()) {
2009  log_msg(0,4,0, "%s error: Vm dimension does not fit to %s file. Aborting recovery! \n",
2010  __func__, param_globals::vofile);
2011  err++;
2012  }
2013 
2014  delete file;
2015  }
2016  else err++;
2017  }
2018 
2019  err = get_global(err, MPI_MAX);
2020 
2021  if(err == 0) {
2022  FILE* fd = static_cast<FILE*>(vm_igb.fileptr());
2023 
2024  // number of data slices
2025  const int num_io = user_globals::tm_manager->timers[iotm_spacedt]->numIOs;
2026 
2027  // scatterers
2029  assert(petsc_to_canonical != NULL);
2030 
2031  // loop over vm slices and recover phie
2032  for(int i=0; i<num_io; i++) {
2033  log_msg(0,0,0, "Step %d / %d", i+1, num_io);
2034  size_t nread = vm->read_binary<float>(fd);
2035 
2036  if(nread != size_t(vm->gsize())) {
2037  log_msg(0,3,0, "%s warning: read incomplete data slice! Aborting!", __func__);
2038  err++;
2039  break;
2040  }
2041 
2042  // permute vm_buff
2043  bool forward = false;
2044  (*petsc_to_canonical)(*vm, forward);
2045 
2046  // do phie computation
2047  recover_phie_std(*vm, phie_rcv);
2048 
2049  phie_rec_out.write_data();
2050  }
2051 
2052  phie_rec_out.close_files_and_cleanup();
2053  }
2054  return err;
2055 }
2056 
2058 {
2059  if(!get_physics(elec_phys) ) {
2060  log_msg(0,0,5, "There seems to be no EP is defined. Phie recovery requires active EP! Aborting!");
2061  return;
2062  }
2063 
2064  int rank = get_rank(), size = get_size();
2065  Electrics* elec = static_cast<Electrics*>(get_physics(elec_phys));
2066 
2067  sf_mesh & imesh = get_mesh(intra_elec_msh);
2068  const std::string basename = param_globals::phie_rec_ptf;
2069  SF::vector<mesh_int_t> ptsidx;
2070 
2071  set_dir(INPUT);
2072  SF::read_points(basename, imesh.comm, data.pts, ptsidx);
2073  make_global(data.pts, imesh.comm); // we want all ranks to have all points
2074 
2075  // set up parallel layout of recovery points
2076  SF::vector<mesh_int_t> layout;
2077  layout_from_count(mesh_int_t(ptsidx.size()), layout, imesh.comm);
2078 
2079  // set up petsc_vector for recovered potentials
2080  SF::init_vector(&data.phie_rec, layout[size], layout[rank+1]-layout[rank], 1, sf_vec::algebraic);
2081 
2082  // get conductivty
2084  data.gBath = static_cast<elecMaterial*>(intra_regions[0].material)->BathVal[0];
2085 }
2086 
2088 {
2089  int rank = get_rank();
2090 
2091  assert(param_globals::bidomain == BIDOMAIN);
2092  double t1, t2;
2093  get_time(t1);
2094 
2095  // set up Extracellular tissue
2098  mtype[Electrics::extra_grid].regionIDs, true, "gregion_e");
2099 
2100  // set up a subset of the complete electrical mappings
2101  int dpn = 1;
2103 
2105  // set up Intracellular tissue
2108  mtype[Electrics::intra_grid].regionIDs, true, "gregion_i");
2109 
2112  }
2113 
2114  // set up stimuli
2115  init_stim_info();
2116  stimuli.resize(param_globals::num_stim);
2117 
2118  for(int i=0; i<param_globals::num_stim; i++) {
2119  // construct new stimulus
2120  stimulus & s = stimuli[i];
2121 
2123  s.translate(i);
2124 
2125  s.setup(i);
2126 
2127  if(s.phys.type == V_ex) {
2128  s.pulse.wform = constPulse;
2129  sample_wave_form(s.pulse, i);
2130  }
2131  }
2132 
2133  set_dir(OUTPUT);
2134 
2135  ellip_solver.init();
2137 
2138  if(param_globals::dump2MatLab) {
2139  std::string bsname = param_globals::dump_basename;
2140  std::string fn;
2141 
2142  set_dir(OUTPUT);
2143  fn = bsname + "_Kie.bin";
2144  ellip_solver.phie_mat->write(fn.c_str());
2145  }
2146 
2147  // the laplace solver executes only once, thus we need a singlestep timer
2148  timer_idx = user_globals::tm_manager->add_singlestep_timer(0.0, 0.0, "laplace trigger", nullptr);
2149 
2150  SF::vector<mesh_int_t>* restr_i = NULL;
2151  SF::vector<mesh_int_t>* restr_e = NULL;
2152 
2153  setup_dataout(param_globals::dataout_e, param_globals::dataout_e_vtx, extra_elec_msh,
2154  restr_e, param_globals::num_io_nodes > 0);
2155  if(param_globals::dataout_e)
2156  output_manager.register_output(ellip_solver.phie, extra_elec_msh, 1, param_globals::phiefile, "mV", restr_e);
2157 
2159  setup_dataout(param_globals::dataout_i, param_globals::dataout_i_vtx, intra_elec_msh,
2160  restr_i, param_globals::num_io_nodes > 0);
2161  if(param_globals::dataout_i)
2162  output_manager.register_output(ellip_solver.phie_i, intra_elec_msh, 1, param_globals::phieifile, "mV", restr_i);
2163  }
2164 
2165  this->initialize_time += timing(t2, t1);
2166 
2167  this->compute_step();
2168 }
2169 
2171 {}
2172 
2174 {
2175  // Laplace compute might be called multiple times, we want to run only once..
2176  if(!ellip_solver.lin_solver) return;
2177 
2178  double t0, t1, dur;
2179  log_msg(0,0,0, "Solving Laplace problem ..");
2180 
2181  get_time(t0);
2183  dur = timing(t1,t0);
2184 
2185  log_msg(0,0,0, "Done in %.5f seconds.", dur);
2186 
2188  this->compute_time += timing(t1, t0);
2189  set_dir(OUTPUT);
2192 
2193  // we clear the elliptic matrices and solver to save some memory when computing
2194  // the laplace solution on-the-fly
2195  delete ellip_solver.mass_e; ellip_solver.mass_e = NULL;
2196  delete ellip_solver.phie_mat; ellip_solver.phie_mat = NULL;
2198 }
2199 
2201 {}
2202 
2203 double Laplace::timer_val(const int timer_id)
2204 {
2205  int sidx = stimidx_from_timeridx(stimuli, timer_id);
2206  double val = 0.0;
2207 
2208  if(sidx != -1) stimuli[sidx].value(val);
2209  else val = std::nan("NaN");
2210  return val;
2211 }
2212 
2213 std::string Laplace::timer_unit(const int timer_id)
2214 {
2215  int sidx = stimidx_from_timeridx(stimuli, timer_id);
2216  std::string s_unit;
2217  if(sidx != -1) s_unit = stimuli[sidx].pulse.wave.f_unit;
2218  return s_unit;
2219 }
2220 
2222  sf_mat & mass_i,
2223  sf_mat & mass_e,
2224  limpet::MULTI_IF *miif,
2225  FILE_SPEC logger)
2226 {
2228 
2229  for(stimulus & s : stimuli) {
2230  if(is_current(s.phys.type) && s.phys.total_current) {
2231  // extracellular current injection
2232  if (s.phys.type == I_ex) {
2233  // compute affected volume in um^3
2234  SF_real vol = get_volume_from_nodes(mass_e, s.electrode.vertices);
2235 
2236  // s->strength holds the total current in uA, compute current density in uA/cm^3
2237  // Theoretically, we don't need to scale the volume to cm^3 here since we later
2238  // multiply with the mass matrix and we get um^3 * uA/um^3 = uA.
2239  // However, for I_ex there is an additional um^3 to cm^3 scaling in phys.scale,
2240  // since I_e is expected to be in uA/cm^3. Therefore, we need to compensate for that to arrive at uA later.
2241  float scale = 1.e12/vol;
2242 
2243  s.pulse.strength *= scale;
2244 
2245  log_msg(logger,0,ECHO,
2246  "%s [Stimulus %d]: current density scaled to %.4g uA/cm^3\n",
2247  s.name.c_str(), s.idx, s.pulse.strength);
2248  }
2249  else if (s.phys.type == I_tm) {
2250  // compute affected volume in um^3
2251  SF_real vol = get_volume_from_nodes(mass_i, s.electrode.vertices);
2252  const sf_mesh & imesh = get_mesh(intra_elec_msh);
2253  const SF::vector<mesh_int_t> & alg_nod = imesh.pl.algebraic_nodes();
2254 
2255  if(alg_idx_map.size() == 0) {
2256  mesh_int_t lidx = 0;
2257  for(mesh_int_t n : alg_nod) {
2258  alg_idx_map[n] = lidx;
2259  lidx++;
2260  }
2261  }
2262 
2263  SF_real surf = 0.0;
2264  for(mesh_int_t n : s.electrode.vertices) {
2265  if(alg_idx_map.count(n)) {
2266  mesh_int_t lidx = alg_idx_map[n];
2267  int r = miif->IIFmask[lidx];
2268  // surf = vol*beta [1/um], surf is in [um^2]
2269  surf = vol * miif->IIF[r]->cgeom().SVratio * param_globals::imp_region[r].volFrac;
2270  //convert to cm^2
2271  surf /= 1.e8;
2272  break;
2273  }
2274  }
2275  surf = get_global(surf, MPI_MAX, PETSC_COMM_WORLD);
2276 
2277  // scale surface density now to result in correct total current
2278  s.pulse.strength /= surf;
2279  log_msg(logger, 0, ECHO,
2280  "%s [Stimulus %d]: current density scaled to %.4g uA/cm^2\n",
2281  s.name.c_str(), s.idx, s.pulse.strength);
2282  }
2283  }
2284  }
2285 }
2286 
2287 
2288 
2289 } // namespace opencarp
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_real gBath
Bath conductivity.
Definition: electrics.h:245
const vector< T > & algebraic_layout() const
Getter function for the global algebraic node layout.
bool have_dbc_stims(const SF::vector< stimulus > &stimuli)
return wheter any stimuli require dirichlet boundary conditions
Definition: electrics.cc:861
#define DATAOUT_NONE
Definition: sim_utils.h:57
int ** NodeLists
local partitioned node lists for each IMP stored
Definition: MULTI_ION_IF.h:201
stim_electrode electrode
electrode geometry
Definition: stimulate.h:168
dbc_manager * dbc
dbcs require a dbc manager
Definition: electrics.h:63
void init_solver(SF::abstract_linear_solver< T, S > **sol)
Definition: SF_init.h:220
igb_output_manager output_manager
class handling the igb output
Definition: electrics.h:281
gvec_data gvec
datastruct holding global IMP state variable output
Definition: electrics.h:278
int * subregtags
FEM tags forming this region.
Definition: fem_types.h:97
char * regname
name of region
Definition: fem_types.h:94
void log_stats(double tm, bool cflg)
Definition: timers.cc:93
virtual void release_ptr(S *&p)=0
vector< T > & get_numbering(SF_nbr nbr_type)
Get the vector defining a certain numbering.
Definition: SF_container.h:452
cond_t g
rule to build conductivity tensor
Definition: fem_types.h:64
stim_pulse pulse
stimulus wave form
Definition: stimulate.h:165
char * dupstr(const char *old_str)
Definition: basics.cc:44
description of materal properties in a mesh
Definition: fem_types.h:110
physMaterial * material
material parameter description
Definition: fem_types.h:98
sf_vec * Irhs
weighted transmembrane currents
Definition: electrics.h:114
void read_indices(SF::vector< T > &idx, const std::string filename, const hashmap::unordered_map< mesh_int_t, mesh_int_t > &dd_map, MPI_Comm comm)
Read indices from a file.
Definition: fem_utils.h:120
void setup_phie_recovery_data(phie_recovery_data &data)
Definition: electrics.cc:2057
void local_petsc_to_nodal_mapping(const meshdata< T, S > &mesh, index_mapping< T > &petsc_to_nodal)
MPI_Comm comm
the parallel mesh is defined on a MPI world
Definition: SF_container.h:392
void recover_phie_std(sf_vec &vm, phie_recovery_data &rcv)
Definition: electrics.cc:1908
SF::vector< mesh_real_t > pts
The phie recovery locations.
Definition: electrics.h:241
void layout_from_count(const T count, vector< T > &layout, MPI_Comm comm)
Definition: SF_network.h:201
void compute_surface_mesh(const meshdata< T, S > &mesh, const SF_nbr numbering, const hashmap::unordered_set< T > &tags, meshdata< T, S > &surfmesh)
Compute the surface of a given mesh.
PETSc numbering of nodes.
Definition: SF_container.h:191
cond_t
description of electrical tissue properties
Definition: fem_types.h:42
#define PETSC_TO_CANONICAL
Permute algebraic data from PETSC to canonical ordering.
Definition: sf_interface.h:74
physMat_t material_type
ID of physics material.
Definition: fem_types.h:53
void solve(sf_mat &Ki, sf_vec &Vmv, sf_vec &tmp_i)
Definition: electrics.cc:1053
#define BIDOMAIN
Definition: sim_utils.h:144
overlapping_layout< T > pl
nodal parallel layout
Definition: SF_container.h:417
int calls
# calls for this interval, this is incremented externally
Definition: timers.h:70
IIF_Mask_t * IIFmask
region for each node
Definition: MULTI_ION_IF.h:214
virtual void set(const vector< T > &idx, const vector< S > &vals, const bool additive=false, const bool local=false)=0
elliptic_solver ellip_solver
Solver for the elliptic bidomain equation.
Definition: electrics.h:270
lin_solver_stats stats
Definition: electrics.h:60
double timer_val(const int timer_id)
figure out current value of a signal linked to a given timer
Definition: electrics.cc:756
void solve(sf_vec &phie_i)
Definition: electrics.cc:1278
sf_vec * Ivol
global Vm vector
Definition: electrics.h:107
void get_kappa(sf_vec &kappa, IMPregion *ir, limpet::MULTI_IF &miif, double k)
compute the vector
Definition: electrics.cc:814
void initialize()
Definition: ionics.cc:60
virtual T lsize() const =0
FILE_SPEC logger
The logger of the physic, each physic should have one.
Definition: physics_types.h:64
timer_manager * tm_manager
a manager for the various physics timers
Definition: main.cc:52
double ExVal[3]
extracellular conductivity eigenvalues
Definition: fem_types.h:62
std::string timer_unit(const int timer_id)
figure out units of a signal linked to a given timer
Definition: electrics.cc:2213
The nodal numbering of the reference mesh (the one stored on HD).
Definition: SF_container.h:189
void unique_resize(vector< T > &_P)
Definition: SF_sort.h:348
bool is_current(stim_t type)
uses current as stimulation
Definition: stimulate.cc:71
const char * name
The name of the physic, each physic should have one.
Definition: physics_types.h:62
sf_mat * lhs_parab
lhs matrix (CN) to solve parabolic
Definition: electrics.h:120
int all
determine all or first instants of activation
Definition: electrics.h:179
int add_eq_timer(double istart, double iend, int ntrig, double iintv, double idur, const char *iname, const char *poolname=nullptr)
Add a equidistant step timer to the array of timers.
Definition: timer_utils.cc:78
SF::vector< mesh_int_t > vertices
Definition: stimulate.h:152
size_t read_ascii(FILE *fd)
double time_step
global reference time step
Definition: timer_utils.h:78
void savequit()
save state and quit simulator
Definition: sim_utils.cc:1646
const char * get_mesh_type_name(mesh_t t)
get a char* to the name of a mesh type
Definition: sf_interface.cc:46
#define ECHO
Definition: basics.h:308
vector< S > xyz
node cooridnates
Definition: SF_container.h:415
void balance_electrode(elliptic_solver &ellip, SF::vector< stimulus > &stimuli, int balance_from, int balance_to)
Definition: electrics.cc:393
T * data()
Pointer to the vector&#39;s start.
Definition: SF_vector.h:91
vector< T > con
Definition: SF_container.h:400
sf_vec * tmp_i1
scratch vector for i-grid
Definition: electrics.h:112
mesh_t
The enum identifying the different meshes we might want to load.
Definition: sf_interface.h:58
T & push_back(T val)
Definition: SF_vector.h:283
bool trigger(int ID) const
Definition: timer_utils.h:166
SF_real get_volume_from_nodes(sf_mat &mass, SF::vector< mesh_int_t > &local_idx)
Definition: fem_utils.cc:202
double InVal[3]
intracellular conductivity eigenvalues
Definition: fem_types.h:61
int N_IIF
how many different IIF&#39;s
Definition: MULTI_ION_IF.h:211
void init_stim_info(void)
uses voltage for stimulation
Definition: stimulate.cc:49
void assemble_lumped_matrix(abstract_matrix< T, S > &mat, meshdata< mesh_int_t, mesh_real_t > &domain, matrix_integrator< mesh_int_t, mesh_real_t > &integrator)
physic_t
Identifier for the different physics we want to set up.
Definition: physics_types.h:51
void close_files_and_cleanup()
close file descriptors
Definition: sim_utils.cc:1527
PotType measurand
quantity being monitored
Definition: electrics.h:192
generic_timing_stats IO_stats
Definition: electrics.h:286
#define DATAOUT_SURF
Definition: sim_utils.h:58
void init_logger(const char *filename)
Definition: timers.cc:77
virtual S sum() const =0
void init_vector(SF::abstract_vector< T, S > **vec)
Definition: SF_init.h:99
void rebuild_mass(FILE_SPEC logger)
Definition: electrics.cc:1016
void region_mask(mesh_t meshspec, SF::vector< RegionSpecs > &regspec, SF::vector< int > &regionIDs, bool mask_elem, const char *reglist)
classify elements/points as belonging to a region
Definition: ionics.cc:362
std::vector< IonIfBase * > IIF
array of IIF&#39;s
Definition: MULTI_ION_IF.h:202
#define ELEM_PETSC_TO_CANONICAL
Permute algebraic element data from PETSC to canonical ordering.
Definition: sf_interface.h:76
int mode
toggle mode from standard to reverse
Definition: electrics.h:178
bool value(double &v) const
Get the current value if the stimulus is active.
Definition: stimulate.cc:430
char * get_file_dir(const char *file)
Definition: sim_utils.cc:1275
bool is_init(const abstract_vector< T, S > *v)
sf_mat * phie_mat
lhs matrix to solve elliptic
Definition: electrics.h:54
const T * end() const
Pointer to the vector&#39;s end.
Definition: SF_vector.h:128
waveform_t wform
wave form of stimulus
Definition: stimulate.h:94
#define DUMP_IC
Definition: electrics.h:39
parabolic_solver parab_solver
Solver for the parabolic bidomain equation.
Definition: electrics.h:272
void compute_IIF(limpet::IonIfBase &pIF, limpet::GlobalData_t **impdata, int n)
Definition: ionics.cc:464
Tissue level electrics, main Electrics physics class.
SF::scattering * get_scattering(const int from, const int to, const SF::SF_nbr nbr, const int dpn)
Get a scattering from the global scatter registry.
virtual S * ptr()=0
#define DUMP_IACT
Definition: electrics.h:41
bool using_legacy_stimuli
flag storing whether legacy stimuli are used
Definition: main.cc:58
void init(sf_vec &vm, sf_vec &phie, int offset, enum physic_t=elec_phys)
initializes all datastructs after electric solver setup
Definition: electrics.cc:1509
virtual void mult(const abstract_vector< T, S > &x, abstract_vector< T, S > &b) const =0
event detection data structures
Definition: electrics.h:175
void dump_state(char *, float, opencarp::mesh_t gid, bool, unsigned int)
sf_vec * Vmv
global Vm vector
Definition: electrics.h:104
double tot_time
total time, this is incremented externally
Definition: timers.h:72
int mesh_int_t
Definition: SF_container.h:46
virtual void write(const char *filename) const =0
void fileptr(gzFile f)
Definition: IGBheader.cc:336
Submesh nodal numbering: The globally ascending sorted reference indices are reindexed.
Definition: SF_container.h:190
void assemble_sv_gvec(gvec_data &gvecs, limpet::MULTI_IF *miif)
Definition: ionics.cc:637
double time
current time
Definition: timer_utils.h:76
int max_nodal_edgecount(const meshdata< T, S > &mesh)
Compute the maximum number of node-to-node edges for a mesh.
Definition: SF_container.h:596
region based variations of arbitrary material parameters
Definition: fem_types.h:93
GlobalData_t *** ldata
data local to each IMP
Definition: MULTI_ION_IF.h:205
void forward(abstract_vector< T, S > &in, abstract_vector< T, S > &out, bool add=false)
Forward scattering.
int check_acts(double tm)
check activations at sim time tm
Definition: electrics.cc:1629
double BathVal[3]
bath conductivity eigenvalues
Definition: fem_types.h:63
std::string name
label stimulus
Definition: stimulate.h:164
ActMethod method
method to check whether activation occured
Definition: electrics.h:176
int timer_idx
the timer index received from the timer manager
Definition: physics_types.h:66
void write_data()
write registered data to disk
Definition: sim_utils.cc:1480
long d_time
current time instance index
Definition: timer_utils.h:77
sf_sol * lin_solver
petsc or ginkgo lin_solver
Definition: electrics.h:57
sf_mesh & get_mesh(const mesh_t gt)
Get a mesh by specifying the gridID.
Definition: sf_interface.cc:33
void register_output_sync(sf_vec *inp_data, const mesh_t inp_meshid, const int dpn, const char *name, const char *units, const SF::vector< mesh_int_t > *idx=NULL, bool elem_data=false)
Definition: sim_utils.cc:1316
#define DUMP_IVOL
Definition: electrics.h:40
float threshold
threshold for detection of activation
Definition: electrics.h:177
SF::scattering * register_permutation(const int mesh_id, const int perm_id, const int dpn)
Register a permutation between two orderings for a mesh.
size_t read_binary(FILE *fd)
int idx
index in global input stimulus array
Definition: stimulate.h:163
const char * get_tsav_ext(double time)
Definition: electrics.cc:870
void init_matrix(SF::abstract_matrix< T, S > **mat)
Definition: SF_init.h:199
manager for dirichlet boundary conditions
Definition: stimulate.h:192
#define ALG_TO_NODAL
Scatter algebraic to nodal.
Definition: sf_interface.h:72
size_t size() const
Definition: hashmap.hpp:687
void constant_total_stimulus_current(SF::vector< stimulus > &stimuli, sf_mat &mass_i, sf_mat &mass_e, limpet::MULTI_IF *miif, FILE_SPEC logger)
Scales stimulus current to maintain constant total current across affected regions.
Definition: electrics.cc:2221
double timer_val(const int timer_id)
figure out current value of a signal linked to a given timer
Definition: electrics.cc:2203
void destroy()
Currently we only need to close the file logger.
Definition: electrics.cc:378
sf_vec * phie
phi_e
Definition: electrics.h:48
#define PSEUDO_BIDM
Definition: sim_utils.h:145
void set_elec_tissue_properties(MaterialType *mtype, Electrics::grid_t g, FILE_SPEC logger)
Fill the RegionSpec of an electrics grid with the associated inputs from the param parameters...
Definition: electrics.cc:100
void set_cond_type(MaterialType &m, cond_t type)
Definition: electrics.cc:838
void make_global(const vector< T > &vec, vector< T > &out, MPI_Comm comm)
make a parallel vector global
Definition: SF_network.h:225
SF::vector< double > el_scale
optionally provided per-element params scale
Definition: fem_types.h:116
void get_time(double &tm)
Definition: basics.h:436
Basic_physic * get_physics(physic_t p, bool error_if_missing)
Convinience function to get a physics.
Definition: sim_utils.cc:864
#define EXP_POSTPROCESS
Definition: sim_utils.h:161
int write_trace()
write traces to file
Definition: signals.h:701
bool dbc_update()
check if dbcs have updated
Definition: stimulate.cc:620
void initialize()
Initialize the Electrics.
Definition: electrics.cc:36
int timer_id
timer for stimulus
Definition: stimulate.h:121
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.
T get_global(T in, MPI_Op OP, MPI_Comm comm=PETSC_COMM_WORLD)
Do a global reduction on a variable.
Definition: basics.h:233
int * N_Nodes
#nodes for each IMP
Definition: MULTI_ION_IF.h:200
void destroy()
Definition: ionics.cc:52
size_t root_count_ascii_lines(std::string file, MPI_Comm comm)
count the lines in a ascii file
void print_act_log(FILE_SPEC logger, const SF::vector< Activation > &acts, int idx)
Definition: electrics.cc:1484
int check_quiescence(double tm, double dt)
check for quiescence
Definition: electrics.cc:1684
V timing(V &t2, const V &t1)
Definition: basics.h:448
void compute_restr_idx(sf_mesh &mesh, SF::vector< mesh_int_t > &inp_idx, SF::vector< mesh_int_t > &idx)
Definition: electrics.cc:549
sig::time_trace wave
wave form of stimulus pulse
Definition: stimulate.h:96
void setup(int idx)
Setup from a param stimulus index.
Definition: stimulate.cc:161
SF::vector< stimulus > stimuli
the electrical stimuli
Definition: electrics.h:263
sf_vec * dphi
Auxiliary vectors.
Definition: electrics.h:244
void rebuild_matrices(MaterialType *mtype, limpet::MULTI_IF &miif, FILE_SPEC logger)
Definition: electrics.cc:1175
MaterialType mtype[2]
the material types of intra_grid and extra_grid grids.
Definition: electrics.h:261
int regID
region ID
Definition: fem_types.h:95
void update_cwd()
save the current working directory to curdir so that we can switch back to it if needed.
Definition: sim_utils.cc:478
void assemble_matrix(abstract_matrix< T, S > &mat, meshdata< mesh_int_t, mesh_real_t > &domain, matrix_integrator< mesh_int_t, mesh_real_t > &integrator)
Generalized matrix assembly.
File descriptor struct.
Definition: basics.h:133
opencarp::sf_vec * gdata[NUM_IMP_DATA_TYPES]
data used by all IMPs
Definition: MULTI_ION_IF.h:216
bool total_current
whether we apply total current scaling
Definition: stimulate.h:140
int set_dir(IO_t dest)
Definition: sim_utils.cc:483
void dup_IMP_node_state(IonIfBase &IF, int from, int to, GlobalData_t **localdata)
hm_int count(const K &key) const
Check if key exists.
Definition: hashmap.hpp:579
sf_mat * mass_e
mass matrix for RHS elliptic calc
Definition: electrics.h:53
int read(bool quiet=false)
Definition: IGBheader.cc:761
sf_vec * phie_rec
The phie recovery output vector buffer.
Definition: electrics.h:242
size_t size() const
The current size of the vector.
Definition: SF_vector.h:104
void log_stats(double tm, bool cflg)
Definition: timers.cc:27
sf_vec * phiesrc
I_e.
Definition: electrics.h:50
void rebuild_stiffness(MaterialType *mtype, SF::vector< stimulus > &stimuli, FILE_SPEC logger)
Definition: electrics.cc:956
int nsubregs
#subregions forming this region
Definition: fem_types.h:96
Electrical stimulation functions.
void log_msg(FILE_SPEC out, int level, unsigned char flag, const char *fmt,...)
Definition: basics.cc:72
#define NONL
Definition: basics.h:312
void init_sv_gvec(gvec_data &GVs, limpet::MULTI_IF *miif, sf_vec &tmpl, igb_output_manager &output_manager)
Definition: ionics.cc:566
#define MONODOMAIN
Definition: sim_utils.h:143
bool mesh_is_registered(const mesh_t gt)
check wheter a SF mesh is set
Definition: sf_interface.cc:59
sf_vec * Iact
global Vm vector
Definition: electrics.h:108
stim_t type
type of stimulus
Definition: stimulate.h:136
#define M_PI
Definition: ION_IF.h:51
void sample_wave_form(stim_pulse &sp, int idx)
sample a signal given in analytic form
Definition: stimulate.cc:330
lin_solver_stats stats
Definition: electrics.h:129
#define DATAOUT_VTX
Definition: sim_utils.h:60
virtual void get_ownership_range(T &start, T &stop) const =0
void output_initial_activations()
output one nodal vector of initial activation time
Definition: electrics.cc:1793
void dump_trace(MULTI_IF *MIIF, limpet::Real time)
#define DATAOUT_VOL
Definition: sim_utils.h:59
grid_t
An electrics grid identifier to distinguish between intra and extra grids.
Definition: electrics.h:257
int numNode
local number of nodes
Definition: MULTI_ION_IF.h:210
const T * begin() const
Pointer to the vector&#39;s start.
Definition: SF_vector.h:116
void f_close(FILE_SPEC &f)
Close a FILE_SPEC.
Definition: basics.cc:162
std::vector< base_timer * > timers
vector containing individual timers
Definition: timer_utils.h:84
void compute_step()
Definition: ionics.cc:35
limpet::MULTI_IF * miif
Definition: ionics.h:66
const meshdata< mesh_int_t, mesh_real_t > * mesh
the connected mesh
std::string timer_unit(const int timer_id)
figure out units of a signal linked to a given timer
Definition: electrics.cc:772
sf_mat * mass_i
lumped for parabolic problem
Definition: electrics.h:118
sf_vec * phie_i
phi_e on intracellular grid
Definition: electrics.h:49
sf_vec * IIon
ionic currents
Definition: electrics.h:103
V dist(const vec3< V > &p1, const vec3< V > &p2)
Definition: vect.h:114
void register_output(sf_vec *inp_data, const mesh_t inp_meshid, const int dpn, const char *name, const char *units, const SF::vector< mesh_int_t > *idx=NULL, bool elem_data=false)
Register a data vector for output.
Definition: sim_utils.cc:1447
LAT_detector lat
the activation time detector
Definition: electrics.h:275
void assign(InputIterator s, InputIterator e)
Assign a memory range.
Definition: SF_vector.h:161
const vector< T > & algebraic_nodes() const
Getter function for the local indices forming the local algebraic node set.
int output_all_activations(FILE_SPEC fp, int *ibuf, double *act_tbuf, int nlacts)
Definition: electrics.cc:1583
std::int32_t SF_int
Use the general std::int32_t as int type.
Definition: SF_globals.h:37
void rebuild_matrices(MaterialType *mtype, SF::vector< stimulus > &stimuli, FILE_SPEC logger)
Definition: electrics.cc:945
double strength
strength of stimulus
Definition: stimulate.h:92
size_t g_numelem
global number of elements
Definition: SF_container.h:386
stim_protocol ptcl
applied stimulation protocol used
Definition: stimulate.h:166
double SF_real
Use the general double as real type.
Definition: SF_globals.h:38
void binary_sort(vector< T > &_V)
Definition: SF_sort.h:284
void translate(int id)
convert legacy definitions to new format
Definition: stimulate.cc:100
stim_physics phys
physics of stimulus
Definition: stimulate.h:167
void apply_stim_to_vector(const stimulus &s, sf_vec &vec, bool add)
Definition: electrics.cc:457
sf_vec * Ic
global Vm vector
Definition: electrics.h:106
virtual void get(const vector< T > &idx, S *out)=0
SF::vector< RegionSpecs > regions
array with region params
Definition: fem_types.h:115
void setup_dataout(const int dataout, std::string dataout_vtx, mesh_t grid, SF::vector< mesh_int_t > *&restr, bool async)
Definition: electrics.cc:617
void resize(size_t n)
Resize a vector.
Definition: SF_vector.h:209
sf_vec * get_data(datavec_t d)
Retrieve a petsc data vector from the data registry.
Definition: sim_utils.cc:880
int get_size(MPI_Comm comm=PETSC_COMM_WORLD)
Definition: basics.h:290
SF::scattering * register_scattering(const int from, const int to, const SF::SF_nbr nbr, const int dpn)
Register a scattering between to grids, or between algebraic and nodal representation of data on the ...
Definition: sf_interface.cc:65
int stimidx_from_timeridx(const SF::vector< stimulus > &stimuli, const int timer_id)
determine link between timer and stimulus
Definition: electrics.cc:786
sf_mat * rhs_parab
rhs matrix to solve parabolic
Definition: electrics.h:119
bool is_dbc(stim_t type)
whether stimulus is a dirichlet type. implies boundary conditions on matrix
Definition: stimulate.cc:76
void backward(abstract_vector< T, S > &in, abstract_vector< T, S > &out, bool add=false)
Backward scattering.
phie_recovery_data phie_rcv
struct holding helper data for phie recovery
Definition: electrics.h:284
centralize time managment and output triggering
Definition: timer_utils.h:73
int get_rank(MPI_Comm comm=PETSC_COMM_WORLD)
Definition: basics.h:276
void compute_restr_idx_async(sf_mesh &mesh, SF::vector< mesh_int_t > &inp_idx, SF::vector< mesh_int_t > &idx)
Definition: electrics.cc:582
int postproc_recover_phie()
Definition: electrics.cc:1974
T local_nodal_to_local_petsc(const meshdata< T, S > &mesh, int rank, T local_nodal)
virtual T gsize() const =0
LAT_detector()
constructor, sets up basic datastructs from global_params
Definition: electrics.cc:1381
#define UM2_to_CM2
convert um^2 to cm^2
Definition: physics_types.h:35
long d_end
final index in multiples of dt
Definition: timer_utils.h:82
FILE_SPEC f_open(const char *fname, const char *mode)
Open a FILE_SPEC.
Definition: basics.cc:135
void read_points(const std::string basename, const MPI_Comm comm, vector< S > &pts, vector< T > &ptsidx)
Read the points and insert them into a list of meshes.
Definition: SF_mesh_io.h:844
Container for a PETSc VecScatter.
int add_singlestep_timer(double tg, double idur, const char *iname, const char *poolname=nullptr)
Definition: timer_utils.h:143