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