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 "mpi_utils.h"
28 #include "physics_types.h"
29 #include "timers.h"
30 #include "stimulate.h"
31 #include "electric_integrators.h"
32 #include "SF_init.h" // for SF::init_xxx()
33 #include "emi.h"
34 #include <array>
35 #include <cmath>
36 #include <cstdint>
37 #include <initializer_list>
38 #include <sys/resource.h>
39 #include <utility>
40 
41 #ifdef WITH_CALIPER
42 #include "caliper/cali.h"
43 #else
44 #include "caliper_hooks.h"
45 #endif
46 
47 namespace opencarp {
48 
49 namespace {
50 
51 template<class Assemble>
52 void assemble_with_exact_preallocation(std::initializer_list<sf_mat*> matrices, Assemble assemble)
53 {
54  bool any_supported = false;
55  bool all_supported = true;
56  bool saw_matrix = false;
57 
58  for(sf_mat* mat : matrices) {
59  if(mat == nullptr) continue;
60 
61  saw_matrix = true;
62  const bool supported = mat->begin_exact_preallocation();
63  any_supported = any_supported || supported;
64  all_supported = all_supported && supported;
65  }
66 
67  if(!saw_matrix) return;
68 
69  // PETSc exact preallocation is a backend-wide mode: either all matrices in
70  // this assembly group support it, or none of them do. Mixed support would
71  // indicate an inconsistent backend state.
72  assert(any_supported == all_supported);
73 
74  if(all_supported) {
75  // First pass collects the sparse graph for PETSc; the second pass below
76  // inserts the real numerical values into the exactly preallocated matrix.
77  assemble();
78  for(sf_mat* mat : matrices) {
79  if(mat != nullptr) mat->finalize_exact_preallocation();
80  }
81  assemble();
82  return;
83  }
84 
85  assemble();
86 }
87 
88 void log_emi_petsc_matrix_preallocation_report(std::initializer_list<std::pair<const char*, sf_mat*>> matrices)
89 {
90 #ifdef WITH_PETSC
91  PetscBool enabled = PETSC_FALSE;
92  PetscOptionsHasName(NULL, NULL, "-mat_view_info", &enabled);
93  if(!enabled) return;
94 
95  // This report estimates PETSc sparse-matrix storage from MatGetInfo().
96  // It is not the total process peak memory. Peak RSS includes meshes,
97  // vectors, solvers, MPI buffers, temporary assembly data, and allocator
98  // overhead, so it should be measured externally, e.g. with
99  // mprof run --include-children followed by mprof peak.
100  int rank = 0;
101  MPI_Comm_rank(PETSC_COMM_WORLD, &rank);
102  if(rank == 0) {
103  log_msg(NULL, 0, 0, "\nEMI PETSc matrix preallocation report");
104  log_msg(NULL, 0, 0, "matrix alloc/used used_est allocated_est overalloc_est");
105  }
106 
107  constexpr double bytes_per_nz = double(sizeof(PetscScalar) + sizeof(PetscInt));
108  constexpr double gib = 1024.0 * 1024.0 * 1024.0;
109  double total_used_gib = 0.0;
110  double total_allocated_gib = 0.0;
111 
112  for(const auto& item : matrices) {
113  auto* petsc_mat = dynamic_cast<SF::petsc_matrix*>(item.second);
114  if(petsc_mat == nullptr || petsc_mat->data == nullptr) continue;
115 
116  MatInfo info;
117  MatGetInfo(petsc_mat->data, MAT_GLOBAL_SUM, &info);
118 
119  const double used = static_cast<double>(info.nz_used);
120  const double allocated = static_cast<double>(info.nz_allocated);
121  const double ratio = used > 0.0 ? allocated / used : 0.0;
122  const double used_gib = used * bytes_per_nz / gib;
123  const double allocated_gib = allocated * bytes_per_nz / gib;
124  const double overallocated_gib = allocated_gib - used_gib;
125  total_used_gib += used_gib;
126  total_allocated_gib += allocated_gib;
127 
128  if(rank == 0) {
129  log_msg(NULL, 0, 0,
130  "%-18s alloc/used=%5.2f used_est=%6.2fG allocated_est=%6.2fG overalloc_est=%6.2fG",
131  item.first, ratio, used_gib, allocated_gib, overallocated_gib);
132  }
133  }
134 
135  if(rank == 0) {
136  const double total_ratio = total_used_gib > 0.0 ? total_allocated_gib / total_used_gib : 0.0;
137  log_msg(NULL, 0, 0,
138  "%-18s alloc/used=%5.2f used_est=%6.2fG allocated_est=%6.2fG overalloc_est=%6.2fG",
139  "TOTAL:", total_ratio, total_used_gib, total_allocated_gib,
140  total_allocated_gib - total_used_gib);
141  }
142 
143  struct rusage usage;
144  getrusage(RUSAGE_SELF, &usage);
145 #ifdef __APPLE__
146  const double local_peak_rss_gib = double(usage.ru_maxrss) / gib;
147 #else
148  const double local_peak_rss_gib = double(usage.ru_maxrss) * 1024.0 / gib;
149 #endif
150  double summed_peak_rss_gib = 0.0;
151  double max_rank_peak_rss_gib = 0.0;
152  MPI_Reduce(&local_peak_rss_gib, &summed_peak_rss_gib, 1, MPI_DOUBLE, MPI_SUM, 0, PETSC_COMM_WORLD);
153  MPI_Reduce(&local_peak_rss_gib, &max_rank_peak_rss_gib, 1, MPI_DOUBLE, MPI_MAX, 0, PETSC_COMM_WORLD);
154 
155  if(rank == 0) {
156  log_msg(NULL, 0, 0,
157  "process peak RSS estimate: summed ranks=%6.2fG max rank=%6.2fG",
158  summed_peak_rss_gib, max_rank_peak_rss_gib);
159  log_msg(NULL, 0, 0,
160  "external peak memory from mprof --include-children is still the recommended whole-run reference.\n");
161  }
162 #else
163  (void)matrices;
164 #endif
165 }
166 
167 } // namespace
168 
169 void log_mesh_local_element_ranges(const sf_mesh& emi_mesh,
170  const sf_mesh& emi_surfmesh_w_counter_face,
171  const sf_mesh& emi_surfmesh_unique_face)
172 {
173  int rank = 0;
174  int comm_size = 0;
175  MPI_Comm_rank(emi_surfmesh_w_counter_face.comm, &rank);
176  MPI_Comm_size(emi_surfmesh_w_counter_face.comm, &comm_size);
177 
178  const size_t local_emi_elems = emi_mesh.l_numelem;
179  const size_t local_both_face_elems = emi_surfmesh_w_counter_face.l_numelem;
180  const size_t local_unique_face_elems = emi_surfmesh_unique_face.l_numelem;
181 
182  std::vector<size_t> all_emi_elems;
183  std::vector<size_t> all_both_face_elems;
184  std::vector<size_t> all_unique_face_elems;
185  if (rank == 0) {
186  all_emi_elems.resize(comm_size, 0);
187  all_both_face_elems.resize(comm_size, 0);
188  all_unique_face_elems.resize(comm_size, 0);
189  }
190 
191  const MPI_Datatype size_mpi_t = mpi_datatype<size_t>();
192  MPI_Gather(&local_emi_elems, 1, size_mpi_t,
193  rank == 0 ? all_emi_elems.data() : nullptr, 1, size_mpi_t,
194  0, emi_surfmesh_w_counter_face.comm);
195  MPI_Gather(&local_both_face_elems, 1, size_mpi_t,
196  rank == 0 ? all_both_face_elems.data() : nullptr, 1, size_mpi_t,
197  0, emi_surfmesh_w_counter_face.comm);
198  MPI_Gather(&local_unique_face_elems, 1, size_mpi_t,
199  rank == 0 ? all_unique_face_elems.data() : nullptr, 1, size_mpi_t,
200  0, emi_surfmesh_w_counter_face.comm);
201 
202  if (rank != 0) return;
203 
204  const auto print_min_max = [](const char* label, const std::vector<size_t>& counts) {
205  if (counts.empty()) return;
206 
207  size_t min_val = counts[0];
208  size_t max_val = counts[0];
209  int min_rank = 0;
210  int max_rank = 0;
211 
212  for (int r = 1; r < static_cast<int>(counts.size()); ++r) {
213  if (counts[r] < min_val) {
214  min_val = counts[r];
215  min_rank = r;
216  }
217  if (counts[r] > max_val) {
218  max_val = counts[r];
219  max_rank = r;
220  }
221  }
222 
223  log_msg(NULL, 0, 0, " %s: \n\t\t min=%zu on rank=%d, \n\t\t max=%zu on rank=%d\n",
224  label, min_val, min_rank, max_val, max_rank);
225  };
226  log_msg(NULL, 0, 0, "\n**********************************");
227  log_msg(NULL, 0, 0, "min/max number of local-element ranges:");
228  print_min_max("emi_mesh", all_emi_elems);
229  print_min_max("emi_surfmesh_w_counter_face", all_both_face_elems);
230  print_min_max("emi_surfmesh_unique_face", all_unique_face_elems);
231  log_msg(NULL, 0, 0, "**********************************");
232 }
233 
234 #ifdef EMI_DEBUG_MESH
235 void log_lhs_positive_definite_probe(SF::abstract_matrix<SF_int, SF_real>* mat,
236  FILE_SPEC logger,
237  const char* stage,
238  int num_trials = 5)
239 {
240  (void)logger;
241  if (param_globals::flavor != std::string("petsc")) return;
242 
243  auto* petsc_mat = dynamic_cast<SF::petsc_matrix*>(mat);
244  if (petsc_mat == nullptr) return;
245 
246  Vec x = NULL, y = NULL;
247  MatCreateVecs(petsc_mat->data, &x, &y);
248 
249  PetscRandom rnd = NULL;
250  PetscRandomCreate(PETSC_COMM_WORLD, &rnd);
251  PetscRandomSetFromOptions(rnd);
252 
253  int rank = 0;
254  MPI_Comm_rank(PETSC_COMM_WORLD, &rank);
255 
256  PetscReal min_q = PETSC_MAX_REAL;
257  PetscReal max_q = -PETSC_MAX_REAL;
258  PetscInt nonpos_count = 0;
259 
260  for (int i = 0; i < num_trials; ++i) {
261  VecSetRandom(x, rnd);
262  MatMult(petsc_mat->data, x, y);
263 
264  PetscScalar q_scalar = 0.0;
265  VecDot(x, y, &q_scalar);
266 
267  const PetscReal q = PetscRealPart(q_scalar);
268  min_q = std::min(min_q, q);
269  max_q = std::max(max_q, q);
270  if (q <= 0.0) nonpos_count++;
271  }
272 
273  if (rank == 0) {
274  PetscPrintf(PETSC_COMM_SELF,
275  "%s: SPD probe with %d random vectors: min(x^T A x)=%g, max(x^T A x)=%g, nonpositive=%d\n",
276  stage, num_trials, double(min_q), double(max_q), int(nonpos_count));
277  }
278 
279  PetscRandomDestroy(&rnd);
280  VecDestroy(&x);
281  VecDestroy(&y);
282 }
283 
284 PetscScalar emi_probe_vector_entry(const PetscInt gid, const int probe_id)
285 {
286  const double x = static_cast<double>(gid + 1);
287 
288  switch (probe_id) {
289  case 0:
290  return std::sin(1.0e-3 * x) + 0.5 * std::cos(3.0e-3 * x);
291  case 1:
292  return std::cos(7.0e-4 * x) - 0.35 * std::sin(2.0e-3 * x);
293  default:
294  return 0.75 * std::sin(1.3e-3 * x) + 0.25 * std::cos(4.0e-3 * x);
295  }
296 }
297 
298 void log_lhs_operator_probe(SF::abstract_matrix<SF_int, SF_real>* mat,
299  FILE_SPEC logger,
300  const char* stage,
301  int num_probes = 3)
302 {
303  (void)logger;
304  if (param_globals::flavor != std::string("petsc")) return;
305 
306  auto* petsc_mat = dynamic_cast<SF::petsc_matrix*>(mat);
307  if (petsc_mat == nullptr) return;
308 
309  Vec x = NULL, y = NULL;
310  MatCreateVecs(petsc_mat->data, &x, &y);
311 
312  PetscInt i_start = 0, i_end = 0;
313  VecGetOwnershipRange(x, &i_start, &i_end);
314 
315  PetscScalar* x_arr = NULL;
316  int rank = 0;
317  MPI_Comm_rank(PETSC_COMM_WORLD, &rank);
318 
319  for (int probe_id = 0; probe_id < num_probes; ++probe_id) {
320  VecGetArray(x, &x_arr);
321  for (PetscInt i = i_start; i < i_end; ++i) {
322  x_arr[i - i_start] = emi_probe_vector_entry(i, probe_id);
323  }
324  VecRestoreArray(x, &x_arr);
325 
326  MatMult(petsc_mat->data, x, y);
327 
328  PetscReal y_norm2 = 0.0;
329  PetscReal y_norminf = 0.0;
330  PetscScalar y_sum = 0.0;
331  PetscScalar xAy = 0.0;
332  VecNorm(y, NORM_2, &y_norm2);
333  VecNorm(y, NORM_INFINITY, &y_norminf);
334  VecSum(y, &y_sum);
335  VecDot(x, y, &xAy);
336 
337  PetscScalar weighted_checksum_local = 0.0;
338  const PetscScalar* y_arr = NULL;
339  VecGetArrayRead(y, &y_arr);
340  for (PetscInt i = i_start; i < i_end; ++i) {
341  const PetscScalar weight = static_cast<PetscScalar>(i + 1);
342  weighted_checksum_local += weight * y_arr[i - i_start];
343  }
344  VecRestoreArrayRead(y, &y_arr);
345 
346  PetscScalar weighted_checksum = 0.0;
347  MPI_Allreduce(&weighted_checksum_local, &weighted_checksum, 1, MPIU_SCALAR, MPI_SUM, PETSC_COMM_WORLD);
348 
349  if (rank == 0) {
350  PetscPrintf(PETSC_COMM_SELF,
351  "%s: operator probe %d ||Ax||_2=%g, ||Ax||_inf=%g, sum(Ax)=%g, x^T A x=%g, weighted_checksum=%g\n",
352  stage, probe_id + 1, double(y_norm2), double(y_norminf), double(PetscRealPart(y_sum)),
353  double(PetscRealPart(xAy)), double(PetscRealPart(weighted_checksum)));
354  }
355  }
356 
357  VecDestroy(&x);
358  VecDestroy(&y);
359 }
360 
361 void log_rhs_probe(SF::abstract_vector<SF_int, SF_real>* vec,
362  const char* stage)
363 {
364  if (param_globals::flavor != std::string("petsc")) return;
365 
366  auto* petsc_vec = dynamic_cast<SF::petsc_vector*>(vec);
367  if (petsc_vec == nullptr) return;
368 
369  PetscReal norm2 = 0.0;
370  PetscReal norminf = 0.0;
371  PetscScalar sum = 0.0;
372  VecNorm(petsc_vec->data, NORM_2, &norm2);
373  VecNorm(petsc_vec->data, NORM_INFINITY, &norminf);
374  VecSum(petsc_vec->data, &sum);
375 
376  PetscInt i_start = 0, i_end = 0;
377  VecGetOwnershipRange(petsc_vec->data, &i_start, &i_end);
378 
379  PetscScalar weighted_checksum_local = 0.0;
380  const PetscScalar* arr = NULL;
381  VecGetArrayRead(petsc_vec->data, &arr);
382  for (PetscInt i = i_start; i < i_end; ++i) {
383  const PetscScalar weight = static_cast<PetscScalar>(i + 1);
384  weighted_checksum_local += weight * arr[i - i_start];
385  }
386  VecRestoreArrayRead(petsc_vec->data, &arr);
387 
388  PetscScalar weighted_checksum = 0.0;
389  MPI_Allreduce(&weighted_checksum_local, &weighted_checksum, 1, MPIU_SCALAR, MPI_SUM, PETSC_COMM_WORLD);
390 
391  int rank = 0;
392  MPI_Comm_rank(PETSC_COMM_WORLD, &rank);
393  if (rank == 0) {
394  PetscPrintf(PETSC_COMM_SELF,
395  "%s: rhs probe ||b||_2=%g, ||b||_inf=%g, sum(b)=%g, weighted_checksum=%g\n",
396  stage, double(norm2), double(norminf), double(PetscRealPart(sum)),
397  double(PetscRealPart(weighted_checksum)));
398  }
399 }
400 
401 void log_linear_system_probe(SF::abstract_matrix<SF_int, SF_real>* mat,
403  const char* stage,
404  int num_probes = 3)
405 {
406  if (param_globals::flavor != std::string("petsc")) return;
407 
408  auto* petsc_mat = dynamic_cast<SF::petsc_matrix*>(mat);
409  auto* petsc_vec = dynamic_cast<SF::petsc_vector*>(vec);
410  if (petsc_mat == nullptr || petsc_vec == nullptr) return;
411 
412  Vec x = NULL, ax = NULL, residual = NULL;
413  MatCreateVecs(petsc_mat->data, &x, &ax);
414  VecDuplicate(ax, &residual);
415 
416  PetscInt i_start = 0, i_end = 0;
417  VecGetOwnershipRange(x, &i_start, &i_end);
418 
419  int rank = 0;
420  MPI_Comm_rank(PETSC_COMM_WORLD, &rank);
421 
422  for (int probe_id = 0; probe_id < num_probes; ++probe_id) {
423  PetscScalar* x_arr = NULL;
424  VecGetArray(x, &x_arr);
425  for (PetscInt i = i_start; i < i_end; ++i) {
426  x_arr[i - i_start] = emi_probe_vector_entry(i, probe_id);
427  }
428  VecRestoreArray(x, &x_arr);
429 
430  MatMult(petsc_mat->data, x, ax);
431  VecWAXPY(residual, -1.0, petsc_vec->data, ax);
432 
433  PetscReal residual_norm2 = 0.0;
434  PetscReal residual_norminf = 0.0;
435  PetscScalar residual_sum = 0.0;
436  PetscScalar xTResidual = 0.0;
437  VecNorm(residual, NORM_2, &residual_norm2);
438  VecNorm(residual, NORM_INFINITY, &residual_norminf);
439  VecSum(residual, &residual_sum);
440  VecDot(x, residual, &xTResidual);
441 
442  PetscScalar weighted_checksum_local = 0.0;
443  const PetscScalar* residual_arr = NULL;
444  VecGetArrayRead(residual, &residual_arr);
445  for (PetscInt i = i_start; i < i_end; ++i) {
446  const PetscScalar weight = static_cast<PetscScalar>(i + 1);
447  weighted_checksum_local += weight * residual_arr[i - i_start];
448  }
449  VecRestoreArrayRead(residual, &residual_arr);
450 
451  PetscScalar weighted_checksum = 0.0;
452  MPI_Allreduce(&weighted_checksum_local, &weighted_checksum, 1, MPIU_SCALAR, MPI_SUM, PETSC_COMM_WORLD);
453 
454  if (rank == 0) {
455  PetscPrintf(PETSC_COMM_SELF,
456  "%s: linear-system probe %d ||Ax-b||_2=%g, ||Ax-b||_inf=%g, sum(Ax-b)=%g, x^T(Ax-b)=%g, weighted_checksum=%g\n",
457  stage, probe_id + 1, double(residual_norm2), double(residual_norminf),
458  double(PetscRealPart(residual_sum)), double(PetscRealPart(xTResidual)),
459  double(PetscRealPart(weighted_checksum)));
460  }
461  }
462 
463  VecDestroy(&x);
464  VecDestroy(&ax);
465  VecDestroy(&residual);
466 }
467 #endif
468 
477 void set_elec_tissue_properties_emi_volume(MaterialType* mtype, hashmap::unordered_set<int> &extra_tags, hashmap::unordered_set<int> &intra_tags, FILE_SPEC logger)
478 {
480  MaterialType *m = mtype;
481 
482  // initialize random conductivity fluctuation structure with PrM values
483  m->regions.resize(param_globals::num_gregions);
484 
485  const char* grid_name = "emi_grid_domain";
486  log_msg(logger, 0, 0, "Setting up %s tissue poperties for %d regions ..", grid_name,
487  param_globals::num_gregions);
488 
489  char buf[64];
490  RegionSpecs* reg = m->regions.data();
491 
492  // default tags for extra and intra domains
493  hashmap::unordered_set<int> extra_tags_default = extra_tags;
494  hashmap::unordered_set<int> intra_tags_default = intra_tags;
495 
496  for (size_t i=0; i<m->regions.size(); i++)
497  {
498  for (int j=0;j<param_globals::gregion[i].num_IDs;j++)
499  {
500  int tag = param_globals::gregion[i].ID[j];
501 
502  // removed tags explicitly defined by user in param_globals::gregion[i>1]
503  if(extra_tags_default.find(tag) != extra_tags_default.end())
504  extra_tags_default.erase(tag);
505 
506  if(intra_tags_default.find(tag) != intra_tags_default.end())
507  intra_tags_default.erase(tag);
508  }
509  }
510 
511  for (size_t i=0; i<m->regions.size(); i++, reg++)
512  {
513  if(!strcmp(param_globals::gregion[i].name, "")) {
514  snprintf(buf, sizeof buf, ", gregion_%d", int(i));
515  param_globals::gregion[i].name = dupstr(buf);
516  }
517 
518  // copy metadata into RegionSpecs
519  reg->regname = strdup(param_globals::gregion[i].name);
520  reg->regID = i;
521 
522  if(i==0) // default Extracellular region
523  reg->nsubregs = extra_tags_default.size();
524  if(i==1) // default intracellular region
525  reg->nsubregs = intra_tags_default.size();
526  if(i>1) // optional: rest of other param_globals::gregion[i>1] defined by user
527  reg->nsubregs = param_globals::gregion[i].num_IDs;
528 
529  if(!reg->nsubregs)
530  reg->subregtags = NULL;
531  else
532  {
533  reg->subregtags = new int[reg->nsubregs];
534 
535  if(i==0){
536  int j = 0;
537  for (int tag : extra_tags_default) {
538  reg->subregtags[j] = tag;
539  j++;
540  }
541  }
542  else if(i==1){
543  int j = 0;
544  for (int tag : intra_tags_default) {
545  reg->subregtags[j] = tag;
546  j++;
547  }
548  }
549  else{
550  for (int j=0;j<reg->nsubregs;j++)
551  reg->subregtags[j] = param_globals::gregion[i].ID[j]; // explicit tags defined by user
552  }
553  }
554 
555  // describe material in given region
556  elecMaterial *emat = new elecMaterial();
557  emat->material_type = ElecMat;
558 
559  // Isotropic conductivity is considered in EMI model.
560  emat->InVal[0] = param_globals::gregion[i].g_bath;
561  emat->InVal[1] = param_globals::gregion[i].g_bath;
562  emat->InVal[2] = param_globals::gregion[i].g_bath;
563 
564  emat->ExVal[0] = param_globals::gregion[i].g_bath;
565  emat->ExVal[1] = param_globals::gregion[i].g_bath;
566  emat->ExVal[2] = param_globals::gregion[i].g_bath;
567 
568  emat->BathVal[0] = param_globals::gregion[i].g_bath;
569  emat->BathVal[1] = param_globals::gregion[i].g_bath;
570  emat->BathVal[2] = param_globals::gregion[i].g_bath;
571 
572  // convert units from S/m -> mS/um
573  for (int j=0; j<3; j++) {
574  emat->InVal[j] *= 1e-3 * param_globals::gregion[i].g_mult;
575  emat->ExVal[j] *= 1e-3 * param_globals::gregion[i].g_mult;
576  emat->BathVal[j] *= 1e-3 * param_globals::gregion[i].g_mult;
577  }
578  reg->material = emat;
579  }
580 
581  if (strlen(param_globals::gi_scale_vec))
582  read_el_scale_vec(param_globals::gi_scale_vec, emi_msh, m->el_scale, m->el_scale_dpn);
583 }
584 
585 void parabolic_solver_emi::init()
586 {
588  double t0, t1, dur;
589  get_time(t0);
590  const int log_flag = param_globals::output_level > 1 ? ECHO : 0;
591  const auto log_init_timing = [&](const char* label, double start) {
592  log_msg(NULL, 0, log_flag, "EMI solver init: %s in %.5f seconds.", label, float(MPI_Wtime() - start));
593  };
594 
595  double phase_t = MPI_Wtime();
596  stats.init_logger("par_stats.dat");
597 
598  // Create/initialise linear solver object: PETSc gets constructed with default settings
599  SF::init_solver(&lin_solver);
600  log_init_timing("linear solver object", phase_t);
601 
602  // EMI mesh: After decoupling the interfaces, we obtain a volumetric mesh with new dofs for all vertices.
603  sf_mesh & emi_mesh = get_mesh(emi_msh);
604  // Surface mesh called emi_surface_counter_msh:
605  // This mesh contains both sides of each interface. At this stage its nodes
606  // already use the decoupled EMI DOF numbering.
607  sf_mesh & emi_surfmesh_w_counter_face = get_mesh(emi_surface_counter_msh);
608  sf_mesh & emi_surfmesh_one_side = get_mesh(emi_surface_msh);
609  sf_mesh & emi_surfmesh_unique_face = get_mesh(emi_surface_unique_face_msh);
610 
611  phase_t = MPI_Wtime();
612  int max_row_entries_emi = max_nodal_edgecount(emi_mesh);
613  log_init_timing("maximum nodal edge counts", phase_t);
614 
615  int rank;
616  MPI_Comm_rank(emi_surfmesh_w_counter_face.comm, &rank);
617 
618  sf_vec::ltype alg_type = sf_vec::algebraic;
619  sf_vec::ltype alg_surface_type = sf_vec::elemwise;
620 
621  int dpn = 1;
622 
623  //-----------------------------------------------------------------
624  // setup vectors
625  //-----------------------------------------------------------------
626  phase_t = MPI_Wtime();
627  SF::init_vector(&ui, emi_mesh, dpn, alg_type);
628  SF::init_vector(&dui, emi_mesh, dpn, alg_type);
629  SF::init_vector(&ui_pre, emi_mesh, dpn, alg_type);
630  SF::init_vector(&Irhs, emi_mesh, dpn, alg_type);
631  SF::init_vector(&Iij_stim, emi_mesh, dpn, alg_type);
632  SF::init_vector(&Iij_temp, emi_mesh, dpn, alg_type);
633  SF::init_vector(&vb_both_face, emi_surfmesh_w_counter_face, dpn, alg_surface_type);
634  SF::init_vector(&vb_unique_face, emi_surfmesh_unique_face, dpn, alg_surface_type);
635  SF::init_vector(&Ib_both_face, emi_surfmesh_w_counter_face, dpn, alg_surface_type);
636  SF::init_vector(&Ib_unique_face, emi_surfmesh_unique_face, dpn, alg_surface_type);
637  log_init_timing("vectors", phase_t);
638 
639  // PETSc matrices are reallocated with an exact sparsity graph by replaying
640  // the real assembly into MATPREALLOCATOR before the numeric assembly. The
641  // scalar hint used here is therefore only a temporary bootstrap value for
642  // PETSc; non-PETSc backends keep the conservative scalar bounds.
643  const bool use_petsc_exact_preallocation = param_globals::flavor == std::string("petsc");
644  const int petsc_initial_prealloc = 1;
645  //---------------------------------------------------------------------
646  // initialize operator matrices B, Bi, and BsM
647  //---------------------------------------------------------------------
648  mesh_int_t M = emi_surfmesh_w_counter_face.g_numelem;
649  mesh_int_t N = emi_mesh.g_numpts;
650  mesh_int_t m = emi_surfmesh_w_counter_face.l_numelem;
651  mesh_int_t m_one_side = emi_surfmesh_one_side.l_numelem;
652  mesh_int_t M_one_side = emi_surfmesh_one_side.g_numelem;
653  mesh_int_t m_unique_face = emi_surfmesh_unique_face.l_numelem;
654  mesh_int_t M_unique_face = emi_surfmesh_unique_face.g_numelem;
655  mesh_int_t n = ui->lsize();
656 
657  if (param_globals::output_level > 1) {
658  log_msg(NULL, 0, 0, "\n**********************************");
659  log_msg(NULL, 0, 0, "#elements of emi surfmesh unique face: %zu", emi_surfmesh_unique_face.g_numelem);
660  log_msg(NULL, 0, 0, "#elements of emi surfmesh one side: %zu", emi_surfmesh_one_side.g_numelem);
661  log_msg(NULL, 0, 0, "#elements of emi surfmesh: %zu", emi_surfmesh_w_counter_face.g_numelem);
662  log_msg(NULL, 0, 0, "#elements of emi mesh: %zu", emi_mesh.g_numelem);
663  log_msg(NULL, 0, 0, "#dofs for emi_mesh: %zu", emi_mesh.g_numpts);
664  log_msg(NULL, 0, 0, "#max_row_entries_emi: %zu", max_row_entries_emi);
665  log_msg(NULL, 0, 0, "**********************************\n");
666  log_mesh_local_element_ranges(emi_mesh, emi_surfmesh_w_counter_face, emi_surfmesh_unique_face);
667  }
668 
669  SF::vector<long int> layout;
670  SF::layout_from_count<long int>(emi_surfmesh_w_counter_face.l_numelem, layout, emi_surfmesh_w_counter_face.comm);
671  mesh_int_t m_l = layout[rank];
672  mesh_int_t n_l = emi_mesh.pl.algebraic_layout()[rank];
673  const SF::vector<mesh_int_t> & alg_nod_surface = emi_surfmesh_w_counter_face.pl.algebraic_nodes();
674 
675  SF::vector<long int> layout_one_side;
676  SF::layout_from_count<long int>(emi_surfmesh_one_side.l_numelem, layout_one_side, emi_surfmesh_one_side.comm);
677  mesh_int_t m_one_side_l = layout_one_side[rank];
678 
679  SF::vector<long int> layout_unique_face;
680  SF::layout_from_count<long int>(emi_surfmesh_unique_face.l_numelem, layout_unique_face, emi_surfmesh_unique_face.comm);
681  mesh_int_t m_unique_face_l = layout_unique_face[rank];
682 
683  // B, Bi, and BsM have face/volume row spaces whose final PETSc sparsity is
684  // collected exactly below by assemble_with_exact_preallocation(). The scalar
685  // fallback remains conservative for non-PETSc backends and off-process row
686  // insertion through the abstract matrix interface. Resting-potential
687  // initialization writes directly into ui and no longer needs a diagonal helper
688  // matrix.
689  phase_t = MPI_Wtime();
690  SF::init_matrix(&B);
691  B->init(M, N, m, n, m_l, use_petsc_exact_preallocation ? petsc_initial_prealloc : max_row_entries_emi*2);
692  B->zero();
693  SF::init_matrix(&Bi);
694  Bi->init(M, N, m, n, m_l, use_petsc_exact_preallocation ? petsc_initial_prealloc : max_row_entries_emi*2);
695  Bi->zero();
696  SF::init_matrix(&BsM);
697  BsM->init(N, M, n, m, n_l, use_petsc_exact_preallocation ? petsc_initial_prealloc : max_row_entries_emi*2);
698  BsM->zero();
699  log_init_timing("EMI coupling matrices", phase_t);
700 
701  // Build the direct unique-face <-> both-face transfer operators
702  phase_t = MPI_Wtime();
703  SF::construct_direct_unique_both_operators(operator_unique_to_both_faces,
704  operator_both_to_unique_face,
705  map_elem_uniqueFace_to_elem_bothface,
706  map_elem_uniqueFace_to_elem_oneface,
707  vec_both_to_one_face,
708  emi_surfmesh_w_counter_face,
709  emi_surfmesh_unique_face,
710  max_row_entries_emi,
711  dpn,
712  alg_surface_type);
713  log_init_timing("unique/both face transfer operators", phase_t);
714 
715  // Initialize interpolation operators B/Bi and the scatter operator BsM.
716  phase_t = MPI_Wtime();
717  assemble_with_exact_preallocation({B, Bi, BsM}, [&]() {
718  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);
719  });
720  log_init_timing("restriction operators", phase_t);
721 
722  //-----------------------------------------------------------------
723  // initialize matrices (LHS, K, M_{emi mesh}, M_{surface mesh})
724  //-----------------------------------------------------------------
725  phase_t = MPI_Wtime();
726  SF::init_matrix(&lhs_emi);
727  SF::init_matrix(&stiffness_emi);
728  SF::init_matrix(&mass_emi);
729  SF::init_matrix(&mass_surf_emi);
730 
731  // These matrices are filled later in rebuild_matrices(). Allocating them
732  // after the transfer/restriction operators reduces the EMI init memory peak.
733  lhs_emi->init(emi_mesh, dpn, dpn, use_petsc_exact_preallocation ? petsc_initial_prealloc : max_row_entries_emi*3);
734  stiffness_emi->init(emi_mesh, dpn, dpn, use_petsc_exact_preallocation ? petsc_initial_prealloc : max_row_entries_emi*2);
735  mass_emi->init(emi_mesh, dpn, dpn, use_petsc_exact_preallocation ? petsc_initial_prealloc : max_row_entries_emi*3);
736  mass_surf_emi->init(emi_mesh, dpn, dpn, use_petsc_exact_preallocation ? petsc_initial_prealloc : max_row_entries_emi*3);
737  log_init_timing("system matrices", phase_t);
738 
739  // DEBUG: Check mesh sizes and mappings
740  #ifdef EMI_DEBUG_MESH
741  {
742  int local_rank;
743  MPI_Comm_rank(emi_mesh.comm, &local_rank);
744 
745  fprintf(stderr, "RANK %d MESH SIZES: one_side=%zu, counter=%zu, unique=%zu\n",
746  local_rank, emi_surfmesh_one_side.l_numelem,
747  emi_surfmesh_w_counter_face.l_numelem, emi_surfmesh_unique_face.l_numelem);
748 
749  fprintf(stderr, "RANK %d MAP SIZES: uniqueFace_to_oneface=%zu\n",
750  local_rank, map_elem_uniqueFace_to_elem_oneface.size());
751  fflush(stderr);
752  }
753  #endif
754 
755  decltype(map_elem_uniqueFace_to_elem_oneface)().swap(map_elem_uniqueFace_to_elem_oneface);
756  decltype(map_elem_uniqueFace_to_elem_bothface)().swap(map_elem_uniqueFace_to_elem_bothface);
757  vec_both_to_one_face = SF::vector<mesh_int_t>();
758 
759  //-----------------------------------------------------------------
760  // setup the ionic current
761  //-----------------------------------------------------------------
762  phase_t = MPI_Wtime();
763  // get initial value of the vm and Iion on the face from the ionicOnFace class
764  sf_vec* vb_ptr = get_data(vm_emi_itf);
765  sf_vec* Ib_ptr = get_data(iion_emi_itf);
766 
767  if(!(vb_ptr != NULL && Ib_ptr != NULL)) {
768  log_msg(0,5,0, "%s error: global Vb and Ib vectors not properly set up! Ionics seem invalid! Aborting!",
769  __func__);
770  EXIT(1);
771  }
772 
773  SF::init_vector(&vb, vb_ptr);
774  vb->shallow_copy(*vb_ptr);
775  SF::init_vector(&Ib, Ib_ptr);
776  Ib->shallow_copy(*Ib_ptr);
777 
778  parab_tech = static_cast<parabolic_solver_emi::parabolic_t>(param_globals::parab_solve_emi);
779  log_init_timing("ionic face vectors", phase_t);
780 
781  dur = timing(t1, t0);
782  log_msg(NULL, 0, log_flag, "EMI solver init total in %.5f seconds.", float(dur));
783 }
784 
785 void parabolic_solver_emi::rebuild_matrices(MaterialType* mtype, limpet::MULTI_IF & miif, SF::vector<stimulus> & stimuli, FILE_SPEC logger)
786 {
788  double start, end, period;
789  get_time(start);
790  double t0, t1, dur;
791  mass_integrator mass_integ;
792  mass_integrator mass_integ_emi;
793  int dpn = 1;
794 
795  int log_flag = param_globals::output_level > 1 ? ECHO : 0;
796  MaterialType & mt = mtype[0];
797  const bool have_dbc = have_dbc_stims(stimuli);
798  const bool use_petsc_exact_preallocation = param_globals::flavor == std::string("petsc");
799  const bool reuse_petsc_fem_preallocation = use_petsc_exact_preallocation && fem_matrices_exact_preallocated;
800 
801  double Dt = user_globals::tm_manager->time_step;
802 
803  cond_t condType = intra_cond;
804  sf_mesh & mesh = get_mesh(emi_msh);
805  sf_mesh & emi_surfmesh_w_counter_face = get_mesh(emi_surface_counter_msh);
806 
807  // set material and conductivity type
808  set_cond_type(mt, condType);
809 
810  // assemble EMI matrices
811  // fill the EMI system
812  {
813  // emi-> stiffness
814  log_msg(NULL, 0, 0, "assemble stiffness matrix");
815  get_time(t0);
816  elec_stiffness_integrator stfn_integ_emi(mt);
817  auto assemble_stiffness_emi = [&]() {
818  stiffness_emi->zero();
819  SF::assemble_matrix(*stiffness_emi, mesh, stfn_integ_emi);
820  };
821  if(reuse_petsc_fem_preallocation) {
822  assemble_stiffness_emi();
823  } else {
824  assemble_with_exact_preallocation({stiffness_emi}, assemble_stiffness_emi);
825  }
826  dur = timing(t1,t0);
827  log_msg(logger,0,log_flag, "Computed parabolic stiffness matrix in %.5f seconds.", float(dur));
828 
829  log_msg(NULL, 0, 0, "assemble mass matrix on the volumetric mesh");
830  get_time(t0);
831  mass_integrator mass_integ;
832  auto assemble_mass_emi = [&]() {
833  mass_emi->zero();
834  SF::assemble_matrix(*mass_emi, mesh, mass_integ);
835  };
836  auto* petsc_mass_emi = use_petsc_exact_preallocation ? dynamic_cast<SF::petsc_matrix*>(mass_emi) : nullptr;
837  auto* petsc_stiffness_emi = use_petsc_exact_preallocation ? dynamic_cast<SF::petsc_matrix*>(stiffness_emi) : nullptr;
838  if(reuse_petsc_fem_preallocation) {
839  assemble_mass_emi();
840  } else if(petsc_mass_emi != nullptr && petsc_stiffness_emi != nullptr) {
841  // mass_emi and stiffness_emi have the same volume FEM sparsity pattern.
842  petsc_mass_emi->duplicate_pattern(*petsc_stiffness_emi);
843  assemble_mass_emi();
844  } else {
845  assemble_with_exact_preallocation({mass_emi}, assemble_mass_emi);
846  }
847  dur = timing(t1,t0);
848  log_msg(logger,0,log_flag, "Computed volumetric mass matrix in %.5f seconds.", float(dur));
849 
850  log_msg(NULL, 0, 0, "assemble LHS matrix and mass matrix on the surface mesh");
851  get_time(t0);
852  auto assemble_lhs_and_surface_mass = [&]() {
853  lhs_emi->zero();
854  mass_surf_emi->zero();
855  // lhs_emi = (CmM/dt + K)
856  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);
857  };
858  if(reuse_petsc_fem_preallocation) {
859  assemble_lhs_and_surface_mass();
860  } else {
861  assemble_with_exact_preallocation({lhs_emi, mass_surf_emi}, assemble_lhs_and_surface_mass);
862  }
863  dur = timing(t1,t0);
864  log_msg(logger,0,log_flag, "Computed parabolic mass matrix in %.5f seconds.", float(dur));
865  }
866 
867 
868  bool same_nonzero = false;
869 
870  // set boundary conditions
871  if(have_dbc) {
872  log_msg(logger,0,log_flag, "lhs matrix enforcing Dirichlet boundaries.");
873  get_time(t0);
874 
875  if(dbc == nullptr)
876  dbc = new dbc_manager(*lhs_emi, stimuli);
877  else
878  dbc->recompute_dbcs();
879 
880  dbc->enforce_dbc_lhs();
881  dur = timing(t1,t0);
882  log_msg(logger,0,log_flag, "lhs matrix Dirichlet enforcing done in %.5f seconds.", float(dur));
883  }
884  else {
885  log_msg(logger,0,ECHO, "without enforcing Dirichlet boundaries on the lhs matrix!");
886  // we are dealing with a singular system
887  phie_mat_has_nullspace = true;
888  }
889 
890  set_dir(INPUT);
891  log_emi_petsc_matrix_preallocation_report({
892  {"B:", B},
893  {"Bi:", Bi},
894  {"BsM:", BsM},
895  {"unique_to_both:", operator_unique_to_both_faces},
896  {"both_to_unique:", operator_both_to_unique_face},
897  {"stiffness_emi:", stiffness_emi},
898  {"mass_emi:", mass_emi},
899  {"mass_surf_emi:", mass_surf_emi},
900  {"lhs_emi:", lhs_emi},
901  });
902  if(use_petsc_exact_preallocation) fem_matrices_exact_preallocated = true;
903  get_time(t0);
904  setup_linear_solver(logger);
905  dur = timing(t1,t0);
906  log_msg(logger,0,log_flag, "Initializing parabolic solver in %.5f seconds.", float(dur));
907  set_dir(OUTPUT);
908 
909  period = timing(end, start);
910 }
911 
912 void parabolic_solver_emi::setup_linear_solver(FILE_SPEC logger)
913 {
914  tol = param_globals::cg_tol_parab;
915  max_it = param_globals::cg_maxit_parab;
916 
917  std::string default_opts;
918  std::string solver_file;
919  solver_file = param_globals::parab_options_file;
920  if (param_globals::flavor == std::string("ginkgo")) {
921  default_opts = std::string(
922  R"(
923 {
924  "type": "solver::Cg",
925  "preconditioner": {
926  "type": "solver::Multigrid",
927  "min_coarse_rows": 8,
928  "max_levels": 16,
929  "default_initial_guess": "zero",
930  "mg_level": [
931  {
932  "type": "multigrid::Pgm",
933  "deterministic": false
934  }
935  ],
936  "coarsest_solver": {
937  "type": "preconditioner::Schwarz",
938  "local_solver": {
939  "type": "preconditioner::Jacobi"
940  }
941  },
942  "criteria": [
943  {
944  "type": "Iteration",
945  "max_iters": 1
946  }
947  ]
948  },
949  "criteria": [
950  {
951  "type": "Iteration",
952  "max_iters": 100
953  },
954  {
955  "type": "ResidualNorm",
956  "reduction_factor": 1e-4
957  }
958  ]
959 }
960  )");
961  } else if (param_globals::flavor == std::string("petsc")) {
962  default_opts = std::string("-ksp_type cg -pc_type gamg -options_left");
963  }
964  lin_solver->setup_solver(*lhs_emi, tol, max_it * 100, param_globals::cg_norm_parab,
965  "parabolic PDE", phie_mat_has_nullspace, logger, solver_file.c_str(),
966  default_opts.c_str());
967 }
968 
969 void parabolic_solver_emi::solve()
970 {
971  switch (parab_tech) {
972  case SEMI_IMPLICIT: solve_semiImplicit(); break;
973  }
974 }
975 
976 void parabolic_solver_emi::solve_semiImplicit()
977 {
978  double t0,t1;
979  get_time(t0);
980 
981  if(dbc != nullptr){
982  CALI_MARK_BEGIN("apply_dbc_rhs");
983  dbc->enforce_dbc_rhs(*ui);
984  CALI_MARK_END("apply_dbc_rhs");
985  }
986 
987  *ui_pre = *ui;
988 
989  // -K*u (K is assembled as -K)
990  CALI_MARK_BEGIN("stiff_mat");
991  stiffness_emi->mult(*ui, *Iij_temp);
992  // rhs = Iij + K*u
993  *Irhs -= *Iij_temp;
994  CALI_MARK_END("stiff_mat");
995 
996  // add volumetric stimulus currents (I_ex, I_in)
997  // rhs = Iij + K*u + M*Iij_stim
998  if (Iij_stim->mag() > 0.0) {
999  CALI_MARK_BEGIN("stim_application");
1000  mass_emi->mult(*Iij_stim, *Iij_temp);
1001  *Irhs -= *Iij_temp;
1002  CALI_MARK_BEGIN("stim_application");
1003  }
1004 
1005  // rhs = -(Iij + K*u + M*Iij_stim)
1006  CALI_MARK_BEGIN("rhs_update");
1007  (*Irhs) *= (-1.0);
1008  CALI_MARK_END("rhs_update");
1009 
1010  // compute step
1011  CALI_MARK_BEGIN("linear_solve");
1012  (*lin_solver)(*dui, *Irhs);
1013  CALI_MARK_END("linear_solve");
1014 
1015  // logfile for solver
1016  if(lin_solver->reason < 0) {
1017  log_msg(0, 5, 0,"%s solver diverged. Reason: %s.", lin_solver->name.c_str(),
1018  petsc_get_converged_reason_str(lin_solver->reason));
1019  EXIT(1);
1020  }
1021 
1022  // update solution:: ui_pre = ui_pre + dui
1023  CALI_MARK_BEGIN("sol_update");
1024  ui_pre->add_scaled(*dui, 1.0);
1025  *ui *=0;
1026  ui->add_scaled(*ui_pre, 1.0);
1027  CALI_MARK_END("sol_update");
1028 
1029  // We need to enforce DBCs again after the solution vector was updated.
1030  // Otherwise, matrix/solver tolerances allow small nonzero values in dui to accumulate in ui.
1031  if(dbc != nullptr){
1032  CALI_MARK_BEGIN("apply_dbc_rhs");
1033  dbc->enforce_dbc_rhs(*ui);
1034  CALI_MARK_END("apply_dbc_rhs");
1035  }
1036 
1037  // treat solver statistics
1038  stats.slvtime += timing(t1, t0);
1039  stats.update_iter(lin_solver->niter);
1040 }
1041 
1043  std::pair<SF::emi_face<mesh_int_t,SF::tuple<mesh_int_t>>,
1044  SF::emi_face<mesh_int_t,SF::tuple<mesh_int_t>>>> & line_face,
1046  std::pair<SF::emi_face<mesh_int_t,SF::triple<mesh_int_t>>,
1047  SF::emi_face<mesh_int_t,SF::triple<mesh_int_t>>>> & tri_face,
1049  std::pair<SF::emi_face<mesh_int_t,SF::quadruple<mesh_int_t>>,
1050  SF::emi_face<mesh_int_t,SF::quadruple<mesh_int_t>>>> & quad_face,
1051  hashmap::unordered_map<std::pair<mesh_int_t,mesh_int_t>, mesh_int_t> & map_vertex_tag_to_dof,
1052  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,
1053  std::vector<std::string> & tags_data)
1054 {
1055  sf_mesh & emi_surfmesh_w_counter_face = get_mesh(emi_surface_counter_msh);
1056 
1057  const SF::vector<mesh_int_t> & tags = emi_surfmesh_w_counter_face.tag;
1058 
1059  const SF::vector<mesh_int_t> & rnod = emi_surfmesh_w_counter_face.get_numbering(SF::NBR_REF);
1061 
1062  for(size_t i=0; i<rnod.size(); i++){
1063  l2g[i] = rnod[i];
1064  }
1065 
1066  for(size_t eidx=0; eidx<emi_surfmesh_w_counter_face.l_numelem; eidx++)
1067  {
1068  std::vector<mesh_int_t> elem_nodes;
1069  mesh_int_t tag = emi_surfmesh_w_counter_face.tag[eidx];
1070  for (int n = emi_surfmesh_w_counter_face.dsp[eidx]; n < emi_surfmesh_w_counter_face.dsp[eidx+1];n++)
1071  {
1072  mesh_int_t l_idx = emi_surfmesh_w_counter_face.con[n];
1073 
1074  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
1075  Index_tag_old = std::make_pair(l2g[l_idx],tags[eidx]);
1076  mesh_int_t dof = map_vertex_tag_to_dof[Index_tag_old];
1077  elem_nodes.push_back(dof);
1078  }
1079 
1080  mesh_int_t tag_first = 0;
1081  mesh_int_t tag_second = 0;
1082  std::string result_first;
1083  std::string result_second;
1084  std::sort(elem_nodes.begin(),elem_nodes.end()); // make the node tuple order-independent
1085 
1086  // Extract all surface face pairs separating regions with different material or boundary tags (ionicFaces)
1087  if(elem_nodes.size()==2){
1089 
1090  key.v1 = elem_nodes[0];
1091  key.v2 = elem_nodes[1];
1092  std::pair<SF::emi_face<mesh_int_t,SF::tuple<mesh_int_t>>,
1093  SF::emi_face<mesh_int_t,SF::tuple<mesh_int_t>>> value = line_face[key];
1094 
1095  tag_first = value.first.tag;
1096  tag_second = value.second.tag;
1097  result_first = std::to_string(tag_first) + ":" + std::to_string(tag_second);
1098  result_second = std::to_string(tag_second) + ":" + std::to_string(tag_first);
1099  }
1100  else if(elem_nodes.size()==3){
1102  key.v1 = elem_nodes[0];
1103  key.v2 = elem_nodes[1];
1104  key.v3 = elem_nodes[2];
1105  std::pair<SF::emi_face<mesh_int_t,SF::triple<mesh_int_t>>,
1106  SF::emi_face<mesh_int_t,SF::triple<mesh_int_t>>> value = tri_face[key];
1107 
1108  tag_first = value.first.tag;
1109  tag_second = value.second.tag;
1110  result_first = std::to_string(tag_first) + ":" + std::to_string(tag_second);
1111  result_second = std::to_string(tag_second) + ":" + std::to_string(tag_first);
1112  }
1113  else if(elem_nodes.size()==4){
1115  key.v1 = elem_nodes[0];
1116  key.v2 = elem_nodes[1];
1117  key.v3 = elem_nodes[2];
1118  key.v4 = elem_nodes[3];
1119  std::pair<SF::emi_face<mesh_int_t,SF::quadruple<mesh_int_t>>,
1120  SF::emi_face<mesh_int_t,SF::quadruple<mesh_int_t>>> value = quad_face[key];
1121 
1122  tag_first = value.first.tag;
1123  tag_second = value.second.tag;
1124  result_first = std::to_string(tag_first) + ":" + std::to_string(tag_second);
1125  result_second = std::to_string(tag_second) + ":" + std::to_string(tag_first);
1126  }
1127  // check if current element tag has a custom region ID
1128  tags_data.push_back(result_first);
1129  tags_data.push_back(result_second);
1130  }
1131 }
1132 
1133 void EMI::initialize()
1134 {
1135  double t1, t2;
1136  get_time(t1);
1137 
1138  set_dir(OUTPUT);
1139 
1140  // open logger
1141  logger = f_open("emi.log", param_globals::experiment != 4 ? "w" : "r");
1142  const int verb = param_globals::output_level;
1143  const auto log_init_timing = [&](const char* label, double start) {
1144  if(verb)
1145  log_msg(logger, 0, ECHO, "EMI init: %s in %.5f seconds.", label, float(MPI_Wtime() - start));
1146  };
1147 
1148  double phase_t = MPI_Wtime();
1149  // Mesh processing step: convert the input mesh into
1150  // - an EMI mesh (with discontinuities at gap junctions and membranes)
1151  // - and generate the corresponding surface mesh
1152  CALI_MARK_BEGIN("mesh_setup");
1153  setup_EMI_mesh();
1154  log_init_timing("mesh setup", phase_t);
1155 
1156  // setup mappings between extra and intra grids, algebraic and nodal,
1157  // and between PETSc and canonical orderings
1158  phase_t = MPI_Wtime();
1159  setup_mappings();
1160  log_init_timing("mesh mappings", phase_t);
1161 
1162  // the ionicOnFace physics is currently triggered from inside the emi to have tighter
1163  // control over it
1164  phase_t = MPI_Wtime();
1165  ion.logger = logger;
1166 
1167  ion.set_surface_mesh_data(parab_solver.line_face,
1168  parab_solver.tri_face,
1169  parab_solver.quad_face,
1170  parab_solver.map_vertex_tag_to_dof);
1171 
1172  // builds per-face adjacency tags
1173  std::vector<std::string> tags_data;
1174  tags_onFace(parab_solver.line_face,
1175  parab_solver.tri_face,
1176  parab_solver.quad_face,
1177  parab_solver.map_vertex_tag_to_dof,
1178  parab_solver.map_vertex_tag_to_dof_petsc,
1179  tags_data);
1180  CALI_MARK_END("mesh_setup");
1181  log_init_timing("ionic face metadata", phase_t);
1182 
1183  ion.set_tags_onFace(tags_data);
1184 
1185  ion.set_face_region_data(parab_solver.intra_tags, parab_solver.map_elem_uniqueFace_to_tags);
1186  phase_t = MPI_Wtime();
1187  ion.initialize();
1188  log_init_timing("ionic model initialization", phase_t);
1189 
1190  phase_t = MPI_Wtime();
1191  // set up tissue properties on the extra and intracellular domains
1192  set_elec_tissue_properties_emi_volume(mtype_vol, parab_solver.extra_tags, parab_solver.intra_tags, logger);
1193  // In EMI, the default extra/intra compartment tags are intentionally handled
1194  // as the implicit regions 0 and 1, so the generic "unassigned" warning is
1195  // not useful here.
1196  region_mask(emi_msh, mtype_vol[0].regions, mtype_vol[0].regionIDs, true, "gregion_vol", false);
1197 
1198  // add electrics timer for time stepping, add to time stepper tool (TS)
1199  double global_time = user_globals::tm_manager->time;
1200  timer_idx = user_globals::tm_manager->add_eq_timer(global_time, param_globals::tend, 0,
1201  param_globals::dt, 0, "elec::ref_dt", "TS");
1202  log_init_timing("tissue properties and timers", phase_t);
1203 
1204  // EMI stimuli setup
1205  CALI_MARK_BEGIN("stimulus_setup");
1206  phase_t = MPI_Wtime();
1207  param_globals::operator_splitting = 0; // EMI does not use operator splitting, so keep stimulus scaling at the monodomain/default setting.
1208  setup_stimuli();
1209  log_init_timing("stimuli", phase_t);
1210  CALI_MARK_END("stimulus_setup");
1211 
1212  // set up the linear equation systems. this needs to happen after the stimuli have been
1213  // set up, since we need boundary condition info
1214  CALI_MARK_BEGIN("solver_setup");
1215  phase_t = MPI_Wtime();
1216  setup_solvers();
1217  log_init_timing("solver setup", phase_t);
1218  CALI_MARK_END("solver_setup");
1219 
1220  phase_t = MPI_Wtime();
1221  // Balance paired electrodes before total-current scaling.
1222  balance_electrodes();
1223  // total current scaling
1224  scale_total_stimulus_current(stimuli, *parab_solver.mass_emi, *parab_solver.mass_surf_emi, logger);
1225  log_init_timing("stimulus current scaling", phase_t);
1226 
1227  sf_mesh & emi_mesh = get_mesh(emi_msh);
1228  sf_mesh & emi_surfmesh_w_counter_face = get_mesh(emi_surface_counter_msh);
1229 
1230  phase_t = MPI_Wtime();
1231  // Initialize ui from the ionic resting membrane voltage:
1232  // - vb is defined on the unique-face ionic layout.
1233  // - The direct unique -> both operator expands it to the both-face layout.
1234  // - assign_resting_potential_from_ionic_models_on_myocyte writes the matching
1235  // membrane voltage into intracellular volume DOFs and zero into extracellular DOFs.
1236  parab_solver.operator_unique_to_both_faces->mult(*parab_solver.vb, *parab_solver.vb_both_face);
1237  SF::assign_resting_potential_from_ionic_models_on_myocyte(*parab_solver.ui,
1238  parab_solver.vb_both_face,
1239  parab_solver.elemTag_emi_mesh,
1240  parab_solver.map_vertex_tag_to_dof_petsc,
1241  parab_solver.line_face, parab_solver.tri_face, parab_solver.quad_face,
1242  emi_surfmesh_w_counter_face, emi_mesh);
1243 
1244  *parab_solver.vb_unique_face = *parab_solver.vb;
1245  log_init_timing("initial membrane state projection", phase_t);
1246 
1247  CALI_MARK_BEGIN("output_setup");
1248  phase_t = MPI_Wtime();
1249  // prepare the electrics output. we skip it if we do post-processing
1250  if(param_globals::experiment != EXP_POSTPROCESS)
1251  setup_output();
1252  log_init_timing("output setup", phase_t);
1253  CALI_MARK_END("output_setup");
1254 
1255  const double init_dur = timing(t2, t1);
1256  this->initialize_time += init_dur;
1257  if(verb)
1258  log_msg(logger, 0, ECHO, "EMI init total in %.5f seconds.", float(init_dur));
1259 }
1260 
1261 void EMI::setup_mappings()
1262 {
1263  bool emi_exits = mesh_is_registered(emi_msh);
1264  assert(emi_exits);
1265  const int dpn = 1;
1266 
1267  if(get_scattering(emi_msh, ALG_TO_NODAL, dpn) == NULL)
1268  {
1269  log_msg(logger, 0, 0, "%s: Setting up intracellular algebraic-to-nodal scattering.", __func__);
1271  }
1272  if(get_permutation(emi_msh, PETSC_TO_CANONICAL, dpn) == NULL)
1273  {
1274  log_msg(logger, 0, 0, "%s: Setting up intracellular PETSc to canonical permutation.", __func__);
1276  }
1277 }
1278 
1279 void EMI::compute_step()
1280 {
1281  double t1, t2;
1282  get_time(t1);
1283 
1284  // activation checking
1285  const double time = user_globals::tm_manager->time,
1286  time_step = user_globals::tm_manager->time_step;
1287 
1288  const int verb = param_globals::output_level;
1289  // We treat stimuli by type:
1290  // - Potential stimuli (Phi_ex, GND_ex, Phi_ex_ol, Phi_in, Phi_in_ol) are
1291  // managed by a dbc_manager and applied to the left- and right-hand side.
1292  // - Current stimuli (I_ex, I_in) are applied to the right hand side,
1293  // while (I_tm) is applied to the vector Ib directly.
1294  CALI_MARK_BEGIN("apply_dbc_lhs");
1295  apply_dbc_stimulus();
1296  CALI_MARK_END("apply_dbc_lhs");
1297 
1298  // compute ionics update
1299  CALI_MARK_BEGIN("ion_compute");
1300  ion.compute_step();
1301  CALI_MARK_END("ion_compute");
1302 
1303  CALI_MARK_BEGIN("apply_stim");
1304  apply_current_stimulus();
1305  CALI_MARK_END("apply_stim");
1306 
1307  // convert Ib -> Irhs
1308  parab_solver.operator_unique_to_both_faces->mult(*parab_solver.Ib, *parab_solver.Ib_both_face);
1309  parab_solver.BsM->mult(*parab_solver.Ib_both_face, *parab_solver.Irhs);
1310 
1311  // solver parabolic system
1312  CALI_MARK_BEGIN("parab_solve");
1313  parab_solver.solve();
1314  {
1315  // v_b = B_i * u
1316  parab_solver.B->mult(*parab_solver.ui, *parab_solver.vb_both_face);
1317  // Direct both -> unique mapping
1318  parab_solver.operator_both_to_unique_face->mult(*parab_solver.vb_both_face, *parab_solver.vb_unique_face);
1319  *parab_solver.vb = *parab_solver.vb_unique_face;
1320  }
1321  CALI_MARK_END("parab_solve");
1322 
1323  if(user_globals::tm_manager->trigger(iotm_console)) {
1324  // output lin solver stats
1325  parab_solver.stats.log_stats(user_globals::tm_manager->time, false);
1326  }
1327  this->compute_time += timing(t2, t1);
1328 
1329  // since the traces have their own timing, we check for trace dumps in the compute step loop
1330  if(user_globals::tm_manager->trigger(iotm_trace))
1332 }
1333 
1334 void EMI::output_step()
1335 {
1336  double t1, t2;
1337  get_time(t1);
1338 
1339  output_manager.write_data();
1340 
1341  double curtime = timing(t2, t1);
1342  this->output_time += curtime;
1343 
1344  IO_stats.calls++;
1345  IO_stats.tot_time += curtime;
1346 
1348  IO_stats.log_stats(user_globals::tm_manager->time, false);
1349 }
1350 
1354 void EMI::destroy()
1355 {
1357  // close logger
1358  f_close(logger);
1359 
1360  // close output files
1361  output_manager.close_files_and_cleanup();
1362 
1363  // destroy ionics
1364  ion.destroy();
1365 }
1366 
1367 void EMI::setup_stimuli()
1368 {
1369  // initialize basic stim info data (used units, supported types, etc)
1370  init_stim_info();
1371 
1372  stimuli.resize(param_globals::num_stim);
1373  for (int i = 0; i < param_globals::num_stim; i++) {
1374  // construct new stimulus
1375  stimulus & s = stimuli[i];
1376 
1378  s.translate(i);
1379 
1380  // we associate to the EMI mesh. this is needed for the stim_phys and stim_electrode setups.
1381  s.associated_intra_mesh = emi_msh, s.associated_extra_mesh = emi_msh;
1382 
1383  s.setup(i);
1384 
1385  if (s.phys.type == Illum) {
1386  log_msg(0, MAX_LOG_LEVEL, ECHO, "Stimulus of type Illum (=6) is not implemented in EMI. Abort.");
1387  EXIT(EXIT_FAILURE);
1388  }
1389 
1390  // Depending on the stimulus type, we make sure to only stimulate the correct regions of the mesh:
1391  // Extracellular stimuli restrict to all DOFs of the extracellular region (including the extracellular side of the membrane).
1392  // Equivalent for intracellular stimuli.
1393  // Stimuli that act directly on the membrane restrict to DOFs with ptsData > 0, i.e. membrane, gap junctions, and complex junctions.
1394  if (is_extra(s.phys.type)) {
1395  SF::vector<mesh_int_t> extra_vertices;
1396  const sf_mesh& mesh = get_mesh(s.associated_extra_mesh);
1397 
1398  // Gather vertices from emi_msh using extra_tags
1399  indices_from_region_tags(extra_vertices, mesh, parab_solver.extra_tags);
1400 
1401  // Restrict electrode vertices to extra region
1402  restrict_to_set(s.electrode.vertices, extra_vertices);
1403  } else if (s.phys.type == I_tm) {
1404  const sf_mesh& mesh = get_mesh(emi_msh);
1405  SF::restrict_to_membrane(s.electrode.vertices, dof2ptsData, mesh);
1406  } else {
1407  SF::vector<mesh_int_t> intra_vertices;
1408  const sf_mesh& mesh = get_mesh(s.associated_intra_mesh);
1409 
1410  // Gather vertices from emi_msh using intra_tags
1411  indices_from_region_tags(intra_vertices, mesh, parab_solver.intra_tags);
1412 
1413  // Restrict electrode vertices to intra region
1414  restrict_to_set(s.electrode.vertices, intra_vertices);
1415  }
1416 
1417  if (s.electrode.dump_vtx) {
1418  set_dir(OUTPUT);
1419  s.dump_vtx_file(i);
1420  }
1421 
1422  if(param_globals::stim[i].pulse.dumpTrace && get_rank() == 0) {
1423  set_dir(OUTPUT);
1424  s.pulse.wave.write_trace(s.name+".trc");
1425  }
1426 
1427  }
1428 }
1429 
1430 void EMI::apply_dbc_stimulus()
1431 {
1432  parabolic_solver_emi& ps = parab_solver;
1433 
1434  // Rebuild only if the active DBC set changed. Time-dependent DBC values with
1435  // the same constrained DOFs are applied through enforce_dbc_rhs().
1436  bool dbcs_have_updated = ps.dbc != nullptr && ps.dbc->dbc_update();
1438 
1439  if (dbcs_have_updated && time_not_final) {
1440  parab_solver.rebuild_matrices(mtype_vol, *ion.miif, stimuli, logger);
1441  }
1442 }
1443 
1444 void EMI::apply_current_stimulus()
1445 {
1446  parabolic_solver_emi& ps = parab_solver;
1447  ps.Iij_stim->set(0.0);
1448 
1449  // iterate over stimuli
1450  for(stimulus & s : stimuli) {
1451  if(s.is_active()) {
1452  switch (s.phys.type) {
1453  case I_tm: {
1454  apply_stim_to_vector(s, *ps.Iij_temp, true);
1455  ps.Bi->mult(*ps.Iij_temp, *ps.Ib_both_face);
1456  ps.operator_both_to_unique_face->mult(*ps.Ib_both_face, *ps.Ib_unique_face);
1457  ps.Ib->add_scaled(*ps.Ib_unique_face, -0.5); // compensate the two-sided contribution produced by Bi.
1458  } break;
1459 
1460  case I_ex:
1461  case I_in: {
1462  apply_stim_to_vector(s, *ps.Iij_stim, true);
1463  } break;
1464 
1465  default: break;
1466  }
1467  }
1468  }
1469 }
1470 
1471 void EMI::balance_electrodes()
1472 {
1473  for (int i = 0; i < param_globals::num_stim; i++) {
1474  if (param_globals::stim[i].crct.balance != -1) {
1475  int from = param_globals::stim[i].crct.balance;
1476  int to = i;
1477 
1478  log_msg(NULL, 0, 0, "Balancing stimulus %d with %d %s-wise.", from, to,
1479  is_current(stimuli[from].phys.type) ? "current" : "voltage");
1480 
1481  stimulus& s_from = stimuli[from];
1482  stimulus& s_to = stimuli[to];
1483 
1484  s_to.pulse = s_from.pulse;
1485  s_to.ptcl = s_from.ptcl;
1486  s_to.phys = s_from.phys;
1487  s_to.pulse.strength *= -1.0;
1488 
1489  if (s_from.phys.type == I_ex || s_from.phys.type == I_in) {
1490  // if from is total current, skip volume based adjustment of strength
1491  // otherwise, scale_total_stimulus_current() will undo the balanced scaling of to.pulse.strength
1492  // scale_total_stimulus_current() will do the scaling based on the volume
1493  if (!s_from.phys.total_current) {
1494  sf_mat& mass = *parab_solver.mass_emi;
1495  SF_real vol0 = get_volume_from_nodes(mass, s_from.electrode.vertices);
1496  SF_real vol1 = get_volume_from_nodes(mass, s_to.electrode.vertices);
1497 
1498  s_to.pulse.strength *= fabs(vol0 / vol1);
1499  }
1500  }
1501  }
1502  }
1503 }
1504 
1505 void EMI::scale_total_stimulus_current(SF::vector<stimulus>& stimuli,
1506  sf_mat& mass_vol,
1507  sf_mat& mass_surf,
1508  FILE_SPEC logger)
1509 {
1510  for (stimulus & s : stimuli){
1511  if(is_current(s.phys.type) && s.phys.total_current){
1512  switch (s.phys.type) {
1513  case I_in:
1514  case I_ex: {
1515  // compute affected volume in um^3
1516  SF_real vol = get_volume_from_nodes(mass_vol, s.electrode.vertices);
1517  // s->strength holds the total current in uA, compute current density in uA/cm^3
1518  // Theoretically, we don't need to scale the volume to cm^3 here since we later
1519  // multiply with the mass matrix and we get um^3 * uA/um^3 = uA.
1520  // However, for I_ex/I_in there is an additional um^3 to cm^3 scaling in phys.scale,
1521  // 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.
1522  assert(vol > 0);
1523  float scale = 1.e12 / vol;
1524 
1525  s.pulse.strength *= scale;
1526 
1527  log_msg(logger, 0, ECHO,
1528  "%s [Stimulus %d]: current density scaled to %.4g uA/cm^3\n",
1529  s.name.c_str(), s.idx, s.pulse.strength);
1530  } break;
1531 
1532  case I_tm: {
1533  // In the EMI model, I_tm only affects the membrane. Therefore, we compute
1534  // the affected membrane surface in um^2 using the membrane mass matrix.
1535  // The electrode vertices are resticted to the membrane during setup, hence this function returns a surface already.
1536  SF_real surf = get_volume_from_nodes(mass_surf, s.electrode.vertices);
1537 
1538  // convert to cm^2
1539  assert(surf > 0);
1540  surf /= 1.e8;
1541 
1542  // scale surface density now to result in correct total current
1543  s.pulse.strength /= surf;
1544  log_msg(logger, 0, ECHO,
1545  "%s [Stimulus %d]: current density scaled to %.4g uA/cm^2\n",
1546  s.name.c_str(), s.idx, s.pulse.strength);
1547  } break;
1548 
1549  default: break;
1550  }
1551  }
1552  }
1553 }
1554 
1555 // Assign a deterministic global element numbering for a surface mesh based on
1556 // element type, tag, and sorted node ids. This only affects output ordering.
1557 static void assign_deterministic_elem_numbering(sf_mesh & mesh)
1558 {
1559  const int KEY_SIZE = 6; // type, tag, n1, n2, n3, n4
1560  int rank = 0, size = 0;
1561  MPI_Comm_rank(mesh.comm, &rank);
1562  MPI_Comm_size(mesh.comm, &size);
1563 
1564  auto make_key = [&](size_t i) {
1565  std::array<mesh_int_t, KEY_SIZE> k;
1566  k.fill(-1);
1567  k[0] = static_cast<mesh_int_t>(mesh.type[i]);
1568  k[1] = mesh.tag[i];
1569 
1570  int nn = 0;
1571  if (mesh.type[i] == SF::Line) nn = 2;
1572  else if (mesh.type[i] == SF::Tri) nn = 3;
1573  else if (mesh.type[i] == SF::Quad) nn = 4;
1574  else nn = 0;
1575 
1576  std::vector<mesh_int_t> nodes;
1577  nodes.reserve(nn);
1578  size_t off = mesh.dsp[i];
1579  for (int j = 0; j < nn; j++) {
1580  nodes.push_back(mesh.con[off + j]);
1581  }
1582  std::sort(nodes.begin(), nodes.end());
1583  for (int j = 0; j < (int)nodes.size(); j++) {
1584  k[2 + j] = nodes[j];
1585  }
1586  return k;
1587  };
1588 
1589  // Pack local keys
1590  std::vector<mesh_int_t> local_keys(mesh.l_numelem * KEY_SIZE, -1);
1591  for (size_t i = 0; i < mesh.l_numelem; i++) {
1592  auto k = make_key(i);
1593  for (int j = 0; j < KEY_SIZE; j++) local_keys[i * KEY_SIZE + j] = k[j];
1594  }
1595 
1596  // Gather sizes
1597  std::vector<int> counts(size, 0), displs(size, 0);
1598  int local_count = (int)local_keys.size();
1599  MPI_Allgather(&local_count, 1, MPI_INT, counts.data(), 1, MPI_INT, mesh.comm);
1600  int total = 0;
1601  for (int r = 0; r < size; r++) {
1602  displs[r] = total;
1603  total += counts[r];
1604  }
1605 
1606  std::vector<mesh_int_t> all_keys;
1607  if (rank == 0) all_keys.resize(total, -1);
1608  const MPI_Datatype key_mpi_t = mpi_datatype<mesh_int_t>();
1609  MPI_Gatherv(local_keys.data(), local_count, key_mpi_t,
1610  rank == 0 ? all_keys.data() : nullptr, counts.data(), displs.data(), key_mpi_t,
1611  0, mesh.comm);
1612 
1613  // Build sorted global keys on rank 0
1614  std::vector<std::array<mesh_int_t, KEY_SIZE>> sorted_keys;
1615  if (rank == 0) {
1616  const int nkeys = total / KEY_SIZE;
1617  sorted_keys.resize(nkeys);
1618  for (int i = 0; i < nkeys; i++) {
1619  std::array<mesh_int_t, KEY_SIZE> k;
1620  for (int j = 0; j < KEY_SIZE; j++) k[j] = all_keys[i * KEY_SIZE + j];
1621  sorted_keys[i] = k;
1622  }
1623  std::sort(sorted_keys.begin(), sorted_keys.end());
1624  }
1625 
1626  // Broadcast sorted keys
1627  int nkeys = 0;
1628  if (rank == 0) nkeys = (int)sorted_keys.size();
1629  MPI_Bcast(&nkeys, 1, MPI_INT, 0, mesh.comm);
1630  std::vector<mesh_int_t> flat_sorted(nkeys * KEY_SIZE, -1);
1631  if (rank == 0) {
1632  for (int i = 0; i < nkeys; i++) {
1633  for (int j = 0; j < KEY_SIZE; j++) flat_sorted[i * KEY_SIZE + j] = sorted_keys[i][j];
1634  }
1635  }
1636  MPI_Bcast(flat_sorted.data(), (int)flat_sorted.size(), key_mpi_t, 0, mesh.comm);
1637 
1638  // Reconstruct sorted_keys on all ranks
1639  if (rank != 0) {
1640  sorted_keys.resize(nkeys);
1641  for (int i = 0; i < nkeys; i++) {
1642  std::array<mesh_int_t, KEY_SIZE> k;
1643  for (int j = 0; j < KEY_SIZE; j++) k[j] = flat_sorted[i * KEY_SIZE + j];
1644  sorted_keys[i] = k;
1645  }
1646  }
1647 
1648  // Assign deterministic element numbering (both REF and SUBMESH)
1649  SF::vector<mesh_int_t> & nbr_ref = mesh.register_numbering(SF::NBR_ELEM_REF);
1650  SF::vector<mesh_int_t> & nbr_sub = mesh.register_numbering(SF::NBR_ELEM_SUBMESH);
1651  nbr_ref.resize(mesh.l_numelem);
1652  nbr_sub.resize(mesh.l_numelem);
1653  for (size_t i = 0; i < mesh.l_numelem; i++) {
1654  auto k = make_key(i);
1655  auto it = std::lower_bound(sorted_keys.begin(), sorted_keys.end(), k);
1656  if (it == sorted_keys.end() || *it != k) {
1657  log_msg(0, 5, 0, "deterministic numbering failed to find key (rank %d, elem %zu)", rank, i);
1658  EXIT(1);
1659  }
1660  mesh_int_t gid = (mesh_int_t)(it - sorted_keys.begin());
1661  nbr_ref[i] = gid;
1662  nbr_sub[i] = gid;
1663  }
1664 }
1665 
1666 void EMI::setup_output()
1667 {
1668  std::string output_base = get_basename(param_globals::meshname);
1669 
1670  set_dir(INPUT);
1671  const bool write_binary =
1672  SF::fileExists(std::string(param_globals::meshname) + ".belem") ||
1673  SF::fileExists(std::string(param_globals::meshname) + ".bpts");
1674 
1675  set_dir(OUTPUT);
1676 
1677  const int gridout_emi = param_globals::gridout_emi;
1678 
1679  // write entire mesh
1680  sf_mesh & mesh = get_mesh(emi_msh);
1681  if(gridout_emi & 2) {
1682  std::string output_file = output_base + "_e";
1683  log_msg(0, 0, 0, "Writing \"%s\" mesh: %s (%s)", mesh.name.c_str(), output_file.c_str(), write_binary ? "binary" : "text");
1684  const double t0 = MPI_Wtime();
1685  write_mesh_parallel(mesh, write_binary, output_file.c_str());
1686  log_msg(0, 0, 0, "Wrote \"%s\" mesh in %.5f seconds.", mesh.name.c_str(), float(MPI_Wtime() - t0));
1687  }
1688  else if(param_globals::output_level > 1) {
1689  log_msg(0, 0, 0, "Skipping \"%s\" mesh output.", mesh.name.c_str());
1690  }
1691  // register output for overall phi on the entire mesh
1692  output_manager.register_output(parab_solver.ui, emi_msh, 1, param_globals::phiefile, "mV", NULL);
1693 
1695  mesh_m.name = "Membrane";
1696  if(gridout_emi & 1) {
1697  std::string output_file = output_base + "_m";
1698  log_msg(0, 0, 0, "Writing \"%s\" mesh: %s (%s)", mesh_m.name.c_str(), output_file.c_str(), write_binary ? "binary" : "text");
1699  const double t0 = MPI_Wtime();
1700  write_mesh_parallel(mesh_m, write_binary, output_file.c_str());
1701  log_msg(0, 0, 0, "Wrote \"%s\" mesh in %.5f seconds.", mesh_m.name.c_str(), float(MPI_Wtime() - t0));
1702  }
1703  else if(param_globals::output_level > 1) {
1704  log_msg(0, 0, 0, "Skipping \"%s\" mesh output.", mesh_m.name.c_str());
1705  }
1706  // register output for Vm on membrane interface
1707  // ensure deterministic element ordering in output
1710  }
1711  output_manager.register_output(parab_solver.vb_unique_face, emi_surface_unique_face_msh, 1, param_globals::vofile, "mV", NULL, true);
1712 
1713  if(param_globals::num_trace) {
1714  sf_mesh & imesh = get_mesh(emi_msh);
1715  open_trace(ion.miif, param_globals::num_trace, param_globals::trace_node, NULL, &imesh);
1716  }
1717 
1718  // initialize generic logger for IO timings per time_dt
1719  IO_stats.init_logger("IO_stats.dat");
1720 }
1721 
1722 void EMI::dump_matrices()
1723 {
1724  std::string bsname = param_globals::dump_basename;
1725  std::string fn;
1726 
1727  set_dir(OUTPUT);
1728 
1729  fn = bsname + "_lhs.bin";
1730  parab_solver.lhs_emi->write(fn.c_str());
1731 
1732  fn = bsname + "_K.bin";
1733  parab_solver.stiffness_emi->write(fn.c_str());
1734 
1735  fn = bsname + "_B.bin";
1736  parab_solver.B->write(fn.c_str());
1737 
1738  fn = bsname + "_Bi.bin";
1739  parab_solver.Bi->write(fn.c_str());
1740 
1741  fn = bsname + "_BsM.bin";
1742  parab_solver.BsM->write(fn.c_str());
1743 
1744  fn = bsname + "_M.bin";
1745  parab_solver.mass_emi->write(fn.c_str());
1746 
1747  fn = bsname + "_Ms.bin";
1748  parab_solver.mass_surf_emi->write(fn.c_str());
1749 
1750 }
1751 
1754 double EMI::timer_val(const int timer_id)
1755 {
1756  // determine
1757  int sidx = stimidx_from_timeridx(stimuli, timer_id);
1758  double val = 0.0;
1759  if(sidx != -1) {
1760  stimuli[sidx].value(val);
1761  }
1762  else
1763  val = std::nan("NaN");
1764 
1765  return val;
1766 }
1767 
1770 std::string EMI::timer_unit(const int timer_id)
1771 {
1772  int sidx = stimidx_from_timeridx(stimuli, timer_id);
1773  std::string s_unit;
1774 
1775  if(sidx != -1)
1776  // found a timer-linked stimulus
1777  s_unit = stimuli[sidx].pulse.wave.f_unit;
1778 
1779  return s_unit;
1780 }
1781 
1782 void EMI::setup_solvers()
1783 {
1784  set_dir(OUTPUT);
1785  const int log_flag = param_globals::output_level > 1 ? ECHO : 0;
1786  double t0 = MPI_Wtime();
1787  parab_solver.init();
1788  log_msg(logger, 0, log_flag, "EMI setup_solvers: parabolic solver init in %.5f seconds.", float(MPI_Wtime() - t0));
1789  t0 = MPI_Wtime();
1790  parab_solver.rebuild_matrices(mtype_vol, *ion.miif, stimuli, logger);
1791  log_msg(logger, 0, log_flag, "EMI setup_solvers: matrix assembly and linear solver setup in %.5f seconds.", float(MPI_Wtime() - t0));
1792 
1793  if(param_globals::dump2MatLab)
1794  dump_matrices();
1795 }
1796 
1797 void extract_unique_tag(SF::vector<mesh_int_t>& unique_tags)
1798 {
1799  MPI_Comm comm = SF_COMM;
1800  int size, rank;
1801  MPI_Comm_size(comm, &size);
1802  MPI_Comm_rank(comm, &rank);
1803 
1804  binary_sort(unique_tags);
1805  unique_resize(unique_tags); // unique_resize is done locally(for each rank)
1806  make_global(unique_tags, comm);
1807  binary_sort(unique_tags);
1808  unique_resize(unique_tags);
1809 }
1810 
1811 void compute_tags_per_rank(int num_tags, SF::vector<mesh_int_t>& num_tags_per_rank)
1812 {
1813  MPI_Comm comm = SF_COMM;
1814  int size, rank;
1815  MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank);
1816  divide(num_tags, size, num_tags_per_rank);
1817 }
1818 
1819 void EMI::setup_EMI_mesh()
1820 {
1821  log_msg(0,0,0, "\n *** Processing EMI mesh ***\n");
1822 
1823  const std::string basename = param_globals::meshname;
1824  const int verb = param_globals::output_level;
1825  std::map<mesh_t, sf_mesh> & mesh_registry = user_globals::mesh_reg;
1826  assert(mesh_registry.count(emi_msh) == 1);
1827 
1828  set_dir(INPUT);
1829 
1830  sf_mesh & emi_mesh = mesh_registry[emi_msh];
1831  sf_mesh & emi_surfmesh_one_side = mesh_registry[emi_surface_msh];
1832  sf_mesh & emi_surfmesh_w_counter_face = mesh_registry[emi_surface_counter_msh];
1833  sf_mesh & emi_surfmesh_unique_face = mesh_registry[emi_surface_unique_face_msh];
1834 
1835  MPI_Comm comm = emi_mesh.comm;
1836 
1837  int size, rank;
1838  double t1, t2, s1, s2;
1839  const double total_setup_t0 = MPI_Wtime();
1840  MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank);
1841 
1842  //-----------------------------------------------------------------
1843  // Validate mesh dimension: EMI only supports 3D volumetric meshes
1844  //-----------------------------------------------------------------
1845  if (emi_mesh.l_numelem > 0) {
1846  // Check first local element type (all elements should be same type in CARP)
1847  SF::elem_t first_elem = emi_mesh.type[0];
1848 
1849  if (first_elem == SF::Line || first_elem == SF::Tri || first_elem == SF::Quad) {
1850  const char* type_name = (first_elem == SF::Line) ? "1D (Line)" :
1851  (first_elem == SF::Tri) ? "2D (Tri)" :
1852  "2D (Quad)";
1853  if (rank == 0) {
1854  log_msg(0, 5, 0, "\n*** ERROR: EMI model requires a 3D volumetric mesh!");
1855  log_msg(0, 5, 0, "*** Current mesh element type: %s", type_name);
1856  log_msg(0, 5, 0, "*** EMI only supports 3D element types: Tetra, Pyramid, Prism, Hexa");
1857  log_msg(0, 5, 0, "*** Please provide a 3D mesh with volume elements.\n");
1858  }
1859  EXIT(EXIT_FAILURE);
1860  }
1861  }
1862 
1863  //-----------------------------------------------------------------
1864  // Step 1: READ *.intra and *.extra
1865  //-----------------------------------------------------------------
1866  int total_num_tags = 0;
1867  if(verb) log_msg(NULL, 0, 0,"\nReading tags for extra and intra regions from input files");
1868  t1 = MPI_Wtime();
1869  {
1870  SF::vector<mesh_int_t> unique_extra_tags;
1871  SF::vector<mesh_int_t> unique_intra_tags;
1872 
1873  if(verb) log_msg(NULL, 0, 0,"Read extracellular tags");
1874  read_indices_global(unique_extra_tags,basename+".extra", comm);
1875  for(mesh_int_t tag:unique_extra_tags){
1876  parab_solver.extra_tags.insert(tag);
1877  }
1878 
1879  if(verb) log_msg(NULL, 0, 0,"Read intracellular tags");
1880  read_indices_global(unique_intra_tags,basename+".intra", comm);
1881  for(mesh_int_t tag:unique_intra_tags){
1882  parab_solver.intra_tags.insert(tag);
1883  }
1884 
1885  total_num_tags = parab_solver.extra_tags.size() + parab_solver.intra_tags.size();
1886  if(total_num_tags < size){
1887  log_msg(0,5,0, "\nThe number of unique tags on EMI mesh is smaller than number of processors!");
1888  EXIT(EXIT_FAILURE);
1889  }
1890  if(verb) log_msg(NULL, 0, 0,"\nextra_tags=%lu, intra_tags=%lu", parab_solver.extra_tags.size(), parab_solver.intra_tags.size());
1891  }
1892  t2 = MPI_Wtime();
1893  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1894 
1895  //-----------------------------------------------------------------
1896  // Step 2: READ points from *.pts
1897  //-----------------------------------------------------------------
1899  SF::vector<mesh_int_t> ptsidx;
1900  SF::vector<mesh_int_t> ptsData;
1901  if(verb) log_msg(NULL, 0, 0,"\nReading points with data on each vertex");
1902  t1 = MPI_Wtime();
1903  SF::read_points(basename, comm, pts, ptsidx);
1904  ptsData.resize(ptsidx.size());
1905  assert(ptsidx.size()==ptsData.size());
1906  t2 = MPI_Wtime();
1907  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1908 
1909  std::list< sf_mesh* > meshlist;
1910  meshlist.push_back(&emi_mesh);
1911 
1912  //-----------------------------------------------------------------
1913  // Step 3: Distribute mesh based on tag or *.part
1914  //-----------------------------------------------------------------
1915  if(verb) log_msg(NULL, 0, 0,"\nDistribute mesh based on tags");
1916  // should be replaced by scotch/pt-scotch for efficiency
1917  t1 = MPI_Wtime();
1918  distribute_elements_based_tags(emi_mesh, total_num_tags);
1919  t2 = MPI_Wtime();
1920  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1921 
1922  //-----------------------------------------------------------------
1923  // Step 4: insert coordinates to the mesh
1924  //-----------------------------------------------------------------
1925  // insert points
1926  if(verb) log_msg(NULL, 0, 0, "\nInserting points");
1927  t1 = MPI_Wtime();
1928  SF::insert_points(pts, ptsidx, meshlist);
1929  t2 = MPI_Wtime();
1930  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1931 
1932  //-----------------------------------------------------------------
1933  // Step 5: extract ptsData based on the location of each vertex
1934  //-----------------------------------------------------------------
1935  if(verb) log_msg(NULL, 0, 0, "\nCompute location of all DOFs on EMI mesh (inner DOFs, membrane, gap junction) saved into ptsData from original mesh");
1936  t1 = MPI_Wtime();
1937  compute_ptsdata_from_original_mesh( emi_mesh,
1938  SF::NBR_REF,
1939  vertex2ptsdata,
1940  parab_solver.extra_tags,
1941  parab_solver.intra_tags);
1942  t2 = MPI_Wtime();
1943  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1944 
1945  //-----------------------------------------------------------------
1946  // Step 6: extract faces and counterfaces on the interfaces (mem/gap)
1947  // and mark the unique faces on surface mesh
1948  // generate required mapping between surface mesh and unique faces
1949  //-----------------------------------------------------------------
1950  t1 = MPI_Wtime();
1951  if(verb) log_msg(NULL, 0, 0, "\nExtract EMI surface mesh");
1952  hashmap::unordered_map<mesh_int_t, SF::emi_index_rank<mesh_int_t>> unused_map_elem_oneface_to_elem_uniqueFace;
1953  extract_face_based_tags(emi_mesh, SF::NBR_REF, vertex2ptsdata,
1954  parab_solver.line_face,
1955  parab_solver.tri_face,
1956  parab_solver.quad_face,
1957  parab_solver.extra_tags,
1958  parab_solver.intra_tags,
1959  emi_surfmesh_one_side, emi_surfmesh_w_counter_face, emi_surfmesh_unique_face,
1960  parab_solver.map_elem_uniqueFace_to_elem_oneface,
1961  unused_map_elem_oneface_to_elem_uniqueFace);
1962  meshlist.push_back(&emi_surfmesh_one_side);
1963  t2 = MPI_Wtime();
1964  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1965  //-----------------------------------------------------------------
1966  // Step 7: register surface meshes based on the {typeof}_face with counter face
1967  //-----------------------------------------------------------------
1968  compute_surface_mesh_with_counter_face(emi_surfmesh_w_counter_face, SF::NBR_REF,
1969  parab_solver.line_face,
1970  parab_solver.tri_face,
1971  parab_solver.quad_face);
1972 
1973  compute_surface_mesh_with_unique_face(emi_surfmesh_unique_face, SF::NBR_REF,
1974  parab_solver.line_face,
1975  parab_solver.tri_face,
1976  parab_solver.quad_face,
1977  parab_solver.map_elem_uniqueFace_to_tags);
1978 
1979  //-----------------------------------------------------------------
1980  // Step 8: create a map between emi_surfmesh_w_counter_face and emi_surfmesh (both -> one)
1981  //-----------------------------------------------------------------
1982  SF::create_reverse_elem_mapping_between_surface_meshes(parab_solver.line_face,
1983  parab_solver.tri_face,
1984  parab_solver.quad_face,
1985  parab_solver.vec_both_to_one_face,
1986  comm);
1987  t2 = MPI_Wtime();
1988  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
1989 
1990  //-----------------------------------------------------------------
1991  // Step 9: global number of interfaces only on emi_surfmesh without counter face
1992  //-----------------------------------------------------------------
1993  if(verb) log_msg(NULL, 0, 0, "\ncompute global number of interface");
1994  size_t global_count_surf = 0;
1995  size_t numelem_surface = emi_surfmesh_one_side.l_numelem;
1996  size_t local_count_surf = numelem_surface;
1997  MPI_Reduce(&local_count_surf, &global_count_surf, 1, mpi_datatype<size_t>(), MPI_SUM, 0, MPI_COMM_WORLD);
1998  if(verb && rank==0) fprintf(stdout, "global number of interfaces = %zu\n", global_count_surf);
1999 
2000  //-----------------------------------------------------------------
2001  // Step 10: submesh_numbering on the emi_mesh and generate parallel layout
2002  //-----------------------------------------------------------------
2003  {
2005  sub_numbering(emi_mesh);
2006  emi_mesh.generate_par_layout();
2007  }
2008 
2009  SF::meshdata<mesh_int_t, mesh_real_t> tmesh_backup_old = emi_mesh;
2010 
2011  //-----------------------------------------------------------------
2012  // Step 11: make a map between key(vertex,tag)-> value(dof) after decoupling interfaces defined on emi mesh
2013  //-----------------------------------------------------------------
2014  // During interface decoupling and the introduction of new degrees of freedom (DoFs),
2015  // we assume that the mesh partitioning is performed at least on a per-tag basis.
2016  // This means that all elements sharing the same tag number belong to the same rank.
2017  t1 = MPI_Wtime();
2018  if(verb) log_msg(NULL, 0, 0, "\ndecouple emi interfaces");
2019  if(verb) log_msg(NULL, 0, 0, "\tcompute map oldIdx to dof");
2020  compute_map_vertex_to_dof(emi_mesh, SF::NBR_REF, vertex2ptsdata, parab_solver.extra_tags, parab_solver.map_vertex_tag_to_dof);
2021 
2022  //-----------------------------------------------------------------
2023  // Step 12: complete the map between key(vertex,tag)-> value(dof) for counter faces defined on emi mesh
2024  //-----------------------------------------------------------------
2025  if(verb) log_msg(NULL, 0, 0, "\tcomplete map oldIdx to dof with counter interface");
2026  // add to the map the counter part of the interface
2027  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);
2028 
2029  //-----------------------------------------------------------------
2030  // Step 13: update EMI mesh with new DOFs, so interface decoupling is applied on the EMI mesh
2031  //-----------------------------------------------------------------
2032  if(verb) log_msg(NULL, 0, 0, "\tupdate mesh with dof");
2033  update_emi_mesh_with_dofs(emi_mesh, SF::NBR_REF, parab_solver.map_vertex_tag_to_dof, parab_solver.dof2vertex);
2034  t2 = MPI_Wtime();
2035  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2036 
2037  //-----------------------------------------------------------------
2038  // Step 14: initialize map_vertex_tag_to_dof_petsc where the key is (vertex,tag) and the value is (dof,petsc)
2039  //-----------------------------------------------------------------
2040  t1 = MPI_Wtime();
2041  if(verb) log_msg(NULL, 0, 0, "\nInitialize petsc =0 for map<oldIdx,tag> -><dof, petsc>");
2042  // Iterate over map to assign the (oldIndx, tag) -> (dof, petsc) atm petsc = 0
2043  for(const auto & key_value : parab_solver.map_vertex_tag_to_dof)
2044  {
2045  mesh_int_t gIndex_old = key_value.first.first;
2046  mesh_int_t tag_old = key_value.first.second;
2047  mesh_int_t dof = key_value.second;
2048 
2049  std::pair <mesh_int_t,mesh_int_t> dof_petsc = std::make_pair(dof,-1); // petsc numbering ...
2050  parab_solver.map_vertex_tag_to_dof_petsc.insert({key_value.first,dof_petsc});
2051  }
2052  t2 = MPI_Wtime();
2053  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2054 
2055  //-----------------------------------------------------------------
2056  // Step 15: insert the coordinates on new DOFs to the EMI mesh
2057  //-----------------------------------------------------------------
2058  t1 = MPI_Wtime();
2059  if(verb) log_msg(NULL, 0, 0, "Inserting points and ptsData of dofs to emi_mesh");
2060  insert_points_ptsData_to_dof(tmesh_backup_old, emi_mesh, SF::NBR_REF, parab_solver.dof2vertex, vertex2ptsdata, dof2ptsData);
2061  t2 = MPI_Wtime();
2062  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2063 
2064  //-----------------------------------------------------------------
2065  // Step 16: submesh_numbering on the emi_mesh and generate parallel layout after decoupling the interfaces on the EMI mesh
2066  //-----------------------------------------------------------------
2067  t1 = MPI_Wtime();
2068  if(verb) log_msg(NULL, 0, 0, "Generating unique PETSc numberings");
2069  {
2071  sub_numbering(emi_mesh);
2072  emi_mesh.generate_par_layout();
2073  }
2074  t2 = MPI_Wtime();
2075  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2076 
2077  //-----------------------------------------------------------------
2078  // Step 17: register petsc_numbering on EMI mesh
2079  //-----------------------------------------------------------------
2080  t1 = MPI_Wtime();
2081  if(verb) log_msg(NULL, 0, 0, "Generating unique PETSc numberings");
2082  {
2083  SF::petsc_numbering<mesh_int_t,mesh_real_t> petsc_numbering(emi_mesh.pl);
2084  petsc_numbering(emi_mesh);
2085  }
2086  t2 = MPI_Wtime();
2087  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2088 
2089  //-----------------------------------------------------------------
2090  // Step 18: complete map_vertex_tag_to_dof_petsc where the key is (vertex,tag) and the value is (dof,petsc) for counter faces
2091  //-----------------------------------------------------------------
2092  if(verb) log_msg(NULL, 0, 0, "Updating the map between indices to PETSc numberings");
2093  t1 = MPI_Wtime();
2094  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);
2095  t2 = MPI_Wtime();
2096  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2097 
2098  //-----------------------------------------------------------------
2099  // Step 19: update values of map {type_of}_face with new DOFs after decoupling
2100  //-----------------------------------------------------------------
2101  t1 = MPI_Wtime();
2102  if(verb) log_msg(NULL, 0, 0, "Updating surface mesh with dof");
2103  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);
2104  t2 = MPI_Wtime();
2105  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2106 
2107  //-----------------------------------------------------------------
2108  // Step 20: register emi_surfmesh for SF::NBR_ELEM_REF, NBR_REF, SF::NBR_SUBMESH
2109  //-----------------------------------------------------------------
2110  t1 = MPI_Wtime();
2111  if(verb) log_msg(NULL, 0, 0, "Layout for element of EMI surfmesh");
2112  SF::vector<mesh_int_t> & emi_surfmesh_elem = emi_surfmesh_one_side.register_numbering(SF::NBR_ELEM_REF);
2113  SF::vector<long int> layout;
2114  SF::layout_from_count<long int>(emi_surfmesh_one_side.l_numelem, layout, emi_surfmesh_one_side.comm);
2115  size_t count = layout[rank+1] - layout[rank];
2116  emi_surfmesh_elem.resize(count);
2117  for (int i = 0; i < count; ++i){
2118  emi_surfmesh_elem[i] = layout[rank]+i;
2119  }
2120 
2121  emi_surfmesh_one_side.localize(SF::NBR_REF);
2122  emi_surfmesh_one_side.register_numbering(SF::NBR_SUBMESH);
2123  t2 = MPI_Wtime();
2124  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2125 
2126  //-----------------------------------------------------------------
2127  // Step 21: register emi_surfmesh_w_counter_face for SF::NBR_ELEM_REF, NBR_REF, SF::NBR_SUBMESH
2128  //-----------------------------------------------------------------
2129  t1 = MPI_Wtime();
2130  if(verb) log_msg(NULL, 0, 0, "Layout for element of EMI surfmesh w counter face");
2131  SF::vector<mesh_int_t> & emi_surfmesh_counter_elem = emi_surfmesh_w_counter_face.register_numbering(SF::NBR_ELEM_REF);
2132  SF::vector<long int> layout_counter;
2133  SF::layout_from_count<long int>(emi_surfmesh_w_counter_face.l_numelem, layout_counter, emi_surfmesh_w_counter_face.comm);
2134  size_t count_counter = layout_counter[rank+1] - layout_counter[rank];
2135  emi_surfmesh_counter_elem.resize(count_counter);
2136  for (int i = 0; i < count_counter; ++i){
2137  emi_surfmesh_counter_elem[i] = layout_counter[rank]+i;
2138  }
2139  emi_surfmesh_w_counter_face.localize(SF::NBR_REF);
2140  emi_surfmesh_w_counter_face.register_numbering(SF::NBR_SUBMESH);
2141  t2 = MPI_Wtime();
2142  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2143 
2144 
2145  //-----------------------------------------------------------------
2146  // Step 22: register emi_surfmesh_unique_face for SF::NBR_ELEM_REF, NBR_REF, SF::NBR_SUBMESH
2147  //-----------------------------------------------------------------
2148  t1 = MPI_Wtime();
2149  if(verb) log_msg(NULL, 0, 0, "Layout for element of EMI unique-face surfmesh");
2150  SF::vector<mesh_int_t> & emi_surfmesh_unique_elem = emi_surfmesh_unique_face.register_numbering(SF::NBR_ELEM_REF);
2151  SF::vector<long int> layout_unique;
2152  SF::layout_from_count<long int>(emi_surfmesh_unique_face.l_numelem, layout_unique, emi_surfmesh_unique_face.comm);
2153  size_t count_unique = layout_unique[rank+1] - layout_unique[rank];
2154  emi_surfmesh_unique_elem.resize(count_unique);
2155  for (int i = 0; i < count_unique; ++i){
2156  emi_surfmesh_unique_elem[i] = layout_unique[rank]+i;
2157  }
2158  emi_surfmesh_unique_face.localize(SF::NBR_REF);
2159  emi_surfmesh_unique_face.register_numbering(SF::NBR_SUBMESH);
2160  t2 = MPI_Wtime();
2161  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2162 
2163  //-----------------------------------------------------------------
2164  // Step 23: insert coordinates to one-sided, both-face, and unique-face surface meshes
2165  //-----------------------------------------------------------------
2166  t1 = MPI_Wtime();
2167  if(verb) log_msg(NULL, 0, 0, "Inserting points to EMI surfmesh");
2168  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);
2169  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);
2170  insert_points_to_surface_mesh(tmesh_backup_old, emi_surfmesh_unique_face, SF::NBR_REF, parab_solver.dof2vertex);
2171  t2 = MPI_Wtime();
2172  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2173 
2174  //-----------------------------------------------------------------
2175  // Step 24: submesh_numbering and PETSc numbering for all EMI surface meshes
2176  //-----------------------------------------------------------------
2177  t1 = MPI_Wtime();
2178  if(verb) log_msg(NULL, 0, 0, "Generating submesh_numbering and PETSc numberings for surface mesh");
2179  {
2181  sub_numbering(emi_surfmesh_one_side);
2182  emi_surfmesh_one_side.generate_par_layout();
2183 
2184  SF::petsc_numbering<mesh_int_t,mesh_real_t> petsc_numbering(emi_surfmesh_one_side.pl);
2185  petsc_numbering(emi_surfmesh_one_side);
2186  }
2187 
2188  {
2190  sub_numbering(emi_surfmesh_w_counter_face);
2191  emi_surfmesh_w_counter_face.generate_par_layout();
2192 
2193  SF::petsc_numbering<mesh_int_t,mesh_real_t> petsc_numbering(emi_surfmesh_w_counter_face.pl);
2194  petsc_numbering(emi_surfmesh_w_counter_face);
2195  }
2196 
2197  {
2199  sub_numbering(emi_surfmesh_unique_face);
2200  emi_surfmesh_unique_face.generate_par_layout();
2201 
2202  SF::petsc_numbering<mesh_int_t,mesh_real_t> petsc_numbering(emi_surfmesh_unique_face.pl);
2203  petsc_numbering(emi_surfmesh_unique_face);
2204  // Ensure deterministic element ordering across MPI for output
2205  assign_deterministic_elem_numbering(emi_surfmesh_unique_face);
2206  }
2207  t2 = MPI_Wtime();
2208  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2209 
2210  //-----------------------------------------------------------------
2211  // Step 25: assign PETSc numbering to map_vertex_tag_to_dof_petsc where the key is (vertex,tag) and value is (dof,petsc)
2212  //-----------------------------------------------------------------
2213  t1 = MPI_Wtime();
2214  if(verb) log_msg(NULL, 0, 0, "assign PETSc numbering for new faces");
2215  SF::assign_petsc_on_counter_face(parab_solver.map_vertex_tag_to_dof_petsc,comm);
2216  {
2217  hashmap::unordered_map<std::pair<mesh_int_t,mesh_int_t>, std::pair<mesh_int_t,mesh_int_t>>::iterator it;
2218  for (it = parab_solver.map_vertex_tag_to_dof_petsc.begin(); it != parab_solver.map_vertex_tag_to_dof_petsc.end(); it++)
2219  {
2220  std::pair <mesh_int_t,mesh_int_t> Index_tag_old = it->first;
2221  std::pair <mesh_int_t,mesh_int_t> dof_petsc = it->second;
2222  parab_solver.dof2petsc[dof_petsc.first] = dof_petsc.second;
2223  parab_solver.petsc2dof[dof_petsc.second] = dof_petsc.first;
2224  }
2225  }
2226 
2227  // DEBUG: Check for invalid PETSc indices after exchange
2228  #ifdef EMI_DEBUG_MESH
2229  {
2230  int invalid_count = 0;
2231  for (const auto& [key, val] : parab_solver.map_vertex_tag_to_dof_petsc) {
2232  if (val.second < 0) {
2233  invalid_count++;
2234  if (invalid_count <= 3) {
2235  fprintf(stderr, "RANK %d INVALID: vertex=%ld tag=%ld dof=%ld petsc=%ld\n",
2236  rank, (long)key.first, (long)key.second, (long)val.first, (long)val.second);
2237  }
2238  }
2239  }
2240  fprintf(stderr, "RANK %d: After Step 26: %d invalid PETSc indices out of %zu total\n",
2241  rank, invalid_count, parab_solver.map_vertex_tag_to_dof_petsc.size());
2242  fflush(stderr);
2243  }
2244  #endif
2245 
2246  t2 = MPI_Wtime();
2247  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2248 
2249  //-----------------------------------------------------------------
2250  // Step 26: update counter faces in map{type_of}_face
2251  //-----------------------------------------------------------------
2252  added_counter_faces_to_map(parab_solver.line_face, parab_solver.tri_face, parab_solver.quad_face);
2253  const double total_setup = MPI_Wtime() - total_setup_t0;
2254  log_msg(0,0,0, "Total setup_EMI_mesh processing time: %.5f sec.", float(total_setup));
2255 
2256  log_msg(0,0,0, "\n *** EMI mesh processing Done ***\n");
2257 }
2258 
2259 void distribute_elements_based_tags(SF::meshdata<mesh_int_t, mesh_real_t>& mesh,
2260  int total_num_tags)
2261 {
2262  MPI_Comm comm = mesh.comm;
2263  int size, rank;
2264  double t1, t2, s1, s2;
2265  MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank);
2266  const int verb = param_globals::output_level;
2267 
2268  if(total_num_tags < size)
2269  {
2270  PetscPrintf(PETSC_COMM_WORLD,"\nThe number of processors should be less than the number of tags, size = %d & ntags = %d !!!\n",
2271  size, total_num_tags);
2272  cleanup_and_exit();
2273  }
2274 
2275  if(verb==10) log_msg(NULL, 0, 0,"\ncompute the number of tags which belongs to one rank");
2276  // compute the number of tags per rank
2277  SF::vector<mesh_int_t> ntags_per_rank;
2278  t1 = MPI_Wtime();
2279  compute_tags_per_rank(total_num_tags, ntags_per_rank);
2280  t2 = MPI_Wtime();
2281  if(verb==10) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2282 
2283  // generate global destination w.r.t. tags
2284  // redistribute elements based on new partition
2285  t1 = MPI_Wtime();
2286  SF::vector<mesh_int_t> part_based_Tags(mesh.l_numelem);
2287  partition_based_tags(total_num_tags, mesh.tag, ntags_per_rank, part_based_Tags);
2288  SF::redistribute_elements(mesh,part_based_Tags);
2289  // permute elements locally first based on the tag then element index
2290  permute_mesh_locally_based_on_tag_elemIdx(mesh);
2291  t2 = MPI_Wtime();
2292  if(verb==10) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2293 }
2294 
2295 // In the EMI model we cannot have partition boundaries crossing cells.
2296 // Therefore this function assigns a partition to each tag. All elements with this tag will go to the same partition.
2297 void partition_based_tags(int num_tags,
2299  SF::vector<mesh_int_t> num_tags_per_rank,
2300  SF::vector<mesh_int_t>& part)
2301 {
2302  const int verb = param_globals::output_level;
2303  MPI_Comm comm = SF_COMM;
2304  int size, rank;
2305  MPI_Comm_size(comm, &size);
2306  MPI_Comm_rank(comm, &rank);
2307 
2308  // we need to have tags_to_rank as a map globally.
2310  // Try to load the mapping from a .part file and fall back on the internal mapping if there is no such file.
2311  // If the mapping is read from file, the num_tags_per_rank argument is not used.
2312  if (!load_partitions_from_file(tags_to_rank_map, num_tags, comm)) {
2313  if(verb==10) log_msg(NULL, 0, 0,"\ncompute the number of unique tags");
2314  SF::vector<mesh_int_t> unique_tags = tag;
2315  double t1 = MPI_Wtime();
2316  extract_unique_tag(unique_tags);
2317  double t2 = MPI_Wtime();
2318  if(verb==10) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2319 
2320  if (unique_tags.size() != static_cast<size_t>(num_tags)) {
2321  log_msg(0,5,0, "\nerror: the number of tags in the EMI mesh does not match the total tags from *.extra and *.intra");
2322  EXIT(1);
2323  }
2324  map_tags_to_rank(size, unique_tags, num_tags_per_rank, tags_to_rank_map);
2325  }
2326 
2327  for (size_t i = 0; i < part.size(); ++i) {
2328  if(tags_to_rank_map.count(tag[i]))
2329  part[i] = tags_to_rank_map[tag[i]];
2330  }
2331 }
2332 
2333 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)
2334 {
2335  // the goal is to map the tag as a key -> rank, should be global one
2336  mesh_int_t t = 0; // tag index
2337  for (size_t r = 0; r < size; ++r) {
2338  for (size_t count = 0; count < num_tags_per_rank[r];) {
2339  mesh_int_t tag = unique_tags[t];
2340 
2341  // // here we consider each tag belongs to only one rank.
2342  tags_to_rank_map.insert({tag, r});
2343  count +=1;
2344  t+=1;
2345  }
2346  }
2347 }
2348 
2349 // This function creates a tag-to-rank mapping based only on the numerical order of the tags.
2350 bool load_partitions_from_file(hashmap::unordered_map<mesh_int_t, mesh_int_t>& tags_to_rank_map,
2351  int expected_num_tags,
2352  MPI_Comm comm)
2353 {
2354  int size, rank;
2355  MPI_Comm_size(comm, &size);
2356  MPI_Comm_rank(comm, &rank);
2357 
2358  SF::vector<int> parts;
2359  const std::string basename = param_globals::meshname;
2360  FILE* fd = fopen((basename + ".part").c_str(), "r");
2361  if (fd != NULL) { // check if the file exists otherwise read_indices_global() crash
2362  read_indices_global(parts, basename + ".part", comm);
2363  fclose(fd);
2364  }
2365 
2366  if (parts.size() == 0) return false; // file does not exist or is empty
2367 
2368  if (parts.size() % 2 != 0) {
2369  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.!");
2370  EXIT(1);
2371  }
2372 
2373  int min_part = std::numeric_limits<int>::max();
2374  int max_part = std::numeric_limits<int>::min();
2375  for(int i = 0; i < parts.size(); i+=2) {
2376  min_part = std::min(min_part, parts[i + 1]);
2377  max_part = std::max(max_part, parts[i + 1]);
2378  tags_to_rank_map.insert({parts[i], parts[i + 1]});
2379  }
2380 
2381  if (tags_to_rank_map.size() != static_cast<size_t>(expected_num_tags)) {
2382  log_msg(0,5,0, "\nerror: the number of tags in the .part file does not match the total tags from *.extra and *.intra");
2383  EXIT(1);
2384  }
2385 
2386  if (min_part < 0 || max_part >= size) {
2387  log_msg(0,5,0,
2388  "\nerror: EMI partition file %s.part is incompatible with this run.\n"
2389  "The file contains partition IDs in [%d, %d], but the current MPI communicator has %d rank(s).\n"
2390  "Remove/regenerate the .part file or run with a matching number of MPI tasks.",
2391  basename.c_str(), min_part, max_part, size);
2392  EXIT(1);
2393  }
2394 
2395  if (rank == 0 && max_part + 1 != size) {
2396  log_msg(0,3,0,
2397  "Warning: EMI partition file %s.part uses %d partition ID(s), but the current run uses %d MPI rank(s).",
2398  basename.c_str(), max_part + 1, size);
2399  }
2400 
2401  return true;
2402 }
2403 
2404 void permute_mesh_locally_based_on_tag_elemIdx(SF::meshdata<mesh_int_t, mesh_real_t>& mesh)
2405 {
2406  mesh.globalize(SF::NBR_REF);
2409  interval(perm, 0, mesh.tag.size());
2410 
2411  SF::vector<mesh_int_t> tags = tmesh.tag;
2413  binary_sort_sort_copy(tags, elemIdx, perm);
2414  permute_mesh(tmesh, mesh, perm);
2415 
2416  mesh.localize(SF::NBR_REF);
2417 }
2418 
2419 } // namespace opencarp
2420 
2421 #endif
opencarp::local_index_t mesh_int_t
Definition: SF_container.h:46
#define SF_COMM
the default SlimFem MPI communicator
Definition: SF_globals.h:28
opencarp::real_t SF_real
Global scalar type.
Definition: SF_globals.h:33
#define MAX_LOG_LEVEL
Definition: basics.h:323
#define ECHO
Definition: basics.h:316
#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
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:627
void insert(InputIterator first, InputIterator last)
Insert Iterator range.
Definition: hashmap.hpp:587
size_t size() const
Definition: hashmap.hpp:735
size_t size() const
Definition: hashmap.hpp:1156
iterator find(const K &key)
Definition: hashmap.hpp:1096
hm_int erase(const K &key)
Definition: hashmap.hpp:1068
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:229
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:953
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
T sum(const vector< T > &vec)
Compute sum of a vector's entries.
Definition: SF_vector.h:340
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:1023
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)
bool fileExists(std::string filename)
Function which checks if a given file exists.
Definition: SF_io_base.h:66
void init_vector(SF::abstract_vector< T, S > **vec)
Definition: SF_init.h:107
void binary_sort(vector< T > &_V)
Definition: SF_sort.h:284
void init_matrix(SF::abstract_matrix< T, S > **mat)
Definition: SF_init.h:208
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
constexpr T min(T a, T b)
Definition: ion_type.h:33
constexpr T max(T a, T b)
Definition: ion_type.h:31
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:55
bool using_legacy_stimuli
flag storing whether legacy stimuli are used
Definition: main.cc:61
std::map< mesh_t, sf_mesh > mesh_reg
Registry for the different meshes used in a multi-physics simulation.
Definition: main.cc:49
int stimidx_from_timeridx(const SF::vector< stimulus > &stimuli, const int timer_id)
determine link between timer and stimulus
Definition: electrics.cc:782
@ 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:1800
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:834
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: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.
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:404
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:453
int set_dir(IO_t dest)
Definition: sim_utils.cc:1347
void cleanup_and_exit()
Definition: sim_utils.cc:2207
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:284
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
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:83
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:859
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:73
@ OUTPUT
Definition: sim_utils.h:54
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
@ 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:444
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
SF::abstract_matrix< SF_int, SF_real > sf_mat
Definition: sf_interface.h:52
V timing(V &t2, const V &t1)
Definition: basics.h:456
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:140
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.