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