openCARP
Doxygen code documentation for the open cardiac electrophysiology simulator openCARP
emi.cc
Go to the documentation of this file.
1 //
2 // Copyright (C) 2020 openCARP project
3 //
4 // This program is licensed under the openCARP Academic Public License (APL)
5 // v1.0: You can use and redistribute it and/or modify it in non-commercial
6 // academic environments under the terms of APL as published by the openCARP
7 // project v1.0, or (at your option) any later version. Commercial use requires
8 // a commercial license (info@opencarp.org).
9 //
10 // This program is distributed without any warranty; see the openCARP APL for
11 // more details.
12 //
13 // You should have received a copy of the openCARP APL along with this program
14 // and can find it online: http://www.opencarp.org/license
15 // ----------------------------------------------------------------------------
16 
24 #if WITH_EMI_MODEL
25 
26 #include "petsc_utils.h"
27 #include "physics_types.h"
28 #include "timers.h"
29 #include "stimulate.h"
30 #include "electric_integrators.h"
31 #include "SF_init.h" // for SF::init_xxx()
32 #include "emi.h"
33 #include <array>
34 
35 #ifdef WITH_CALIPER
36 #include "caliper/cali.h"
37 #else
38 #include "caliper_hooks.h"
39 #endif
40 
41 namespace opencarp {
42 
43 void log_mesh_local_element_ranges(const sf_mesh& emi_mesh,
44  const sf_mesh& emi_surfmesh_w_counter_face,
45  const sf_mesh& emi_surfmesh_unique_face)
46 {
47  int rank = 0;
48  int comm_size = 0;
49  MPI_Comm_rank(emi_surfmesh_w_counter_face.comm, &rank);
50  MPI_Comm_size(emi_surfmesh_w_counter_face.comm, &comm_size);
51 
52  const unsigned long long local_emi_elems =
53  static_cast<unsigned long long>(emi_mesh.l_numelem);
54  const unsigned long long local_both_face_elems =
55  static_cast<unsigned long long>(emi_surfmesh_w_counter_face.l_numelem);
56  const unsigned long long local_unique_face_elems =
57  static_cast<unsigned long long>(emi_surfmesh_unique_face.l_numelem);
58 
59  std::vector<unsigned long long> all_emi_elems;
60  std::vector<unsigned long long> all_both_face_elems;
61  std::vector<unsigned long long> all_unique_face_elems;
62  if (rank == 0) {
63  all_emi_elems.resize(comm_size, 0);
64  all_both_face_elems.resize(comm_size, 0);
65  all_unique_face_elems.resize(comm_size, 0);
66  }
67 
68  MPI_Gather(&local_emi_elems, 1, MPI_UNSIGNED_LONG_LONG,
69  rank == 0 ? all_emi_elems.data() : nullptr, 1, MPI_UNSIGNED_LONG_LONG,
70  0, emi_surfmesh_w_counter_face.comm);
71  MPI_Gather(&local_both_face_elems, 1, MPI_UNSIGNED_LONG_LONG,
72  rank == 0 ? all_both_face_elems.data() : nullptr, 1, MPI_UNSIGNED_LONG_LONG,
73  0, emi_surfmesh_w_counter_face.comm);
74  MPI_Gather(&local_unique_face_elems, 1, MPI_UNSIGNED_LONG_LONG,
75  rank == 0 ? all_unique_face_elems.data() : nullptr, 1, MPI_UNSIGNED_LONG_LONG,
76  0, emi_surfmesh_w_counter_face.comm);
77 
78  if (rank != 0) return;
79 
80  const auto print_min_max = [](const char* label, const std::vector<unsigned long long>& counts) {
81  if (counts.empty()) return;
82 
83  unsigned long long min_val = counts[0];
84  unsigned long long max_val = counts[0];
85  int min_rank = 0;
86  int max_rank = 0;
87 
88  for (int r = 1; r < static_cast<int>(counts.size()); ++r) {
89  if (counts[r] < min_val) {
90  min_val = counts[r];
91  min_rank = r;
92  }
93  if (counts[r] > max_val) {
94  max_val = counts[r];
95  max_rank = r;
96  }
97  }
98 
99  log_msg(NULL, 0, 0, " %s: \n\t\t min=%llu on rank=%d, \n\t\t max=%llu on rank=%d\n",
100  label, min_val, min_rank, max_val, max_rank);
101  };
102  log_msg(NULL, 0, 0, "\n**********************************");
103  log_msg(NULL, 0, 0, "min/max number of local-element ranges:");
104  print_min_max("emi_mesh", all_emi_elems);
105  print_min_max("emi_surfmesh_w_counter_face", all_both_face_elems);
106  print_min_max("emi_surfmesh_unique_face", all_unique_face_elems);
107  log_msg(NULL, 0, 0, "**********************************");
108 }
109 
110 void set_elec_tissue_properties_emi_volume(MaterialType* mtype, hashmap::unordered_set<int> &extra_tags, hashmap::unordered_set<int> &intra_tags, FILE_SPEC logger)
111 {
113  MaterialType *m = mtype;
114 
115  // initialize random conductivity fluctuation structure with PrM values
116  m->regions.resize(param_globals::num_gregions);
117 
118  const char* grid_name = "emi_grid_domain";
119  log_msg(logger, 0, 0, "Setting up %s tissue poperties for %d regions ..", grid_name,
120  param_globals::num_gregions);
121 
122  char buf[64];
123  RegionSpecs* reg = m->regions.data();
124 
125  // default tags for extra and intra domains
126  hashmap::unordered_set<int> extra_tags_default = extra_tags;
127  hashmap::unordered_set<int> intra_tags_default = intra_tags;
128 
129  for (size_t i=0; i<m->regions.size(); i++)
130  {
131  for (int j=0;j<param_globals::gregion[i].num_IDs;j++)
132  {
133  int tag = param_globals::gregion[i].ID[j];
134 
135  // removed tags explicitly defined by user in param_globals::gregion[i>1]
136  if(extra_tags_default.find(tag) != extra_tags_default.end())
137  extra_tags_default.erase(tag);
138 
139  if(intra_tags_default.find(tag) != intra_tags_default.end())
140  intra_tags_default.erase(tag);
141  }
142  }
143 
144  for (size_t i=0; i<m->regions.size(); i++, reg++)
145  {
146  if(!strcmp(param_globals::gregion[i].name, "")) {
147  snprintf(buf, sizeof buf, ", gregion_%d", int(i));
148  param_globals::gregion[i].name = dupstr(buf);
149  }
150 
151  // copy metadata into RegionSpecs
152  reg->regname = strdup(param_globals::gregion[i].name);
153  reg->regID = i;
154 
155  if(i==0) // default Extracellular region
156  reg->nsubregs = extra_tags_default.size();
157  if(i==1) // default intracellular region
158  reg->nsubregs = intra_tags_default.size();
159  if(i>1) // optional: rest of other param_globals::gregion[i>1] defined by user
160  reg->nsubregs = param_globals::gregion[i].num_IDs;
161 
162  if(!reg->nsubregs)
163  reg->subregtags = NULL;
164  else
165  {
166  reg->subregtags = new int[reg->nsubregs];
167 
168  if(i==0){
169  int j = 0;
170  for (int tag : extra_tags_default) {
171  reg->subregtags[j] = tag;
172  j++;
173  }
174  }
175  else if(i==1){
176  int j = 0;
177  for (int tag : intra_tags_default) {
178  reg->subregtags[j] = tag;
179  j++;
180  }
181  }
182  else{
183  for (int j=0;j<reg->nsubregs;j++)
184  reg->subregtags[j] = param_globals::gregion[i].ID[j]; // explicit tags defined by user
185  }
186  }
187 
188  // describe material in given region
189  elecMaterial *emat = new elecMaterial();
190  emat->material_type = ElecMat;
191 
192  // Isotropic conductivity is considered in EMI model.
193  emat->InVal[0] = param_globals::gregion[i].g_bath;
194  emat->InVal[1] = param_globals::gregion[i].g_bath;
195  emat->InVal[2] = param_globals::gregion[i].g_bath;
196 
197  emat->ExVal[0] = param_globals::gregion[i].g_bath;
198  emat->ExVal[1] = param_globals::gregion[i].g_bath;
199  emat->ExVal[2] = param_globals::gregion[i].g_bath;
200 
201  emat->BathVal[0] = param_globals::gregion[i].g_bath;
202  emat->BathVal[1] = param_globals::gregion[i].g_bath;
203  emat->BathVal[2] = param_globals::gregion[i].g_bath;
204 
205  // convert units from S/m -> mS/um
206  for (int j=0; j<3; j++) {
207  emat->InVal[j] *= 1e-3 * param_globals::gregion[i].g_mult;
208  emat->ExVal[j] *= 1e-3 * param_globals::gregion[i].g_mult;
209  emat->BathVal[j] *= 1e-3 * param_globals::gregion[i].g_mult;
210  }
211  reg->material = emat;
212  }
213 
214  if(strlen(param_globals::gi_scale_vec))
215  {
216  mesh_t mt = emi_msh;
217  sf_mesh & mesh = get_mesh(mt);
218  const char* file = param_globals::gi_scale_vec;
219 
220  size_t num_file_entries = SF::root_count_ascii_lines(file, mesh.comm);
221 
222  if(num_file_entries != mesh.g_numelem)
223  log_msg(0,4,0, "%s warning: number of %s conductivity scaling entries does not match number of elements!",
224  __func__, get_mesh_type_name(mt));
225 
226  // set up parallel element vector and read data
227  sf_vec *escale;
228  SF::init_vector(&escale, get_mesh(mt), 1, sf_vec::elemwise);
229  escale->read_ascii(param_globals::gi_scale_vec);
230 
231  if(get_size() > 1) {
232  // set up element vector permutation and permute
233  if(get_permutation(mt, ELEM_PETSC_TO_CANONICAL, 1) == NULL) {
235  }
237  sc(*escale, false);
238  }
239 
240  // copy data into SF::vector
241  SF_real* p = escale->ptr();
242  m->el_scale.assign(p, p + escale->lsize());
243  escale->release_ptr(p);
244  }
245 }
246 
247 void parabolic_solver_emi::init()
248 {
250  double t0, t1, dur;
251  get_time(t0);
252  stats.init_logger("par_stats.dat");
253 
254  // Create/initialise linear solver object: PETSc gets constructed with default settings
255  SF::init_solver(&lin_solver);
256 
257  // EMI mesh: After decoupling the interfaces, we obtain a volumetric mesh with new dofs for all vertices.
258  sf_mesh & emi_mesh = get_mesh(emi_msh);
259  // Surface mesh called emi_surface_counter_msh:
260  // This mesh contains only the interface faces, where the indices correspond
261  // to the vertices of the original input mesh.
262  // Each face has a corresponding counter-face, and they are identified by different tag numbers in each rank.
263  sf_mesh & emi_surfmesh_w_counter_face = get_mesh(emi_surface_counter_msh);
264  sf_mesh & emi_surfmesh_one_side = get_mesh(emi_surface_msh);
265  sf_mesh & emi_surfmesh_unique_face = get_mesh(emi_surface_unique_face_msh);
266 
267  int max_row_entries_emi = max_nodal_edgecount(emi_mesh);
268  int max_row_entries_emi_surfmesh = max_nodal_edgecount(emi_surfmesh_w_counter_face);
269 
270  int rank;
271  MPI_Comm_rank(emi_surfmesh_w_counter_face.comm, &rank);
272 
273  sf_vec::ltype alg_type = sf_vec::algebraic;
274  sf_vec::ltype alg_surface_type = sf_vec::elemwise;
275 
276  int dpn = 1;
277 
278  //-----------------------------------------------------------------
279  // setup vectors
280  //-----------------------------------------------------------------
281  SF::init_vector(&ui, emi_mesh, dpn, alg_type);
282  SF::init_vector(&dui, emi_mesh, dpn, alg_type);
283  SF::init_vector(&ui_pre, emi_mesh, dpn, alg_type);
284  SF::init_vector(&Irhs, emi_mesh, dpn, alg_type);
285  SF::init_vector(&Iij_stim, emi_mesh, dpn, alg_type);
286  SF::init_vector(&Iij_temp, emi_mesh, dpn, alg_type);
287  SF::init_vector(&vb_both_face, emi_surfmesh_w_counter_face, dpn, alg_surface_type);
288  SF::init_vector(&vb_unique_face, emi_surfmesh_unique_face, dpn, alg_surface_type);
289  SF::init_vector(&Ib_both_face, emi_surfmesh_w_counter_face, dpn, alg_surface_type);
290  SF::init_vector(&Ib_unique_face, emi_surfmesh_unique_face, dpn, alg_surface_type);
291 
292  //-----------------------------------------------------------------
293  // initialize matrices (LHS, K, M_{emi mesh}, M_{surface mesh})
294  //-----------------------------------------------------------------
295  SF::init_matrix(&lhs_emi);
296  SF::init_matrix(&stiffness_emi);
297  SF::init_matrix(&mass_emi);
298  SF::init_matrix(&mass_surf_emi);
299 
300  lhs_emi->init(emi_mesh, dpn, dpn, max_row_entries_emi*3);
301  stiffness_emi->init(emi_mesh, dpn, dpn, max_row_entries_emi*2);
302  mass_emi->init(emi_mesh, dpn, dpn, max_row_entries_emi*3);
303  mass_surf_emi->init(emi_mesh, dpn, dpn, max_row_entries_emi*3);
304  //---------------------------------------------------------------------
305  // initialize operator matrices B, Bs, Vm_myocyte_emi
306  //---------------------------------------------------------------------
307  mesh_int_t M = emi_surfmesh_w_counter_face.g_numelem;
308  mesh_int_t N = emi_mesh.g_numpts;
309  mesh_int_t m = emi_surfmesh_w_counter_face.l_numelem;
310  mesh_int_t m_one_side = emi_surfmesh_one_side.l_numelem;
311  mesh_int_t M_one_side = emi_surfmesh_one_side.g_numelem;
312  mesh_int_t m_unique_face = emi_surfmesh_unique_face.l_numelem;
313  mesh_int_t M_unique_face = emi_surfmesh_unique_face.g_numelem;
314  mesh_int_t n = ui->lsize();
315 
316  log_msg(NULL, 0, 0, "\n**********************************");
317  log_msg(NULL, 0, 0, "#elements of emi surfmesh unique face: %zu", emi_surfmesh_unique_face.g_numelem);
318  log_msg(NULL, 0, 0, "#elements of emi surfmesh one side: %zu", emi_surfmesh_one_side.g_numelem);
319  log_msg(NULL, 0, 0, "#elements of emi surfmesh: %zu", emi_surfmesh_w_counter_face.g_numelem);
320  log_msg(NULL, 0, 0, "#elements of emi mesh: %zu", emi_mesh.g_numelem);
321  log_msg(NULL, 0, 0, "#dofs for emi_mesh: %zu", emi_mesh.g_numpts);
322  log_msg(NULL, 0, 0, "#max_row_entries_emi: %zu", max_row_entries_emi);
323  log_msg(NULL, 0, 0, "**********************************\n");
324  log_mesh_local_element_ranges(emi_mesh, emi_surfmesh_w_counter_face, emi_surfmesh_unique_face);
325 
326  SF::vector<long int> layout;
327  SF::layout_from_count<long int>(emi_surfmesh_w_counter_face.l_numelem, layout, emi_surfmesh_w_counter_face.comm);
328  mesh_int_t m_l = layout[rank];
329  mesh_int_t n_l = emi_mesh.pl.algebraic_layout()[rank];
330  const SF::vector<mesh_int_t> & alg_nod_surface = emi_surfmesh_w_counter_face.pl.algebraic_nodes();
331 
332  SF::vector<long int> layout_one_side;
333  SF::layout_from_count<long int>(emi_surfmesh_one_side.l_numelem, layout_one_side, emi_surfmesh_one_side.comm);
334  mesh_int_t m_one_side_l = layout_one_side[rank];
335 
336  SF::vector<long int> layout_unique_face;
337  SF::layout_from_count<long int>(emi_surfmesh_unique_face.l_numelem, layout_unique_face, emi_surfmesh_unique_face.comm);
338  mesh_int_t m_unique_face_l = layout_unique_face[rank];
339 
340  // To support a higher number of possible intersections between faces (e.g., multiple contacts on the membrane),
341  // such as when one myocyte is surrounded by more than one extracellular region,
342  // we slightly overallocate the maximum number of row entries (max_row_entries)
343  // for the matrices B, Bi, BsM, and Vm_myocyte_emi to ensure sufficient storage for all coupling cases.
344  SF::init_matrix(&B);
345  B->init(M, N, m, n, m_l, max_row_entries_emi*2);
346  B->zero();
347  SF::init_matrix(&Bi);
348  Bi->init(M, N, m, n, m_l, max_row_entries_emi*2);
349  Bi->zero();
350  SF::init_matrix(&BsM);
351  BsM->init(N, M, n, m, n_l, max_row_entries_emi*2);
352  BsM->zero();
353  SF::init_matrix(&Vm_myocyte_emi);
354  Vm_myocyte_emi->init(N, N, n, n, n_l, max_row_entries_emi*2);
355  Vm_myocyte_emi->zero();
356 
357  // Build the direct unique-face <-> both-face transfer operators
358  SF::construct_direct_unique_both_operators(operator_unique_to_both_faces,
359  operator_both_to_unique_face,
360  map_elem_uniqueFace_to_elem_bothface,
361  map_elem_uniqueFace_to_elem_oneface,
362  vec_both_to_one_face,
363  emi_surfmesh_w_counter_face,
364  emi_surfmesh_unique_face,
365  max_row_entries_emi,
366  dpn,
367  alg_surface_type);
368 
369  // initialize Interpolation operator by B and Scatter operator by Bs
370  SF::assemble_restrict_operator(*B, *Bi, *BsM, elemTag_surface_w_counter_mesh, map_vertex_tag_to_dof_petsc, line_face, tri_face, quad_face, emi_surfmesh_w_counter_face, emi_mesh, UM2_to_CM2);
371 
372  // DEBUG: Check mesh sizes and mappings
373  #ifdef EMI_DEBUG_MESH
374  {
375  int local_rank;
376  MPI_Comm_rank(emi_mesh.comm, &local_rank);
377 
378  fprintf(stderr, "RANK %d MESH SIZES: one_side=%zu, counter=%zu, unique=%zu\n",
379  local_rank, emi_surfmesh_one_side.l_numelem,
380  emi_surfmesh_w_counter_face.l_numelem, emi_surfmesh_unique_face.l_numelem);
381 
382  fprintf(stderr, "RANK %d MAP SIZES: uniqueFace_to_oneface=%zu\n",
383  local_rank, map_elem_uniqueFace_to_elem_oneface.size());
384  fflush(stderr);
385  }
386  #endif
387 
388  //-----------------------------------------------------------------
389  // setup the ionic current
390  //-----------------------------------------------------------------
391  // get initial value of the vm and Iion on the face from the ionicOnFace class
392  sf_vec* vb_ptr = get_data(vm_emi_itf);
393  sf_vec* Ib_ptr = get_data(iion_emi_itf);
394 
395  if(!(vb_ptr != NULL && Ib_ptr != NULL)) {
396  log_msg(0,5,0, "%s error: global Vb and Ib vectors not properly set up! Ionics seem invalid! Aborting!",
397  __func__);
398  EXIT(1);
399  }
400 
401  SF::init_vector(&vb, vb_ptr);
402  vb->shallow_copy(*vb_ptr);
403  SF::init_vector(&Ib, Ib_ptr);
404  Ib->shallow_copy(*Ib_ptr);
405 
406  parab_tech = static_cast<parabolic_solver_emi::parabolic_t>(param_globals::parab_solve_emi);
407 
408  dur = timing(t1, t0);
409 }
410 
411 void parabolic_solver_emi::rebuild_matrices(MaterialType* mtype, limpet::MULTI_IF & miif, SF::vector<stimulus> & stimuli, FILE_SPEC logger)
412 {
414  double start, end, period;
415  get_time(start);
416  double t0, t1, dur;
417  mass_integrator mass_integ;
418  mass_integrator mass_integ_emi;
419  int dpn = 1;
420 
421  int log_flag = param_globals::output_level > 1 ? ECHO : 0;
422  MaterialType & mt = mtype[0];
423  const bool have_dbc = have_dbc_stims(stimuli);
424 
425  double Dt = user_globals::tm_manager->time_step;
426 
427  cond_t condType = intra_cond;
428  sf_mesh & mesh = get_mesh(emi_msh);
429  sf_mesh & emi_surfmesh_w_counter_face = get_mesh(emi_surface_counter_msh);
430 
431  // set material and conductivity type
432  set_cond_type(mt, condType);
433 
434  // assemble EMI matrices
435  // fill the EMI system
436  {
437  // emi-> stiffness
438  log_msg(NULL, 0, 0, "assemble stiffness matrix");
439  get_time(t0);
440  elec_stiffness_integrator stfn_integ_emi(mt);
441  stiffness_emi->zero();
442  SF::assemble_matrix(*stiffness_emi, mesh, stfn_integ_emi);
443  dur = timing(t1,t0);
444  log_msg(logger,0,log_flag, "Computed parabolic stiffness matrix in %.5f seconds.", float(dur));
445 
446  log_msg(NULL, 0, 0, "assemble mass matrix on the volumetric mesh");
447  get_time(t0);
448  mass_integrator mass_integ;
449  mass_emi->zero();
450  SF::assemble_matrix(*mass_emi, mesh, mass_integ);
451  dur = timing(t1,t0);
452  log_msg(logger,0,log_flag, "Computed volumetric mass matrix in %.5f seconds.", float(dur));
453 
454  log_msg(NULL, 0, 0, "assemble LHS matrix and mass matrix on the surface mesh");
455  get_time(t0);
456  lhs_emi->zero();
457  // lhs_emi = (CmM/dt + K)
458  SF::assemble_lhs_emi(*lhs_emi, *mass_surf_emi, mesh, emi_surfmesh_w_counter_face, map_vertex_tag_to_dof_petsc, line_face, tri_face, quad_face, stfn_integ_emi, mass_integ_emi, -1., UM2_to_CM2 / Dt);
459  dur = timing(t1,t0);
460  log_msg(logger,0,log_flag, "Computed parabolic mass matrix in %.5f seconds.", float(dur));
461  }
462 
463 
464  bool same_nonzero = false;
465 
466  // set boundary conditions
467  if(have_dbc) {
468  log_msg(logger,0,log_flag, "lhs matrix enforcing Dirichlet boundaries.");
469  get_time(t0);
470 
471  if(dbc == nullptr)
472  dbc = new dbc_manager(*lhs_emi, stimuli);
473  else
474  dbc->recompute_dbcs();
475 
476  dbc->enforce_dbc_lhs();
477 
478  dur = timing(t1,t0);
479  log_msg(logger,0,log_flag, "lhs matrix Dirichlet enforcing done in %.5f seconds.", float(dur));
480  }
481  else {
482  log_msg(logger,0,ECHO, "without enforcing Dirichlet boundaries on the lhs matrix!");
483  // we are dealing with a singular system
484  phie_mat_has_nullspace = true;
485  }
486 
487  set_dir(INPUT);
488  get_time(t0);
489  setup_linear_solver(logger);
490  dur = timing(t1,t0);
491  log_msg(logger,0,log_flag, "Initializing parabolic solver in %.5f seconds.", float(dur));
492  set_dir(OUTPUT);
493 
494  period = timing(end, start);
495 }
496 
497 void parabolic_solver_emi::setup_linear_solver(FILE_SPEC logger)
498 {
499  tol = param_globals::cg_tol_parab;
500  max_it = param_globals::cg_maxit_parab;
501 
502  std::string default_opts;
503  std::string solver_file;
504  solver_file = param_globals::parab_options_file;
505  if (param_globals::flavor == std::string("ginkgo")) {
506  default_opts = std::string(
507  R"(
508 {
509  "type": "solver::Cg",
510  "preconditioner": {
511  "type": "solver::Multigrid",
512  "min_coarse_rows": 8,
513  "max_levels": 16,
514  "default_initial_guess": "zero",
515  "mg_level": [
516  {
517  "type": "multigrid::Pgm",
518  "deterministic": false
519  }
520  ],
521  "coarsest_solver": {
522  "type": "preconditioner::Schwarz",
523  "local_solver": {
524  "type": "preconditioner::Jacobi"
525  }
526  },
527  "criteria": [
528  {
529  "type": "Iteration",
530  "max_iters": 1
531  }
532  ]
533  },
534  "criteria": [
535  {
536  "type": "Iteration",
537  "max_iters": 100
538  },
539  {
540  "type": "ResidualNorm",
541  "reduction_factor": 1e-4
542  }
543  ]
544 }
545  )");
546  } else if (param_globals::flavor == std::string("petsc")) {
547  default_opts = std::string("-ksp_type cg -pc_type gamg -options_left");
548  }
549  lin_solver->setup_solver(*lhs_emi, tol, max_it * 100, param_globals::cg_norm_parab,
550  "parabolic PDE", phie_mat_has_nullspace, logger, solver_file.c_str(),
551  default_opts.c_str());
552 }
553 
554 void parabolic_solver_emi::solve()
555 {
556  switch (parab_tech) {
557  case SEMI_IMPLICIT: solve_semiImplicit(); break;
558  }
559 }
560 
561 void parabolic_solver_emi::solve_semiImplicit()
562 {
563  double t0,t1;
564  get_time(t0);
565 
566  if(dbc != nullptr){
567  CALI_MARK_BEGIN("apply_dbc_rhs");
568  dbc->enforce_dbc_rhs(*ui);
569  CALI_MARK_END("apply_dbc_rhs");
570  }
571 
572  *ui_pre = *ui;
573 
574  // -K*u (K is assembled as -K)
575  CALI_MARK_BEGIN("stiff_mat");
576  stiffness_emi->mult(*ui, *Iij_temp);
577  // rhs = Iij + K*u
578  *Irhs -= *Iij_temp;
579  CALI_MARK_END("stiff_mat");
580 
581  // add volumetric stimulus currents (I_ex, I_in)
582  // rhs = Iij + K*u + M*Iij_stim
583  if (Iij_stim->mag() > 0.0) {
584  CALI_MARK_BEGIN("stim_application");
585  mass_emi->mult(*Iij_stim, *Iij_temp);
586  *Irhs -= *Iij_temp;
587  CALI_MARK_BEGIN("stim_application");
588  }
589 
590  // rhs = -(Iij + K*u + M*Iij_stim)
591  CALI_MARK_BEGIN("rhs_update");
592  (*Irhs) *= (-1.0);
593  CALI_MARK_END("rhs_update");
594 
595  // compute step
596  CALI_MARK_BEGIN("linear_solve");
597  (*lin_solver)(*dui, *Irhs);
598  CALI_MARK_END("linear_solve");
599 
600  // logfile for solver
601  if(lin_solver->reason < 0) {
602  log_msg(0, 5, 0,"%s solver diverged. Reason: %s.", lin_solver->name.c_str(),
603  petsc_get_converged_reason_str(lin_solver->reason));
604  EXIT(1);
605  }
606 
607  // update solution:: ui_pre = ui_pre + dui
608  CALI_MARK_BEGIN("sol_update");
609  ui_pre->add_scaled(*dui, 1.0);
610  *ui *=0;
611  ui->add_scaled(*ui_pre, 1.0);
612  CALI_MARK_END("sol_update");
613 
614  // We need to enforce DBCs again after the solution vector was updated.
615  // Otherwise, matrix/solver tolerances allow small nonzero values in dui to accumulate in ui.
616  if(dbc != nullptr){
617  CALI_MARK_BEGIN("apply_dbc_rhs");
618  dbc->enforce_dbc_rhs(*ui);
619  CALI_MARK_END("apply_dbc_rhs");
620  }
621 
622  // treat solver statistics
623  stats.slvtime += timing(t1, t0);
624  stats.update_iter(lin_solver->niter);
625 }
626 
628  std::pair<SF::emi_face<mesh_int_t,SF::tuple<mesh_int_t>>,
629  SF::emi_face<mesh_int_t,SF::tuple<mesh_int_t>>>> & line_face,
631  std::pair<SF::emi_face<mesh_int_t,SF::triple<mesh_int_t>>,
632  SF::emi_face<mesh_int_t,SF::triple<mesh_int_t>>>> & tri_face,
634  std::pair<SF::emi_face<mesh_int_t,SF::quadruple<mesh_int_t>>,
635  SF::emi_face<mesh_int_t,SF::quadruple<mesh_int_t>>>> & quad_face,
636  hashmap::unordered_map<std::pair<mesh_int_t,mesh_int_t>, mesh_int_t> & map_vertex_tag_to_dof,
637  hashmap::unordered_map<std::pair<mesh_int_t,mesh_int_t>, std::pair<mesh_int_t,mesh_int_t>> & map_vertex_tag_to_dof_petsc,
638  std::vector<std::string> & tags_data)
639 {
640  sf_mesh & emi_surfmesh_w_counter_face = get_mesh(emi_surface_counter_msh);
641 
642  const SF::vector<mesh_int_t> & tags = emi_surfmesh_w_counter_face.tag;
643 
644  const SF::vector<mesh_int_t> & rnod = emi_surfmesh_w_counter_face.get_numbering(SF::NBR_REF);
646 
647  for(size_t i=0; i<rnod.size(); i++){
648  l2g[i] = rnod[i];
649  }
650 
651  for(size_t eidx=0; eidx<emi_surfmesh_w_counter_face.l_numelem; eidx++)
652  {
653  std::vector<int> elem_nodes;
654  mesh_int_t tag = emi_surfmesh_w_counter_face.tag[eidx];
655  for (int n = emi_surfmesh_w_counter_face.dsp[eidx]; n < emi_surfmesh_w_counter_face.dsp[eidx+1];n++)
656  {
657  mesh_int_t l_idx = emi_surfmesh_w_counter_face.con[n];
658 
659  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
660  Index_tag_old = std::make_pair(l2g[l_idx],tags[eidx]);
661  mesh_int_t dof = map_vertex_tag_to_dof[Index_tag_old];
662  elem_nodes.push_back(dof);
663  }
664 
665  mesh_int_t tag_first = 0;
666  mesh_int_t tag_second = 0;
667  std::string result_first;
668  std::string result_second;
669  std::sort(elem_nodes.begin(),elem_nodes.end()); // make the node tuple order-independent
670 
671  // Extract all surface face pairs separating regions with different material or boundary tags (ionicFaces)
672  if(elem_nodes.size()==2){
674 
675  key.v1 = elem_nodes[0];
676  key.v2 = elem_nodes[1];
677  std::pair<SF::emi_face<mesh_int_t,SF::tuple<mesh_int_t>>,
678  SF::emi_face<mesh_int_t,SF::tuple<mesh_int_t>>> value = line_face[key];
679 
680  tag_first = value.first.tag;
681  tag_second = value.second.tag;
682  result_first = std::to_string(tag_first) + ":" + std::to_string(tag_second);
683  result_second = std::to_string(tag_second) + ":" + std::to_string(tag_first);
684  }
685  else if(elem_nodes.size()==3){
687  key.v1 = elem_nodes[0];
688  key.v2 = elem_nodes[1];
689  key.v3 = elem_nodes[2];
690  std::pair<SF::emi_face<mesh_int_t,SF::triple<mesh_int_t>>,
691  SF::emi_face<mesh_int_t,SF::triple<mesh_int_t>>> value = tri_face[key];
692 
693  tag_first = value.first.tag;
694  tag_second = value.second.tag;
695  result_first = std::to_string(tag_first) + ":" + std::to_string(tag_second);
696  result_second = std::to_string(tag_second) + ":" + std::to_string(tag_first);
697  }
698  else if(elem_nodes.size()==4){
700  key.v1 = elem_nodes[0];
701  key.v2 = elem_nodes[1];
702  key.v3 = elem_nodes[2];
703  key.v4 = elem_nodes[3];
704  std::pair<SF::emi_face<mesh_int_t,SF::quadruple<mesh_int_t>>,
705  SF::emi_face<mesh_int_t,SF::quadruple<mesh_int_t>>> value = quad_face[key];
706 
707  tag_first = value.first.tag;
708  tag_second = value.second.tag;
709  result_first = std::to_string(tag_first) + ":" + std::to_string(tag_second);
710  result_second = std::to_string(tag_second) + ":" + std::to_string(tag_first);
711  }
712  // check if current element tag has a custom region ID
713  tags_data.push_back(result_first);
714  tags_data.push_back(result_second);
715  }
716 }
717 
718 void EMI::initialize()
719 {
720  double t1, t2;
721  get_time(t1);
722 
723  set_dir(OUTPUT);
724 
725  // open logger
726  logger = f_open("emi.log", param_globals::experiment != 4 ? "w" : "r");
727  const int verb = param_globals::output_level;
728  // Mesh processing step: convert the input mesh into
729  // - an EMI mesh (with discontinuities at gap junctions and membranes)
730  // - and generate the corresponding surface mesh
731  CALI_MARK_BEGIN("mesh_setup");
732  setup_EMI_mesh();
733 
734  // setup mappings between extra and intra grids, algebraic and nodal,
735  // and between PETSc and canonical orderings
736  setup_mappings();
737 
738  // the ionicOnFace physics is currently triggered from inside the emi to have tighter
739  // control over it
740  ion.logger = logger;
741 
742  ion.set_surface_mesh_data(parab_solver.line_face,
743  parab_solver.tri_face,
744  parab_solver.quad_face,
745  parab_solver.map_vertex_tag_to_dof);
746 
747  // builds per-face adjacency tags
748  std::vector<std::string> tags_data;
749  tags_onFace(parab_solver.line_face,
750  parab_solver.tri_face,
751  parab_solver.quad_face,
752  parab_solver.map_vertex_tag_to_dof,
753  parab_solver.map_vertex_tag_to_dof_petsc,
754  tags_data);
755  CALI_MARK_END("mesh_setup");
756 
757  ion.set_tags_onFace(tags_data);
758 
759  ion.set_face_region_data(parab_solver.intra_tags, parab_solver.map_elem_uniqueFace_to_tags);
760  ion.initialize();
761 
762  // set up tissue properties on the extra and intracellular domains
763  set_elec_tissue_properties_emi_volume(mtype_vol, parab_solver.extra_tags, parab_solver.intra_tags, logger);
764  region_mask(emi_msh, mtype_vol[0].regions, mtype_vol[0].regionIDs, true, "gregion_vol");
765 
766  // add electrics timer for time stepping, add to time stepper tool (TS)
767  double global_time = user_globals::tm_manager->time;
768  timer_idx = user_globals::tm_manager->add_eq_timer(global_time, param_globals::tend, 0,
769  param_globals::dt, 0, "elec::ref_dt", "TS");
770 
771  // EMI stimuli setup
772  CALI_MARK_BEGIN("stimulus_setup");
773  param_globals::operator_splitting = 0; // as long as we don't use operator splitting, we default to 0 so we can keep the stimuli scaling mint
774  setup_stimuli();
775  CALI_MARK_END("stimulus_setup");
776 
777  // set up the linear equation systems. this needs to happen after the stimuli have been
778  // set up, since we need boundary condition info
779  CALI_MARK_BEGIN("solver_setup");
780  setup_solvers();
781  CALI_MARK_END("solver_setup");
782 
783  // balance electrodes, we may need the extracellular mass matrix
784  balance_electrodes();
785  // total current scaling
786  scale_total_stimulus_current(stimuli, *parab_solver.mass_emi, *parab_solver.mass_surf_emi, logger);
787 
788  sf_mesh & emi_mesh = get_mesh(emi_msh);
789  sf_mesh & emi_surfmesh_w_counter_face = get_mesh(emi_surface_counter_msh);
790 
791  // - Extract the resting potential from v_b, which is defined only on the membrane faces.
792  // (we do not care about gap junction in this case)
793  // - Project this resting potential from the surface mesh onto the EMI mesh.
794  // - assign the corresponding resting potential on the memebrane to all DOFs
795  // myocyte that is associated with those membrane faces.
796  // (dofs on the interfaces of myocytes and all inner dofs located inside of myocytes).
797  // - any DOF with tag number belongs to the extra_tags set, is assigned to zero.
798  // - We provide a mapping between the vertices of the surface mesh(vertices of the original input mesh)
799  // and those of the EMI mesh (with updated indices).
800  // Based on the face type (line_face, tri_face, or quad_face) and the tag numbers that
801  // identify paired faces (face and counter-face),
802  // we can determine whether a given face belongs to a membrane or a gap-junction region.
803  // - Direct unique -> both mapping
804  parab_solver.operator_unique_to_both_faces->mult(*parab_solver.vb, *parab_solver.vb_both_face);
805  SF::assign_resting_potential_from_ionic_models_on_myocyte(*parab_solver.Vm_myocyte_emi,
806  parab_solver.vb_both_face,
807  parab_solver.elemTag_emi_mesh,
808  parab_solver.map_vertex_tag_to_dof_petsc,
809  parab_solver.line_face, parab_solver.tri_face, parab_solver.quad_face,
810  emi_surfmesh_w_counter_face, emi_mesh);
811 
812  parab_solver.Vm_myocyte_emi->get_diagonal(*parab_solver.ui); // initialize all myocytes to the resting potential based on ionic models
813  *parab_solver.vb_unique_face = *parab_solver.vb;
814 
815  CALI_MARK_BEGIN("output_setup");
816  // prepare the electrics output. we skip it if we do post-processing
817  if(param_globals::experiment != EXP_POSTPROCESS)
818  setup_output();
819  CALI_MARK_END("output_setup");
820 
821  this->initialize_time += timing(t2, t1);
822 }
823 
824 void EMI::setup_mappings()
825 {
826  bool emi_exits = mesh_is_registered(emi_msh);
827  assert(emi_exits);
828  const int dpn = 1;
829 
830  if(get_scattering(emi_msh, ALG_TO_NODAL, dpn) == NULL)
831  {
832  log_msg(logger, 0, 0, "%s: Setting up intracellular algebraic-to-nodal scattering.", __func__);
834  }
835  if(get_permutation(emi_msh, PETSC_TO_CANONICAL, dpn) == NULL)
836  {
837  log_msg(logger, 0, 0, "%s: Setting up intracellular PETSc to canonical permutation.", __func__);
839  }
840 }
841 
842 void EMI::compute_step()
843 {
844  double t1, t2;
845  get_time(t1);
846 
847  // activation checking
848  const double time = user_globals::tm_manager->time,
849  time_step = user_globals::tm_manager->time_step;
850 
851  const int verb = param_globals::output_level;
852  // We are treating stimuli by type:
853  // - Potential stimuli (Phi_ex, GND_ex, Phi_ex_ol) are managed by a dbc_manager and are applied
854  // to the left and right hand side of the equation.
855  // - Current stimuli (I_ex, I_in) are applied to the right hand side,
856  // while (I_tm) is applied to the vector Ib directly.
857  CALI_MARK_BEGIN("apply_dbc_lhs");
858  apply_dbc_stimulus();
859  CALI_MARK_END("apply_dbc_lhs");
860 
861  // compute ionics update
862  CALI_MARK_BEGIN("ion_compute");
863  ion.compute_step();
864  CALI_MARK_END("ion_compute");
865 
866  CALI_MARK_BEGIN("apply_stim");
867  apply_current_stimulus();
868  CALI_MARK_END("apply_stim");
869 
870  // convert Ib -> Irhs
871  parab_solver.operator_unique_to_both_faces->mult(*parab_solver.Ib, *parab_solver.Ib_both_face);
872  parab_solver.BsM->mult(*parab_solver.Ib_both_face, *parab_solver.Irhs);
873 
874  // solver parabolic system
875  CALI_MARK_BEGIN("parab_solve");
876  parab_solver.solve();
877  {
878  // v_b = B_i * u
879  parab_solver.B->mult(*parab_solver.ui, *parab_solver.vb_both_face);
880  // Direct both -> unique mapping
881  parab_solver.operator_both_to_unique_face->mult(*parab_solver.vb_both_face, *parab_solver.vb_unique_face);
882  *parab_solver.vb = *parab_solver.vb_unique_face;
883  }
884  CALI_MARK_END("parab_solve");
885 
886  if(user_globals::tm_manager->trigger(iotm_console)) {
887  // output lin solver stats
888  parab_solver.stats.log_stats(user_globals::tm_manager->time, false);
889  }
890  this->compute_time += timing(t2, t1);
891 
892  // since the traces have their own timing, we check for trace dumps in the compute step loop
895 }
896 
897 void EMI::output_step()
898 {
899  double t1, t2;
900  get_time(t1);
901 
902  output_manager.write_data();
903 
904  double curtime = timing(t2, t1);
905  this->output_time += curtime;
906 
907  IO_stats.calls++;
908  IO_stats.tot_time += curtime;
909 
911  IO_stats.log_stats(user_globals::tm_manager->time, false);
912 }
913 
917 void EMI::destroy()
918 {
920  // close logger
921  f_close(logger);
922 
923  // close output files
924  output_manager.close_files_and_cleanup();
925 
926  // destroy ionics
927  ion.destroy();
928 }
929 
930 void EMI::setup_stimuli()
931 {
932  // initialize basic stim info data (used units, supported types, etc)
933  init_stim_info();
934 
935  stimuli.resize(param_globals::num_stim);
936  for (int i = 0; i < param_globals::num_stim; i++) {
937  // construct new stimulus
938  stimulus & s = stimuli[i];
939 
941  s.translate(i);
942 
943  // we associate to the EMI mesh. this is needed for the stim_phys and stim_electrode setups.
944  s.associated_intra_mesh = emi_msh, s.associated_extra_mesh = emi_msh;
945 
946  s.setup(i);
947 
948  if (s.phys.type == Illum) {
949  log_msg(0, MAX_LOG_LEVEL, ECHO, "Stimulus of type Illum (=6) is not implemented in EMI. Abort.");
950  EXIT(EXIT_FAILURE);
951  }
952 
953  // Depending on the stimulus type, we make sure to only stimulate the correct regions of the mesh:
954  // Extracellular stimuli restrict to all DOFs of the extracellular region (including the extracellular side of the membrane).
955  // Equivalent for intracellular stimuli.
956  // Stimuli that act directly on the membrane restrict to DOFs with ptsData > 0, i.e. membrane, gap junctions, and complex junctions.
957  if (is_extra(s.phys.type)) {
958  SF::vector<mesh_int_t> extra_vertices;
959  const sf_mesh& mesh = get_mesh(s.associated_extra_mesh);
960 
961  // Gather vertices from emi_msh using extra_tags
962  indices_from_region_tags(extra_vertices, mesh, parab_solver.extra_tags);
963 
964  // Restrict electrode vertices to extra region
965  restrict_to_set(s.electrode.vertices, extra_vertices);
966  } else if (s.phys.type == I_tm) {
967  const sf_mesh& mesh = get_mesh(emi_msh);
968  SF::restrict_to_membrane(s.electrode.vertices, dof2ptsData, mesh);
969  } else {
970  SF::vector<mesh_int_t> intra_vertices;
971  const sf_mesh& mesh = get_mesh(s.associated_intra_mesh);
972 
973  // Gather vertices from emi_msh using intra_tags
974  indices_from_region_tags(intra_vertices, mesh, parab_solver.intra_tags);
975 
976  // Restrict electrode vertices to intra region
977  restrict_to_set(s.electrode.vertices, intra_vertices);
978  }
979 
980  if (s.electrode.dump_vtx) {
981  set_dir(OUTPUT);
982  s.dump_vtx_file(i);
983  }
984 
985  if(param_globals::stim[i].pulse.dumpTrace && get_rank() == 0) {
986  set_dir(OUTPUT);
987  s.pulse.wave.write_trace(s.name+".trc");
988  }
989 
990  }
991 }
992 
993 void EMI::apply_dbc_stimulus()
994 {
995  parabolic_solver_emi& ps = parab_solver;
996 
997  // we check if the DBC layout changed, if so we recompute the matrix and the dbc_manager
998  bool dbcs_have_updated = ps.dbc != nullptr && ps.dbc->dbc_update();
1000 
1001  if (dbcs_have_updated && time_not_final) {
1002  parab_solver.rebuild_matrices(mtype_vol, *ion.miif, stimuli, logger);
1003  }
1004 }
1005 
1006 void EMI::apply_current_stimulus()
1007 {
1008  parabolic_solver_emi& ps = parab_solver;
1009  ps.Iij_stim->set(0.0);
1010 
1011  // iterate over stimuli
1012  for(stimulus & s : stimuli) {
1013  if(s.is_active()) {
1014  switch (s.phys.type) {
1015  case I_tm: {
1016  apply_stim_to_vector(s, *ps.Iij_temp, true);
1017  ps.Bi->mult(*ps.Iij_temp, *ps.Ib_both_face);
1018  ps.operator_both_to_unique_face->mult(*ps.Ib_both_face, *ps.Ib_unique_face);
1019  ps.Ib->add_scaled(*ps.Ib_unique_face, -0.5); // scale by 0.5 to revert 2x scaling in Bi*Iij
1020  } break;
1021 
1022  case I_ex:
1023  case I_in: {
1024  apply_stim_to_vector(s, *ps.Iij_stim, true);
1025  } break;
1026 
1027  default: break;
1028  }
1029  }
1030  }
1031 }
1032 
1033 void EMI::balance_electrodes()
1034 {
1035  for (int i = 0; i < param_globals::num_stim; i++) {
1036  if (param_globals::stim[i].crct.balance != -1) {
1037  int from = param_globals::stim[i].crct.balance;
1038  int to = i;
1039 
1040  log_msg(NULL, 0, 0, "Balancing stimulus %d with %d %s-wise.", from, to,
1041  is_current(stimuli[from].phys.type) ? "current" : "voltage");
1042 
1043  stimulus& s_from = stimuli[from];
1044  stimulus& s_to = stimuli[to];
1045 
1046  s_to.pulse = s_from.pulse;
1047  s_to.ptcl = s_from.ptcl;
1048  s_to.phys = s_from.phys;
1049  s_to.pulse.strength *= -1.0;
1050 
1051  if (s_from.phys.type == I_ex || s_from.phys.type == I_in) {
1052  // if from is total current, skip volume based adjustment of strength
1053  // otherwise, calling constant_total_stimulus_current() will undo the balanced scaling of to.pulse.strength
1054  // constant_total_stimulus_current() will do the scaling based on the volume
1055  if (!s_from.phys.total_current) {
1056  sf_mat& mass = *parab_solver.mass_emi;
1057  SF_real vol0 = get_volume_from_nodes(mass, s_from.electrode.vertices);
1058  SF_real vol1 = get_volume_from_nodes(mass, s_to.electrode.vertices);
1059 
1060  s_to.pulse.strength *= fabs(vol0 / vol1);
1061  }
1062  }
1063  }
1064  }
1065 }
1066 
1067 void EMI::scale_total_stimulus_current(SF::vector<stimulus>& stimuli,
1068  sf_mat& mass_vol,
1069  sf_mat& mass_surf,
1070  FILE_SPEC logger)
1071 {
1072  for (stimulus & s : stimuli){
1073  if(is_current(s.phys.type) && s.phys.total_current){
1074  switch (s.phys.type) {
1075  case I_in:
1076  case I_ex: {
1077  // compute affected volume in um^3
1078  SF_real vol = get_volume_from_nodes(mass_vol, s.electrode.vertices);
1079  // s->strength holds the total current in uA, compute current density in uA/cm^3
1080  // Theoretically, we don't need to scale the volume to cm^3 here since we later
1081  // multiply with the mass matrix and we get um^3 * uA/um^3 = uA.
1082  // However, for I_ex/I_in there is an additional um^3 to cm^3 scaling in phys.scale,
1083  // since I_ex/I_in is expected to be in uA/cm^3. Therefore, we need to compensate for that to arrive at uA later.
1084  assert(vol > 0);
1085  float scale = 1.e12 / vol;
1086 
1087  s.pulse.strength *= scale;
1088 
1089  log_msg(logger, 0, ECHO,
1090  "%s [Stimulus %d]: current density scaled to %.4g uA/cm^3\n",
1091  s.name.c_str(), s.idx, s.pulse.strength);
1092  } break;
1093 
1094  case I_tm: {
1095  // In the EMI model, I_tm only affects the membrane. Therefore, we compute
1096  // the affected membrane surface in um^2 using the membrane mass matrix.
1097  // The electrode vertices are resticted to the membrane during setup, hence this function returns a surface already.
1098  SF_real surf = get_volume_from_nodes(mass_surf, s.electrode.vertices);
1099 
1100  // convert to cm^2
1101  assert(surf > 0);
1102  surf /= 1.e8;
1103 
1104  // scale surface density now to result in correct total current
1105  s.pulse.strength /= surf;
1106  log_msg(logger, 0, ECHO,
1107  "%s [Stimulus %d]: current density scaled to %.4g uA/cm^2\n",
1108  s.name.c_str(), s.idx, s.pulse.strength);
1109  } break;
1110 
1111  default: break;
1112  }
1113  }
1114  }
1115 }
1116 
1117 // Assign a deterministic global element numbering for a surface mesh based on
1118 // element type, tag, and sorted node ids. This only affects output ordering.
1119 static void assign_deterministic_elem_numbering(sf_mesh & mesh)
1120 {
1121  const int KEY_SIZE = 6; // type, tag, n1, n2, n3, n4
1122  int rank = 0, size = 0;
1123  MPI_Comm_rank(mesh.comm, &rank);
1124  MPI_Comm_size(mesh.comm, &size);
1125 
1126  auto make_key = [&](size_t i) {
1127  std::array<long long, KEY_SIZE> k;
1128  k.fill(-1);
1129  k[0] = (long long)mesh.type[i];
1130  k[1] = (long long)mesh.tag[i];
1131 
1132  int nn = 0;
1133  if (mesh.type[i] == SF::Line) nn = 2;
1134  else if (mesh.type[i] == SF::Tri) nn = 3;
1135  else if (mesh.type[i] == SF::Quad) nn = 4;
1136  else nn = 0;
1137 
1138  std::vector<long long> nodes;
1139  nodes.reserve(nn);
1140  size_t off = mesh.dsp[i];
1141  for (int j = 0; j < nn; j++) {
1142  nodes.push_back((long long)mesh.con[off + j]);
1143  }
1144  std::sort(nodes.begin(), nodes.end());
1145  for (int j = 0; j < (int)nodes.size(); j++) {
1146  k[2 + j] = nodes[j];
1147  }
1148  return k;
1149  };
1150 
1151  // Pack local keys
1152  std::vector<long long> local_keys(mesh.l_numelem * KEY_SIZE, -1);
1153  for (size_t i = 0; i < mesh.l_numelem; i++) {
1154  auto k = make_key(i);
1155  for (int j = 0; j < KEY_SIZE; j++) local_keys[i * KEY_SIZE + j] = k[j];
1156  }
1157 
1158  // Gather sizes
1159  std::vector<int> counts(size, 0), displs(size, 0);
1160  int local_count = (int)local_keys.size();
1161  MPI_Allgather(&local_count, 1, MPI_INT, counts.data(), 1, MPI_INT, mesh.comm);
1162  int total = 0;
1163  for (int r = 0; r < size; r++) {
1164  displs[r] = total;
1165  total += counts[r];
1166  }
1167 
1168  std::vector<long long> all_keys;
1169  if (rank == 0) all_keys.resize(total, -1);
1170  MPI_Gatherv(local_keys.data(), local_count, MPI_LONG_LONG,
1171  rank == 0 ? all_keys.data() : nullptr, counts.data(), displs.data(), MPI_LONG_LONG,
1172  0, mesh.comm);
1173 
1174  // Build sorted global keys on rank 0
1175  std::vector<std::array<long long, KEY_SIZE>> sorted_keys;
1176  if (rank == 0) {
1177  const int nkeys = total / KEY_SIZE;
1178  sorted_keys.resize(nkeys);
1179  for (int i = 0; i < nkeys; i++) {
1180  std::array<long long, KEY_SIZE> k;
1181  for (int j = 0; j < KEY_SIZE; j++) k[j] = all_keys[i * KEY_SIZE + j];
1182  sorted_keys[i] = k;
1183  }
1184  std::sort(sorted_keys.begin(), sorted_keys.end());
1185  }
1186 
1187  // Broadcast sorted keys
1188  int nkeys = 0;
1189  if (rank == 0) nkeys = (int)sorted_keys.size();
1190  MPI_Bcast(&nkeys, 1, MPI_INT, 0, mesh.comm);
1191  std::vector<long long> flat_sorted(nkeys * KEY_SIZE, -1);
1192  if (rank == 0) {
1193  for (int i = 0; i < nkeys; i++) {
1194  for (int j = 0; j < KEY_SIZE; j++) flat_sorted[i * KEY_SIZE + j] = sorted_keys[i][j];
1195  }
1196  }
1197  MPI_Bcast(flat_sorted.data(), (int)flat_sorted.size(), MPI_LONG_LONG, 0, mesh.comm);
1198 
1199  // Reconstruct sorted_keys on all ranks
1200  if (rank != 0) {
1201  sorted_keys.resize(nkeys);
1202  for (int i = 0; i < nkeys; i++) {
1203  std::array<long long, KEY_SIZE> k;
1204  for (int j = 0; j < KEY_SIZE; j++) k[j] = flat_sorted[i * KEY_SIZE + j];
1205  sorted_keys[i] = k;
1206  }
1207  }
1208 
1209  // Assign deterministic element numbering (both REF and SUBMESH)
1210  SF::vector<mesh_int_t> & nbr_ref = mesh.register_numbering(SF::NBR_ELEM_REF);
1211  SF::vector<mesh_int_t> & nbr_sub = mesh.register_numbering(SF::NBR_ELEM_SUBMESH);
1212  nbr_ref.resize(mesh.l_numelem);
1213  nbr_sub.resize(mesh.l_numelem);
1214  for (size_t i = 0; i < mesh.l_numelem; i++) {
1215  auto k = make_key(i);
1216  auto it = std::lower_bound(sorted_keys.begin(), sorted_keys.end(), k);
1217  if (it == sorted_keys.end() || *it != k) {
1218  log_msg(0, 5, 0, "deterministic numbering failed to find key (rank %d, elem %zu)", rank, i);
1219  EXIT(1);
1220  }
1221  mesh_int_t gid = (mesh_int_t)(it - sorted_keys.begin());
1222  nbr_ref[i] = gid;
1223  nbr_sub[i] = gid;
1224  }
1225 }
1226 
1227 void EMI::setup_output()
1228 {
1229  bool write_binary = false;
1230  std::string output_base = get_basename(param_globals::meshname);
1231  set_dir(OUTPUT);
1232 
1233  // write entire mesh
1234  sf_mesh & mesh = get_mesh(emi_msh);
1235  std::string output_file = output_base + "_e";
1236  log_msg(0, 0, 0, "Writing \"%s\" mesh: %s", mesh.name.c_str(), output_file.c_str());
1237  write_mesh_parallel(mesh, write_binary, output_file.c_str());
1238  // register output for overall phi on the entire mesh
1239  output_manager.register_output(parab_solver.ui, emi_msh, 1, param_globals::phiefile, "mV", NULL);
1240 
1241  // write membrane mesh
1243  mesh_m.name = "Membrane";
1244  output_file = output_base + "_m";
1245  log_msg(0, 0, 0, "Writing \"%s\" mesh: %s", mesh_m.name.c_str(), output_file.c_str());
1246  write_mesh_parallel(mesh_m, write_binary, output_file.c_str());
1247  // register output for Vm on membrane interface
1248  // ensure deterministic element ordering in output
1251  }
1252  output_manager.register_output(parab_solver.vb_unique_face, emi_surface_unique_face_msh, 1, param_globals::vofile, "mV", NULL, true);
1253 
1254  if(param_globals::num_trace) {
1255  sf_mesh & imesh = get_mesh(emi_msh);
1256  open_trace(ion.miif, param_globals::num_trace, param_globals::trace_node, NULL, &imesh);
1257  }
1258 
1259  // initialize generic logger for IO timings per time_dt
1260  IO_stats.init_logger("IO_stats.dat");
1261 }
1262 
1263 void EMI::dump_matrices()
1264 {
1265  std::string bsname = param_globals::dump_basename;
1266  std::string fn;
1267 
1268  set_dir(OUTPUT);
1269 
1270  fn = bsname + "_lhs.bin";
1271  parab_solver.lhs_emi->write(fn.c_str());
1272 
1273  fn = bsname + "_K.bin";
1274  parab_solver.stiffness_emi->write(fn.c_str());
1275 
1276  fn = bsname + "_B.bin";
1277  parab_solver.B->write(fn.c_str());
1278 
1279  fn = bsname + "_Bi.bin";
1280  parab_solver.Bi->write(fn.c_str());
1281 
1282  fn = bsname + "_BsM.bin";
1283  parab_solver.BsM->write(fn.c_str());
1284 
1285  fn = bsname + "_M.bin";
1286  parab_solver.mass_emi->write(fn.c_str());
1287 
1288  fn = bsname + "_Ms.bin";
1289  parab_solver.mass_surf_emi->write(fn.c_str());
1290 
1291 }
1292 
1295 double EMI::timer_val(const int timer_id)
1296 {
1297  // determine
1298  int sidx = stimidx_from_timeridx(stimuli, timer_id);
1299  double val = 0.0;
1300  if(sidx != -1) {
1301  stimuli[sidx].value(val);
1302  }
1303  else
1304  val = std::nan("NaN");
1305 
1306  return val;
1307 }
1308 
1311 std::string EMI::timer_unit(const int timer_id)
1312 {
1313  int sidx = stimidx_from_timeridx(stimuli, timer_id);
1314  std::string s_unit;
1315 
1316  if(sidx != -1)
1317  // found a timer-linked stimulus
1318  s_unit = stimuli[sidx].pulse.wave.f_unit;
1319 
1320  return s_unit;
1321 }
1322 
1323 void EMI::setup_solvers()
1324 {
1325  set_dir(OUTPUT);
1326  parab_solver.init();
1327  parab_solver.rebuild_matrices(mtype_vol, *ion.miif, stimuli, logger);
1328 
1329  if(param_globals::dump2MatLab)
1330  dump_matrices();
1331 }
1332 
1333 void extract_unique_tag(SF::vector<mesh_int_t>& unique_tags)
1334 {
1335  MPI_Comm comm = SF_COMM;
1336  int size, rank;
1337  MPI_Comm_size(comm, &size);
1338  MPI_Comm_rank(comm, &rank);
1339 
1340  binary_sort(unique_tags);
1341  unique_resize(unique_tags); // unique_resize is done locally(for each rank)
1342  make_global(unique_tags, comm);
1343  binary_sort(unique_tags);
1344  unique_resize(unique_tags);
1345 }
1346 
1347 void compute_tags_per_rank(int num_tags, SF::vector<mesh_int_t>& num_tags_per_rank)
1348 {
1349  MPI_Comm comm = SF_COMM;
1350  int size, rank;
1351  MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank);
1352  divide(num_tags, size, num_tags_per_rank);
1353 }
1354 
1355 void EMI::setup_EMI_mesh()
1356 {
1357  log_msg(0,0,0, "\n *** Processing EMI mesh ***\n");
1358 
1359  const std::string basename = param_globals::meshname;
1360  const int verb = param_globals::output_level;
1361  std::map<mesh_t, sf_mesh> & mesh_registry = user_globals::mesh_reg;
1362  assert(mesh_registry.count(emi_msh) == 1);
1363 
1364  set_dir(INPUT);
1365 
1366  sf_mesh & emi_mesh = mesh_registry[emi_msh];
1367  sf_mesh & emi_surfmesh_one_side = mesh_registry[emi_surface_msh];
1368  sf_mesh & emi_surfmesh_w_counter_face = mesh_registry[emi_surface_counter_msh];
1369  sf_mesh & emi_surfmesh_unique_face = mesh_registry[emi_surface_unique_face_msh];
1370 
1371  MPI_Comm comm = emi_mesh.comm;
1372 
1373  int size, rank;
1374  double t1, t2, s1, s2;
1375  const double total_setup_t0 = MPI_Wtime();
1376  MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank);
1377 
1378  //-----------------------------------------------------------------
1379  // Validate mesh dimension: EMI only supports 3D volumetric meshes
1380  //-----------------------------------------------------------------
1381  if (emi_mesh.l_numelem > 0) {
1382  // Check first local element type (all elements should be same type in CARP)
1383  SF::elem_t first_elem = emi_mesh.type[0];
1384 
1385  if (first_elem == SF::Line || first_elem == SF::Tri || first_elem == SF::Quad) {
1386  const char* type_name = (first_elem == SF::Line) ? "1D (Line)" :
1387  (first_elem == SF::Tri) ? "2D (Tri)" :
1388  "2D (Quad)";
1389  if (rank == 0) {
1390  log_msg(0, 5, 0, "\n*** ERROR: EMI model requires a 3D volumetric mesh!");
1391  log_msg(0, 5, 0, "*** Current mesh element type: %s", type_name);
1392  log_msg(0, 5, 0, "*** EMI only supports 3D element types: Tetra, Pyramid, Prism, Hexa");
1393  log_msg(0, 5, 0, "*** Please provide a 3D mesh with volume elements.\n");
1394  }
1395  EXIT(EXIT_FAILURE);
1396  }
1397  }
1398 
1399  //-----------------------------------------------------------------
1400  // Step 1: READ *.intra and *.extra
1401  //-----------------------------------------------------------------
1402  int total_num_tags = 0;
1403  if(verb) log_msg(NULL, 0, 0,"\nReading tags for extra and intra regions from input files");
1404  t1 = MPI_Wtime();
1405  {
1406  SF::vector<mesh_int_t> unique_extra_tags;
1407  SF::vector<mesh_int_t> unique_intra_tags;
1408 
1409  if(verb) log_msg(NULL, 0, 0,"Read extracellular tags");
1410  read_indices_global(unique_extra_tags,basename+".extra", comm);
1411  for(mesh_int_t tag:unique_extra_tags){
1412  parab_solver.extra_tags.insert(tag);
1413  }
1414 
1415  if(verb) log_msg(NULL, 0, 0,"Read intracellular tags");
1416  read_indices_global(unique_intra_tags,basename+".intra", comm);
1417  for(mesh_int_t tag:unique_intra_tags){
1418  parab_solver.intra_tags.insert(tag);
1419  }
1420 
1421  total_num_tags = parab_solver.extra_tags.size() + parab_solver.intra_tags.size();
1422  if(total_num_tags < size){
1423  log_msg(0,5,0, "\nThe number of unique tags on EMI mesh is smaller than number of processors!");
1424  EXIT(EXIT_FAILURE);
1425  }
1426  if(verb) log_msg(NULL, 0, 0,"\nextra_tags=%lu, intra_tags=%lu", parab_solver.extra_tags.size(), parab_solver.intra_tags.size());
1427  }
1428  t2 = MPI_Wtime();
1429  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1430 
1431  //-----------------------------------------------------------------
1432  // Step 2: READ points from *.pts
1433  //-----------------------------------------------------------------
1435  SF::vector<mesh_int_t> ptsidx;
1436  SF::vector<mesh_int_t> ptsData;
1437  if(verb) log_msg(NULL, 0, 0,"\nReading points with data on each vertex");
1438  t1 = MPI_Wtime();
1439  SF::read_points(basename, comm, pts, ptsidx);
1440  ptsData.resize(ptsidx.size());
1441  assert(ptsidx.size()==ptsData.size());
1442  t2 = MPI_Wtime();
1443  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1444 
1445  std::list< sf_mesh* > meshlist;
1446  meshlist.push_back(&emi_mesh);
1447 
1448  //-----------------------------------------------------------------
1449  // Step 3: Distrubute mesh based on tag or *.part
1450  //-----------------------------------------------------------------
1451  if(verb) log_msg(NULL, 0, 0,"\nDistribute mesh based on tags");
1452  // should be replaced by scotch/pt-scotch for efficiency
1453  t1 = MPI_Wtime();
1454  distribute_elements_based_tags(emi_mesh, total_num_tags);
1455  t2 = MPI_Wtime();
1456  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1457 
1458  //-----------------------------------------------------------------
1459  // Step 4: insert coordinates to the mesh
1460  //-----------------------------------------------------------------
1461  // insert points
1462  if(verb) log_msg(NULL, 0, 0, "\nInserting points");
1463  t1 = MPI_Wtime();
1464  SF::insert_points(pts, ptsidx, meshlist);
1465  t2 = MPI_Wtime();
1466  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1467 
1468  //-----------------------------------------------------------------
1469  // Step 5: extract ptsdata based on the location of each vertex
1470  //-----------------------------------------------------------------
1471  if(verb) log_msg(NULL, 0, 0, "\nCompute location of all dofs on emi mesh(inner dofs, memebarne, gap junction) saved into ptsData from original mesh");
1472  t1 = MPI_Wtime();
1473  compute_ptsdata_from_original_mesh( emi_mesh,
1474  SF::NBR_REF,
1475  vertex2ptsdata,
1476  parab_solver.extra_tags,
1477  parab_solver.intra_tags);
1478  t2 = MPI_Wtime();
1479  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1480 
1481  //-----------------------------------------------------------------
1482  // Step 6: extract faces and counterfaces on the interfaces (mem/gap)
1483  // and mark the unique faces on surface mesh
1484  // generate required mapping between surface mesh and unique faces
1485  //-----------------------------------------------------------------
1486  t1 = MPI_Wtime();
1487  if(verb) log_msg(NULL, 0, 0, "\nExtract EMI surface mesh");
1488  hashmap::unordered_map<mesh_int_t, SF::emi_index_rank<mesh_int_t>> unused_map_elem_oneface_to_elem_uniqueFace;
1489  extract_face_based_tags(emi_mesh, SF::NBR_REF, vertex2ptsdata,
1490  parab_solver.line_face,
1491  parab_solver.tri_face,
1492  parab_solver.quad_face,
1493  parab_solver.extra_tags,
1494  parab_solver.intra_tags,
1495  emi_surfmesh_one_side, emi_surfmesh_w_counter_face, emi_surfmesh_unique_face,
1496  parab_solver.map_elem_uniqueFace_to_elem_oneface,
1497  unused_map_elem_oneface_to_elem_uniqueFace);
1498  meshlist.push_back(&emi_surfmesh_one_side);
1499  t2 = MPI_Wtime();
1500  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1501  //-----------------------------------------------------------------
1502  // Step 7: register surface meshes based on the {typeof}_face with counter face
1503  //-----------------------------------------------------------------
1504  compute_surface_mesh_with_counter_face(emi_surfmesh_w_counter_face, SF::NBR_REF,
1505  parab_solver.line_face,
1506  parab_solver.tri_face,
1507  parab_solver.quad_face);
1508 
1509  compute_surface_mesh_with_unique_face(emi_surfmesh_unique_face, SF::NBR_REF,
1510  parab_solver.line_face,
1511  parab_solver.tri_face,
1512  parab_solver.quad_face,
1513  parab_solver.map_elem_uniqueFace_to_tags);
1514 
1515  //-----------------------------------------------------------------
1516  // Step 8: create a map between emi_surfmesh_w_counter_face and emi_surfmesh (both -> one)
1517  //-----------------------------------------------------------------
1518  SF::create_reverse_elem_mapping_between_surface_meshes(parab_solver.line_face,
1519  parab_solver.tri_face,
1520  parab_solver.quad_face,
1521  parab_solver.vec_both_to_one_face,
1522  comm);
1523  t2 = MPI_Wtime();
1524  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1525 
1526  //-----------------------------------------------------------------
1527  // Step 9: global number of interfaces only on emi_surfmesh without counter face
1528  //-----------------------------------------------------------------
1529  if(verb) log_msg(NULL, 0, 0, "\ncompute global number of interface");
1530  size_t global_count_surf = 0;
1531  size_t numelem_surface = emi_surfmesh_one_side.l_numelem;
1532  unsigned long long local_count_surf = static_cast<unsigned long long>(numelem_surface);
1533  unsigned long long global_count_surf_ull = 0;
1534  MPI_Reduce(&local_count_surf, &global_count_surf_ull, 1, MPI_UNSIGNED_LONG_LONG, MPI_SUM, 0, MPI_COMM_WORLD);
1535  global_count_surf = static_cast<size_t>(global_count_surf_ull);
1536  if(verb && rank==0) fprintf(stdout, "global number of interfaces = %zu\n", global_count_surf);
1537 
1538  //-----------------------------------------------------------------
1539  // Step 10: submesh_numbering on the emi_mesh and generate parallel layout
1540  //-----------------------------------------------------------------
1541  {
1543  sub_numbering(emi_mesh);
1544  emi_mesh.generate_par_layout();
1545  }
1546 
1547  SF::meshdata<mesh_int_t, mesh_real_t> tmesh_backup_old = emi_mesh;
1548 
1549  //-----------------------------------------------------------------
1550  // Step 11: make a map bwteen key(vertex,tag)-> value(dof) after decoupling interfaces defined on emi mesh
1551  //-----------------------------------------------------------------
1552  // During interface decoupling and the introduction of new degrees of freedom (DoFs),
1553  // we assume that the mesh partitioning is performed at least on a per-tag basis.
1554  // This means that all elements sharing the same tag number belong to the same rank.
1555  t1 = MPI_Wtime();
1556  if(verb) log_msg(NULL, 0, 0, "\ndecouple emi interfaces");
1557  if(verb) log_msg(NULL, 0, 0, "\tcompute map oldIdx to dof");
1558  compute_map_vertex_to_dof(emi_mesh, SF::NBR_REF, vertex2ptsdata, parab_solver.extra_tags, parab_solver.map_vertex_tag_to_dof);
1559 
1560  //-----------------------------------------------------------------
1561  // Step 12: complete the map bwteen key(vertex,tag)-> value(dof) for counter faces defined on emi mesh
1562  //-----------------------------------------------------------------
1563  if(verb) log_msg(NULL, 0, 0, "\tcomplete map oldIdx to dof with counter interface");
1564  // add to the map the counter part of the interface
1565  complete_map_vertex_to_dof_with_counter_face(parab_solver.line_face, parab_solver.tri_face, parab_solver.quad_face, parab_solver.map_vertex_tag_to_dof);
1566 
1567  //-----------------------------------------------------------------
1568  // Step 13: update emi mesh with new dofs, so decoupling the interfaces applied on emi mesh
1569  //-----------------------------------------------------------------
1570  if(verb) log_msg(NULL, 0, 0, "\tupdate mesh with dof");
1571  update_emi_mesh_with_dofs(emi_mesh, SF::NBR_REF, parab_solver.map_vertex_tag_to_dof, parab_solver.dof2vertex);
1572  t2 = MPI_Wtime();
1573  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1574 
1575  //-----------------------------------------------------------------
1576  // Step 14: set map_vertex_tag_to_dof_petsc where the key(vertex,tag) and value(dof,petsc)
1577  //-----------------------------------------------------------------
1578  t1 = MPI_Wtime();
1579  if(verb) log_msg(NULL, 0, 0, "\nInitialize petsc =0 for map<oldIdx,tag> -><dof, petsc>");
1580  // Iterate over map to assign the (oldIndx, tag) -> (dof, petsc) atm petsc = 0
1581  for(const auto & key_value : parab_solver.map_vertex_tag_to_dof)
1582  {
1583  mesh_int_t gIndex_old = key_value.first.first;
1584  mesh_int_t tag_old = key_value.first.second;
1585  mesh_int_t dof = key_value.second;
1586 
1587  std::pair <mesh_int_t,mesh_int_t> dof_petsc = std::make_pair(dof,-1); // petsc numbering ...
1588  parab_solver.map_vertex_tag_to_dof_petsc.insert({key_value.first,dof_petsc});
1589  }
1590  t2 = MPI_Wtime();
1591  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1592 
1593  //-----------------------------------------------------------------
1594  // Step 16: insert the coordinates on new dofs to the emi mesh
1595  //-----------------------------------------------------------------
1596  t1 = MPI_Wtime();
1597  if(verb) log_msg(NULL, 0, 0, "Inserting points and ptsData of dofs to emi_mesh");
1598  insert_points_ptsData_to_dof(tmesh_backup_old, emi_mesh, SF::NBR_REF, parab_solver.dof2vertex, vertex2ptsdata, dof2ptsData);
1599  t2 = MPI_Wtime();
1600  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1601 
1602  //-----------------------------------------------------------------
1603  // Step 17: submesh_numbering on the emi_mesh and generate parallel layout after decoupling the interfaces on the emi mesh
1604  //-----------------------------------------------------------------
1605  t1 = MPI_Wtime();
1606  if(verb) log_msg(NULL, 0, 0, "Generating unique PETSc numberings");
1607  {
1609  sub_numbering(emi_mesh);
1610  emi_mesh.generate_par_layout();
1611  }
1612  t2 = MPI_Wtime();
1613  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1614 
1615  //-----------------------------------------------------------------
1616  // Step 18: register petsc_numbering on emi mesh
1617  //-----------------------------------------------------------------
1618  t1 = MPI_Wtime();
1619  if(verb) log_msg(NULL, 0, 0, "Generating unique PETSc numberings");
1620  {
1621  SF::petsc_numbering<mesh_int_t,mesh_real_t> petsc_numbering(emi_mesh.pl);
1622  petsc_numbering(emi_mesh);
1623  }
1624  t2 = MPI_Wtime();
1625  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1626 
1627  //-----------------------------------------------------------------
1628  // Step 19: complete map_vertex_tag_to_dof_petsc where the key(vertex,tag) and value(dof,petsc) for counter faces on emi mesh
1629  //-----------------------------------------------------------------
1630  if(verb) log_msg(NULL, 0, 0, "Updating the map between indices to PETSc numberings");
1631  t1 = MPI_Wtime();
1632  update_map_indices_to_petsc(emi_mesh, SF::NBR_REF, SF::NBR_PETSC, parab_solver.extra_tags, parab_solver.map_vertex_tag_to_dof_petsc, parab_solver.dof2vertex, parab_solver.elemTag_emi_mesh);
1633  t2 = MPI_Wtime();
1634  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1635 
1636  //-----------------------------------------------------------------
1637  // Step 20: update value of map {type_of}_face with new dofs after decoupling
1638  //-----------------------------------------------------------------
1639  t1 = MPI_Wtime();
1640  if(verb) log_msg(NULL, 0, 0, "Updating surface mesh with dof");
1641  update_faces_on_surface_mesh_after_decoupling_with_dofs(emi_surfmesh_one_side, parab_solver.map_vertex_tag_to_dof, parab_solver.line_face, parab_solver.tri_face, parab_solver.quad_face);
1642  t2 = MPI_Wtime();
1643  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1644 
1645  //-----------------------------------------------------------------
1646  // Step 21: register emi_surfmesh for SF::NBR_ELEM_REF, NBR_REF, SF::NBR_SUBMESH
1647  //-----------------------------------------------------------------
1648  t1 = MPI_Wtime();
1649  if(verb) log_msg(NULL, 0, 0, "Layout for element of EMI surfmesh");
1650  SF::vector<mesh_int_t> & emi_surfmesh_elem = emi_surfmesh_one_side.register_numbering(SF::NBR_ELEM_REF);
1651  SF::vector<long int> layout;
1652  SF::layout_from_count<long int>(emi_surfmesh_one_side.l_numelem, layout, emi_surfmesh_one_side.comm);
1653  size_t count = layout[rank+1] - layout[rank];
1654  emi_surfmesh_elem.resize(count);
1655  for (int i = 0; i < count; ++i){
1656  emi_surfmesh_elem[i] = layout[rank]+i;
1657  }
1658 
1659  emi_surfmesh_one_side.localize(SF::NBR_REF);
1660  emi_surfmesh_one_side.register_numbering(SF::NBR_SUBMESH);
1661  t2 = MPI_Wtime();
1662  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1663 
1664  //-----------------------------------------------------------------
1665  // Step 22: register emi_surfmesh_w_counter_face for SF::NBR_ELEM_REF, NBR_REF, SF::NBR_SUBMESH
1666  //-----------------------------------------------------------------
1667  t1 = MPI_Wtime();
1668  if(verb) log_msg(NULL, 0, 0, "Layout for element of EMI surfmesh w counter face");
1669  SF::vector<mesh_int_t> & emi_surfmesh_counter_elem = emi_surfmesh_w_counter_face.register_numbering(SF::NBR_ELEM_REF);
1670  SF::vector<long int> layout_counter;
1671  SF::layout_from_count<long int>(emi_surfmesh_w_counter_face.l_numelem, layout_counter, emi_surfmesh_w_counter_face.comm);
1672  size_t count_counter = layout_counter[rank+1] - layout_counter[rank];
1673  emi_surfmesh_counter_elem.resize(count_counter);
1674  for (int i = 0; i < count_counter; ++i){
1675  emi_surfmesh_counter_elem[i] = layout_counter[rank]+i;
1676  }
1677  emi_surfmesh_w_counter_face.localize(SF::NBR_REF);
1678  emi_surfmesh_w_counter_face.register_numbering(SF::NBR_SUBMESH);
1679  t2 = MPI_Wtime();
1680  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1681 
1682 
1683  //-----------------------------------------------------------------
1684  // Step 23: register emi_surfmesh_w_counter_face for SF::NBR_ELEM_REF, NBR_REF, SF::NBR_SUBMESH
1685  //-----------------------------------------------------------------
1686  t1 = MPI_Wtime();
1687  if(verb) log_msg(NULL, 0, 0, "Layout for element of EMI surfmesh w counter face");
1688  SF::vector<mesh_int_t> & emi_surfmesh_unique_elem = emi_surfmesh_unique_face.register_numbering(SF::NBR_ELEM_REF);
1689  SF::vector<long int> layout_unique;
1690  SF::layout_from_count<long int>(emi_surfmesh_unique_face.l_numelem, layout_unique, emi_surfmesh_unique_face.comm);
1691  size_t count_unique = layout_unique[rank+1] - layout_unique[rank];
1692  emi_surfmesh_unique_elem.resize(count_unique);
1693  for (int i = 0; i < count_unique; ++i){
1694  emi_surfmesh_unique_elem[i] = layout_unique[rank]+i;
1695  }
1696  emi_surfmesh_unique_face.localize(SF::NBR_REF);
1697  emi_surfmesh_unique_face.register_numbering(SF::NBR_SUBMESH);
1698  t2 = MPI_Wtime();
1699  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1700 
1701  //-----------------------------------------------------------------
1702  // Step 24: insert coorinates to emi_surfmesh_one_side and emi_surfmesh_w_counter_face
1703  //-----------------------------------------------------------------
1704  t1 = MPI_Wtime();
1705  if(verb) log_msg(NULL, 0, 0, "Inserting points to EMI surfmesh");
1706  insert_points_to_surface_mesh(tmesh_backup_old, emi_surfmesh_one_side, SF::NBR_REF, parab_solver.dof2vertex, parab_solver.extra_tags, parab_solver.elemTag_surface_mesh);
1707  insert_points_to_surface_mesh(tmesh_backup_old, emi_surfmesh_w_counter_face, SF::NBR_REF, parab_solver.dof2vertex, parab_solver.extra_tags, parab_solver.elemTag_surface_w_counter_mesh);
1708  insert_points_to_surface_mesh(tmesh_backup_old, emi_surfmesh_unique_face, SF::NBR_REF, parab_solver.dof2vertex);
1709  t2 = MPI_Wtime();
1710  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1711 
1712  //-----------------------------------------------------------------
1713  // Step 25: submesh_numbering & petc numebring for both emi_surfmesh_one_side and emi_surfmesh_w_counter_face
1714  //-----------------------------------------------------------------
1715  t1 = MPI_Wtime();
1716  if(verb) log_msg(NULL, 0, 0, "Generating submesh_numbering and PETSc numberings for surface mesh");
1717  {
1719  sub_numbering(emi_surfmesh_one_side);
1720  emi_surfmesh_one_side.generate_par_layout();
1721 
1722  SF::petsc_numbering<mesh_int_t,mesh_real_t> petsc_numbering(emi_surfmesh_one_side.pl);
1723  petsc_numbering(emi_surfmesh_one_side);
1724  }
1725 
1726  {
1728  sub_numbering(emi_surfmesh_w_counter_face);
1729  emi_surfmesh_w_counter_face.generate_par_layout();
1730 
1731  SF::petsc_numbering<mesh_int_t,mesh_real_t> petsc_numbering(emi_surfmesh_w_counter_face.pl);
1732  petsc_numbering(emi_surfmesh_w_counter_face);
1733  }
1734 
1735  {
1737  sub_numbering(emi_surfmesh_unique_face);
1738  emi_surfmesh_unique_face.generate_par_layout();
1739 
1740  SF::petsc_numbering<mesh_int_t,mesh_real_t> petsc_numbering(emi_surfmesh_unique_face.pl);
1741  petsc_numbering(emi_surfmesh_unique_face);
1742  // Ensure deterministic element ordering across MPI for output
1743  assign_deterministic_elem_numbering(emi_surfmesh_unique_face);
1744  }
1745  t2 = MPI_Wtime();
1746  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1747 
1748  //-----------------------------------------------------------------
1749  // Step 26: assign petsc numbering to map_vertex_tag_to_dof_petsc where the key(vertex,tag) and value(dof,petsc)
1750  //-----------------------------------------------------------------
1751  t1 = MPI_Wtime();
1752  if(verb) log_msg(NULL, 0, 0, "assign PETSc numbering for new faces");
1753  SF::assign_petsc_on_counter_face(parab_solver.map_vertex_tag_to_dof_petsc,comm);
1754  {
1755  hashmap::unordered_map<std::pair<mesh_int_t,mesh_int_t>, std::pair<mesh_int_t,mesh_int_t>>::iterator it;
1756  for (it = parab_solver.map_vertex_tag_to_dof_petsc.begin(); it != parab_solver.map_vertex_tag_to_dof_petsc.end(); it++)
1757  {
1758  std::pair <mesh_int_t,mesh_int_t> Index_tag_old = it->first;
1759  std::pair <mesh_int_t,mesh_int_t> dof_petsc = it->second;
1760  parab_solver.dof2petsc[dof_petsc.first] = dof_petsc.second;
1761  parab_solver.petsc2dof[dof_petsc.second] = dof_petsc.first;
1762  }
1763  }
1764 
1765  // DEBUG: Check for invalid PETSc indices after exchange
1766  #ifdef EMI_DEBUG_MESH
1767  {
1768  int invalid_count = 0;
1769  for (const auto& [key, val] : parab_solver.map_vertex_tag_to_dof_petsc) {
1770  if (val.second < 0) {
1771  invalid_count++;
1772  if (invalid_count <= 3) {
1773  fprintf(stderr, "RANK %d INVALID: vertex=%ld tag=%ld dof=%ld petsc=%ld\n",
1774  rank, (long)key.first, (long)key.second, (long)val.first, (long)val.second);
1775  }
1776  }
1777  }
1778  fprintf(stderr, "RANK %d: After Step 26: %d invalid PETSc indices out of %zu total\n",
1779  rank, invalid_count, parab_solver.map_vertex_tag_to_dof_petsc.size());
1780  fflush(stderr);
1781  }
1782  #endif
1783 
1784  t2 = MPI_Wtime();
1785  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1786 
1787  //-----------------------------------------------------------------
1788  // Step 27: update counter faces to the map{type_of}_face
1789  //-----------------------------------------------------------------
1790  added_counter_faces_to_map(parab_solver.line_face, parab_solver.tri_face, parab_solver.quad_face);
1791  const double total_setup = MPI_Wtime() - total_setup_t0;
1792  log_msg(0,0,0, "Total setup_EMI_mesh processing time: %.5f sec.", float(total_setup));
1793 
1794  log_msg(0,0,0, "\n *** EMI mesh processing Done ***\n");
1795 }
1796 
1797 void distribute_elements_based_tags(SF::meshdata<mesh_int_t, mesh_real_t>& mesh,
1798  int total_num_tags)
1799 {
1800  MPI_Comm comm = mesh.comm;
1801  int size, rank;
1802  double t1, t2, s1, s2;
1803  MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank);
1804  const int verb = param_globals::output_level;
1805 
1806  if(total_num_tags < size)
1807  {
1808  PetscPrintf(PETSC_COMM_WORLD,"\nThe number of processors should be less than the number of tags, size = %d & ntags = %d !!!\n",
1809  size, total_num_tags);
1810  cleanup_and_exit();
1811  }
1812 
1813  if(verb==10) log_msg(NULL, 0, 0,"\ncompute the number of tags which belongs to one rank");
1814  // compute the number of tags per rank
1815  SF::vector<mesh_int_t> ntags_per_rank;
1816  t1 = MPI_Wtime();
1817  compute_tags_per_rank(total_num_tags, ntags_per_rank);
1818  t2 = MPI_Wtime();
1819  if(verb==10) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1820 
1821  // generate global destination w.r.t. tags
1822  // redistribute elements based on new partition
1823  t1 = MPI_Wtime();
1824  SF::vector<mesh_int_t> part_based_Tags(mesh.l_numelem);
1825  partition_based_tags(total_num_tags, mesh.tag, ntags_per_rank, part_based_Tags);
1826  SF::redistribute_elements(mesh,part_based_Tags);
1827  // permute elements locally first based on the tag then element index
1828  permute_mesh_locally_based_on_tag_elemIdx(mesh);
1829  t2 = MPI_Wtime();
1830  if(verb==10) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1831 }
1832 
1833 // In the EMI model we cannot have partition boundaries crossing cells.
1834 // Therefore this function assigns a partition to each tag. All elements with this tag will go to the same partition.
1835 void partition_based_tags(int num_tags,
1837  SF::vector<mesh_int_t> num_tags_per_rank,
1838  SF::vector<mesh_int_t>& part)
1839 {
1840  const int verb = param_globals::output_level;
1841  MPI_Comm comm = SF_COMM;
1842  int size, rank;
1843  MPI_Comm_size(comm, &size);
1844  MPI_Comm_rank(comm, &rank);
1845 
1846  // we need to have tags_to_rank as a map globally.
1848  // Try to load the mapping from a .part file and fall back on the internal mapping if there is no such file.
1849  // If the mapping is read from file, the num_tags_per_rank argument is not used.
1850  if (!load_partitions_from_file(tags_to_rank_map, num_tags, comm)) {
1851  if(verb==10) log_msg(NULL, 0, 0,"\ncompute the number of unique tags");
1852  SF::vector<mesh_int_t> unique_tags = tag;
1853  double t1 = MPI_Wtime();
1854  extract_unique_tag(unique_tags);
1855  double t2 = MPI_Wtime();
1856  if(verb==10) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1857 
1858  if (unique_tags.size() != static_cast<size_t>(num_tags)) {
1859  log_msg(0,5,0, "\nerror: the number of tags in the EMI mesh does not match the total tags from *.extra and *.intra");
1860  EXIT(1);
1861  }
1862  map_tags_to_rank(size, unique_tags, num_tags_per_rank, tags_to_rank_map);
1863  }
1864 
1865  for (size_t i = 0; i < part.size(); ++i) {
1866  if(tags_to_rank_map.count(tag[i]))
1867  part[i] = tags_to_rank_map[tag[i]];
1868  }
1869 }
1870 
1871 void map_tags_to_rank(int size, const SF::vector<mesh_int_t> & unique_tags, const SF::vector<mesh_int_t> & num_tags_per_rank, hashmap::unordered_map<mesh_int_t, mesh_int_t> &tags_to_rank_map)
1872 {
1873  // the goal is to map the tag as a key -> rank, should be global one
1874  mesh_int_t t = 0; // tag index
1875  for (size_t r = 0; r < size; ++r) {
1876  for (size_t count = 0; count < num_tags_per_rank[r];) {
1877  mesh_int_t tag = unique_tags[t];
1878 
1879  // // here we consider each tag belongs to only one rank.
1880  tags_to_rank_map.insert({tag, r});
1881  count +=1;
1882  t+=1;
1883  }
1884  }
1885 }
1886 
1887 // This function creates a tag-to-rank mapping based only on the numerical order of the tags.
1888 bool load_partitions_from_file(hashmap::unordered_map<mesh_int_t, mesh_int_t>& tags_to_rank_map,
1889  int expected_num_tags,
1890  MPI_Comm comm)
1891 {
1892  FILE_SPEC tags_to_part = nullptr;
1893 
1894  SF::vector<int> parts;
1895  const std::string basename = param_globals::meshname;
1896  FILE* fd = fopen((basename + ".part").c_str(), "r");
1897  if (fd != NULL) { // check if the file exists otherwise read_indices_global() crash
1898  read_indices_global(parts, basename + ".part", comm);
1899  fclose(fd);
1900  }
1901 
1902  if (parts.size() == 0) return false; // file does not exist or is empty
1903 
1904  if (parts.size() % 2 != 0) {
1905  log_msg(0,5,0, "\nThe part file should contain 2 lines per tag, one for the tag number and the next for its associated partition number.!");
1906  EXIT(1);
1907  }
1908 
1909  for(int i = 0; i < parts.size(); i+=2) {
1910  tags_to_rank_map.insert({parts[i], parts[i + 1]});
1911  }
1912 
1913  if (tags_to_rank_map.size() != static_cast<size_t>(expected_num_tags)) {
1914  log_msg(0,5,0, "\nerror: the number of tags in the .part file does not match the total tags from *.extra and *.intra");
1915  EXIT(1);
1916  }
1917 
1918  return true;
1919 }
1920 
1921 void permute_mesh_locally_based_on_tag_elemIdx(SF::meshdata<mesh_int_t, mesh_real_t>& mesh)
1922 {
1923  mesh.globalize(SF::NBR_REF);
1926  interval(perm, 0, mesh.tag.size());
1927 
1928  SF::vector<mesh_int_t> tags = tmesh.tag;
1930  binary_sort_sort_copy(tags, elemIdx, perm);
1931  permute_mesh(tmesh, mesh, perm);
1932 
1933  mesh.localize(SF::NBR_REF);
1934 }
1935 
1936 } // namespace opencarp
1937 
1938 #endif
int mesh_int_t
Definition: SF_container.h:46
#define SF_COMM
the default SlimFem MPI communicator
Definition: SF_globals.h:27
double SF_real
Use the general double as real type.
Definition: SF_globals.h:38
#define MAX_LOG_LEVEL
Definition: basics.h:331
#define ECHO
Definition: basics.h:324
#define CALI_CXX_MARK_FUNCTION
Definition: caliper_hooks.h:5
#define CALI_MARK_BEGIN(_str)
Definition: caliper_hooks.h:3
#define CALI_MARK_END(_str)
Definition: caliper_hooks.h:4
void globalize(SF_nbr nbr_type)
Localize the connectivity data w.r.t. a given numbering.
Definition: SF_container.h:525
size_t l_numelem
local number of elements
Definition: SF_container.h:399
void localize(SF_nbr nbr_type)
Localize the connectivity data w.r.t. a given numbering.
Definition: SF_container.h:496
MPI_Comm comm
the parallel mesh is defined on a MPI world
Definition: SF_container.h:404
vector< T > & get_numbering(SF_nbr nbr_type)
Get the vector defining a certain numbering.
Definition: SF_container.h:464
vector< T > tag
element tag
Definition: SF_container.h:417
Functor class generating a numbering optimized for PETSc.
Definition: SF_numbering.h:231
Container for a PETSc VecScatter.
Functor class applying a submesh renumbering.
Definition: SF_numbering.h:68
A vector storing arbitrary data.
Definition: SF_vector.h:43
size_t size() const
The current size of the vector.
Definition: SF_vector.h:104
void resize(size_t n)
Resize a vector.
Definition: SF_vector.h:209
hm_int count(const K &key) const
Check if key exists.
Definition: hashmap.hpp:623
void insert(InputIterator first, InputIterator last)
Insert Iterator range.
Definition: hashmap.hpp:583
size_t size() const
Definition: hashmap.hpp:731
size_t size() const
Definition: hashmap.hpp:1152
iterator find(const K &key)
Definition: hashmap.hpp:1092
hm_int erase(const K &key)
Definition: hashmap.hpp:1064
long d_time
current time instance index
Definition: timer_utils.h:77
double time_step
global reference time step
Definition: timer_utils.h:78
int add_eq_timer(double istart, double iend, int ntrig, double iintv, double idur, const char *iname, const char *poolname=nullptr)
Add a equidistant step timer to the array of timers.
Definition: timer_utils.cc:78
long d_end
final index in multiples of dt
Definition: timer_utils.h:82
double time
current time
Definition: timer_utils.h:76
EMI model based on computed current on the faces, main EMI physics class.
void init_solver(SF::abstract_linear_solver< T, S > **sol)
Definition: SF_init.h:220
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:824
void interval(vector< T > &vec, size_t start, size_t end)
Create an integer interval between start and end.
Definition: SF_vector.h:350
void make_global(const vector< T > &vec, vector< T > &out, MPI_Comm comm)
make a parallel vector global
Definition: SF_network.h:225
void permute_mesh(const meshdata< T, S > &inmesh, meshdata< T, S > &outmesh, const vector< T > &perm)
Permute the element data of a mesh based on a given permutation.
Definition: SF_mesh_utils.h:56
void unique_resize(vector< T > &_P)
Definition: SF_sort.h:348
void divide(const size_t gsize, const size_t num_parts, vector< T > &loc_sizes)
divide gsize into num_parts local parts with even distribution of the remainder
Definition: SF_vector.h:358
void count(const vector< T > &data, vector< S > &cnt)
Count number of occurrences of indices.
Definition: SF_vector.h:332
void insert_points(const vector< S > &pts, const vector< T > &ptsidx, std::list< meshdata< T, S > * > &meshlist)
Insert the points from the read-in buffers into a list of distributed meshes.
Definition: SF_mesh_io.h:894
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.
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:608
void redistribute_elements(meshdata< T, S > &mesh, meshdata< T, S > &sendbuff, vector< T > &part)
Redistribute the element data of a parallel mesh among the ranks based on a partitioning.
void restrict_to_set(vector< T > &v, const hashmap::unordered_set< T > &set)
size_t root_count_ascii_lines(std::string file, MPI_Comm comm)
count the lines in a ascii file
void init_vector(SF::abstract_vector< T, S > **vec)
Definition: SF_init.h:99
void binary_sort(vector< T > &_V)
Definition: SF_sort.h:284
void init_matrix(SF::abstract_matrix< T, S > **mat)
Definition: SF_init.h:199
void write_mesh_parallel(const meshdata< T, S > &mesh, bool binary, std::string basename)
Write a parallel mesh to harddisk without gathering it on one rank.
elem_t
element type enum
Definition: SF_container.h:53
@ Line
Definition: SF_container.h:61
@ Tri
Definition: SF_container.h:60
@ Quad
Definition: SF_container.h:59
void binary_sort_sort_copy(vector< T > &_V, vector< T > &_W, vector< S > &_A)
Definition: SF_sort.h:335
@ NBR_PETSC
PETSc numbering of nodes.
Definition: SF_container.h:203
@ NBR_ELEM_REF
The element numbering of the reference mesh (the one stored on HD).
Definition: SF_container.h:204
@ NBR_REF
The nodal numbering of the reference mesh (the one stored on HD).
Definition: SF_container.h:201
@ NBR_SUBMESH
Submesh nodal numbering: The globally ascending sorted reference indices are reindexed.
Definition: SF_container.h:202
@ NBR_ELEM_SUBMESH
Submesh element numbering: The globally ascending sorted reference indices are reindexed.
Definition: SF_container.h:205
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.
timer_manager * tm_manager
a manager for the various physics timers
Definition: main.cc:58
bool using_legacy_stimuli
flag storing whether legacy stimuli are used
Definition: main.cc:64
std::map< mesh_t, sf_mesh > mesh_reg
Registry for the different meshes used in a multi-physics simulation.
Definition: main.cc:52
int stimidx_from_timeridx(const SF::vector< stimulus > &stimuli, const int timer_id)
determine link between timer and stimulus
Definition: electrics.cc:800
@ iotm_console
Definition: timer_utils.h:44
@ iotm_trace
Definition: timer_utils.h:44
sf_vec * get_data(datavec_t d)
Retrieve a petsc data vector from the data registry.
Definition: sim_utils.cc:917
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:852
SF_real get_volume_from_nodes(sf_mat &mass, SF::vector< mesh_int_t > &local_idx)
Definition: fem_utils.cc:217
sf_mesh & get_mesh(const mesh_t gt)
Get a mesh by specifying the gridID.
Definition: sf_interface.cc:33
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:69
cond_t
description of electrical tissue properties
Definition: fem_types.h:42
@ intra_cond
Definition: fem_types.h:43
SF::scattering * get_permutation(const int mesh_id, const int perm_id, const int dpn)
Get the PETSC to canonical permutation scattering for a given mesh and number of dpn.
SF::meshdata< mesh_int_t, mesh_real_t > sf_mesh
Definition: sf_interface.h:48
void apply_stim_to_vector(const stimulus &s, sf_vec &vec, bool add)
Definition: electrics.cc:471
int set_dir(IO_t dest)
Definition: sim_utils.cc:494
void cleanup_and_exit()
Definition: sim_utils.cc:1304
void read_indices_global(SF::vector< T > &idx, const std::string filename, MPI_Comm comm)
Definition: fem_utils.h:53
int get_rank(MPI_Comm comm=PETSC_COMM_WORLD)
Definition: basics.h:292
void indices_from_region_tags(SF::vector< mesh_int_t > &idx, const sf_mesh &mesh, const hashmap::unordered_set< int > &tags)
Populate vertex data with the vertices of multiple tag regions.
Definition: fem_utils.cc:169
const char * get_mesh_type_name(mesh_t t)
get a char* to the name of a mesh type
Definition: sf_interface.cc:46
void region_mask(mesh_t meshspec, SF::vector< RegionSpecs > &regspec, SF::vector< int > &regionIDs, bool mask_elem, const char *reglist)
classify elements/points as belonging to a region
Definition: ionics.cc:363
void init_stim_info(void)
uses potential for stimulation
Definition: stimulate.cc:49
bool is_extra(stim_t type)
whether stimulus is on extra grid (or on intra)
Definition: stimulate.cc:82
FILE_SPEC f_open(const char *fname, const char *mode)
Open a FILE_SPEC.
Definition: basics.cc:138
bool have_dbc_stims(const SF::vector< stimulus > &stimuli)
return wheter any stimuli require dirichlet boundary conditions
Definition: electrics.cc:875
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:72
@ OUTPUT
Definition: sim_utils.h:55
char * dupstr(const char *old_str)
Definition: basics.cc:44
void log_msg(FILE_SPEC out, int level, unsigned char flag, const char *fmt,...)
Definition: basics.cc:72
mesh_t
The enum identifying the different meshes we might want to load.
Definition: sf_interface.h:59
@ emi_surface_unique_face_msh
Definition: sf_interface.h:68
@ emi_surface_msh
Definition: sf_interface.h:66
@ emi_surface_counter_msh
Definition: sf_interface.h:67
void get_time(double &tm)
Definition: basics.h:452
bool mesh_is_registered(const mesh_t gt)
check wheter a SF mesh is set
Definition: sf_interface.cc:63
SF::abstract_vector< SF_int, SF_real > sf_vec
Definition: sf_interface.h:50
int get_size(MPI_Comm comm=PETSC_COMM_WORLD)
Definition: basics.h:306
SF::abstract_matrix< SF_int, SF_real > sf_mat
Definition: sf_interface.h:52
V timing(V &t2, const V &t1)
Definition: basics.h:464
std::string get_basename(const std::string &path)
Definition: basics.cc:61
void f_close(FILE_SPEC &f)
Close a FILE_SPEC.
Definition: basics.cc:165
@ ElecMat
Definition: fem_types.h:39
file_desc * FILE_SPEC
Definition: basics.h:138
Basic physics types.
#define UM2_to_CM2
convert um^2 to cm^2
Definition: physics_types.h:35
#define PETSC_TO_CANONICAL
Permute algebraic data from PETSC to canonical ordering.
Definition: sf_interface.h:79
#define ALG_TO_NODAL
Scatter algebraic to nodal.
Definition: sf_interface.h:77
#define ELEM_PETSC_TO_CANONICAL
Permute algebraic element data from PETSC to canonical ordering.
Definition: sf_interface.h:81
#define EXP_POSTPROCESS
Definition: sim_utils.h:172
Electrical stimulation functions.