openCARP
Doxygen code documentation for the open cardiac electrophysiology simulator openCARP
emi.cc
Go to the documentation of this file.
1 // SPDX-FileCopyrightText: Copyright (c) NumeriCor GmbH
2 // SPDX-License-Identifier: Apache-2.0
3 
11 #if WITH_EMI_MODEL
12 
13 #include "backend.h"
14 #include "mpi_utils.h"
15 #include "physics_types.h"
16 #include "timers.h"
17 #include "stimulate.h"
18 #include "electric_integrators.h"
19 #include "runtime.hpp"
20 #include "SF_init.h" // for SF::init_xxx()
21 #if defined(EMI_DEBUG_MESH) && defined(WITH_PETSC)
22 #include "SF_petsc_matrix.h" // for the debug probes of the PETSc matrices
23 #endif
24 #include "emi.h"
25 #include <algorithm>
26 #include <array>
27 #include <cmath>
28 #include <cstdint>
29 #include <cstring>
30 #include <initializer_list>
31 #include <sstream>
32 #include <sys/resource.h>
33 #include <utility>
34 
35 #ifdef WITH_CALIPER
36 #include "caliper/cali.h"
37 #else
38 #include "caliper_hooks.h"
39 #endif
40 
41 namespace opencarp {
42 
43 namespace {
44 
45 template<class Assemble>
46 void assemble_with_exact_preallocation(std::initializer_list<sf_mat*> matrices, Assemble assemble)
47 {
48  bool any_supported = false;
49  bool all_supported = true;
50  bool saw_matrix = false;
51 
52  for(sf_mat* mat : matrices) {
53  if(mat == nullptr) continue;
54 
55  saw_matrix = true;
56  const bool supported = mat->begin_exact_preallocation();
57  any_supported = any_supported || supported;
58  all_supported = all_supported && supported;
59  }
60 
61  if(!saw_matrix) return;
62 
63  // PETSc exact preallocation is a backend-wide mode: either all matrices in
64  // this assembly group support it, or none of them do. Mixed support would
65  // indicate an inconsistent backend state.
66  assert(any_supported == all_supported);
67 
68  if(all_supported) {
69  // First pass collects the sparse graph for PETSc; the second pass below
70  // inserts the real numerical values into the exactly preallocated matrix.
71  assemble();
72  for(sf_mat* mat : matrices) {
73  if(mat != nullptr) mat->finalize_exact_preallocation();
74  }
75  assemble();
76  return;
77  }
78 
79  assemble();
80 }
81 
82 bool parse_emi_output_tags(const char* tag_list,
83  const hashmap::unordered_set<int>& extra_tags,
84  const hashmap::unordered_set<int>& intra_tags,
85  hashmap::unordered_set<int>& output_tags)
86 {
87  static const char* parameter_name = "gridout_tags";
88  const std::string spec = tag_list ? tag_list : "";
89 
90  std::vector<int> tags;
91  std::string error;
92  if(!opencarp::paramschema::parse_idset_spec(spec, &tags, &error)) {
93  log_msg(0, 5, ECHO, "Could not parse %s: %s.", parameter_name, error.c_str());
94  EXIT(EXIT_FAILURE);
95  }
96 
97  if(tags.size() == 0) return false;
98 
99  output_tags.clear();
100  output_tags.insert(tags.begin(), tags.end());
101 
102  SF::vector<int> missing_tags;
103  for(int tag_id : output_tags) {
104  if(extra_tags.count(tag_id) == 0 && intra_tags.count(tag_id) == 0) {
105  missing_tags.push_back(tag_id);
106  }
107  }
108 
109  if(missing_tags.size()) {
110  binary_sort(missing_tags);
111 
112  std::stringstream msg;
113  for(size_t i = 0; i < missing_tags.size(); i++) {
114  if(i) msg << ", ";
115  msg << missing_tags[i];
116  }
117 
118  log_msg(0, 3, ECHO,
119  "Warning: ignoring %s tag(s) not listed in the EMI extra/intra tag sets: %s.",
120  parameter_name, msg.str().c_str());
121 
122  for(int tag_id : missing_tags)
123  output_tags.erase(tag_id);
124 
125  if(output_tags.size() == 0) {
126  log_msg(0, 5, ECHO, "%s did not match any EMI extra/intra tag.", parameter_name);
127  EXIT(EXIT_FAILURE);
128  }
129  }
130 
131  log_msg(0, 0, 0, "Restricting EMI output to %zu tag(s) from %s.",
132  output_tags.size(), parameter_name);
133  return true;
134 }
135 
136 const char* elem_type_name(SF::elem_t type)
137 {
138  switch(type) {
139  case SF::Line: return "Ln";
140  case SF::Tri: return "Tr";
141  case SF::Quad: return "Qd";
142  case SF::Tetra: return "Tt";
143  case SF::Pyramid: return "Py";
144  case SF::Prism: return "Pr";
145  case SF::Hexa: return "Hx";
146  default: return "";
147  }
148 }
149 
150 struct restricted_point_record {
151  mesh_int_t idx;
152  mesh_real_t xyz[3];
153 };
154 
155 std::string gather_rank_text_root(const std::string& local_text, MPI_Comm comm)
156 {
157  int rank = 0, size = 0;
158  MPI_Comm_rank(comm, &rank);
159  MPI_Comm_size(comm, &size);
160 
161  std::string all_text;
162  if(rank == 0)
163  all_text = local_text;
164 
165  for(int pid = 1; pid < size; pid++) {
166  if(rank == pid) {
167  size_t len = local_text.size();
168  MPI_Send(&len, sizeof(size_t), MPI_BYTE, 0, SF_MPITAG, comm);
169  if(len)
170  MPI_Send(local_text.data(), static_cast<int>(len), MPI_CHAR, 0, SF_MPITAG, comm);
171  } else if(rank == 0) {
172  MPI_Status stat;
173  size_t len = 0;
174  MPI_Recv(&len, sizeof(size_t), MPI_BYTE, pid, SF_MPITAG, comm, &stat);
175  if(len) {
176  size_t offset = all_text.size();
177  all_text.resize(offset + len);
178  MPI_Recv(all_text.data() + offset, static_cast<int>(len), MPI_CHAR, pid, SF_MPITAG, comm, &stat);
179  }
180  }
181  }
182 
183  return all_text;
184 }
185 
186 struct direct_element_record {
187  mesh_int_t idx;
188  SF::elem_t type;
189  int tag;
190  SF::vector<mesh_int_t> node_ref;
191  std::string elem_line;
192  std::string fib_line;
193 };
194 
195 void write_direct_restricted_mesh_text_root(const sf_mesh& mesh,
196  const std::string& output_file,
197  const SF::vector<bool>& keep_elem)
198 {
199  MPI_Comm comm = mesh.comm;
200  int rank = 0, size = 0;
201  MPI_Comm_rank(comm, &rank);
202  MPI_Comm_size(comm, &size);
203 
204  const SF::vector<mesh_int_t>& elem_ref = mesh.get_numbering(SF::NBR_ELEM_REF);
205  const SF::vector<mesh_int_t>& node_ref = mesh.get_numbering(SF::NBR_REF);
206  const bool write_fibers = mesh.fib.size() == mesh.l_numelem * 3;
207 
209  std::ostringstream elem_records;
210 
211  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++) {
212  if(!keep_elem[eidx]) continue;
213 
214  const char* type_name = elem_type_name(mesh.type[eidx]);
215  if(type_name[0] == '\0') {
216  log_msg(0, 5, ECHO, "Unsupported element type in restricted EMI output.");
217  EXIT(EXIT_FAILURE);
218  }
219 
220  elem_records << elem_ref[eidx] << '\t' << type_name << '\t' << mesh.tag[eidx] << '\t';
221  for(mesh_int_t j = mesh.dsp[eidx]; j < mesh.dsp[eidx + 1]; j++) {
222  const mesh_int_t local_node = mesh.con[j];
223  elem_records << node_ref[local_node] << ' ';
224 
225  restricted_point_record point;
226  point.idx = node_ref[local_node];
227  point.xyz[0] = mesh.xyz[local_node * 3 + 0];
228  point.xyz[1] = mesh.xyz[local_node * 3 + 1];
229  point.xyz[2] = mesh.xyz[local_node * 3 + 2];
230  local_points.push_back(point);
231  }
232 
233  elem_records << '\t';
234  if(write_fibers) {
235  elem_records << mesh.fib[eidx * 3 + 0] << ' '
236  << mesh.fib[eidx * 3 + 1] << ' '
237  << mesh.fib[eidx * 3 + 2];
238  if(mesh.she.size() == mesh.fib.size()) {
239  elem_records << ' ' << mesh.she[eidx * 3 + 0] << ' '
240  << mesh.she[eidx * 3 + 1] << ' '
241  << mesh.she[eidx * 3 + 2];
242  }
243  }
244  elem_records << '\n';
245  }
246 
247  std::sort(local_points.begin(), local_points.end(),
248  [](const restricted_point_record& lhs, const restricted_point_record& rhs) {
249  return lhs.idx < rhs.idx;
250  });
251  auto unique_end = std::unique(local_points.begin(), local_points.end(),
252  [](const restricted_point_record& lhs,
253  const restricted_point_record& rhs) {
254  return lhs.idx == rhs.idx;
255  });
256  local_points.resize(unique_end - local_points.begin());
257 
259  if(rank == 0)
260  all_points = local_points;
261 
262  for(int pid = 1; pid < size; pid++) {
263  if(rank == pid) {
264  size_t len = local_points.size();
265  MPI_Send(&len, sizeof(size_t), MPI_BYTE, 0, SF_MPITAG, comm);
266  if(len)
267  MPI_Send(local_points.data(), static_cast<int>(len * sizeof(restricted_point_record)),
268  MPI_BYTE, 0, SF_MPITAG, comm);
269  } else if(rank == 0) {
270  MPI_Status stat;
271  size_t len = 0;
272  MPI_Recv(&len, sizeof(size_t), MPI_BYTE, pid, SF_MPITAG, comm, &stat);
273  if(len) {
274  size_t offset = all_points.size();
275  all_points.resize(offset + len);
276  MPI_Recv(all_points.data() + offset,
277  static_cast<int>(len * sizeof(restricted_point_record)),
278  MPI_BYTE, pid, SF_MPITAG, comm, &stat);
279  }
280  }
281  }
282 
283  const std::string all_elem_records = gather_rank_text_root(elem_records.str(), comm);
284  if(rank != 0) return;
285 
286  std::sort(all_points.begin(), all_points.end(),
287  [](const restricted_point_record& lhs, const restricted_point_record& rhs) {
288  return lhs.idx < rhs.idx;
289  });
290  unique_end = std::unique(all_points.begin(), all_points.end(),
291  [](const restricted_point_record& lhs,
292  const restricted_point_record& rhs) {
293  return lhs.idx == rhs.idx;
294  });
295  all_points.resize(unique_end - all_points.begin());
296 
298  point_map.reserve(all_points.size());
299  for(size_t i = 0; i < all_points.size(); i++)
300  point_map[all_points[i].idx] = static_cast<mesh_int_t>(i);
301 
303  std::istringstream input(all_elem_records);
304  std::string line;
305  while(std::getline(input, line)) {
306  if(line.empty()) continue;
307 
308  std::istringstream rec(line);
309  std::string type_name;
310  direct_element_record elem;
311  rec >> elem.idx >> type_name >> elem.tag;
312  elem.type = SF::getElemTypeID(const_cast<char*>(type_name.c_str()));
313  rec >> std::ws;
314 
315  std::string nodes;
316  std::getline(rec, nodes, '\t');
317  std::istringstream node_input(nodes);
318  mesh_int_t node = 0;
319  while(node_input >> node)
320  elem.node_ref.push_back(node);
321 
322  std::getline(rec, elem.fib_line);
323  elements.push_back(elem);
324  }
325 
326  if(elements.size() == 0) {
327  log_msg(0, 5, ECHO, "Restricted EMI output mesh \"%s\" is empty.", mesh.name.c_str());
328  EXIT(EXIT_FAILURE);
329  }
330 
331  for(direct_element_record& elem : elements) {
332  std::ostringstream elem_line;
333  elem_line << elem_type_name(elem.type);
334  for(mesh_int_t ref_node : elem.node_ref) {
335  auto it = point_map.find(ref_node);
336  if(it == point_map.end()) {
337  log_msg(0, 5, ECHO, "Restricted EMI output mesh element references an unknown point.");
338  EXIT(EXIT_FAILURE);
339  }
340  elem_line << ' ' << it->second;
341  }
342  elem_line << ' ' << elem.tag;
343  elem.elem_line = elem_line.str();
344  }
345 
346  std::sort(elements.begin(), elements.end(),
347  [](const direct_element_record& lhs, const direct_element_record& rhs) {
348  if(lhs.idx != rhs.idx) return lhs.idx < rhs.idx;
349  if(lhs.elem_line != rhs.elem_line) return lhs.elem_line < rhs.elem_line;
350  return lhs.fib_line < rhs.fib_line;
351  });
352 
353  FILE* pts_fd = fopen((output_file + ".pts").c_str(), "w");
354  if(pts_fd == nullptr) {
355  log_msg(0, 5, ECHO, "Could not open restricted EMI output file %s.pts.", output_file.c_str());
356  EXIT(EXIT_FAILURE);
357  }
358  fprintf(pts_fd, "%zu\n", all_points.size());
359  for(const restricted_point_record& point : all_points)
360  fprintf(pts_fd, "%.16g %.16g %.16g\n",
361  static_cast<double>(point.xyz[0]),
362  static_cast<double>(point.xyz[1]),
363  static_cast<double>(point.xyz[2]));
364  fclose(pts_fd);
365 
366  FILE* elem_fd = fopen((output_file + ".elem").c_str(), "w");
367  if(elem_fd == nullptr) {
368  log_msg(0, 5, ECHO, "Could not open restricted EMI output file %s.elem.", output_file.c_str());
369  EXIT(EXIT_FAILURE);
370  }
371  fprintf(elem_fd, "%zu\n", elements.size());
372  for(const direct_element_record& elem : elements) {
373  fputs(elem.elem_line.c_str(), elem_fd);
374  fputc('\n', elem_fd);
375  }
376  fclose(elem_fd);
377 
378  if(write_fibers) {
379  FILE* lon_fd = fopen((output_file + ".lon").c_str(), "w");
380  if(lon_fd == nullptr) {
381  log_msg(0, 5, ECHO, "Could not open restricted EMI output file %s.lon.", output_file.c_str());
382  EXIT(EXIT_FAILURE);
383  }
384  for(const direct_element_record& elem : elements) {
385  fputs(elem.fib_line.c_str(), lon_fd);
386  fputc('\n', lon_fd);
387  }
388  fclose(lon_fd);
389  }
390 }
391 
392 void build_emi_volume_output_restriction(sf_mesh& mesh,
393  const hashmap::unordered_set<int>& output_tags,
394  SF::vector<mesh_int_t>& phie_output_idx)
395 {
396  const SF::vector<mesh_int_t>& nbr = mesh.get_numbering(SF::NBR_SUBMESH);
397  SF::vector<mesh_int_t> selected_nodes;
398 
399  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++) {
400  if(output_tags.count(mesh.tag[eidx]) == 0) continue;
401 
402  for(mesh_int_t j = mesh.dsp[eidx]; j < mesh.dsp[eidx + 1]; j++)
403  selected_nodes.push_back(nbr[mesh.con[j]]);
404  }
405 
406  binary_sort(selected_nodes);
407  unique_resize(selected_nodes);
408  compute_restr_idx(mesh, selected_nodes, phie_output_idx);
409 }
410 
411 void build_emi_surface_output_restriction(sf_mesh& mesh,
412  const hashmap::unordered_set<int>& output_tags,
413  const hashmap::unordered_map<mesh_int_t, std::pair<mesh_int_t, mesh_int_t>>& face_tags,
414  SF::vector<mesh_int_t>& vm_output_idx)
415 {
416  const SF::vector<mesh_int_t>& nbr = mesh.get_numbering(SF::NBR_ELEM_SUBMESH);
417  const SF::vector<mesh_int_t>& layout = mesh.epl.algebraic_layout();
418  const mesh_int_t start = layout[get_rank()];
419  const mesh_int_t stop = layout[get_rank() + 1];
420 
421  vm_output_idx.resize(0);
422  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++) {
423  bool keep = output_tags.count(mesh.tag[eidx]) != 0;
424  auto it = face_tags.find(eidx);
425  if(it != face_tags.end()) {
426  keep = keep ||
427  output_tags.count(static_cast<int>(it->second.first)) != 0 ||
428  output_tags.count(static_cast<int>(it->second.second)) != 0;
429  }
430 
431  if(keep && nbr[eidx] >= start && nbr[eidx] < stop)
432  vm_output_idx.push_back(nbr[eidx] - start);
433  }
434 
435  binary_sort(vm_output_idx);
436  unique_resize(vm_output_idx);
437 }
438 
439 template<class Keep>
440 void write_emi_output_mesh(const sf_mesh& mesh,
441  bool write_binary,
442  const std::string& output_file,
443  const char* full_mesh_name,
444  Keep keep)
445 {
446  SF::vector<bool> keep_elem(mesh.l_numelem, false);
447  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++)
448  keep_elem[eidx] = keep(eidx);
449 
450  if(!write_binary) {
451  write_direct_restricted_mesh_text_root(mesh, output_file, keep_elem);
452  return;
453  }
454 
455  sf_mesh out_mesh;
456  out_mesh.name = mesh.name;
457  extract_mesh(keep_elem, mesh, out_mesh);
458 
459  if(out_mesh.g_numelem == 0) {
460  log_msg(0, 5, ECHO, "Restricted EMI output mesh \"%s\" is empty.", full_mesh_name);
461  EXIT(EXIT_FAILURE);
462  }
463 
464  rebalance_mesh(out_mesh);
465 
466  SF::vector<mesh_real_t> pts(mesh.xyz);
467  SF::vector<mesh_int_t> ptsidx(mesh.get_numbering(SF::NBR_REF));
468  std::list<sf_mesh*> meshlist;
469  meshlist.push_back(&out_mesh);
470  insert_points(pts, ptsidx, meshlist);
471 
473  numbering(out_mesh);
474  out_mesh.generate_par_layout();
475 
476  write_mesh_parallel(out_mesh, write_binary, output_file.c_str());
477 }
478 
479 // header prefixed to the EMI bulk-potential companion file (<roe>.emifld). It
480 // binds the field to its ionic checkpoint: real_bytes and gsize guard precision
481 // and mesh, roe_hash fingerprints the exact companion .roe so a stale field file
482 // from a different run on the same mesh cannot be silently accepted.
483 struct emifld_header {
484  char magic[8]; // "EMIFLD\0\0"
485  uint32_t version;
486  uint32_t real_bytes; // sizeof(SF_real)
487  uint64_t gsize; // global DOF count
488  uint64_t roe_hash; // FNV-1a over the entire companion .roe
489 };
490 static_assert(sizeof(emifld_header) == 32, "emifld_header must be tightly packed");
491 
492 const char EMIFLD_MAGIC[8] = {'E', 'M', 'I', 'F', 'L', 'D', '\0', '\0'};
493 const uint32_t EMIFLD_VERSION = 1;
494 
495 // FNV-1a over the whole file; rank-0 local. Same constants as IonIfBase::sv_fingerprint.
496 bool fnv1a_file(const char* path, uint64_t& out)
497 {
498  constexpr uint64_t kFNVOffsetBasis = 0xcbf29ce484222325ULL;
499  constexpr uint64_t kFNVPrime = 0x100000001b3ULL;
500  FILE* f = fopen(path, "rb");
501  if (!f) return false;
502  uint64_t h = kFNVOffsetBasis;
503  unsigned char buf[1 << 16];
504  size_t n;
505  while ((n = fread(buf, 1, sizeof buf, f)) > 0)
506  for (size_t i = 0; i < n; i++) { h ^= buf[i]; h *= kFNVPrime; }
507  const bool ok = !ferror(f);
508  fclose(f);
509  if (ok) out = h;
510  return ok;
511 }
512 
513 } // namespace
514 
515 void log_mesh_local_element_ranges(const sf_mesh& emi_mesh,
516  const sf_mesh& emi_surfmesh_w_counter_face,
517  const sf_mesh& emi_surfmesh_unique_face)
518 {
519  int rank = 0;
520  int comm_size = 0;
521  MPI_Comm_rank(emi_surfmesh_w_counter_face.comm, &rank);
522  MPI_Comm_size(emi_surfmesh_w_counter_face.comm, &comm_size);
523 
524  const size_t local_emi_elems = emi_mesh.l_numelem;
525  const size_t local_both_face_elems = emi_surfmesh_w_counter_face.l_numelem;
526  const size_t local_unique_face_elems = emi_surfmesh_unique_face.l_numelem;
527 
528  std::vector<size_t> all_emi_elems;
529  std::vector<size_t> all_both_face_elems;
530  std::vector<size_t> all_unique_face_elems;
531  if (rank == 0) {
532  all_emi_elems.resize(comm_size, 0);
533  all_both_face_elems.resize(comm_size, 0);
534  all_unique_face_elems.resize(comm_size, 0);
535  }
536 
537  const MPI_Datatype size_mpi_t = mpi_datatype<size_t>();
538  MPI_Gather(&local_emi_elems, 1, size_mpi_t,
539  rank == 0 ? all_emi_elems.data() : nullptr, 1, size_mpi_t,
540  0, emi_surfmesh_w_counter_face.comm);
541  MPI_Gather(&local_both_face_elems, 1, size_mpi_t,
542  rank == 0 ? all_both_face_elems.data() : nullptr, 1, size_mpi_t,
543  0, emi_surfmesh_w_counter_face.comm);
544  MPI_Gather(&local_unique_face_elems, 1, size_mpi_t,
545  rank == 0 ? all_unique_face_elems.data() : nullptr, 1, size_mpi_t,
546  0, emi_surfmesh_w_counter_face.comm);
547 
548  if (rank != 0) return;
549 
550  const auto print_min_max = [](const char* label, const std::vector<size_t>& counts) {
551  if (counts.empty()) return;
552 
553  size_t min_val = counts[0];
554  size_t max_val = counts[0];
555  int min_rank = 0;
556  int max_rank = 0;
557 
558  for (int r = 1; r < static_cast<int>(counts.size()); ++r) {
559  if (counts[r] < min_val) {
560  min_val = counts[r];
561  min_rank = r;
562  }
563  if (counts[r] > max_val) {
564  max_val = counts[r];
565  max_rank = r;
566  }
567  }
568 
569  log_msg(NULL, 0, 0, " %s: \n\t\t min=%zu on rank=%d, \n\t\t max=%zu on rank=%d\n",
570  label, min_val, min_rank, max_val, max_rank);
571  };
572  log_msg(NULL, 0, 0, "\n**********************************");
573  log_msg(NULL, 0, 0, "min/max number of local-element ranges:");
574  print_min_max("emi_mesh", all_emi_elems);
575  print_min_max("emi_surfmesh_w_counter_face", all_both_face_elems);
576  print_min_max("emi_surfmesh_unique_face", all_unique_face_elems);
577  log_msg(NULL, 0, 0, "**********************************");
578 }
579 
580 #if defined(EMI_DEBUG_MESH) && defined(WITH_PETSC)
581 void log_lhs_positive_definite_probe(SF::abstract_matrix<SF_int, SF_real>* mat,
582  FILE_SPEC logger,
583  const char* stage,
584  int num_trials = 5)
585 {
586  (void)logger;
587  if (param_globals::flavor != std::string("petsc")) return;
588 
589  auto* petsc_mat = dynamic_cast<SF::petsc_matrix*>(mat);
590  if (petsc_mat == nullptr) return;
591 
592  Vec x = NULL, y = NULL;
593  MatCreateVecs(petsc_mat->data, &x, &y);
594 
595  PetscRandom rnd = NULL;
596  PetscRandomCreate(SF_COMM, &rnd);
597  PetscRandomSetFromOptions(rnd);
598 
599  int rank = 0;
600  MPI_Comm_rank(SF_COMM, &rank);
601 
602  PetscReal min_q = PETSC_MAX_REAL;
603  PetscReal max_q = -PETSC_MAX_REAL;
604  PetscInt nonpos_count = 0;
605 
606  for (int i = 0; i < num_trials; ++i) {
607  VecSetRandom(x, rnd);
608  MatMult(petsc_mat->data, x, y);
609 
610  PetscScalar q_scalar = 0.0;
611  VecDot(x, y, &q_scalar);
612 
613  const PetscReal q = PetscRealPart(q_scalar);
614  min_q = std::min(min_q, q);
615  max_q = std::max(max_q, q);
616  if (q <= 0.0) nonpos_count++;
617  }
618 
619  if (rank == 0) {
620  PetscPrintf(PETSC_COMM_SELF,
621  "%s: SPD probe with %d random vectors: min(x^T A x)=%g, max(x^T A x)=%g, nonpositive=%d\n",
622  stage, num_trials, double(min_q), double(max_q), int(nonpos_count));
623  }
624 
625  PetscRandomDestroy(&rnd);
626  VecDestroy(&x);
627  VecDestroy(&y);
628 }
629 
630 PetscScalar emi_probe_vector_entry(const PetscInt gid, const int probe_id)
631 {
632  const double x = static_cast<double>(gid + 1);
633 
634  switch (probe_id) {
635  case 0:
636  return std::sin(1.0e-3 * x) + 0.5 * std::cos(3.0e-3 * x);
637  case 1:
638  return std::cos(7.0e-4 * x) - 0.35 * std::sin(2.0e-3 * x);
639  default:
640  return 0.75 * std::sin(1.3e-3 * x) + 0.25 * std::cos(4.0e-3 * x);
641  }
642 }
643 
644 void log_lhs_operator_probe(SF::abstract_matrix<SF_int, SF_real>* mat,
645  FILE_SPEC logger,
646  const char* stage,
647  int num_probes = 3)
648 {
649  (void)logger;
650  if (param_globals::flavor != std::string("petsc")) return;
651 
652  auto* petsc_mat = dynamic_cast<SF::petsc_matrix*>(mat);
653  if (petsc_mat == nullptr) return;
654 
655  Vec x = NULL, y = NULL;
656  MatCreateVecs(petsc_mat->data, &x, &y);
657 
658  PetscInt i_start = 0, i_end = 0;
659  VecGetOwnershipRange(x, &i_start, &i_end);
660 
661  PetscScalar* x_arr = NULL;
662  int rank = 0;
663  MPI_Comm_rank(SF_COMM, &rank);
664 
665  for (int probe_id = 0; probe_id < num_probes; ++probe_id) {
666  VecGetArray(x, &x_arr);
667  for (PetscInt i = i_start; i < i_end; ++i) {
668  x_arr[i - i_start] = emi_probe_vector_entry(i, probe_id);
669  }
670  VecRestoreArray(x, &x_arr);
671 
672  MatMult(petsc_mat->data, x, y);
673 
674  PetscReal y_norm2 = 0.0;
675  PetscReal y_norminf = 0.0;
676  PetscScalar y_sum = 0.0;
677  PetscScalar xAy = 0.0;
678  VecNorm(y, NORM_2, &y_norm2);
679  VecNorm(y, NORM_INFINITY, &y_norminf);
680  VecSum(y, &y_sum);
681  VecDot(x, y, &xAy);
682 
683  PetscScalar weighted_checksum_local = 0.0;
684  const PetscScalar* y_arr = NULL;
685  VecGetArrayRead(y, &y_arr);
686  for (PetscInt i = i_start; i < i_end; ++i) {
687  const PetscScalar weight = static_cast<PetscScalar>(i + 1);
688  weighted_checksum_local += weight * y_arr[i - i_start];
689  }
690  VecRestoreArrayRead(y, &y_arr);
691 
692  PetscScalar weighted_checksum = 0.0;
693  MPI_Allreduce(&weighted_checksum_local, &weighted_checksum, 1, MPIU_SCALAR, MPI_SUM, SF_COMM);
694 
695  if (rank == 0) {
696  PetscPrintf(PETSC_COMM_SELF,
697  "%s: operator probe %d ||Ax||_2=%g, ||Ax||_inf=%g, sum(Ax)=%g, x^T A x=%g, weighted_checksum=%g\n",
698  stage, probe_id + 1, double(y_norm2), double(y_norminf), double(PetscRealPart(y_sum)),
699  double(PetscRealPart(xAy)), double(PetscRealPart(weighted_checksum)));
700  }
701  }
702 
703  VecDestroy(&x);
704  VecDestroy(&y);
705 }
706 
707 void log_rhs_probe(SF::abstract_vector<SF_int, SF_real>* vec,
708  const char* stage)
709 {
710  if (param_globals::flavor != std::string("petsc")) return;
711 
712  auto* petsc_vec = dynamic_cast<SF::petsc_vector*>(vec);
713  if (petsc_vec == nullptr) return;
714 
715  PetscReal norm2 = 0.0;
716  PetscReal norminf = 0.0;
717  PetscScalar sum = 0.0;
718  VecNorm(petsc_vec->data, NORM_2, &norm2);
719  VecNorm(petsc_vec->data, NORM_INFINITY, &norminf);
720  VecSum(petsc_vec->data, &sum);
721 
722  PetscInt i_start = 0, i_end = 0;
723  VecGetOwnershipRange(petsc_vec->data, &i_start, &i_end);
724 
725  PetscScalar weighted_checksum_local = 0.0;
726  const PetscScalar* arr = NULL;
727  VecGetArrayRead(petsc_vec->data, &arr);
728  for (PetscInt i = i_start; i < i_end; ++i) {
729  const PetscScalar weight = static_cast<PetscScalar>(i + 1);
730  weighted_checksum_local += weight * arr[i - i_start];
731  }
732  VecRestoreArrayRead(petsc_vec->data, &arr);
733 
734  PetscScalar weighted_checksum = 0.0;
735  MPI_Allreduce(&weighted_checksum_local, &weighted_checksum, 1, MPIU_SCALAR, MPI_SUM, SF_COMM);
736 
737  int rank = 0;
738  MPI_Comm_rank(SF_COMM, &rank);
739  if (rank == 0) {
740  PetscPrintf(PETSC_COMM_SELF,
741  "%s: rhs probe ||b||_2=%g, ||b||_inf=%g, sum(b)=%g, weighted_checksum=%g\n",
742  stage, double(norm2), double(norminf), double(PetscRealPart(sum)),
743  double(PetscRealPart(weighted_checksum)));
744  }
745 }
746 
747 void log_linear_system_probe(SF::abstract_matrix<SF_int, SF_real>* mat,
749  const char* stage,
750  int num_probes = 3)
751 {
752  if (param_globals::flavor != std::string("petsc")) return;
753 
754  auto* petsc_mat = dynamic_cast<SF::petsc_matrix*>(mat);
755  auto* petsc_vec = dynamic_cast<SF::petsc_vector*>(vec);
756  if (petsc_mat == nullptr || petsc_vec == nullptr) return;
757 
758  Vec x = NULL, ax = NULL, residual = NULL;
759  MatCreateVecs(petsc_mat->data, &x, &ax);
760  VecDuplicate(ax, &residual);
761 
762  PetscInt i_start = 0, i_end = 0;
763  VecGetOwnershipRange(x, &i_start, &i_end);
764 
765  int rank = 0;
766  MPI_Comm_rank(SF_COMM, &rank);
767 
768  for (int probe_id = 0; probe_id < num_probes; ++probe_id) {
769  PetscScalar* x_arr = NULL;
770  VecGetArray(x, &x_arr);
771  for (PetscInt i = i_start; i < i_end; ++i) {
772  x_arr[i - i_start] = emi_probe_vector_entry(i, probe_id);
773  }
774  VecRestoreArray(x, &x_arr);
775 
776  MatMult(petsc_mat->data, x, ax);
777  VecWAXPY(residual, -1.0, petsc_vec->data, ax);
778 
779  PetscReal residual_norm2 = 0.0;
780  PetscReal residual_norminf = 0.0;
781  PetscScalar residual_sum = 0.0;
782  PetscScalar xTResidual = 0.0;
783  VecNorm(residual, NORM_2, &residual_norm2);
784  VecNorm(residual, NORM_INFINITY, &residual_norminf);
785  VecSum(residual, &residual_sum);
786  VecDot(x, residual, &xTResidual);
787 
788  PetscScalar weighted_checksum_local = 0.0;
789  const PetscScalar* residual_arr = NULL;
790  VecGetArrayRead(residual, &residual_arr);
791  for (PetscInt i = i_start; i < i_end; ++i) {
792  const PetscScalar weight = static_cast<PetscScalar>(i + 1);
793  weighted_checksum_local += weight * residual_arr[i - i_start];
794  }
795  VecRestoreArrayRead(residual, &residual_arr);
796 
797  PetscScalar weighted_checksum = 0.0;
798  MPI_Allreduce(&weighted_checksum_local, &weighted_checksum, 1, MPIU_SCALAR, MPI_SUM, SF_COMM);
799 
800  if (rank == 0) {
801  PetscPrintf(PETSC_COMM_SELF,
802  "%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",
803  stage, probe_id + 1, double(residual_norm2), double(residual_norminf),
804  double(PetscRealPart(residual_sum)), double(PetscRealPart(xTResidual)),
805  double(PetscRealPart(weighted_checksum)));
806  }
807  }
808 
809  VecDestroy(&x);
810  VecDestroy(&ax);
811  VecDestroy(&residual);
812 }
813 #endif
814 
823 void set_elec_tissue_properties_emi_volume(MaterialType* mtype, hashmap::unordered_set<int> &extra_tags, hashmap::unordered_set<int> &intra_tags, FILE_SPEC logger)
824 {
826  MaterialType *m = mtype;
827 
828  // initialize random conductivity fluctuation structure with PrM values
829  m->regions.resize(param_globals::num_gregions);
830 
831  const char* grid_name = "emi_grid_domain";
832  log_msg(logger, 0, 0, "Setting up %s tissue poperties for %d regions ..", grid_name,
833  param_globals::num_gregions);
834 
835  char buf[64];
836  RegionSpecs* reg = m->regions.data();
837 
838  // default tags for extra and intra domains
839  hashmap::unordered_set<int> extra_tags_default = extra_tags;
840  hashmap::unordered_set<int> intra_tags_default = intra_tags;
841 
842  // gregion[0] and gregion[1] own every extra- and intracellular tag not claimed elsewhere,
843  // so tags listed on them would conflict with that; overrides belong in gregion[2+]
844  for (size_t i=0; i<std::min<size_t>(2, m->regions.size()); i++)
845  if (param_globals::gregion[i].num_IDs) {
846  log_msg(logger, 5, ECHO|FLUSH, "gregion[%zu] is the default %s region in EMI and takes no IDs. "
847  "Assign tags with a different conductivity to gregion[2] or higher.\n",
848  i, i == 0 ? "extracellular" : "intracellular");
849  EXIT(EXIT_FAILURE);
850  }
851 
852  for (size_t i=0; i<m->regions.size(); i++)
853  {
854  for (int j=0;j<param_globals::gregion[i].num_IDs;j++)
855  {
856  int tag = param_globals::gregion[i].ID[j];
857 
858  // removed tags explicitly defined by user in param_globals::gregion[i>1]
859  if(extra_tags_default.find(tag) != extra_tags_default.end())
860  extra_tags_default.erase(tag);
861 
862  if(intra_tags_default.find(tag) != intra_tags_default.end())
863  intra_tags_default.erase(tag);
864  }
865  }
866 
867  for (size_t i=0; i<m->regions.size(); i++, reg++)
868  {
869  if(!strcmp(param_globals::gregion[i].name, "")) {
870  snprintf(buf, sizeof buf, ", gregion_%d", int(i));
871  param_globals::gregion[i].name = dupstr(buf);
872  }
873 
874  // copy metadata into RegionSpecs
875  reg->regname = strdup(param_globals::gregion[i].name);
876  reg->regID = i;
877 
878  if(i==0) // default Extracellular region
879  reg->nsubregs = extra_tags_default.size();
880  if(i==1) // default intracellular region
881  reg->nsubregs = intra_tags_default.size();
882  if(i>1) // optional: rest of other param_globals::gregion[i>1] defined by user
883  reg->nsubregs = param_globals::gregion[i].num_IDs;
884 
885  if(!reg->nsubregs)
886  reg->subregtags = NULL;
887  else
888  {
889  reg->subregtags = new int[reg->nsubregs];
890 
891  if(i==0){
892  int j = 0;
893  for (int tag : extra_tags_default) {
894  reg->subregtags[j] = tag;
895  j++;
896  }
897  }
898  else if(i==1){
899  int j = 0;
900  for (int tag : intra_tags_default) {
901  reg->subregtags[j] = tag;
902  j++;
903  }
904  }
905  else{
906  for (int j=0;j<reg->nsubregs;j++)
907  reg->subregtags[j] = param_globals::gregion[i].ID[j]; // explicit tags defined by user
908  }
909  }
910 
911  // describe material in given region
912  elecMaterial *emat = new elecMaterial();
913  emat->material_type = ElecMat;
914 
915  // Isotropic conductivity is considered in EMI model.
916  emat->InVal[0] = param_globals::gregion[i].g_bath;
917  emat->InVal[1] = param_globals::gregion[i].g_bath;
918  emat->InVal[2] = param_globals::gregion[i].g_bath;
919 
920  emat->ExVal[0] = param_globals::gregion[i].g_bath;
921  emat->ExVal[1] = param_globals::gregion[i].g_bath;
922  emat->ExVal[2] = param_globals::gregion[i].g_bath;
923 
924  emat->BathVal[0] = param_globals::gregion[i].g_bath;
925  emat->BathVal[1] = param_globals::gregion[i].g_bath;
926  emat->BathVal[2] = param_globals::gregion[i].g_bath;
927 
928  // convert units from S/m -> mS/um
929  for (int j=0; j<3; j++) {
930  emat->InVal[j] *= 1e-3 * param_globals::gregion[i].g_mult;
931  emat->ExVal[j] *= 1e-3 * param_globals::gregion[i].g_mult;
932  emat->BathVal[j] *= 1e-3 * param_globals::gregion[i].g_mult;
933  }
934  reg->material = emat;
935  }
936 
937  if (strlen(param_globals::gi_scale_vec))
938  read_el_scale_vec(param_globals::gi_scale_vec, emi_msh, m->el_scale, m->el_scale_dpn);
939 }
940 
941 void parabolic_solver_emi::init()
942 {
944  double t0, t1, dur;
945  get_time(t0);
946  const int log_flag = param_globals::output_level > 1 ? ECHO : 0;
947  const auto log_init_timing = [&](const char* label, double start) {
948  log_msg(NULL, 0, log_flag, "EMI solver init: %s in %.5f seconds.", label, float(MPI_Wtime() - start));
949  };
950 
951  double phase_t = MPI_Wtime();
952  stats.init_logger("par_stats.dat");
953 
954  // Create/initialise linear solver object: PETSc gets constructed with default settings
955  SF::init_solver(&lin_solver);
956  log_init_timing("linear solver object", phase_t);
957 
958  // EMI mesh: After decoupling the interfaces, we obtain a volumetric mesh with new dofs for all vertices.
959  sf_mesh & emi_mesh = get_mesh(emi_msh);
960  // Surface mesh called emi_surface_counter_msh:
961  // This mesh contains both sides of each interface. At this stage its nodes
962  // already use the decoupled EMI DOF numbering.
963  sf_mesh & emi_surfmesh_w_counter_face = get_mesh(emi_surface_counter_msh);
964  sf_mesh & emi_surfmesh_one_side = get_mesh(emi_surface_msh);
965  sf_mesh & emi_surfmesh_unique_face = get_mesh(emi_surface_unique_face_msh);
966 
967  phase_t = MPI_Wtime();
968  int max_row_entries_emi = max_nodal_edgecount(emi_mesh);
969  log_init_timing("maximum nodal edge counts", phase_t);
970 
971  int rank;
972  MPI_Comm_rank(emi_surfmesh_w_counter_face.comm, &rank);
973 
974  sf_vec::ltype alg_type = sf_vec::algebraic;
975  sf_vec::ltype alg_surface_type = sf_vec::elemwise;
976 
977  int dpn = 1;
978 
979  //-----------------------------------------------------------------
980  // setup vectors
981  //-----------------------------------------------------------------
982  phase_t = MPI_Wtime();
983  SF::init_vector(&ui, emi_mesh, dpn, alg_type);
984  SF::init_vector(&dui, emi_mesh, dpn, alg_type);
985  SF::init_vector(&ui_pre, emi_mesh, dpn, alg_type);
986  SF::init_vector(&Irhs, emi_mesh, dpn, alg_type);
987  SF::init_vector(&Iij_stim, emi_mesh, dpn, alg_type);
988  SF::init_vector(&Iij_temp, emi_mesh, dpn, alg_type);
989  SF::init_vector(&vb_both_face, emi_surfmesh_w_counter_face, dpn, alg_surface_type);
990  SF::init_vector(&vb_unique_face, emi_surfmesh_unique_face, dpn, alg_surface_type);
991  SF::init_vector(&Ib_both_face, emi_surfmesh_w_counter_face, dpn, alg_surface_type);
992  SF::init_vector(&Ib_unique_face, emi_surfmesh_unique_face, dpn, alg_surface_type);
993  log_init_timing("vectors", phase_t);
994 
995  // PETSc matrices are reallocated with an exact sparsity graph by replaying
996  // the real assembly into MATPREALLOCATOR before the numeric assembly. The
997  // scalar hint used here is therefore only a temporary bootstrap value for
998  // PETSc; non-PETSc backends keep the conservative scalar bounds.
999  const bool use_petsc_exact_preallocation = param_globals::flavor == std::string("petsc");
1000  const int petsc_initial_prealloc = 1;
1001  //---------------------------------------------------------------------
1002  // initialize operator matrices B, Bi, and BsM
1003  //---------------------------------------------------------------------
1004  mesh_int_t M = emi_surfmesh_w_counter_face.g_numelem;
1005  mesh_int_t N = emi_mesh.g_numpts;
1006  mesh_int_t m = emi_surfmesh_w_counter_face.l_numelem;
1007  mesh_int_t m_one_side = emi_surfmesh_one_side.l_numelem;
1008  mesh_int_t M_one_side = emi_surfmesh_one_side.g_numelem;
1009  mesh_int_t m_unique_face = emi_surfmesh_unique_face.l_numelem;
1010  mesh_int_t M_unique_face = emi_surfmesh_unique_face.g_numelem;
1011  mesh_int_t n = ui->lsize();
1012 
1013  if (param_globals::output_level > 1) {
1014  log_msg(NULL, 0, 0, "\n**********************************");
1015  log_msg(NULL, 0, 0, "#elements of emi surfmesh unique face: %zu", emi_surfmesh_unique_face.g_numelem);
1016  log_msg(NULL, 0, 0, "#elements of emi surfmesh one side: %zu", emi_surfmesh_one_side.g_numelem);
1017  log_msg(NULL, 0, 0, "#elements of emi surfmesh: %zu", emi_surfmesh_w_counter_face.g_numelem);
1018  log_msg(NULL, 0, 0, "#elements of emi mesh: %zu", emi_mesh.g_numelem);
1019  log_msg(NULL, 0, 0, "#dofs for emi_mesh: %zu", emi_mesh.g_numpts);
1020  log_msg(NULL, 0, 0, "#max_row_entries_emi: %zu", max_row_entries_emi);
1021  log_msg(NULL, 0, 0, "**********************************\n");
1022  log_mesh_local_element_ranges(emi_mesh, emi_surfmesh_w_counter_face, emi_surfmesh_unique_face);
1023  }
1024 
1025  SF::vector<long int> layout;
1026  SF::layout_from_count<long int>(emi_surfmesh_w_counter_face.l_numelem, layout, emi_surfmesh_w_counter_face.comm);
1027  mesh_int_t m_l = layout[rank];
1028  mesh_int_t n_l = emi_mesh.pl.algebraic_layout()[rank];
1029  const SF::vector<mesh_int_t> & alg_nod_surface = emi_surfmesh_w_counter_face.pl.algebraic_nodes();
1030 
1031  SF::vector<long int> layout_one_side;
1032  SF::layout_from_count<long int>(emi_surfmesh_one_side.l_numelem, layout_one_side, emi_surfmesh_one_side.comm);
1033  mesh_int_t m_one_side_l = layout_one_side[rank];
1034 
1035  SF::vector<long int> layout_unique_face;
1036  SF::layout_from_count<long int>(emi_surfmesh_unique_face.l_numelem, layout_unique_face, emi_surfmesh_unique_face.comm);
1037  mesh_int_t m_unique_face_l = layout_unique_face[rank];
1038 
1039  // B, Bi, and BsM have face/volume row spaces whose final PETSc sparsity is
1040  // collected exactly below by assemble_with_exact_preallocation(). The scalar
1041  // fallback remains conservative for non-PETSc backends and off-process row
1042  // insertion through the abstract matrix interface. Resting-potential
1043  // initialization writes directly into ui and no longer needs a diagonal helper
1044  // matrix.
1045  phase_t = MPI_Wtime();
1046  SF::init_matrix(&B);
1047  B->init(M, N, m, n, m_l, use_petsc_exact_preallocation ? petsc_initial_prealloc : max_row_entries_emi*2);
1048  B->zero();
1049  SF::init_matrix(&Bi);
1050  Bi->init(M, N, m, n, m_l, use_petsc_exact_preallocation ? petsc_initial_prealloc : max_row_entries_emi*2);
1051  Bi->zero();
1052  SF::init_matrix(&BsM);
1053  BsM->init(N, M, n, m, n_l, use_petsc_exact_preallocation ? petsc_initial_prealloc : max_row_entries_emi*2);
1054  BsM->zero();
1055  log_init_timing("EMI coupling matrices", phase_t);
1056 
1057  // Build the direct unique-face <-> both-face transfer operators
1058  phase_t = MPI_Wtime();
1059  SF::construct_direct_unique_both_operators(operator_unique_to_both_faces,
1060  operator_both_to_unique_face,
1061  map_elem_uniqueFace_to_elem_bothface,
1062  map_elem_uniqueFace_to_elem_oneface,
1063  vec_both_to_one_face,
1064  emi_surfmesh_w_counter_face,
1065  emi_surfmesh_unique_face,
1066  max_row_entries_emi,
1067  dpn,
1068  alg_surface_type);
1069  log_init_timing("unique/both face transfer operators", phase_t);
1070 
1071  // Initialize interpolation operators B/Bi and the scatter operator BsM.
1072  phase_t = MPI_Wtime();
1073  assemble_with_exact_preallocation({B, Bi, BsM}, [&]() {
1074  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);
1075  });
1076  log_init_timing("restriction operators", phase_t);
1077 
1078  //-----------------------------------------------------------------
1079  // initialize matrices (LHS, K, M_{emi mesh}, M_{surface mesh})
1080  //-----------------------------------------------------------------
1081  phase_t = MPI_Wtime();
1082  SF::init_matrix(&lhs_emi);
1083  SF::init_matrix(&stiffness_emi);
1084  SF::init_matrix(&mass_emi);
1085  SF::init_matrix(&mass_surf_emi);
1086 
1087  // These matrices are filled later in rebuild_matrices(). Allocating them
1088  // after the transfer/restriction operators reduces the EMI init memory peak.
1089  lhs_emi->init(emi_mesh, dpn, dpn, use_petsc_exact_preallocation ? petsc_initial_prealloc : max_row_entries_emi*3);
1090  stiffness_emi->init(emi_mesh, dpn, dpn, use_petsc_exact_preallocation ? petsc_initial_prealloc : max_row_entries_emi*2);
1091  mass_emi->init(emi_mesh, dpn, dpn, use_petsc_exact_preallocation ? petsc_initial_prealloc : max_row_entries_emi*3);
1092  mass_surf_emi->init(emi_mesh, dpn, dpn, use_petsc_exact_preallocation ? petsc_initial_prealloc : max_row_entries_emi*3);
1093  log_init_timing("system matrices", phase_t);
1094 
1095  // DEBUG: Check mesh sizes and mappings
1096  #ifdef EMI_DEBUG_MESH
1097  {
1098  int local_rank;
1099  MPI_Comm_rank(emi_mesh.comm, &local_rank);
1100 
1101  fprintf(stderr, "RANK %d MESH SIZES: one_side=%zu, counter=%zu, unique=%zu\n",
1102  local_rank, emi_surfmesh_one_side.l_numelem,
1103  emi_surfmesh_w_counter_face.l_numelem, emi_surfmesh_unique_face.l_numelem);
1104 
1105  fprintf(stderr, "RANK %d MAP SIZES: uniqueFace_to_oneface=%zu\n",
1106  local_rank, map_elem_uniqueFace_to_elem_oneface.size());
1107  fflush(stderr);
1108  }
1109  #endif
1110 
1111  decltype(map_elem_uniqueFace_to_elem_oneface)().swap(map_elem_uniqueFace_to_elem_oneface);
1112  decltype(map_elem_uniqueFace_to_elem_bothface)().swap(map_elem_uniqueFace_to_elem_bothface);
1113  vec_both_to_one_face = SF::vector<mesh_int_t>();
1114 
1115  //-----------------------------------------------------------------
1116  // setup the ionic current
1117  //-----------------------------------------------------------------
1118  phase_t = MPI_Wtime();
1119  // get initial value of the vm and Iion on the face from the ionicOnFace class
1120  sf_vec* vb_ptr = get_data(vm_emi_itf);
1121  sf_vec* Ib_ptr = get_data(iion_emi_itf);
1122 
1123  if(!(vb_ptr != NULL && Ib_ptr != NULL)) {
1124  log_msg(0,5,0, "%s error: global Vb and Ib vectors not properly set up! Ionics seem invalid! Aborting!",
1125  __func__);
1126  EXIT(1);
1127  }
1128 
1129  SF::init_vector(&vb, vb_ptr);
1130  vb->shallow_copy(*vb_ptr);
1131  SF::init_vector(&Ib, Ib_ptr);
1132  Ib->shallow_copy(*Ib_ptr);
1133 
1134  parab_tech = static_cast<parabolic_solver_emi::parabolic_t>(param_globals::parab_solve_emi);
1135  log_init_timing("ionic face vectors", phase_t);
1136 
1137  dur = timing(t1, t0);
1138  log_msg(NULL, 0, log_flag, "EMI solver init total in %.5f seconds.", float(dur));
1139 }
1140 
1141 void parabolic_solver_emi::rebuild_matrices(MaterialType* mtype, limpet::MULTI_IF & miif, SF::vector<stimulus> & stimuli, FILE_SPEC logger)
1142 {
1144  double start, end, period;
1145  get_time(start);
1146  double t0, t1, dur;
1147  mass_integrator mass_integ;
1148  mass_integrator mass_integ_emi;
1149  int dpn = 1;
1150 
1151  int log_flag = param_globals::output_level > 1 ? ECHO : 0;
1152  MaterialType & mt = mtype[0];
1153  const bool have_dbc = have_dbc_stims(stimuli);
1154  const bool use_petsc_exact_preallocation = param_globals::flavor == std::string("petsc");
1155  const bool reuse_petsc_fem_preallocation = use_petsc_exact_preallocation && fem_matrices_exact_preallocated;
1156 
1157  double Dt = user_globals::tm_manager->time_step;
1158 
1159  cond_t condType = intra_cond;
1160  sf_mesh & mesh = get_mesh(emi_msh);
1161  sf_mesh & emi_surfmesh_w_counter_face = get_mesh(emi_surface_counter_msh);
1162 
1163  // set material and conductivity type
1164  set_cond_type(mt, condType);
1165 
1166  // assemble EMI matrices
1167  // fill the EMI system
1168  {
1169  // emi-> stiffness
1170  log_msg(NULL, 0, 0, "assemble stiffness matrix");
1171  get_time(t0);
1172  elec_stiffness_integrator stfn_integ_emi(mt);
1173  auto assemble_stiffness_emi = [&]() {
1174  stiffness_emi->zero();
1175  SF::assemble_matrix(*stiffness_emi, mesh, stfn_integ_emi);
1176  };
1177  if(reuse_petsc_fem_preallocation) {
1178  assemble_stiffness_emi();
1179  } else {
1180  assemble_with_exact_preallocation({stiffness_emi}, assemble_stiffness_emi);
1181  }
1182  dur = timing(t1,t0);
1183  log_msg(logger,0,log_flag, "Computed parabolic stiffness matrix in %.5f seconds.", float(dur));
1184 
1185  log_msg(NULL, 0, 0, "assemble mass matrix on the volumetric mesh");
1186  get_time(t0);
1187  mass_integrator mass_integ;
1188  auto assemble_mass_emi = [&]() {
1189  mass_emi->zero();
1190  SF::assemble_matrix(*mass_emi, mesh, mass_integ);
1191  };
1192  if(reuse_petsc_fem_preallocation) {
1193  assemble_mass_emi();
1194  } else if(use_petsc_exact_preallocation && mass_emi->duplicate_pattern_of(*stiffness_emi)) {
1195  // mass_emi and stiffness_emi have the same volume FEM sparsity pattern.
1196  assemble_mass_emi();
1197  } else {
1198  assemble_with_exact_preallocation({mass_emi}, assemble_mass_emi);
1199  }
1200  dur = timing(t1,t0);
1201  log_msg(logger,0,log_flag, "Computed volumetric mass matrix in %.5f seconds.", float(dur));
1202 
1203  log_msg(NULL, 0, 0, "assemble LHS matrix and mass matrix on the surface mesh");
1204  get_time(t0);
1205  auto assemble_lhs_and_surface_mass = [&]() {
1206  lhs_emi->zero();
1207  mass_surf_emi->zero();
1208  // lhs_emi = (CmM/dt + K)
1209  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);
1210  };
1211  if(reuse_petsc_fem_preallocation) {
1212  assemble_lhs_and_surface_mass();
1213  } else {
1214  assemble_with_exact_preallocation({lhs_emi, mass_surf_emi}, assemble_lhs_and_surface_mass);
1215  }
1216  dur = timing(t1,t0);
1217  log_msg(logger,0,log_flag, "Computed parabolic mass matrix in %.5f seconds.", float(dur));
1218  }
1219 
1220  bool same_nonzero = false;
1221 
1222  // set boundary conditions
1223  if(have_dbc) {
1224  log_msg(logger,0,log_flag, "lhs matrix enforcing Dirichlet boundaries.");
1225  get_time(t0);
1226 
1227  if(dbc == nullptr)
1228  dbc = new dbc_manager(*lhs_emi, stimuli);
1229  else
1230  dbc->recompute_dbcs();
1231 
1232  dbc->enforce_dbc_lhs();
1233  dur = timing(t1,t0);
1234  log_msg(logger,0,log_flag, "lhs matrix Dirichlet enforcing done in %.5f seconds.", float(dur));
1235  }
1236  else {
1237  log_msg(logger,0,ECHO, "without enforcing Dirichlet boundaries on the lhs matrix!");
1238  // we are dealing with a singular system
1239  phie_mat_has_nullspace = true;
1240  }
1241 
1242  set_dir(INPUT);
1243 #ifdef WITH_PETSC
1244  if(petsc_active()) SF::petsc_log_preallocation_report({
1245  {"B:", B},
1246  {"Bi:", Bi},
1247  {"BsM:", BsM},
1248  {"unique_to_both:", operator_unique_to_both_faces},
1249  {"both_to_unique:", operator_both_to_unique_face},
1250  {"stiffness_emi:", stiffness_emi},
1251  {"mass_emi:", mass_emi},
1252  {"mass_surf_emi:", mass_surf_emi},
1253  {"lhs_emi:", lhs_emi},
1254  });
1255 #endif
1256  if(use_petsc_exact_preallocation) fem_matrices_exact_preallocated = true;
1257  get_time(t0);
1258  setup_linear_solver(logger);
1259  dur = timing(t1,t0);
1260  log_msg(logger,0,log_flag, "Initializing parabolic solver in %.5f seconds.", float(dur));
1261  set_dir(OUTPUT);
1262 
1263  period = timing(end, start);
1264 }
1265 
1266 void parabolic_solver_emi::setup_linear_solver(FILE_SPEC logger)
1267 {
1268  tol = param_globals::cg_tol_parab;
1269  max_it = param_globals::cg_maxit_parab;
1270 
1271  std::string default_opts;
1272  std::string solver_file;
1273  solver_file = param_globals::parab_options_file;
1274  if (param_globals::flavor == std::string("ginkgo")) {
1275  default_opts = std::string(
1276  R"(
1277 {
1278  "type": "solver::Cg",
1279  "preconditioner": {
1280  "type": "solver::Multigrid",
1281  "min_coarse_rows": 8,
1282  "max_levels": 16,
1283  "default_initial_guess": "zero",
1284  "mg_level": [
1285  {
1286  "type": "multigrid::Pgm",
1287  "deterministic": false
1288  }
1289  ],
1290  "coarsest_solver": {
1291  "type": "preconditioner::Schwarz",
1292  "local_solver": {
1293  "type": "preconditioner::Jacobi"
1294  }
1295  },
1296  "criteria": [
1297  {
1298  "type": "Iteration",
1299  "max_iters": 1
1300  }
1301  ]
1302  },
1303  "criteria": [
1304  {
1305  "type": "Iteration",
1306  "max_iters": 100
1307  },
1308  {
1309  "type": "ResidualNorm",
1310  "reduction_factor": 1e-4
1311  }
1312  ]
1313 }
1314  )");
1315  } else if (param_globals::flavor == std::string("petsc")) {
1316  default_opts = std::string("-ksp_type cg -pc_type gamg -options_left");
1317  }
1318  lin_solver->setup_solver(*lhs_emi, tol, max_it * 100, param_globals::cg_norm_parab,
1319  "parabolic PDE", phie_mat_has_nullspace, logger, solver_file.c_str(),
1320  default_opts.c_str());
1321 }
1322 
1323 void parabolic_solver_emi::solve()
1324 {
1325  switch (parab_tech) {
1326  case SEMI_IMPLICIT: solve_semiImplicit(); break;
1327  }
1328 }
1329 
1330 void parabolic_solver_emi::solve_semiImplicit()
1331 {
1332  double t0,t1;
1333  get_time(t0);
1334 
1335  if(dbc != nullptr){
1336  CALI_MARK_BEGIN("apply_dbc_rhs");
1337  dbc->enforce_dbc_rhs(*ui);
1338  CALI_MARK_END("apply_dbc_rhs");
1339  }
1340 
1341  *ui_pre = *ui;
1342 
1343  // -K*u (K is assembled as -K)
1344  CALI_MARK_BEGIN("stiff_mat");
1345  stiffness_emi->mult(*ui, *Iij_temp);
1346  // rhs = Iij + K*u
1347  *Irhs -= *Iij_temp;
1348  CALI_MARK_END("stiff_mat");
1349 
1350  // add volumetric stimulus currents (I_ex, I_in)
1351  // rhs = Iij + K*u + M*Iij_stim
1352  if (Iij_stim->mag() > 0.0) {
1353  CALI_MARK_BEGIN("stim_application");
1354  mass_emi->mult(*Iij_stim, *Iij_temp);
1355  *Irhs -= *Iij_temp;
1356  CALI_MARK_END("stim_application");
1357  }
1358 
1359  // rhs = -(Iij + K*u + M*Iij_stim)
1360  CALI_MARK_BEGIN("rhs_update");
1361  (*Irhs) *= (-1.0);
1362  CALI_MARK_END("rhs_update");
1363 
1364  // For a pure-Neumann EMI system, compatibility concerns the complete
1365  // assembled right-hand side, not individual stimulus definitions.
1366  lin_solver->project_nullspace(*Irhs);
1367 
1368  // compute step
1369  CALI_MARK_BEGIN("linear_solve");
1370  (*lin_solver)(*dui, *Irhs);
1371  CALI_MARK_END("linear_solve");
1372 
1373  // logfile for solver
1374  if(lin_solver->reason < 0) {
1375  log_msg(0, 5, 0,"%s solver failed to converge after %d iterations with residual %g. Reason: %s.", lin_solver->name.c_str(),
1376  static_cast<int>(lin_solver->niter), lin_solver->final_residual, lin_solver->reason_str());
1377  EXIT(1);
1378  }
1379 
1380  // update solution:: ui_pre = ui_pre + dui
1381  CALI_MARK_BEGIN("sol_update");
1382  ui_pre->add_scaled(*dui, 1.0);
1383  *ui *=0;
1384  ui->add_scaled(*ui_pre, 1.0);
1385  CALI_MARK_END("sol_update");
1386 
1387  // We need to enforce DBCs again after the solution vector was updated.
1388  // Otherwise, matrix/solver tolerances allow small nonzero values in dui to accumulate in ui.
1389  if(dbc != nullptr){
1390  CALI_MARK_BEGIN("apply_dbc_rhs");
1391  dbc->enforce_dbc_rhs(*ui);
1392  CALI_MARK_END("apply_dbc_rhs");
1393  }
1394 
1395  // treat solver statistics
1396  stats.slvtime += timing(t1, t0);
1397  stats.update_iter(lin_solver->niter);
1398 }
1399 
1401  std::pair<SF::emi_face<mesh_int_t,SF::tuple<mesh_int_t>>,
1402  SF::emi_face<mesh_int_t,SF::tuple<mesh_int_t>>>> & line_face,
1404  std::pair<SF::emi_face<mesh_int_t,SF::triple<mesh_int_t>>,
1405  SF::emi_face<mesh_int_t,SF::triple<mesh_int_t>>>> & tri_face,
1407  std::pair<SF::emi_face<mesh_int_t,SF::quadruple<mesh_int_t>>,
1408  SF::emi_face<mesh_int_t,SF::quadruple<mesh_int_t>>>> & quad_face,
1409  hashmap::unordered_map<std::pair<mesh_int_t,mesh_int_t>, mesh_int_t> & map_vertex_tag_to_dof,
1410  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,
1411  std::vector<std::string> & tags_data)
1412 {
1413  sf_mesh & emi_surfmesh_w_counter_face = get_mesh(emi_surface_counter_msh);
1414 
1415  const SF::vector<mesh_int_t> & tags = emi_surfmesh_w_counter_face.tag;
1416 
1417  const SF::vector<mesh_int_t> & rnod = emi_surfmesh_w_counter_face.get_numbering(SF::NBR_REF);
1419 
1420  for(size_t i=0; i<rnod.size(); i++){
1421  l2g[i] = rnod[i];
1422  }
1423 
1424  for(size_t eidx=0; eidx<emi_surfmesh_w_counter_face.l_numelem; eidx++)
1425  {
1426  std::vector<mesh_int_t> elem_nodes;
1427  mesh_int_t tag = emi_surfmesh_w_counter_face.tag[eidx];
1428  for (int n = emi_surfmesh_w_counter_face.dsp[eidx]; n < emi_surfmesh_w_counter_face.dsp[eidx+1];n++)
1429  {
1430  mesh_int_t l_idx = emi_surfmesh_w_counter_face.con[n];
1431 
1432  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
1433  Index_tag_old = std::make_pair(l2g[l_idx],tags[eidx]);
1434  mesh_int_t dof = map_vertex_tag_to_dof[Index_tag_old];
1435  elem_nodes.push_back(dof);
1436  }
1437 
1438  mesh_int_t tag_first = 0;
1439  mesh_int_t tag_second = 0;
1440  std::string result_first;
1441  std::string result_second;
1442  std::sort(elem_nodes.begin(),elem_nodes.end()); // make the node tuple order-independent
1443 
1444  // Extract all surface face pairs separating regions with different material or boundary tags (ionicFaces)
1445  if(elem_nodes.size()==2){
1447 
1448  key.v1 = elem_nodes[0];
1449  key.v2 = elem_nodes[1];
1450  std::pair<SF::emi_face<mesh_int_t,SF::tuple<mesh_int_t>>,
1451  SF::emi_face<mesh_int_t,SF::tuple<mesh_int_t>>> value = line_face[key];
1452 
1453  tag_first = value.first.tag;
1454  tag_second = value.second.tag;
1455  result_first = std::to_string(tag_first) + ":" + std::to_string(tag_second);
1456  result_second = std::to_string(tag_second) + ":" + std::to_string(tag_first);
1457  }
1458  else if(elem_nodes.size()==3){
1460  key.v1 = elem_nodes[0];
1461  key.v2 = elem_nodes[1];
1462  key.v3 = elem_nodes[2];
1463  std::pair<SF::emi_face<mesh_int_t,SF::triple<mesh_int_t>>,
1464  SF::emi_face<mesh_int_t,SF::triple<mesh_int_t>>> value = tri_face[key];
1465 
1466  tag_first = value.first.tag;
1467  tag_second = value.second.tag;
1468  result_first = std::to_string(tag_first) + ":" + std::to_string(tag_second);
1469  result_second = std::to_string(tag_second) + ":" + std::to_string(tag_first);
1470  }
1471  else if(elem_nodes.size()==4){
1473  key.v1 = elem_nodes[0];
1474  key.v2 = elem_nodes[1];
1475  key.v3 = elem_nodes[2];
1476  key.v4 = elem_nodes[3];
1477  std::pair<SF::emi_face<mesh_int_t,SF::quadruple<mesh_int_t>>,
1478  SF::emi_face<mesh_int_t,SF::quadruple<mesh_int_t>>> value = quad_face[key];
1479 
1480  tag_first = value.first.tag;
1481  tag_second = value.second.tag;
1482  result_first = std::to_string(tag_first) + ":" + std::to_string(tag_second);
1483  result_second = std::to_string(tag_second) + ":" + std::to_string(tag_first);
1484  }
1485  // check if current element tag has a custom region ID
1486  tags_data.push_back(result_first);
1487  tags_data.push_back(result_second);
1488  }
1489 }
1490 
1491 void EMI::initialize()
1492 {
1493  double t1, t2;
1494  get_time(t1);
1495 
1496  set_dir(OUTPUT);
1497 
1498  // open logger
1499  logger = f_open("emi.log", param_globals::experiment != 4 ? "w" : "r");
1500  const int verb = param_globals::output_level;
1501  const auto log_init_timing = [&](const char* label, double start) {
1502  if(verb)
1503  log_msg(logger, 0, ECHO, "EMI init: %s in %.5f seconds.", label, float(MPI_Wtime() - start));
1504  };
1505 
1506  double phase_t = MPI_Wtime();
1507  // Mesh processing step: convert the input mesh into
1508  // - an EMI mesh (with discontinuities at gap junctions and membranes)
1509  // - and generate the corresponding surface mesh
1510  CALI_MARK_BEGIN("mesh_setup");
1511  setup_EMI_mesh();
1512  log_init_timing("mesh setup", phase_t);
1513 
1514  // setup mappings between extra and intra grids, algebraic and nodal,
1515  // and between PETSc and canonical orderings
1516  phase_t = MPI_Wtime();
1517  setup_mappings();
1518  log_init_timing("mesh mappings", phase_t);
1519 
1520  // the ionicOnFace physics is currently triggered from inside the emi to have tighter
1521  // control over it
1522  phase_t = MPI_Wtime();
1523  ion.logger = logger;
1524 
1525  ion.set_surface_mesh_data(parab_solver.line_face,
1526  parab_solver.tri_face,
1527  parab_solver.quad_face,
1528  parab_solver.map_vertex_tag_to_dof);
1529 
1530  // builds per-face adjacency tags
1531  std::vector<std::string> tags_data;
1532  tags_onFace(parab_solver.line_face,
1533  parab_solver.tri_face,
1534  parab_solver.quad_face,
1535  parab_solver.map_vertex_tag_to_dof,
1536  parab_solver.map_vertex_tag_to_dof_petsc,
1537  tags_data);
1538  CALI_MARK_END("mesh_setup");
1539  log_init_timing("ionic face metadata", phase_t);
1540 
1541  ion.set_tags_onFace(tags_data);
1542 
1543  ion.set_face_region_data(parab_solver.intra_tags, parab_solver.map_elem_uniqueFace_to_tags);
1544  phase_t = MPI_Wtime();
1545  ion.initialize();
1546  log_init_timing("ionic model initialization", phase_t);
1547 
1548  phase_t = MPI_Wtime();
1549  // set up tissue properties on the extra and intracellular domains
1550  set_elec_tissue_properties_emi_volume(mtype_vol, parab_solver.extra_tags, parab_solver.intra_tags, logger);
1551  region_mask(emi_msh, mtype_vol[0].regions, mtype_vol[0].regionIDs, true, "gregion_vol", logger);
1552 
1553  // add electrics timer for time stepping, add to time stepper tool (TS)
1554  double global_time = user_globals::tm_manager->time;
1555  timer_idx = user_globals::tm_manager->add_eq_timer(global_time, param_globals::tend, 0,
1556  param_globals::dt, 0, "elec::ref_dt", "TS");
1557  log_init_timing("tissue properties and timers", phase_t);
1558 
1559  // EMI stimuli setup
1560  CALI_MARK_BEGIN("stimulus_setup");
1561  phase_t = MPI_Wtime();
1562  param_globals::operator_splitting = 0; // EMI does not use operator splitting, so keep stimulus scaling at the monodomain/default setting.
1563  setup_stimuli();
1564  log_init_timing("stimuli", phase_t);
1565  CALI_MARK_END("stimulus_setup");
1566 
1567  // set up the linear equation systems. this needs to happen after the stimuli have been
1568  // set up, since we need boundary condition info
1569  CALI_MARK_BEGIN("solver_setup");
1570  phase_t = MPI_Wtime();
1571  setup_solvers();
1572  log_init_timing("solver setup", phase_t);
1573  CALI_MARK_END("solver_setup");
1574 
1575  phase_t = MPI_Wtime();
1576  // Balance paired electrodes before total-current scaling.
1577  balance_electrodes();
1578  setup_itm_faces();
1579  // total current scaling
1580  scale_total_stimulus_current(stimuli, *parab_solver.mass_emi, logger);
1581  log_init_timing("stimulus current scaling", phase_t);
1582 
1583  sf_mesh & emi_mesh = get_mesh(emi_msh);
1584  sf_mesh & emi_surfmesh_w_counter_face = get_mesh(emi_surface_counter_msh);
1585 
1586  phase_t = MPI_Wtime();
1587  // Initialize ui from the ionic resting membrane voltage:
1588  // - vb is defined on the unique-face ionic layout.
1589  // - The direct unique -> both operator expands it to the both-face layout.
1590  // - assign_resting_potential_from_ionic_models_on_myocyte writes the matching
1591  // membrane voltage into intracellular volume DOFs and zero into extracellular DOFs.
1592  parab_solver.operator_unique_to_both_faces->mult(*parab_solver.vb, *parab_solver.vb_both_face);
1593  SF::assign_resting_potential_from_ionic_models_on_myocyte(*parab_solver.ui,
1594  parab_solver.vb_both_face,
1595  parab_solver.elemTag_emi_mesh,
1596  parab_solver.map_vertex_tag_to_dof_petsc,
1597  parab_solver.line_face, parab_solver.tri_face, parab_solver.quad_face,
1598  emi_surfmesh_w_counter_face, emi_mesh);
1599 
1600  // on restart, replace the reconstructed field with the exact bulk potential
1601  // saved at checkpoint time; this avoids the transient the reconstruction would
1602  // otherwise seed (u_extra = 0 is not the elliptically-consistent field)
1603  if (strlen(param_globals::start_statef) > 0)
1604  restore_field_state(param_globals::start_statef);
1605 
1606  *parab_solver.vb_unique_face = *parab_solver.vb;
1607  log_init_timing("initial membrane state projection", phase_t);
1608 
1609  CALI_MARK_BEGIN("output_setup");
1610  phase_t = MPI_Wtime();
1611  // prepare the electrics output. we skip it if we do post-processing
1612  if(param_globals::experiment != EXP_POSTPROCESS)
1613  setup_output();
1614  log_init_timing("output setup", phase_t);
1615  CALI_MARK_END("output_setup");
1616 
1617  const double init_dur = timing(t2, t1);
1618  this->initialize_time += init_dur;
1619  if(verb)
1620  log_msg(logger, 0, ECHO, "EMI init total in %.5f seconds.", float(init_dur));
1621 }
1622 
1623 void EMI::setup_mappings()
1624 {
1625  bool emi_exits = mesh_is_registered(emi_msh);
1626  assert(emi_exits);
1627  const int dpn = 1;
1628 
1629  if(get_scattering(emi_msh, ALG_TO_NODAL, dpn) == NULL)
1630  {
1631  log_msg(logger, 0, 0, "%s: Setting up intracellular algebraic-to-nodal scattering.", __func__);
1633  }
1634  if(get_permutation(emi_msh, PETSC_TO_CANONICAL, dpn) == NULL)
1635  {
1636  log_msg(logger, 0, 0, "%s: Setting up intracellular PETSc to canonical permutation.", __func__);
1638  }
1639 }
1640 
1641 void EMI::checkpointing()
1642 {
1643  const timer_manager & tm = *user_globals::tm_manager;
1644 
1645  // regular user selected state save
1646  if (tm.trigger(iotm_chkpt_list)) {
1647  char save_fnm[1024];
1648  const char* tsav_ext = get_tsav_ext(tm.time);
1649 
1650  snprintf(save_fnm, sizeof save_fnm, "%s.%s.roe", param_globals::write_statef, tsav_ext);
1651 
1652  ion.miif->dump_state(save_fnm, tm.time, ion.ion_domain, false, GIT_COMMIT_COUNT);
1653  dump_field_state(save_fnm);
1654  }
1655 
1656  // checkpointing based on interval
1657  if (tm.trigger(iotm_chkpt_intv)) {
1658  char save_fnm[1024];
1659  snprintf(save_fnm, sizeof save_fnm, "checkpoint.%.1f.roe", tm.time);
1660  ion.miif->dump_state(save_fnm, tm.time, ion.ion_domain, false, GIT_COMMIT_COUNT);
1661  dump_field_state(save_fnm);
1662  }
1663 }
1664 
1665 void EMI::dump_field_state(const char* roe_fnm)
1666 {
1667  const int dpn = 1;
1668  std::string fnm = std::string(roe_fnm) + ".emifld";
1669 
1670  const uint64_t gsize = parab_solver.ui->gsize();
1671 
1672  // fail loudly if the companion file cannot be written or its ionic checkpoint
1673  // cannot be fingerprinted, matching MULTI_IF::dump_state; a silently missing or
1674  // unbound field file would let a later restart seed an inconsistent bulk field
1675  int rank = get_rank();
1676  FILE* fd = NULL;
1677  int error = 0;
1678  emifld_header hdr;
1679  if (rank == 0) {
1680  memcpy(hdr.magic, EMIFLD_MAGIC, sizeof hdr.magic);
1681  hdr.version = EMIFLD_VERSION;
1682  hdr.real_bytes = sizeof(SF_real);
1683  hdr.gsize = gsize;
1684  // hash the just-written .roe (dump_state has already flushed and closed it)
1685  if (!fnv1a_file(roe_fnm, hdr.roe_hash)) {
1686  log_msg(logger, 5, 0, "Cannot fingerprint ionic checkpoint %s for the EMI field file.", roe_fnm);
1687  error++;
1688  } else if (!(fd = fopen(fnm.c_str(), "wb"))) {
1689  log_msg(logger, 5, 0, "Cannot open EMI field checkpoint %s for writing.", fnm.c_str());
1690  error++;
1691  }
1692  }
1693  if (get_global(error, MPI_SUM)) EXIT(EXIT_FAILURE);
1694 
1695  log_msg(logger, 0, 0, "Saving EMI bulk potential field in file: %s", fnm.c_str());
1696 
1697  // the header is rank-0-local metadata, so write it with fwrite before the
1698  // payload, matching MULTI_IF::dump_state; the distributed field itself goes
1699  // through root_write. write_binary/root_write append no length prefix, so a
1700  // leading header is clean
1701  if (rank == 0) fwrite(&hdr, sizeof hdr, 1, fd);
1702 
1703  // write the bulk potential in canonical order so the field is portable across
1704  // partitionings, mirroring how MULTI_IF::dump_state stores the ionic state
1705  sf_vec* canon;
1706  SF::init_vector(&canon, parab_solver.ui);
1707  // registered unconditionally by setup_mappings() during initialize()
1708  assert(get_permutation(emi_msh, PETSC_TO_CANONICAL, dpn) != NULL);
1709  get_permutation(emi_msh, PETSC_TO_CANONICAL, dpn)->forward(*parab_solver.ui, *canon);
1710  canon->write_binary<SF_real>(fd);
1711  delete canon;
1712 
1713  if (fd) fclose(fd);
1714 }
1715 
1716 void EMI::restore_field_state(const char* roe_fnm)
1717 {
1718  const int dpn = 1;
1719  std::string fnm = std::string(roe_fnm) + ".emifld";
1720 
1721  // start_statef is given relative to the input directory, like the ionic checkpoint
1722  set_dir(INPUT);
1723 
1724  // every ionic checkpoint is written with a matching field file whose header is
1725  // fingerprinted against that .roe. A missing, corrupt, mismatched, or stale
1726  // companion means an inconsistent restart: abort instead of reconstructing.
1727  const uint64_t gsize = parab_solver.ui->gsize();
1728  int err = 0;
1729  FILE* fd = nullptr;
1730  if (get_rank() == 0) {
1731  fd = fopen(fnm.c_str(), "rb");
1732  if (!fd) {
1733  log_msg(logger, 5, 0, "Cannot open EMI field checkpoint %s.", fnm.c_str());
1734  err = 1;
1735  } else {
1736  emifld_header hdr;
1737  const long expected = static_cast<long>(sizeof hdr + gsize * sizeof(SF_real));
1738  fseek(fd, 0, SEEK_END);
1739  const long actual = ftell(fd);
1740  rewind(fd);
1741  uint64_t roe_hash = 0;
1742  if (fread(&hdr, sizeof hdr, 1, fd) != 1) {
1743  log_msg(logger, 5, 0, "EMI field checkpoint %s is truncated.", fnm.c_str());
1744  err = 1;
1745  } else if (memcmp(hdr.magic, EMIFLD_MAGIC, sizeof hdr.magic) != 0 || hdr.version != EMIFLD_VERSION) {
1746  log_msg(logger, 5, 0, "%s is not a version-%u EMI field checkpoint.", fnm.c_str(), EMIFLD_VERSION);
1747  err = 1;
1748  } else if (hdr.real_bytes != sizeof(SF_real) || hdr.gsize != gsize || actual != expected) {
1749  log_msg(logger, 5, 0, "EMI field checkpoint %s does not match this run (wrong precision, mesh, or size).", fnm.c_str());
1750  err = 1;
1751  } else if (!fnv1a_file(roe_fnm, roe_hash) || hdr.roe_hash != roe_hash) {
1752  log_msg(logger, 5, 0, "EMI field checkpoint %s does not belong to ionic checkpoint %s.", fnm.c_str(), roe_fnm);
1753  err = 1;
1754  }
1755  if (err) { fclose(fd); fd = nullptr; }
1756  }
1757  }
1758  if (get_global(err, MPI_SUM)) EXIT(EXIT_FAILURE);
1759 
1760  // the header fread above left rank 0's fd at the payload; read_binary continues from there
1761  size_t nrd = parab_solver.ui->read_binary<SF_real>(fd);
1762  if (get_rank() == 0) fclose(fd);
1763  if (nrd != static_cast<size_t>(gsize)) {
1764  log_msg(logger, 5, 0, "Short read of EMI field checkpoint %s (%zu of %lu values).",
1765  fnm.c_str(), nrd, static_cast<unsigned long>(gsize));
1766  EXIT(EXIT_FAILURE);
1767  }
1768  // registered unconditionally by setup_mappings() during initialize()
1769  assert(get_permutation(emi_msh, PETSC_TO_CANONICAL, dpn) != NULL);
1770  (*get_permutation(emi_msh, PETSC_TO_CANONICAL, dpn))(*parab_solver.ui, false);
1771  log_msg(logger, 0, 0, "Restored EMI bulk potential field from %s.", fnm.c_str());
1772 
1773  set_dir(OUTPUT);
1774 }
1775 
1776 void EMI::compute_step()
1777 {
1778  double t1, t2;
1779  get_time(t1);
1780 
1781  // if requested, we checkpoint the current state
1782  checkpointing();
1783 
1784  // activation checking
1785  const double time = user_globals::tm_manager->time,
1786  time_step = user_globals::tm_manager->time_step;
1787 
1788  const int verb = param_globals::output_level;
1789  // We treat stimuli by type:
1790  // - Potential stimuli (Phi_ex, GND_ex, Phi_ex_ol, Phi_in, Phi_in_ol) are
1791  // managed by a dbc_manager and applied to the left- and right-hand side.
1792  // - Current stimuli (I_ex, I_in) are applied to the right hand side,
1793  // while (I_tm) is applied to the vector Ib directly.
1794  CALI_MARK_BEGIN("apply_dbc_lhs");
1795  apply_dbc_stimulus();
1796  CALI_MARK_END("apply_dbc_lhs");
1797 
1798  // compute ionics update
1799  CALI_MARK_BEGIN("ion_compute");
1800  ion.compute_step();
1801  CALI_MARK_END("ion_compute");
1802 
1803  CALI_MARK_BEGIN("apply_stim");
1804  apply_current_stimulus();
1805  CALI_MARK_END("apply_stim");
1806 
1807  // convert Ib -> Irhs
1808  parab_solver.operator_unique_to_both_faces->mult(*parab_solver.Ib, *parab_solver.Ib_both_face);
1809  parab_solver.BsM->mult(*parab_solver.Ib_both_face, *parab_solver.Irhs);
1810 
1811  // solver parabolic system
1812  CALI_MARK_BEGIN("parab_solve");
1813  parab_solver.solve();
1814  {
1815  // v_b = B_i * u
1816  parab_solver.B->mult(*parab_solver.ui, *parab_solver.vb_both_face);
1817  // Direct both -> unique mapping
1818  parab_solver.operator_both_to_unique_face->mult(*parab_solver.vb_both_face, *parab_solver.vb_unique_face);
1819  *parab_solver.vb = *parab_solver.vb_unique_face;
1820  }
1821  CALI_MARK_END("parab_solve");
1822 
1823  if(user_globals::tm_manager->trigger(iotm_console)) {
1824  // output lin solver stats
1825  parab_solver.stats.log_stats(user_globals::tm_manager->time, false);
1826  }
1827  this->compute_time += timing(t2, t1);
1828 
1829  // since the traces have their own timing, we check for trace dumps in the compute step loop
1830  if(user_globals::tm_manager->trigger(iotm_trace))
1832 }
1833 
1834 void EMI::output_step()
1835 {
1836  double t1, t2;
1837  get_time(t1);
1838 
1839  assemble_sv_gvec_onFace(gvec, ion.miif);
1840  output_manager.write_data();
1841 
1842  double curtime = timing(t2, t1);
1843  this->output_time += curtime;
1844 
1845  IO_stats.calls++;
1846  IO_stats.tot_time += curtime;
1847 
1849  IO_stats.log_stats(user_globals::tm_manager->time, false);
1850 }
1851 
1855 void EMI::destroy()
1856 {
1858  // destroy ionics before closing the logger: the ionic interface holds an alias of it
1859  ion.destroy();
1860 
1861  for (sf_vec* v : itm_faces) delete v;
1862  itm_faces.resize(0);
1863 
1864  // close output files
1865  output_manager.close_files_and_cleanup();
1866 
1867  // close logger
1868  f_close(logger);
1869 }
1870 
1871 void EMI::setup_stimuli()
1872 {
1873  // initialize basic stim info data (used units, supported types, etc)
1874  init_stim_info();
1875 
1876  stimuli.resize(param_globals::num_stim);
1877  for (int i = 0; i < param_globals::num_stim; i++) {
1878  // construct new stimulus
1879  stimulus & s = stimuli[i];
1880 
1882  s.translate(i);
1883 
1884  // we associate to the EMI mesh. this is needed for the stim_phys and stim_electrode setups.
1885  s.associated_intra_mesh = emi_msh, s.associated_extra_mesh = emi_msh;
1886 
1887  s.setup(i);
1888 
1889  if (s.phys.type == Illum) {
1890  log_msg(0, MAX_LOG_LEVEL, ECHO, "Stimulus of type Illum (=6) is not implemented in EMI. Abort.");
1891  EXIT(EXIT_FAILURE);
1892  }
1893 
1894  // Depending on the stimulus type, we make sure to only stimulate the correct regions of the mesh:
1895  // Extracellular stimuli restrict to all DOFs of the extracellular region (including the extracellular side of the membrane).
1896  // Equivalent for intracellular stimuli.
1897  // Stimuli that act directly on the membrane restrict to DOFs with ptsData > 0, i.e. membrane, gap junctions, and complex junctions.
1898  if (is_extra(s.phys.type)) {
1899  SF::vector<mesh_int_t> extra_vertices;
1900  const sf_mesh& mesh = get_mesh(s.associated_extra_mesh);
1901 
1902  // Gather vertices from emi_msh using extra_tags
1903  indices_from_region_tags(extra_vertices, mesh, parab_solver.extra_tags);
1904 
1905  // Restrict electrode vertices to extra region
1906  restrict_to_set(s.electrode.vertices, extra_vertices);
1907  } else if (s.phys.type == I_tm) {
1908  const sf_mesh& mesh = get_mesh(emi_msh);
1909  SF::restrict_to_membrane(s.electrode.vertices, dof2ptsData, mesh);
1910  } else {
1911  SF::vector<mesh_int_t> intra_vertices;
1912  const sf_mesh& mesh = get_mesh(s.associated_intra_mesh);
1913 
1914  // Gather vertices from emi_msh using intra_tags
1915  indices_from_region_tags(intra_vertices, mesh, parab_solver.intra_tags);
1916 
1917  // Restrict electrode vertices to intra region
1918  restrict_to_set(s.electrode.vertices, intra_vertices);
1919  }
1920 
1921  if (s.electrode.dump_vtx) {
1922  set_dir(OUTPUT);
1923  s.dump_vtx_file(i);
1924  }
1925 
1926  if(param_globals::stim[i].pulse.dumpTrace && get_rank() == 0) {
1927  set_dir(OUTPUT);
1928  s.pulse.wave.write_trace(s.name+".trc");
1929  }
1930 
1931  }
1932 }
1933 
1934 void EMI::apply_dbc_stimulus()
1935 {
1936  parabolic_solver_emi& ps = parab_solver;
1937 
1938  // Rebuild only if the active DBC set changed. Time-dependent DBC values with
1939  // the same constrained DOFs are applied through enforce_dbc_rhs().
1940  bool dbcs_have_updated = ps.dbc != nullptr && ps.dbc->dbc_update();
1942 
1943  if (dbcs_have_updated && time_not_final) {
1944  parab_solver.rebuild_matrices(mtype_vol, *ion.miif, stimuli, logger);
1945  }
1946 }
1947 
1948 void EMI::apply_current_stimulus()
1949 {
1950  parabolic_solver_emi& ps = parab_solver;
1951  ps.Iij_stim->set(0.0);
1952 
1953  // iterate over stimuli
1954  for(stimulus & s : stimuli) {
1955  if(s.is_active()) {
1956  switch (s.phys.type) {
1957  case I_tm: {
1958  double val;
1959  s.value(val);
1960  ps.Ib->add_scaled(*itm_faces[s.idx], -val);
1961  } break;
1962 
1963  case I_ex:
1964  case I_in: {
1965  apply_stim_to_vector(s, *ps.Iij_stim, true);
1966  } break;
1967 
1968  default: break;
1969  }
1970  }
1971  }
1972 }
1973 
1974 void EMI::balance_electrodes()
1975 {
1976  for (int i = 0; i < param_globals::num_stim; i++) {
1977  if (param_globals::stim[i].crct.balance != -1) {
1978  int from = param_globals::stim[i].crct.balance;
1979  int to = i;
1980 
1981  log_msg(NULL, 0, 0, "Balancing stimulus %d with %d %s-wise.", from, to,
1982  is_current(stimuli[from].phys.type) ? "current" : "voltage");
1983 
1984  stimulus& s_from = stimuli[from];
1985  stimulus& s_to = stimuli[to];
1986 
1987  s_to.pulse = s_from.pulse;
1988  s_to.ptcl = s_from.ptcl;
1989  s_to.phys = s_from.phys;
1990  s_to.pulse.strength *= -1.0;
1991 
1992  if (s_from.phys.type == I_ex || s_from.phys.type == I_in) {
1993  // if from is total current, skip volume based adjustment of strength
1994  // otherwise, scale_total_stimulus_current() will undo the balanced scaling of to.pulse.strength
1995  // scale_total_stimulus_current() will do the scaling based on the volume
1996  if (!s_from.phys.total_current) {
1997  sf_mat& mass = *parab_solver.mass_emi;
1998  SF_real vol0 = get_volume_from_nodes(mass, s_from.electrode.vertices);
1999  SF_real vol1 = get_volume_from_nodes(mass, s_to.electrode.vertices);
2000 
2001  s_to.pulse.strength *= fabs(vol0 / vol1);
2002  }
2003  }
2004  }
2005  }
2006 }
2007 
2008 void EMI::setup_itm_faces()
2009 {
2010  parabolic_solver_emi& ps = parab_solver;
2011  const int rank = get_rank();
2012  itm_faces.assign(stimuli.size(), nullptr);
2013 
2014  sf_vec* diff;
2015  SF::init_vector(&diff, ps.Ib_both_face);
2016 
2017  for (stimulus & s : stimuli) {
2018  if (s.phys.type != I_tm) continue;
2019 
2020  // indicator of the electrode DOFs
2021  sf_vec& w = *ps.Iij_temp;
2022  const SF::vector<mesh_int_t>& vtx = s.electrode.vertices;
2023  SF::vector<SF_int> idx(vtx.size());
2024  for (size_t i = 0; i < vtx.size(); i++) idx[i] = SF::local_nodal_to_local_petsc(*w.mesh, rank, vtx[i]);
2025  w.set(0.0);
2026  w.set(idx, 1.0, false, true);
2027 
2028  // with mean indicators a and b on the two sides, Bi*w = a+b and |B*w| = |a-b|
2029  // on both faces of an interface, so max(a,b) = (Bi*w + |B*w|) / 2
2030  ps.Bi->mult(w, *ps.Ib_both_face);
2031  ps.B->mult(w, *diff);
2032  SF_real* sum = ps.Ib_both_face->ptr();
2033  const SF_real* d = diff->const_ptr();
2034  for (SF_int i = 0; i < diff->lsize(); i++) sum[i] = 0.5 * (sum[i] + std::fabs(d[i]));
2035  diff->const_release_ptr(d);
2036  ps.Ib_both_face->release_ptr(sum);
2037 
2038  SF::init_vector(&itm_faces[s.idx], ps.Ib_unique_face);
2039  ps.operator_both_to_unique_face->mult(*ps.Ib_both_face, *itm_faces[s.idx]);
2040 
2041  // not on gap junctions: B orients them by tag order, not by the side of the electrode
2042  SF_real* c = itm_faces[s.idx]->ptr();
2043  for (const auto& [e, tags] : ps.map_elem_uniqueFace_to_tags)
2044  if (ps.intra_tags.count(tags.first) && ps.intra_tags.count(tags.second)) c[e] = 0;
2045  itm_faces[s.idx]->release_ptr(c);
2046  }
2047  delete diff;
2048 }
2049 
2051 static SF_real covered_area(const sf_vec & share, const sf_mesh & faces)
2052 {
2053  const SF_real* c = share.const_ptr();
2054  double area = 0;
2055  for (size_t e = 0; e < faces.l_numelem; e++) {
2056  if (c[e] == 0) continue;
2057  SF::Point p[4];
2058  const mesh_int_t n = faces.dsp[e + 1] - faces.dsp[e];
2059  for (mesh_int_t k = 0; k < n; k++) p[k] = SF::arr_to_point(&faces.xyz[3 * faces.con[faces.dsp[e] + k]]);
2060 
2061  double a = 0;
2062  if (n == 2) a = std::sqrt(SF::inner_prod(p[1] - p[0], p[1] - p[0]));
2063  else if (n == 3) a = SF::computeTriangleArea(p[0], p[1], p[2]);
2064  else if (n == 4) a = SF::computeTriangleArea(p[0], p[1], p[3]) + SF::computeTriangleArea(p[1], p[2], p[3]);
2065  area += c[e] * a;
2066  }
2067  share.const_release_ptr(c);
2068  return get_global(SF_real(area), MPI_SUM, faces.comm);
2069 }
2070 
2071 void EMI::scale_total_stimulus_current(SF::vector<stimulus>& stimuli,
2072  sf_mat& mass_vol,
2073  FILE_SPEC logger)
2074 {
2075  for (stimulus & s : stimuli){
2076  if(is_current(s.phys.type) && s.phys.total_current){
2077  switch (s.phys.type) {
2078  case I_in:
2079  case I_ex: {
2080  // compute affected volume in um^3
2081  SF_real vol = get_volume_from_nodes(mass_vol, s.electrode.vertices);
2082  // s->strength holds the total current in uA, compute current density in uA/cm^3
2083  // Theoretically, we don't need to scale the volume to cm^3 here since we later
2084  // multiply with the mass matrix and we get um^3 * uA/um^3 = uA.
2085  // However, for I_ex/I_in there is an additional um^3 to cm^3 scaling in phys.scale,
2086  // 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.
2087  assert(vol > 0);
2088  SF_real scale = 1.e12 / vol;
2089 
2090  s.pulse.strength *= scale;
2091 
2092  log_msg(logger, 0, ECHO,
2093  "%s [Stimulus %d]: current density scaled to %.4g uA/cm^3\n",
2094  s.name.c_str(), s.idx, s.pulse.strength);
2095  } break;
2096 
2097  case I_tm: {
2098  // In the EMI model, I_tm only affects the membrane: the membrane area in um^2 it covers
2099  SF_real surf = covered_area(*itm_faces[s.idx], get_mesh(emi_surface_unique_face_msh));
2100  if (surf <= 0) {
2101  log_msg(0, 5, ECHO, "%s [Stimulus %d]: a total current needs an electrode on the membrane, but it covers no membrane face.",
2102  s.name.c_str(), s.idx);
2103  EXIT(EXIT_FAILURE);
2104  }
2105 
2106  // convert to cm^2
2107  surf /= 1.e8;
2108 
2109  // scale surface density now to result in correct total current
2110  s.pulse.strength /= surf;
2111  log_msg(logger, 0, ECHO,
2112  "%s [Stimulus %d]: current density scaled to %.4g uA/cm^2\n",
2113  s.name.c_str(), s.idx, s.pulse.strength);
2114  } break;
2115 
2116  default: break;
2117  }
2118  }
2119  }
2120 }
2121 
2122 // Assign a deterministic global element numbering for a surface mesh based on
2123 // element type, tag, and sorted node ids. This only affects output ordering.
2124 static void assign_deterministic_elem_numbering(sf_mesh & mesh)
2125 {
2126  const int KEY_SIZE = 6; // type, tag, n1, n2, n3, n4
2127  int rank = 0, size = 0;
2128  MPI_Comm_rank(mesh.comm, &rank);
2129  MPI_Comm_size(mesh.comm, &size);
2130 
2131  auto make_key = [&](size_t i) {
2132  std::array<mesh_int_t, KEY_SIZE> k;
2133  k.fill(-1);
2134  k[0] = static_cast<mesh_int_t>(mesh.type[i]);
2135  k[1] = mesh.tag[i];
2136 
2137  int nn = 0;
2138  if (mesh.type[i] == SF::Line) nn = 2;
2139  else if (mesh.type[i] == SF::Tri) nn = 3;
2140  else if (mesh.type[i] == SF::Quad) nn = 4;
2141  else nn = 0;
2142 
2143  std::vector<mesh_int_t> nodes;
2144  nodes.reserve(nn);
2145  size_t off = mesh.dsp[i];
2146  for (int j = 0; j < nn; j++) {
2147  nodes.push_back(mesh.con[off + j]);
2148  }
2149  std::sort(nodes.begin(), nodes.end());
2150  for (int j = 0; j < (int)nodes.size(); j++) {
2151  k[2 + j] = nodes[j];
2152  }
2153  return k;
2154  };
2155 
2156  // Pack local keys
2157  std::vector<mesh_int_t> local_keys(mesh.l_numelem * KEY_SIZE, -1);
2158  for (size_t i = 0; i < mesh.l_numelem; i++) {
2159  auto k = make_key(i);
2160  for (int j = 0; j < KEY_SIZE; j++) local_keys[i * KEY_SIZE + j] = k[j];
2161  }
2162 
2163  // Gather sizes
2164  std::vector<int> counts(size, 0), displs(size, 0);
2165  int local_count = (int)local_keys.size();
2166  MPI_Allgather(&local_count, 1, MPI_INT, counts.data(), 1, MPI_INT, mesh.comm);
2167  int total = 0;
2168  for (int r = 0; r < size; r++) {
2169  displs[r] = total;
2170  total += counts[r];
2171  }
2172 
2173  std::vector<mesh_int_t> all_keys;
2174  if (rank == 0) all_keys.resize(total, -1);
2175  const MPI_Datatype key_mpi_t = mpi_datatype<mesh_int_t>();
2176  MPI_Gatherv(local_keys.data(), local_count, key_mpi_t,
2177  rank == 0 ? all_keys.data() : nullptr, counts.data(), displs.data(), key_mpi_t,
2178  0, mesh.comm);
2179 
2180  // Build sorted global keys on rank 0
2181  std::vector<std::array<mesh_int_t, KEY_SIZE>> sorted_keys;
2182  if (rank == 0) {
2183  const int nkeys = total / KEY_SIZE;
2184  sorted_keys.resize(nkeys);
2185  for (int i = 0; i < nkeys; i++) {
2186  std::array<mesh_int_t, KEY_SIZE> k;
2187  for (int j = 0; j < KEY_SIZE; j++) k[j] = all_keys[i * KEY_SIZE + j];
2188  sorted_keys[i] = k;
2189  }
2190  std::sort(sorted_keys.begin(), sorted_keys.end());
2191  }
2192 
2193  // Broadcast sorted keys
2194  int nkeys = 0;
2195  if (rank == 0) nkeys = (int)sorted_keys.size();
2196  MPI_Bcast(&nkeys, 1, MPI_INT, 0, mesh.comm);
2197  std::vector<mesh_int_t> flat_sorted(nkeys * KEY_SIZE, -1);
2198  if (rank == 0) {
2199  for (int i = 0; i < nkeys; i++) {
2200  for (int j = 0; j < KEY_SIZE; j++) flat_sorted[i * KEY_SIZE + j] = sorted_keys[i][j];
2201  }
2202  }
2203  MPI_Bcast(flat_sorted.data(), (int)flat_sorted.size(), key_mpi_t, 0, mesh.comm);
2204 
2205  // Reconstruct sorted_keys on all ranks
2206  if (rank != 0) {
2207  sorted_keys.resize(nkeys);
2208  for (int i = 0; i < nkeys; i++) {
2209  std::array<mesh_int_t, KEY_SIZE> k;
2210  for (int j = 0; j < KEY_SIZE; j++) k[j] = flat_sorted[i * KEY_SIZE + j];
2211  sorted_keys[i] = k;
2212  }
2213  }
2214 
2215  // Assign deterministic element numbering (both REF and SUBMESH)
2216  SF::vector<mesh_int_t> & nbr_ref = mesh.register_numbering(SF::NBR_ELEM_REF);
2217  SF::vector<mesh_int_t> & nbr_sub = mesh.register_numbering(SF::NBR_ELEM_SUBMESH);
2218  nbr_ref.resize(mesh.l_numelem);
2219  nbr_sub.resize(mesh.l_numelem);
2220  for (size_t i = 0; i < mesh.l_numelem; i++) {
2221  auto k = make_key(i);
2222  auto it = std::lower_bound(sorted_keys.begin(), sorted_keys.end(), k);
2223  if (it == sorted_keys.end() || *it != k) {
2224  log_msg(0, 5, 0, "deterministic numbering failed to find key (rank %d, elem %zu)", rank, i);
2225  EXIT(1);
2226  }
2227  mesh_int_t gid = (mesh_int_t)(it - sorted_keys.begin());
2228  nbr_ref[i] = gid;
2229  nbr_sub[i] = gid;
2230  }
2231 }
2232 
2233 void EMI::setup_output()
2234 {
2235  std::string output_base = get_basename(param_globals::meshname);
2236 
2237  set_dir(INPUT);
2238  const bool write_binary =
2239  SF::fileExists(std::string(param_globals::meshname) + ".belem") ||
2240  SF::fileExists(std::string(param_globals::meshname) + ".bpts");
2241  const bool restrict_output =
2242  parse_emi_output_tags(param_globals::gridout_tags,
2243  parab_solver.extra_tags, parab_solver.intra_tags,
2244  output_tags);
2245 
2246  set_dir(OUTPUT);
2247 
2248  const int gridout_emi = param_globals::gridout_emi;
2249 
2250  if(restrict_output && param_globals::num_io_nodes > 0) {
2251  log_msg(0, 5, ECHO, "Restricted EMI output with gridout_tags is not supported with async I/O.");
2252  EXIT(EXIT_FAILURE);
2253  }
2254 
2255  // write entire mesh
2256  sf_mesh & mesh = get_mesh(emi_msh);
2257  if(restrict_output) {
2258  build_emi_volume_output_restriction(mesh, output_tags, phie_output_idx);
2259  if(get_global(static_cast<long int>(phie_output_idx.size()), MPI_SUM, SF_COMM) == 0) {
2260  log_msg(0, 5, ECHO, "Restricted EMI volume output is empty.");
2261  EXIT(EXIT_FAILURE);
2262  }
2263  }
2264 
2265  if(gridout_emi & 2) {
2266  std::string output_file = output_base + "_e";
2267  log_msg(0, 0, 0, "Writing \"%s\" mesh: %s (%s)", mesh.name.c_str(), output_file.c_str(), write_binary ? "binary" : "text");
2268  const double t0 = MPI_Wtime();
2269  if(restrict_output) {
2270  write_emi_output_mesh(mesh, write_binary, output_file, mesh.name.c_str(),
2271  [&](size_t eidx) { return output_tags.count(mesh.tag[eidx]) != 0; });
2272  } else {
2273  write_mesh_parallel(mesh, write_binary, output_file.c_str());
2274  }
2275  log_msg(0, 0, 0, "Wrote \"%s\" mesh in %.5f seconds.", mesh.name.c_str(), float(MPI_Wtime() - t0));
2276  }
2277  else if(param_globals::output_level > 1) {
2278  log_msg(0, 0, 0, "Skipping \"%s\" mesh output.", mesh.name.c_str());
2279  }
2280  // register output for overall phi on the entire mesh; an empty file name skips it
2281  if(param_globals::phiefile[0] != '\0')
2282  output_manager.register_output(parab_solver.ui, emi_msh, 1, param_globals::phiefile, "mV",
2283  restrict_output ? &phie_output_idx : NULL);
2284 
2286  mesh_m.name = "Membrane";
2287  if(restrict_output) {
2288  build_emi_surface_output_restriction(mesh_m, output_tags, parab_solver.map_elem_uniqueFace_to_tags,
2289  vm_output_idx);
2290  if(get_global(static_cast<long int>(vm_output_idx.size()), MPI_SUM, SF_COMM) == 0) {
2291  log_msg(0, 5, ECHO, "Restricted EMI membrane output is empty.");
2292  EXIT(EXIT_FAILURE);
2293  }
2294  }
2295 
2296  if(gridout_emi & 1) {
2297  std::string output_file = output_base + "_m";
2298  log_msg(0, 0, 0, "Writing \"%s\" mesh: %s (%s)", mesh_m.name.c_str(), output_file.c_str(), write_binary ? "binary" : "text");
2299  const double t0 = MPI_Wtime();
2300  if(restrict_output) {
2301  write_emi_output_mesh(mesh_m, write_binary, output_file, mesh_m.name.c_str(),
2302  [&](size_t eidx) {
2303  bool keep = output_tags.count(mesh_m.tag[eidx]) != 0;
2304  auto it = parab_solver.map_elem_uniqueFace_to_tags.find(eidx);
2305  if(it != parab_solver.map_elem_uniqueFace_to_tags.end()) {
2306  keep = keep ||
2307  output_tags.count(static_cast<int>(it->second.first)) != 0 ||
2308  output_tags.count(static_cast<int>(it->second.second)) != 0;
2309  }
2310  return keep;
2311  });
2312  } else {
2313  write_mesh_parallel(mesh_m, write_binary, output_file.c_str());
2314  }
2315  log_msg(0, 0, 0, "Wrote \"%s\" mesh in %.5f seconds.", mesh_m.name.c_str(), float(MPI_Wtime() - t0));
2316  }
2317  else if(param_globals::output_level > 1) {
2318  log_msg(0, 0, 0, "Skipping \"%s\" mesh output.", mesh_m.name.c_str());
2319  }
2320  // register output for Vm on membrane interface
2321  // ensure deterministic element ordering in output
2324  }
2325  if(param_globals::vofile[0] != '\0')
2326  output_manager.register_output(parab_solver.vb_unique_face, emi_surface_unique_face_msh, 1,
2327  param_globals::vofile, "mV",
2328  restrict_output ? &vm_output_idx : NULL, true);
2329 
2330  // register gvec[] state-variable output on the membrane faces.
2331  // parab_solver.vb shares the ionic miif element layout, so it is the
2332  // correct template for the ownership ranges used in assemble_sv_gvec_onFace.
2333  init_sv_gvec_onFace(gvec, ion.miif, *parab_solver.vb, output_manager,
2334  restrict_output ? &vm_output_idx : NULL);
2335 
2336  if(param_globals::num_trace) {
2337  sf_mesh & imesh = get_mesh(emi_msh);
2338  open_trace(ion.miif, param_globals::num_trace, param_globals::trace_node, NULL, &imesh);
2339  }
2340 
2341  // initialize generic logger for IO timings per time_dt
2342  IO_stats.init_logger("IO_stats.dat");
2343 }
2344 
2345 void EMI::dump_matrices()
2346 {
2347  std::string bsname = param_globals::dump_basename;
2348  std::string fn;
2349 
2350  set_dir(OUTPUT);
2351 
2352  fn = bsname + "_lhs.bin";
2353  parab_solver.lhs_emi->write(fn.c_str());
2354 
2355  fn = bsname + "_K.bin";
2356  parab_solver.stiffness_emi->write(fn.c_str());
2357 
2358  fn = bsname + "_B.bin";
2359  parab_solver.B->write(fn.c_str());
2360 
2361  fn = bsname + "_Bi.bin";
2362  parab_solver.Bi->write(fn.c_str());
2363 
2364  fn = bsname + "_BsM.bin";
2365  parab_solver.BsM->write(fn.c_str());
2366 
2367  fn = bsname + "_M.bin";
2368  parab_solver.mass_emi->write(fn.c_str());
2369 
2370  fn = bsname + "_Ms.bin";
2371  parab_solver.mass_surf_emi->write(fn.c_str());
2372 
2373 }
2374 
2377 double EMI::timer_val(const int timer_id)
2378 {
2379  // determine
2380  int sidx = stimidx_from_timeridx(stimuli, timer_id);
2381  double val = 0.0;
2382  if(sidx != -1) {
2383  stimuli[sidx].value(val);
2384  }
2385  else
2386  val = std::nan("NaN");
2387 
2388  return val;
2389 }
2390 
2393 std::string EMI::timer_unit(const int timer_id)
2394 {
2395  int sidx = stimidx_from_timeridx(stimuli, timer_id);
2396  std::string s_unit;
2397 
2398  if(sidx != -1)
2399  // found a timer-linked stimulus
2400  s_unit = stimuli[sidx].pulse.wave.f_unit;
2401 
2402  return s_unit;
2403 }
2404 
2405 void EMI::setup_solvers()
2406 {
2407  set_dir(OUTPUT);
2408  const int log_flag = param_globals::output_level > 1 ? ECHO : 0;
2409  double t0 = MPI_Wtime();
2410  parab_solver.init();
2411  log_msg(logger, 0, log_flag, "EMI setup_solvers: parabolic solver init in %.5f seconds.", float(MPI_Wtime() - t0));
2412  t0 = MPI_Wtime();
2413  parab_solver.rebuild_matrices(mtype_vol, *ion.miif, stimuli, logger);
2414  log_msg(logger, 0, log_flag, "EMI setup_solvers: matrix assembly and linear solver setup in %.5f seconds.", float(MPI_Wtime() - t0));
2415 
2416  if(param_globals::dump2MatLab)
2417  dump_matrices();
2418 }
2419 
2420 void extract_unique_tag(SF::vector<mesh_int_t>& unique_tags)
2421 {
2422  MPI_Comm comm = SF_COMM;
2423  int size, rank;
2424  MPI_Comm_size(comm, &size);
2425  MPI_Comm_rank(comm, &rank);
2426 
2427  binary_sort(unique_tags);
2428  unique_resize(unique_tags); // unique_resize is done locally(for each rank)
2429  make_global(unique_tags, comm);
2430  binary_sort(unique_tags);
2431  unique_resize(unique_tags);
2432 }
2433 
2434 void compute_tags_per_rank(int num_tags, SF::vector<mesh_int_t>& num_tags_per_rank)
2435 {
2436  MPI_Comm comm = SF_COMM;
2437  int size, rank;
2438  MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank);
2439  divide(num_tags, size, num_tags_per_rank);
2440 }
2441 
2442 void EMI::setup_EMI_mesh()
2443 {
2444  log_msg(0,0,0, "\n *** Processing EMI mesh ***\n");
2445 
2446  const std::string basename = param_globals::meshname;
2447  const int verb = param_globals::output_level;
2448  std::map<mesh_t, sf_mesh> & mesh_registry = user_globals::mesh_reg;
2449  assert(mesh_registry.count(emi_msh) == 1);
2450 
2451  set_dir(INPUT);
2452 
2453  sf_mesh & emi_mesh = mesh_registry[emi_msh];
2454  sf_mesh & emi_surfmesh_one_side = mesh_registry[emi_surface_msh];
2455  sf_mesh & emi_surfmesh_w_counter_face = mesh_registry[emi_surface_counter_msh];
2456  sf_mesh & emi_surfmesh_unique_face = mesh_registry[emi_surface_unique_face_msh];
2457 
2458  MPI_Comm comm = emi_mesh.comm;
2459 
2460  int size, rank;
2461  double t1, t2, s1, s2;
2462  const double total_setup_t0 = MPI_Wtime();
2463  MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank);
2464 
2465  //-----------------------------------------------------------------
2466  // Validate mesh dimension: EMI only supports 3D volumetric meshes
2467  //-----------------------------------------------------------------
2468  if (emi_mesh.l_numelem > 0) {
2469  // Check first local element type (all elements should be same type in CARP)
2470  SF::elem_t first_elem = emi_mesh.type[0];
2471 
2472  if (first_elem == SF::Line || first_elem == SF::Tri || first_elem == SF::Quad) {
2473  const char* type_name = (first_elem == SF::Line) ? "1D (Line)" :
2474  (first_elem == SF::Tri) ? "2D (Tri)" :
2475  "2D (Quad)";
2476  if (rank == 0) {
2477  log_msg(0, 5, 0, "\n*** ERROR: EMI model requires a 3D volumetric mesh!");
2478  log_msg(0, 5, 0, "*** Current mesh element type: %s", type_name);
2479  log_msg(0, 5, 0, "*** EMI only supports 3D element types: Tetra, Pyramid, Prism, Hexa");
2480  log_msg(0, 5, 0, "*** Please provide a 3D mesh with volume elements.\n");
2481  }
2482  EXIT(EXIT_FAILURE);
2483  }
2484  }
2485 
2486  //-----------------------------------------------------------------
2487  // Step 1: READ *.intra and *.extra
2488  //-----------------------------------------------------------------
2489  int total_num_tags = 0;
2490  if(verb) log_msg(NULL, 0, 0,"\nReading tags for extra and intra regions from input files");
2491  t1 = MPI_Wtime();
2492  {
2493  SF::vector<mesh_int_t> unique_extra_tags;
2494  SF::vector<mesh_int_t> unique_intra_tags;
2495 
2496  if(verb) log_msg(NULL, 0, 0,"Read extracellular tags");
2497  read_indices_global(unique_extra_tags,basename+".extra", comm);
2498  for(mesh_int_t tag:unique_extra_tags){
2499  parab_solver.extra_tags.insert(tag);
2500  }
2501 
2502  if(verb) log_msg(NULL, 0, 0,"Read intracellular tags");
2503  read_indices_global(unique_intra_tags,basename+".intra", comm);
2504  for(mesh_int_t tag:unique_intra_tags){
2505  parab_solver.intra_tags.insert(tag);
2506  }
2507 
2508  total_num_tags = parab_solver.extra_tags.size() + parab_solver.intra_tags.size();
2509  if(total_num_tags < size){
2510  log_msg(0,5,0, "\nThe number of unique tags on EMI mesh is smaller than number of processors!");
2511  EXIT(EXIT_FAILURE);
2512  }
2513  if(verb) log_msg(NULL, 0, 0,"\nextra_tags=%lu, intra_tags=%lu", parab_solver.extra_tags.size(), parab_solver.intra_tags.size());
2514  }
2515  t2 = MPI_Wtime();
2516  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2517 
2518  //-----------------------------------------------------------------
2519  // Step 2: READ points from *.pts
2520  //-----------------------------------------------------------------
2522  SF::vector<mesh_int_t> ptsidx;
2523  SF::vector<mesh_int_t> ptsData;
2524  if(verb) log_msg(NULL, 0, 0,"\nReading points with data on each vertex");
2525  t1 = MPI_Wtime();
2526  SF::read_points(basename, comm, pts, ptsidx);
2527  ptsData.resize(ptsidx.size());
2528  assert(ptsidx.size()==ptsData.size());
2529  t2 = MPI_Wtime();
2530  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2531 
2532  std::list< sf_mesh* > meshlist;
2533  meshlist.push_back(&emi_mesh);
2534 
2535  //-----------------------------------------------------------------
2536  // Step 3: Distribute mesh based on tag or *.part
2537  //-----------------------------------------------------------------
2538  if(verb) log_msg(NULL, 0, 0,"\nDistribute mesh based on tags");
2539  // should be replaced by scotch/pt-scotch for efficiency
2540  t1 = MPI_Wtime();
2541  distribute_elements_based_tags(emi_mesh, total_num_tags);
2542  t2 = MPI_Wtime();
2543  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2544 
2545  //-----------------------------------------------------------------
2546  // Step 4: insert coordinates to the mesh
2547  //-----------------------------------------------------------------
2548  // insert points
2549  if(verb) log_msg(NULL, 0, 0, "\nInserting points");
2550  t1 = MPI_Wtime();
2551  SF::insert_points(pts, ptsidx, meshlist);
2552  t2 = MPI_Wtime();
2553  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2554 
2555  //-----------------------------------------------------------------
2556  // Step 5: extract ptsData based on the location of each vertex
2557  //-----------------------------------------------------------------
2558  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");
2559  t1 = MPI_Wtime();
2560  compute_ptsdata_from_original_mesh( emi_mesh,
2561  SF::NBR_REF,
2562  vertex2ptsdata,
2563  parab_solver.extra_tags,
2564  parab_solver.intra_tags);
2565  t2 = MPI_Wtime();
2566  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2567 
2568  //-----------------------------------------------------------------
2569  // Step 6: extract faces and counterfaces on the interfaces (mem/gap)
2570  // and mark the unique faces on surface mesh
2571  // generate required mapping between surface mesh and unique faces
2572  //-----------------------------------------------------------------
2573  t1 = MPI_Wtime();
2574  if(verb) log_msg(NULL, 0, 0, "\nExtract EMI surface mesh");
2575  hashmap::unordered_map<mesh_int_t, SF::emi_index_rank<mesh_int_t>> unused_map_elem_oneface_to_elem_uniqueFace;
2576  extract_face_based_tags(emi_mesh, SF::NBR_REF, vertex2ptsdata,
2577  parab_solver.line_face,
2578  parab_solver.tri_face,
2579  parab_solver.quad_face,
2580  parab_solver.extra_tags,
2581  parab_solver.intra_tags,
2582  emi_surfmesh_one_side, emi_surfmesh_w_counter_face, emi_surfmesh_unique_face,
2583  parab_solver.map_elem_uniqueFace_to_elem_oneface,
2584  unused_map_elem_oneface_to_elem_uniqueFace);
2585  meshlist.push_back(&emi_surfmesh_one_side);
2586  t2 = MPI_Wtime();
2587  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2588  //-----------------------------------------------------------------
2589  // Step 7: register surface meshes based on the {typeof}_face with counter face
2590  //-----------------------------------------------------------------
2591  compute_surface_mesh_with_counter_face(emi_surfmesh_w_counter_face, SF::NBR_REF,
2592  parab_solver.line_face,
2593  parab_solver.tri_face,
2594  parab_solver.quad_face);
2595 
2596  compute_surface_mesh_with_unique_face(emi_surfmesh_unique_face, SF::NBR_REF,
2597  parab_solver.line_face,
2598  parab_solver.tri_face,
2599  parab_solver.quad_face,
2600  parab_solver.map_elem_uniqueFace_to_tags);
2601 
2602  //-----------------------------------------------------------------
2603  // Step 8: create a map between emi_surfmesh_w_counter_face and emi_surfmesh (both -> one)
2604  //-----------------------------------------------------------------
2605  SF::create_reverse_elem_mapping_between_surface_meshes(parab_solver.line_face,
2606  parab_solver.tri_face,
2607  parab_solver.quad_face,
2608  parab_solver.vec_both_to_one_face,
2609  comm);
2610  t2 = MPI_Wtime();
2611  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2612 
2613  //-----------------------------------------------------------------
2614  // Step 9: global number of interfaces only on emi_surfmesh without counter face
2615  //-----------------------------------------------------------------
2616  if(verb) log_msg(NULL, 0, 0, "\ncompute global number of interface");
2617  size_t global_count_surf = 0;
2618  size_t numelem_surface = emi_surfmesh_one_side.l_numelem;
2619  size_t local_count_surf = numelem_surface;
2620  MPI_Reduce(&local_count_surf, &global_count_surf, 1, mpi_datatype<size_t>(), MPI_SUM, 0, comm);
2621  if(verb && rank==0) fprintf(stdout, "global number of interfaces = %zu\n", global_count_surf);
2622 
2623  //-----------------------------------------------------------------
2624  // Step 10: submesh_numbering on the emi_mesh and generate parallel layout
2625  //-----------------------------------------------------------------
2626  {
2628  sub_numbering(emi_mesh);
2629  emi_mesh.generate_par_layout();
2630  }
2631 
2632  SF::meshdata<mesh_int_t, mesh_real_t> tmesh_backup_old = emi_mesh;
2633 
2634  //-----------------------------------------------------------------
2635  // Step 11: make a map between key(vertex,tag)-> value(dof) after decoupling interfaces defined on emi mesh
2636  //-----------------------------------------------------------------
2637  // During interface decoupling and the introduction of new degrees of freedom (DoFs),
2638  // we assume that the mesh partitioning is performed at least on a per-tag basis.
2639  // This means that all elements sharing the same tag number belong to the same rank.
2640  t1 = MPI_Wtime();
2641  if(verb) log_msg(NULL, 0, 0, "\ndecouple emi interfaces");
2642  if(verb) log_msg(NULL, 0, 0, "\tcompute map oldIdx to dof");
2643  compute_map_vertex_to_dof(emi_mesh, SF::NBR_REF, vertex2ptsdata, parab_solver.extra_tags, parab_solver.map_vertex_tag_to_dof);
2644 
2645  //-----------------------------------------------------------------
2646  // Step 12: complete the map between key(vertex,tag)-> value(dof) for counter faces defined on emi mesh
2647  //-----------------------------------------------------------------
2648  if(verb) log_msg(NULL, 0, 0, "\tcomplete map oldIdx to dof with counter interface");
2649  // add to the map the counter part of the interface
2650  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);
2651 
2652  //-----------------------------------------------------------------
2653  // Step 13: update EMI mesh with new DOFs, so interface decoupling is applied on the EMI mesh
2654  //-----------------------------------------------------------------
2655  if(verb) log_msg(NULL, 0, 0, "\tupdate mesh with dof");
2656  update_emi_mesh_with_dofs(emi_mesh, SF::NBR_REF, parab_solver.map_vertex_tag_to_dof, parab_solver.dof2vertex);
2657  t2 = MPI_Wtime();
2658  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2659 
2660  //-----------------------------------------------------------------
2661  // Step 14: initialize map_vertex_tag_to_dof_petsc where the key is (vertex,tag) and the value is (dof,petsc)
2662  //-----------------------------------------------------------------
2663  t1 = MPI_Wtime();
2664  if(verb) log_msg(NULL, 0, 0, "\nInitialize petsc =0 for map<oldIdx,tag> -><dof, petsc>");
2665  // Iterate over map to assign the (oldIndx, tag) -> (dof, petsc) atm petsc = 0
2666  for(const auto & key_value : parab_solver.map_vertex_tag_to_dof)
2667  {
2668  mesh_int_t gIndex_old = key_value.first.first;
2669  mesh_int_t tag_old = key_value.first.second;
2670  mesh_int_t dof = key_value.second;
2671 
2672  std::pair <mesh_int_t,mesh_int_t> dof_petsc = std::make_pair(dof,-1); // petsc numbering ...
2673  parab_solver.map_vertex_tag_to_dof_petsc.insert({key_value.first,dof_petsc});
2674  }
2675  t2 = MPI_Wtime();
2676  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2677 
2678  //-----------------------------------------------------------------
2679  // Step 15: insert the coordinates on new DOFs to the EMI mesh
2680  //-----------------------------------------------------------------
2681  t1 = MPI_Wtime();
2682  if(verb) log_msg(NULL, 0, 0, "Inserting points and ptsData of dofs to emi_mesh");
2683  insert_points_ptsData_to_dof(tmesh_backup_old, emi_mesh, SF::NBR_REF, parab_solver.dof2vertex, vertex2ptsdata, dof2ptsData);
2684  t2 = MPI_Wtime();
2685  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2686 
2687  //-----------------------------------------------------------------
2688  // Step 16: submesh_numbering on the emi_mesh and generate parallel layout after decoupling the interfaces on the EMI mesh
2689  //-----------------------------------------------------------------
2690  t1 = MPI_Wtime();
2691  if(verb) log_msg(NULL, 0, 0, "Generating unique PETSc numberings");
2692  {
2694  sub_numbering(emi_mesh);
2695  emi_mesh.generate_par_layout();
2696  }
2697  t2 = MPI_Wtime();
2698  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2699 
2700  //-----------------------------------------------------------------
2701  // Step 17: register petsc_numbering on EMI mesh
2702  //-----------------------------------------------------------------
2703  t1 = MPI_Wtime();
2704  if(verb) log_msg(NULL, 0, 0, "Generating unique PETSc numberings");
2705  {
2706  SF::petsc_numbering<mesh_int_t,mesh_real_t> petsc_numbering(emi_mesh.pl, param_globals::renumbering);
2707  petsc_numbering(emi_mesh);
2708  }
2709  t2 = MPI_Wtime();
2710  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2711 
2712  //-----------------------------------------------------------------
2713  // Step 18: complete map_vertex_tag_to_dof_petsc where the key is (vertex,tag) and the value is (dof,petsc) for counter faces
2714  //-----------------------------------------------------------------
2715  if(verb) log_msg(NULL, 0, 0, "Updating the map between indices to PETSc numberings");
2716  t1 = MPI_Wtime();
2717  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);
2718  t2 = MPI_Wtime();
2719  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2720 
2721  //-----------------------------------------------------------------
2722  // Step 19: update values of map {type_of}_face with new DOFs after decoupling
2723  //-----------------------------------------------------------------
2724  t1 = MPI_Wtime();
2725  if(verb) log_msg(NULL, 0, 0, "Updating surface mesh with dof");
2726  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);
2727  t2 = MPI_Wtime();
2728  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2729 
2730  //-----------------------------------------------------------------
2731  // Step 20: register emi_surfmesh for SF::NBR_ELEM_REF, NBR_REF, SF::NBR_SUBMESH
2732  //-----------------------------------------------------------------
2733  t1 = MPI_Wtime();
2734  if(verb) log_msg(NULL, 0, 0, "Layout for element of EMI surfmesh");
2735  SF::vector<mesh_int_t> & emi_surfmesh_elem = emi_surfmesh_one_side.register_numbering(SF::NBR_ELEM_REF);
2736  SF::vector<long int> layout;
2737  SF::layout_from_count<long int>(emi_surfmesh_one_side.l_numelem, layout, emi_surfmesh_one_side.comm);
2738  size_t count = layout[rank+1] - layout[rank];
2739  emi_surfmesh_elem.resize(count);
2740  for (int i = 0; i < count; ++i){
2741  emi_surfmesh_elem[i] = layout[rank]+i;
2742  }
2743 
2744  emi_surfmesh_one_side.localize(SF::NBR_REF);
2745  emi_surfmesh_one_side.register_numbering(SF::NBR_SUBMESH);
2746  t2 = MPI_Wtime();
2747  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2748 
2749  //-----------------------------------------------------------------
2750  // Step 21: register emi_surfmesh_w_counter_face for SF::NBR_ELEM_REF, NBR_REF, SF::NBR_SUBMESH
2751  //-----------------------------------------------------------------
2752  t1 = MPI_Wtime();
2753  if(verb) log_msg(NULL, 0, 0, "Layout for element of EMI surfmesh w counter face");
2754  SF::vector<mesh_int_t> & emi_surfmesh_counter_elem = emi_surfmesh_w_counter_face.register_numbering(SF::NBR_ELEM_REF);
2755  SF::vector<long int> layout_counter;
2756  SF::layout_from_count<long int>(emi_surfmesh_w_counter_face.l_numelem, layout_counter, emi_surfmesh_w_counter_face.comm);
2757  size_t count_counter = layout_counter[rank+1] - layout_counter[rank];
2758  emi_surfmesh_counter_elem.resize(count_counter);
2759  for (int i = 0; i < count_counter; ++i){
2760  emi_surfmesh_counter_elem[i] = layout_counter[rank]+i;
2761  }
2762  emi_surfmesh_w_counter_face.localize(SF::NBR_REF);
2763  emi_surfmesh_w_counter_face.register_numbering(SF::NBR_SUBMESH);
2764  t2 = MPI_Wtime();
2765  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2766 
2767 
2768  //-----------------------------------------------------------------
2769  // Step 22: register emi_surfmesh_unique_face for SF::NBR_ELEM_REF, NBR_REF, SF::NBR_SUBMESH
2770  //-----------------------------------------------------------------
2771  t1 = MPI_Wtime();
2772  if(verb) log_msg(NULL, 0, 0, "Layout for element of EMI unique-face surfmesh");
2773  SF::vector<mesh_int_t> & emi_surfmesh_unique_elem = emi_surfmesh_unique_face.register_numbering(SF::NBR_ELEM_REF);
2774  SF::vector<long int> layout_unique;
2775  SF::layout_from_count<long int>(emi_surfmesh_unique_face.l_numelem, layout_unique, emi_surfmesh_unique_face.comm);
2776  size_t count_unique = layout_unique[rank+1] - layout_unique[rank];
2777  emi_surfmesh_unique_elem.resize(count_unique);
2778  for (int i = 0; i < count_unique; ++i){
2779  emi_surfmesh_unique_elem[i] = layout_unique[rank]+i;
2780  }
2781  emi_surfmesh_unique_face.localize(SF::NBR_REF);
2782  emi_surfmesh_unique_face.register_numbering(SF::NBR_SUBMESH);
2783  t2 = MPI_Wtime();
2784  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2785 
2786  //-----------------------------------------------------------------
2787  // Step 23: insert coordinates to one-sided, both-face, and unique-face surface meshes
2788  //-----------------------------------------------------------------
2789  t1 = MPI_Wtime();
2790  if(verb) log_msg(NULL, 0, 0, "Inserting points to EMI surfmesh");
2791  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);
2792  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);
2793  insert_points_to_surface_mesh(tmesh_backup_old, emi_surfmesh_unique_face, SF::NBR_REF, parab_solver.dof2vertex);
2794  t2 = MPI_Wtime();
2795  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2796 
2797  //-----------------------------------------------------------------
2798  // Step 24: submesh_numbering and PETSc numbering for all EMI surface meshes
2799  //-----------------------------------------------------------------
2800  t1 = MPI_Wtime();
2801  if(verb) log_msg(NULL, 0, 0, "Generating submesh_numbering and PETSc numberings for surface mesh");
2802  {
2804  sub_numbering(emi_surfmesh_one_side);
2805  emi_surfmesh_one_side.generate_par_layout();
2806 
2807  SF::petsc_numbering<mesh_int_t,mesh_real_t> petsc_numbering(emi_surfmesh_one_side.pl);
2808  petsc_numbering(emi_surfmesh_one_side);
2809  }
2810 
2811  {
2813  sub_numbering(emi_surfmesh_w_counter_face);
2814  emi_surfmesh_w_counter_face.generate_par_layout();
2815 
2816  SF::petsc_numbering<mesh_int_t,mesh_real_t> petsc_numbering(emi_surfmesh_w_counter_face.pl);
2817  petsc_numbering(emi_surfmesh_w_counter_face);
2818  }
2819 
2820  {
2822  sub_numbering(emi_surfmesh_unique_face);
2823  emi_surfmesh_unique_face.generate_par_layout();
2824 
2825  SF::petsc_numbering<mesh_int_t,mesh_real_t> petsc_numbering(emi_surfmesh_unique_face.pl);
2826  petsc_numbering(emi_surfmesh_unique_face);
2827  // Ensure deterministic element ordering across MPI for output
2828  assign_deterministic_elem_numbering(emi_surfmesh_unique_face);
2829  }
2830  t2 = MPI_Wtime();
2831  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2832 
2833  //-----------------------------------------------------------------
2834  // Step 25: assign PETSc numbering to map_vertex_tag_to_dof_petsc where the key is (vertex,tag) and value is (dof,petsc)
2835  //-----------------------------------------------------------------
2836  t1 = MPI_Wtime();
2837  if(verb) log_msg(NULL, 0, 0, "assign PETSc numbering for new faces");
2838  SF::assign_petsc_on_counter_face(parab_solver.map_vertex_tag_to_dof_petsc,comm);
2839  {
2840  hashmap::unordered_map<std::pair<mesh_int_t,mesh_int_t>, std::pair<mesh_int_t,mesh_int_t>>::iterator it;
2841  for (it = parab_solver.map_vertex_tag_to_dof_petsc.begin(); it != parab_solver.map_vertex_tag_to_dof_petsc.end(); it++)
2842  {
2843  std::pair <mesh_int_t,mesh_int_t> Index_tag_old = it->first;
2844  std::pair <mesh_int_t,mesh_int_t> dof_petsc = it->second;
2845  parab_solver.dof2petsc[dof_petsc.first] = dof_petsc.second;
2846  parab_solver.petsc2dof[dof_petsc.second] = dof_petsc.first;
2847  }
2848  }
2849  // DEBUG: Check for invalid PETSc indices after exchange
2850  #ifdef EMI_DEBUG_MESH
2851  {
2852  int invalid_count = 0;
2853  for (const auto& [key, val] : parab_solver.map_vertex_tag_to_dof_petsc) {
2854  if (val.second < 0) {
2855  invalid_count++;
2856  if (invalid_count <= 3) {
2857  fprintf(stderr, "RANK %d INVALID: vertex=%ld tag=%ld dof=%ld petsc=%ld\n",
2858  rank, (long)key.first, (long)key.second, (long)val.first, (long)val.second);
2859  }
2860  }
2861  }
2862  fprintf(stderr, "RANK %d: After Step 26: %d invalid PETSc indices out of %zu total\n",
2863  rank, invalid_count, parab_solver.map_vertex_tag_to_dof_petsc.size());
2864  fflush(stderr);
2865  }
2866  #endif
2867 
2868  t2 = MPI_Wtime();
2869  if(verb) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2870 
2871  //-----------------------------------------------------------------
2872  // Step 26: update counter faces in map{type_of}_face
2873  //-----------------------------------------------------------------
2874  added_counter_faces_to_map(parab_solver.line_face, parab_solver.tri_face, parab_solver.quad_face);
2875  const double total_setup = MPI_Wtime() - total_setup_t0;
2876  log_msg(0,0,0, "Total setup_EMI_mesh processing time: %.5f sec.", float(total_setup));
2877 
2878  log_msg(0,0,0, "\n *** EMI mesh processing Done ***\n");
2879 }
2880 
2881 void distribute_elements_based_tags(SF::meshdata<mesh_int_t, mesh_real_t>& mesh,
2882  int total_num_tags)
2883 {
2884  MPI_Comm comm = mesh.comm;
2885  int size, rank;
2886  double t1, t2, s1, s2;
2887  MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank);
2888  const int verb = param_globals::output_level;
2889 
2890  if(total_num_tags < size)
2891  {
2892  log_msg(NULL, 0, 0, "\nThe number of processors should be less than the number of tags, size = %d & ntags = %d !!!",
2893  size, total_num_tags);
2894  cleanup_and_exit();
2895  }
2896 
2897  if(verb==10) log_msg(NULL, 0, 0,"\ncompute the number of tags which belongs to one rank");
2898  // compute the number of tags per rank
2899  SF::vector<mesh_int_t> ntags_per_rank;
2900  t1 = MPI_Wtime();
2901  compute_tags_per_rank(total_num_tags, ntags_per_rank);
2902  t2 = MPI_Wtime();
2903  if(verb==10) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2904 
2905  // generate global destination w.r.t. tags
2906  // redistribute elements based on new partition
2907  t1 = MPI_Wtime();
2908  SF::vector<mesh_int_t> part_based_Tags(mesh.l_numelem);
2909  partition_based_tags(total_num_tags, mesh.tag, ntags_per_rank, part_based_Tags);
2910  SF::redistribute_elements(mesh,part_based_Tags);
2911  // permute elements locally first based on the tag then element index
2912  permute_mesh_locally_based_on_tag_elemIdx(mesh);
2913  t2 = MPI_Wtime();
2914  if(verb==10) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2915 }
2916 
2917 // In the EMI model we cannot have partition boundaries crossing cells.
2918 // Therefore this function assigns a partition to each tag. All elements with this tag will go to the same partition.
2919 void partition_based_tags(int num_tags,
2921  SF::vector<mesh_int_t> num_tags_per_rank,
2922  SF::vector<mesh_int_t>& part)
2923 {
2924  const int verb = param_globals::output_level;
2925  MPI_Comm comm = SF_COMM;
2926  int size, rank;
2927  MPI_Comm_size(comm, &size);
2928  MPI_Comm_rank(comm, &rank);
2929 
2930  // we need to have tags_to_rank as a map globally.
2932  // Try to load the mapping from a .part file and fall back on the internal mapping if there is no such file.
2933  // If the mapping is read from file, the num_tags_per_rank argument is not used.
2934  if (!load_partitions_from_file(tags_to_rank_map, num_tags, comm)) {
2935  if(verb==10) log_msg(NULL, 0, 0,"\ncompute the number of unique tags");
2936  SF::vector<mesh_int_t> unique_tags = tag;
2937  double t1 = MPI_Wtime();
2938  extract_unique_tag(unique_tags);
2939  double t2 = MPI_Wtime();
2940  if(verb==10) log_msg(NULL, 0, 0, "Done in %f sec.", float(t2 - t1));
2941 
2942  if (unique_tags.size() != static_cast<size_t>(num_tags)) {
2943  log_msg(0,5,0, "\nerror: the number of tags in the EMI mesh does not match the total tags from *.extra and *.intra");
2944  EXIT(1);
2945  }
2946  map_tags_to_rank(size, unique_tags, num_tags_per_rank, tags_to_rank_map);
2947  }
2948 
2949  for (size_t i = 0; i < part.size(); ++i) {
2950  if(tags_to_rank_map.count(tag[i]))
2951  part[i] = tags_to_rank_map[tag[i]];
2952  }
2953 }
2954 
2955 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)
2956 {
2957  // the goal is to map the tag as a key -> rank, should be global one
2958  mesh_int_t t = 0; // tag index
2959  for (size_t r = 0; r < size; ++r) {
2960  for (size_t count = 0; count < num_tags_per_rank[r];) {
2961  mesh_int_t tag = unique_tags[t];
2962 
2963  // // here we consider each tag belongs to only one rank.
2964  tags_to_rank_map.insert({tag, r});
2965  count +=1;
2966  t+=1;
2967  }
2968  }
2969 }
2970 
2971 // This function creates a tag-to-rank mapping based only on the numerical order of the tags.
2972 bool load_partitions_from_file(hashmap::unordered_map<mesh_int_t, mesh_int_t>& tags_to_rank_map,
2973  int expected_num_tags,
2974  MPI_Comm comm)
2975 {
2976  int size, rank;
2977  MPI_Comm_size(comm, &size);
2978  MPI_Comm_rank(comm, &rank);
2979 
2980  SF::vector<int> parts;
2981  const std::string basename = param_globals::meshname;
2982  FILE* fd = fopen((basename + ".part").c_str(), "r");
2983  if (fd != NULL) { // check if the file exists otherwise read_indices_global() crash
2984  read_indices_global(parts, basename + ".part", comm);
2985  fclose(fd);
2986  }
2987 
2988  if (parts.size() == 0) return false; // file does not exist or is empty
2989 
2990  if (parts.size() % 2 != 0) {
2991  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.!");
2992  EXIT(1);
2993  }
2994 
2995  int min_part = std::numeric_limits<int>::max();
2996  int max_part = std::numeric_limits<int>::min();
2997  for(int i = 0; i < parts.size(); i+=2) {
2998  min_part = std::min(min_part, parts[i + 1]);
2999  max_part = std::max(max_part, parts[i + 1]);
3000  tags_to_rank_map.insert({parts[i], parts[i + 1]});
3001  }
3002 
3003  if (tags_to_rank_map.size() != static_cast<size_t>(expected_num_tags)) {
3004  log_msg(0,5,0, "\nerror: the number of tags in the .part file does not match the total tags from *.extra and *.intra");
3005  EXIT(1);
3006  }
3007 
3008  if (min_part < 0 || max_part >= size) {
3009  log_msg(0,5,0,
3010  "\nerror: EMI partition file %s.part is incompatible with this run.\n"
3011  "The file contains partition IDs in [%d, %d], but the current MPI communicator has %d rank(s).\n"
3012  "Remove/regenerate the .part file or run with a matching number of MPI tasks.",
3013  basename.c_str(), min_part, max_part, size);
3014  EXIT(1);
3015  }
3016 
3017  if (rank == 0 && max_part + 1 != size) {
3018  log_msg(0,3,0,
3019  "Warning: EMI partition file %s.part uses %d partition ID(s), but the current run uses %d MPI rank(s).",
3020  basename.c_str(), max_part + 1, size);
3021  }
3022 
3023  return true;
3024 }
3025 
3026 void permute_mesh_locally_based_on_tag_elemIdx(SF::meshdata<mesh_int_t, mesh_real_t>& mesh)
3027 {
3028  mesh.globalize(SF::NBR_REF);
3031  interval(perm, 0, mesh.tag.size());
3032 
3033  SF::vector<mesh_int_t> tags = tmesh.tag;
3035  binary_sort_sort_copy(tags, elemIdx, perm);
3036  permute_mesh(tmesh, mesh, perm);
3037 
3038  mesh.localize(SF::NBR_REF);
3039 }
3040 
3041 } // namespace opencarp
3042 
3043 #endif
opencarp::local_index_t mesh_int_t
Definition: SF_container.h:31
float mesh_real_t
Definition: SF_container.h:32
#define SF_COMM
the default SlimFem MPI communicator
Definition: SF_globals.h:25
opencarp::real_t SF_real
Global scalar type.
Definition: SF_globals.h:30
opencarp::global_index_t SF_int
Global algebraic index type.
Definition: SF_globals.h:29
#define SF_MPITAG
the MPI tag when communicating
Definition: SF_globals.h:27
Starts and stops the linear algebra backend the flavor parameter selects.
#define EXIT(A)
finalize the backend, if it runs, and exit
Definition: backend.h:23
#define FLUSH
Definition: basics.h:305
#define MAX_LOG_LEVEL
Definition: basics.h:309
#define ECHO
Definition: basics.h:302
#define CALI_CXX_MARK_FUNCTION
Definition: caliper_hooks.h:8
#define CALI_MARK_BEGIN(_str)
Definition: caliper_hooks.h:6
#define CALI_MARK_END(_str)
Definition: caliper_hooks.h:7
void globalize(SF_nbr nbr_type)
Localize the connectivity data w.r.t. a given numbering.
Definition: SF_container.h:510
size_t l_numelem
local number of elements
Definition: SF_container.h:384
std::string name
the mesh name
Definition: SF_container.h:392
void localize(SF_nbr nbr_type)
Localize the connectivity data w.r.t. a given numbering.
Definition: SF_container.h:481
MPI_Comm comm
the parallel mesh is defined on a MPI world
Definition: SF_container.h:389
vector< T > & get_numbering(SF_nbr nbr_type)
Get the vector defining a certain numbering.
Definition: SF_container.h:449
vector< T > tag
element tag
Definition: SF_container.h:402
Functor class generating a numbering optimized for PETSc.
Definition: SF_numbering.h:238
void forward(abstract_vector< T, S > &in, abstract_vector< T, S > &out, bool add=false)
Forward scattering.
Functor class applying a submesh renumbering.
Definition: SF_numbering.h:55
size_t size() const
The current size of the vector.
Definition: SF_vector.h:89
void resize(size_t n)
Resize a vector.
Definition: SF_vector.h:194
const T * end() const
Pointer to the vector's end.
Definition: SF_vector.h:113
const T * begin() const
Pointer to the vector's start.
Definition: SF_vector.h:101
T * data()
Pointer to the vector's start.
Definition: SF_vector.h:76
T & push_back(T val)
Definition: SF_vector.h:268
iterator find(const K &key)
Search for key. Return iterator.
Definition: hashmap.hpp:626
hm_int count(const K &key) const
Check if key exists.
Definition: hashmap.hpp:612
void reserve(size_t n)
Definition: hashmap.hpp:719
void insert(InputIterator first, InputIterator last)
Insert Iterator range.
Definition: hashmap.hpp:572
size_t size() const
Definition: hashmap.hpp:720
size_t size() const
Definition: hashmap.hpp:1141
iterator find(const K &key)
Definition: hashmap.hpp:1081
hm_int erase(const K &key)
Definition: hashmap.hpp:1053
hm_int count(const K &key) const
Definition: hashmap.hpp:1067
void insert(InputIterator first, InputIterator last)
Definition: hashmap.hpp:1037
long d_time
current time instance index
Definition: timer_utils.h:63
double time_step
global reference time step
Definition: timer_utils.h:64
int add_eq_timer(double istart, double iend, int ntrig, double iintv, double idur, const char *iname, const char *poolname=nullptr)
Add a equidistant step timer to the array of timers.
Definition: timer_utils.cc:63
long d_end
final index in multiples of dt
Definition: timer_utils.h:68
double time
current time
Definition: timer_utils.h:62
EMI model based on computed current on the faces, main EMI physics class.
#define log_msg(F, L, O,...)
Definition: filament.h:8
void init_solver(SF::abstract_linear_solver< T, S > **sol)
Definition: SF_init.h:249
Point arr_to_point(V *arr)
Definition: SF_container.h:57
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:948
void interval(vector< T > &vec, size_t start, size_t end)
Create an integer interval between start and end.
Definition: SF_vector.h:335
void make_global(const vector< T > &vec, vector< T > &out, MPI_Comm comm)
make a parallel vector global
Definition: SF_network.h:210
void rebalance_mesh(meshdata< T, S > &mesh)
Rebalance the parallel distribution of a mesh, if a local size is 0.
void extract_mesh(const vector< bool > &keep, const meshdata< T, S > &mesh, meshdata< T, S > &submesh)
Extract a submesh from a given mesh.
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:41
double inner_prod(const Point &a, const Point &b)
Definition: SF_container.h:75
T sum(const vector< T > &vec)
Compute sum of a vector's entries.
Definition: SF_vector.h:325
void unique_resize(vector< T > &_P)
Definition: SF_sort.h:338
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:343
void count(const vector< T > &data, vector< S > &cnt)
Count number of occurrences of indices.
Definition: SF_vector.h:317
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:1046
void assemble_matrix(abstract_matrix< T, S > &mat, meshdata< mesh_int_t, mesh_real_t > &domain, matrix_integrator< mesh_int_t, mesh_real_t > &integrator)
Generalized matrix assembly.
Definition: SF_fem_utils.h:995
int max_nodal_edgecount(const meshdata< T, S > &mesh)
Compute the maximum number of node-to-node edges for a mesh.
Definition: SF_container.h:593
void 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.
T local_nodal_to_local_petsc(const meshdata< T, S > &mesh, int rank, T local_nodal)
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:69
elem_t getElemTypeID(char *eletype)
Generate element type enum from string.
Definition: SF_container.h:152
void init_vector(SF::abstract_vector< T, S > **vec)
Definition: SF_init.h:127
void binary_sort(vector< T > &_V)
Definition: SF_sort.h:274
void init_matrix(SF::abstract_matrix< T, S > **mat)
Definition: SF_init.h:228
void write_mesh_parallel(const meshdata< T, S > &mesh, bool binary, std::string basename)
elem_t
element type enum
Definition: SF_container.h:38
@ Line
Definition: SF_container.h:46
@ Tri
Definition: SF_container.h:45
@ Prism
Definition: SF_container.h:43
@ Pyramid
Definition: SF_container.h:42
@ Tetra
Definition: SF_container.h:39
@ Quad
Definition: SF_container.h:44
@ Hexa
Definition: SF_container.h:40
void binary_sort_sort_copy(vector< T > &_V, vector< T > &_W, vector< S > &_A)
Definition: SF_sort.h:325
@ NBR_PETSC
PETSc numbering of nodes.
Definition: SF_container.h:188
@ NBR_ELEM_REF
The element numbering of the reference mesh (the one stored on HD).
Definition: SF_container.h:189
@ NBR_REF
The nodal numbering of the reference mesh (the one stored on HD).
Definition: SF_container.h:186
@ NBR_SUBMESH
Submesh nodal numbering: The globally ascending sorted reference indices are reindexed.
Definition: SF_container.h:187
@ NBR_ELEM_SUBMESH
Submesh element numbering: The globally ascending sorted reference indices are reindexed.
Definition: SF_container.h:190
constexpr T min(T a, T b)
Definition: ion_type.h:18
constexpr T max(T a, T b)
Definition: ion_type.h:16
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:40
bool using_legacy_stimuli
flag storing whether legacy stimuli are used
Definition: main.cc:46
std::map< mesh_t, sf_mesh > mesh_reg
Registry for the different meshes used in a multi-physics simulation.
Definition: main.cc:34
void region_mask(mesh_t meshspec, SF::vector< RegionSpecs > &regspec, SF::vector< int > &regionIDs, bool mask_elem, const char *reglist, FILE_SPEC logger)
classify elements/points as belonging to a region
Definition: ionics.cc:340
int stimidx_from_timeridx(const SF::vector< stimulus > &stimuli, const int timer_id)
determine link between timer and stimulus
Definition: electrics.cc:839
@ iotm_chkpt_list
Definition: timer_utils.h:29
@ iotm_console
Definition: timer_utils.h:29
@ iotm_trace
Definition: timer_utils.h:29
@ iotm_chkpt_intv
Definition: timer_utils.h:29
sf_vec * get_data(datavec_t d)
Retrieve a petsc data vector from the data registry.
Definition: sim_utils.cc:2084
bool petsc_active()
Definition: backend.cc:53
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:891
void read_el_scale_vec(const char *file, mesh_t mt, SF::vector< double > &el_scale, int &el_scale_dpn)
SF_real get_volume_from_nodes(sf_mat &mass, SF::vector< mesh_int_t > &local_idx)
Definition: fem_utils.cc:202
sf_mesh & get_mesh(const mesh_t gt)
Get a mesh by specifying the gridID.
Definition: sf_interface.cc:18
SF::scattering * register_scattering(const int from, const int to, const SF::SF_nbr nbr, const int dpn)
Register a scattering between to grids, or between algebraic and nodal representation of data on the ...
Definition: sf_interface.cc:54
cond_t
description of electrical tissue properties
Definition: fem_types.h:27
@ intra_cond
Definition: fem_types.h:28
SF::scattering * get_permutation(const int mesh_id, const int perm_id, const int dpn)
Get the PETSC to canonical permutation scattering for a given mesh and number of dpn.
SF::meshdata< mesh_int_t, mesh_real_t > sf_mesh
Definition: sf_interface.h:33
void apply_stim_to_vector(const stimulus &s, sf_vec &vec, bool add)
Definition: electrics.cc:435
int set_dir(IO_t dest)
Definition: sim_utils.cc:1619
void cleanup_and_exit()
Definition: sim_utils.cc:2654
void read_indices_global(SF::vector< T > &idx, const std::string filename, MPI_Comm comm)
Definition: fem_utils.h:38
T get_global(T in, MPI_Op OP, MPI_Comm comm=SF_COMM)
Do a global reduction on a variable.
Definition: basics.h:219
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:154
void init_stim_info(void)
uses potential for stimulation
Definition: stimulate.cc:34
bool is_extra(stim_t type)
whether stimulus is on extra grid (or on intra)
Definition: stimulate.cc:68
FILE_SPEC f_open(const char *fname, const char *mode)
Open a FILE_SPEC.
Definition: basics.cc:111
bool have_dbc_stims(const SF::vector< stimulus > &stimuli)
return wheter any stimuli require dirichlet boundary conditions
Definition: electrics.cc:916
SF::scattering * register_permutation(const int mesh_id, const int perm_id, const int dpn)
Register a permutation between two orderings for a mesh.
bool is_current(stim_t type)
uses current as stimulation
Definition: stimulate.cc:58
@ OUTPUT
Definition: sim_utils.h:39
char * dupstr(const char *old_str)
Definition: basics.cc:27
void compute_restr_idx(sf_mesh &mesh, SF::vector< mesh_int_t > &inp_idx, SF::vector< mesh_int_t > &idx)
Definition: electrics.cc:527
void log_msg(FILE_SPEC out, int level, unsigned char flag, const char *fmt,...)
Definition: basics.cc:55
@ emi_surface_unique_face_msh
Definition: sf_interface.h:53
@ emi_surface_msh
Definition: sf_interface.h:51
@ emi_surface_counter_msh
Definition: sf_interface.h:52
void get_time(double &tm)
Definition: basics.h:430
bool mesh_is_registered(const mesh_t gt)
check wheter a SF mesh is set
Definition: sf_interface.cc:48
SF::abstract_vector< SF_int, SF_real > sf_vec
Definition: sf_interface.h:35
const char * get_tsav_ext(double time)
Definition: electrics.cc:925
int get_rank(MPI_Comm comm=SF_COMM)
Definition: basics.h:270
SF::abstract_matrix< SF_int, SF_real > sf_mat
Definition: sf_interface.h:37
V timing(V &t2, const V &t1)
Definition: basics.h:442
std::string get_basename(const std::string &path)
Definition: basics.cc:44
void f_close(FILE_SPEC &f)
Close a FILE_SPEC.
Definition: basics.cc:138
@ ElecMat
Definition: fem_types.h:24
file_desc * FILE_SPEC
Definition: basics.h:126
Basic physics types.
#define UM2_to_CM2
convert um^2 to cm^2
Definition: physics_types.h:20
#define PETSC_TO_CANONICAL
Permute algebraic data from PETSC to canonical ordering.
Definition: sf_interface.h:64
#define ALG_TO_NODAL
Scatter algebraic to nodal.
Definition: sf_interface.h:62
#define ELEM_PETSC_TO_CANONICAL
Permute algebraic element data from PETSC to canonical ordering.
Definition: sf_interface.h:66
#define EXP_POSTPROCESS
Definition: sim_utils.h:192
Electrical stimulation functions.
Point and vector struct.
Definition: SF_container.h:50