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