openCARP
Doxygen code documentation for the open cardiac electrophysiology simulator openCARP
SF_fem_utils_emi.h
Go to the documentation of this file.
1 // SPDX-FileCopyrightText: Copyright (c) NumeriCor GmbH
2 // SPDX-License-Identifier: Apache-2.0
3 
14 #if WITH_EMI_MODEL
15 
16 #ifndef _SF_FEM_EMI_H
17 #define _SF_FEM_EMI_H
18 
19 #include "SF_container.h"
20 #include "SF_fem_utils.h"
21 #include "SF_init.h"
22 #include "mpi_utils.h"
23 #include <array>
24 #include <cmath>
25 #include <limits>
26 #include <stdexcept>
27 #include <type_traits>
28 namespace SF {
29 
40 inline double computeTriangleArea(const SF::Point& p1, const SF::Point& p2, const SF::Point& p3)
41 {
42  // Define two edges of the triangle
43  Point edge1 = p2 - p1;
44  Point edge2 = p3 - p1;
45 
46  // Compute the cross product
47  Point crossProduct = cross(edge1,edge2);
48 
49  // Compute the magnitude of the cross product
50  double crossProductMagnitude = mag(crossProduct);
51 
52  return 0.5 * crossProductMagnitude;
53 }
54 
65 inline void compute_barycentric_coordinates_coefficients(const SF::Point& p1, const SF::Point& p2, const SF::Point& P, std::array<double, SF_MAX_ELEM_NODES>& interpolationCoefficients, double & length)
66 {
67  SF::Point d = p2 - p1;
68  double lengthSquared = inner_prod(d, d);
69  length = std::sqrt(lengthSquared);
70 
71  SF::Point v = P - p1;
72  double lambda2 = inner_prod(v, d) / lengthSquared;
73  double lambda1 = 1.0 - lambda2;
74 
75  interpolationCoefficients[0] = lambda1;
76  interpolationCoefficients[1] = lambda2;
77 }
78 
90 inline void compute_barycentric_coordinates_coefficients(const SF::Point& p1, const SF::Point& p2, const SF::Point& p3, const SF::Point& P, std::array<double, SF_MAX_ELEM_NODES>& interpolationCoefficients, double & area)
91 {
92 
93  area = computeTriangleArea(p1, p2, p3);
94 
95  // Vectors for the edges of the triangle
96  SF::Point v0 = p2 - p1;
97  SF::Point v1 = p3 - p1;
98  SF::Point v2 = P - p1;
99 
100  // Dot products
101  double d00 = inner_prod(v0,v0);
102  double d01 = inner_prod(v0,v1);
103  double d11 = inner_prod(v1,v1);
104  double d20 = inner_prod(v2,v0);
105  double d21 = inner_prod(v2,v1);
106 
107  // Compute barycentric coordinates
108  double denom = d00 * d11 - d01 * d01;
109  double beta = (d11 * d20 - d01 * d21) / denom;
110  double gamma = (d00 * d21 - d01 * d20) / denom;
111  double alpha = 1.0 - beta - gamma;
112  interpolationCoefficients[0] = alpha; // α
113  interpolationCoefficients[1] = beta; // β
114  interpolationCoefficients[2] = gamma; // ζ
115 }
116 
117 
130 inline void compute_barycentric_coordinates_coefficients(const SF::Point& p1, const SF::Point& p2, const SF::Point& p3, const SF::Point& p4, const SF::Point& P, std::array<double, SF_MAX_ELEM_NODES>& interpolationCoefficients, double & area)
131 {
132  // Area: compute as sum of two triangles
133  double area1 = computeTriangleArea(p1, p2, p4);
134  double area2 = computeTriangleArea(p2, p3, p4);
135  area = area1 + area2;
136 
137  // Define local axes
138  SF::Point origin = p1;
139  SF::Point u_dir = p2 - p1;
140  SF::Point v_dir = p4 - p1;
141 
142  // Project P into the local (u,v) frame
143  SF::Point d = P - origin;
144  double u = inner_prod(d, u_dir) / inner_prod(u_dir, u_dir);
145  double v = inner_prod(d, v_dir) / inner_prod(v_dir, v_dir);
146 
147  // Clamp u and v to [0,1] to avoid extrapolation (optional)
148  u = SF::clamp(u, 0.0, 1.0);
149  v = SF::clamp(v, 0.0, 1.0);
150 
151  // Bilinear shape functions
152  interpolationCoefficients[0] = (1 - u) * (1 - v); // α
153  interpolationCoefficients[1] = u * (1 - v); // β
154  interpolationCoefficients[2] = u * v; // γ
155  interpolationCoefficients[3] = (1 - u) * v; // ζ
156 }
157 
169 template<class S>
170 inline void compute_integrate_matrix_barycentric(vector<SF::Point> face_coordinates, SF_int nnodes, dmat<SF_real> & ebuff, dmat<SF_real> & ebuff_s, dmat<SF_real> & ebuff_counter, S mass_scale)
171 {
172  //compute the barycentric coordinates: calculateCentroid
173  double x = 0;
174  double y = 0;
175  double z = 0;
176  for (int i = 0; i < nnodes; ++i)
177  {
178  x += face_coordinates[i].x;
179  y += face_coordinates[i].y;
180  z += face_coordinates[i].z;
181  }
182  SF::Point b; b.x = x/nnodes; b.y = y/nnodes; b.z = z/nnodes;
183 
184  std::array<double, SF_MAX_ELEM_NODES> interpolationCoefficients{};
185  double area;
186  if(nnodes==2){
187  compute_barycentric_coordinates_coefficients(face_coordinates[0], face_coordinates[1], b, interpolationCoefficients, area);
188  }
189  else if(nnodes==3)
190  {
191  compute_barycentric_coordinates_coefficients(face_coordinates[0], face_coordinates[1], face_coordinates[2], b, interpolationCoefficients, area);
192  }
193  else if(nnodes==4){
194  compute_barycentric_coordinates_coefficients(face_coordinates[0], face_coordinates[1], face_coordinates[2], face_coordinates[3], b, interpolationCoefficients, area);
195  }
196 
197  // Use the same face shape weights for the scatter mass as for interpolation.
198  if(nnodes==2){
199  ebuff[0][0] = interpolationCoefficients[0];
200  ebuff[0][1] = interpolationCoefficients[1];
201 
202  ebuff_counter[0][0] = interpolationCoefficients[0];
203  ebuff_counter[0][1] = interpolationCoefficients[1];
204 
205  ebuff_s[0][0] = mass_scale*area*interpolationCoefficients[0];
206  ebuff_s[0][1] = mass_scale*area*interpolationCoefficients[1];
207  }
208  else if(nnodes==3){
209  ebuff[0][0] = interpolationCoefficients[0];
210  ebuff[0][1] = interpolationCoefficients[1];
211  ebuff[0][2] = interpolationCoefficients[2];
212 
213  ebuff_counter[0][0] = interpolationCoefficients[0];
214  ebuff_counter[0][1] = interpolationCoefficients[1];
215  ebuff_counter[0][2] = interpolationCoefficients[2];
216 
217  ebuff_s[0][0] = mass_scale*area*interpolationCoefficients[0];
218  ebuff_s[0][1] = mass_scale*area*interpolationCoefficients[1];
219  ebuff_s[0][2] = mass_scale*area*interpolationCoefficients[2];
220  }
221  else if(nnodes==4){
222  ebuff[0][0] = interpolationCoefficients[0];
223  ebuff[0][1] = interpolationCoefficients[1];
224  ebuff[0][2] = interpolationCoefficients[2];
225  ebuff[0][3] = interpolationCoefficients[3];
226 
227  ebuff_counter[0][0] = interpolationCoefficients[0];
228  ebuff_counter[0][1] = interpolationCoefficients[1];
229  ebuff_counter[0][2] = interpolationCoefficients[2];
230  ebuff_counter[0][3] = interpolationCoefficients[3];
231 
232  ebuff_s[0][0] = mass_scale*area*interpolationCoefficients[0];
233  ebuff_s[0][1] = mass_scale*area*interpolationCoefficients[1];
234  ebuff_s[0][2] = mass_scale*area*interpolationCoefficients[2];
235  ebuff_s[0][3] = mass_scale*area*interpolationCoefficients[3];
236  }
237 }
238 
239 
271 template<class T, class S, class emi_index_rank>
272 inline void construct_direct_unique_both_operators(
273  SF::abstract_matrix<T, S>*& operator_unique_to_both_faces,
274  SF::abstract_matrix<T, S>*& operator_both_to_unique_face,
275  hashmap::unordered_map<mesh_int_t, std::pair<emi_index_rank, emi_index_rank>>& map_elem_uniqueFace_to_elem_bothface,
276  const hashmap::unordered_map<mesh_int_t, std::pair<emi_index_rank, emi_index_rank>>& map_elem_uniqueFace_to_elem_oneface,
277  const SF::vector<mesh_int_t>& vec_both_to_one_face,
278  const SF::meshdata<mesh_int_t, mesh_real_t>& emi_surfmesh_w_counter_face,
279  const SF::meshdata<mesh_int_t, mesh_real_t>& emi_surfmesh_unique_face,
280  int max_row_entries_emi,
281  int dpn,
282  typename SF::abstract_vector<T, S>::ltype alg_surface_type)
283 {
284  // Global/local dimensions for the direct unique-face <-> both-face operators.
285  T M = emi_surfmesh_w_counter_face.g_numelem;
286  T m = emi_surfmesh_w_counter_face.l_numelem;
287  T M_unique_face = emi_surfmesh_unique_face.g_numelem;
288  T m_unique_face = emi_surfmesh_unique_face.l_numelem;
289 
290  int rank = 0;
291  int comm_size = 0;
292  MPI_Comm_rank(emi_surfmesh_w_counter_face.comm, &rank);
293  MPI_Comm_size(emi_surfmesh_w_counter_face.comm, &comm_size);
294 
295  SF::vector<long int> layout;
296  SF::layout_from_count<long int>(emi_surfmesh_w_counter_face.l_numelem, layout, emi_surfmesh_w_counter_face.comm);
297  T m_l = layout[rank];
298 
299  SF::vector<long int> layout_unique_face;
300  SF::layout_from_count<long int>(emi_surfmesh_unique_face.l_numelem, layout_unique_face, emi_surfmesh_unique_face.comm);
301  T m_unique_face_l = layout_unique_face[rank];
302 
303  // Rebuild the direct mapping from scratch every time the operators are assembled.
304  map_elem_uniqueFace_to_elem_bothface.clear();
305 
306  // Gather the local both->one lookup so every rank can recover the matching
307  // both-face indices for remote one-face owners.
308  std::vector<int> both_counts(comm_size, 0), both_displs(comm_size, 0);
309  int local_both_count = static_cast<int>(vec_both_to_one_face.size());
310  MPI_Allgather(&local_both_count, 1, MPI_INT, both_counts.data(), 1, MPI_INT,
311  emi_surfmesh_w_counter_face.comm);
312 
313  int total_both_count = 0;
314  for (int i = 0; i < comm_size; i++) {
315  both_displs[i] = total_both_count;
316  total_both_count += both_counts[i];
317  }
318 
319  std::vector<mesh_int_t> all_both_to_one(total_both_count);
320  // Exchange raw bytes so the gather stays correct even if mesh_int_t differs
321  // from int on a given build.
322  std::vector<int> both_byte_counts(comm_size, 0), both_byte_displs(comm_size, 0);
323  for (int i = 0; i < comm_size; i++) {
324  both_byte_counts[i] = both_counts[i] * static_cast<int>(sizeof(mesh_int_t));
325  both_byte_displs[i] = both_displs[i] * static_cast<int>(sizeof(mesh_int_t));
326  }
327  const int local_both_bytes = local_both_count * static_cast<int>(sizeof(mesh_int_t));
328  MPI_Allgatherv(reinterpret_cast<const unsigned char*>(vec_both_to_one_face.data()),
329  local_both_bytes, MPI_BYTE,
330  reinterpret_cast<unsigned char*>(all_both_to_one.data()),
331  both_byte_counts.data(), both_byte_displs.data(), MPI_BYTE,
332  emi_surfmesh_w_counter_face.comm);
333 
334  // Build per-rank one->both lookup tables. A one-face entry can map to up to
335  // two both-face entries, so we store first and second occurrences explicitly.
336  std::vector<std::vector<mesh_int_t>> one_to_both_first(comm_size);
337  std::vector<std::vector<mesh_int_t>> one_to_both_second(comm_size);
338  for (int r = 0; r < comm_size; r++) {
339  mesh_int_t max_one = -1;
340  for (int i = 0; i < both_counts[r]; i++) {
341  mesh_int_t one_idx = all_both_to_one[both_displs[r] + i];
342  if (one_idx > max_one) max_one = one_idx;
343  }
344  if (max_one < 0) continue;
345 
346  one_to_both_first[r].assign(max_one + 1, -1);
347  one_to_both_second[r].assign(max_one + 1, -1);
348  for (int i = 0; i < both_counts[r]; i++) {
349  mesh_int_t one_idx = all_both_to_one[both_displs[r] + i];
350  if (one_idx < 0) continue;
351  if (one_to_both_first[r][one_idx] < 0) one_to_both_first[r][one_idx] = i;
352  else one_to_both_second[r][one_idx] = i;
353  }
354  }
355 
356  // Convert the stored unique->one relation into a direct unique->both relation.
357  for (const auto& [unique_idx, one_face_pair] : map_elem_uniqueFace_to_elem_oneface) {
358  const auto& first_oneface = one_face_pair.first;
359  const auto& second_oneface = one_face_pair.second;
360 
361  auto map_one_to_both = [&](const emi_index_rank& one_face_idx, bool use_second) {
362  emi_index_rank both_face_idx;
363  both_face_idx.index = -1;
364  both_face_idx.rank = -1;
365 
366  if (one_face_idx.rank < 0 || one_face_idx.rank >= comm_size || one_face_idx.index < 0) {
367  return both_face_idx;
368  }
369  if (one_face_idx.index >= static_cast<int>(one_to_both_first[one_face_idx.rank].size())) {
370  return both_face_idx;
371  }
372 
373  mesh_int_t both_idx = one_to_both_first[one_face_idx.rank][one_face_idx.index];
374  if (use_second &&
375  one_face_idx.index < static_cast<int>(one_to_both_second[one_face_idx.rank].size()) &&
376  one_to_both_second[one_face_idx.rank][one_face_idx.index] >= 0) {
377  both_idx = one_to_both_second[one_face_idx.rank][one_face_idx.index];
378  }
379 
380  if (both_idx >= 0) {
381  both_face_idx.index = both_idx;
382  both_face_idx.rank = one_face_idx.rank;
383  }
384  return both_face_idx;
385  };
386 
387  const bool same_oneface =
388  (first_oneface.index >= 0 && second_oneface.index >= 0 &&
389  first_oneface.index == second_oneface.index &&
390  first_oneface.rank == second_oneface.rank);
391 
392  map_elem_uniqueFace_to_elem_bothface[unique_idx] = std::make_pair(
393  map_one_to_both(first_oneface, false),
394  map_one_to_both(second_oneface, same_oneface));
395  }
396 
397  // Assemble the expansion operator unique -> both. Each unique-face column
398  // writes into one or two both-face rows, depending on ownership/splitting.
399  SF::init_matrix(&operator_unique_to_both_faces);
400  operator_unique_to_both_faces->init(M, M_unique_face, m, m_unique_face, m_l, 1);
401  operator_unique_to_both_faces->zero();
402 
403  // Broadcast owner-provided row locations once so each rank can insert its
404  // owned rows without repeating the communication during exact-preallocation
405  // replay. This is a known remaining EMI scalability limit for 64-bit builds:
406  // see docs/64BIT_SUPPORT.md.
407  if (emi_surfmesh_unique_face.g_numelem > static_cast<size_t>(std::numeric_limits<int>::max())) {
408  throw std::runtime_error(
409  "EMI unique-face transfer operator currently requires fewer than INT_MAX unique faces");
410  }
411  const int M_unique_global = static_cast<int>(emi_surfmesh_unique_face.g_numelem);
412  std::vector<int> first_rank(M_unique_global, -1), first_idx(M_unique_global, -1);
413  std::vector<int> second_rank(M_unique_global, -1), second_idx(M_unique_global, -1);
414 
415  for (const auto& [local_unique_idx, both_pair] : map_elem_uniqueFace_to_elem_bothface) {
416  int global_unique = static_cast<int>(layout_unique_face[rank] + local_unique_idx);
417  if (global_unique < 0 || global_unique >= M_unique_global) continue;
418 
419  first_rank[global_unique] = static_cast<int>(both_pair.first.rank);
420  first_idx[global_unique] = static_cast<int>(both_pair.first.index);
421  second_rank[global_unique] = static_cast<int>(both_pair.second.rank);
422  second_idx[global_unique] = static_cast<int>(both_pair.second.index);
423  }
424 
425  MPI_Allreduce(MPI_IN_PLACE, first_rank.data(), M_unique_global, MPI_INT, MPI_MAX, emi_surfmesh_w_counter_face.comm);
426  MPI_Allreduce(MPI_IN_PLACE, first_idx.data(), M_unique_global, MPI_INT, MPI_MAX, emi_surfmesh_w_counter_face.comm);
427  MPI_Allreduce(MPI_IN_PLACE, second_rank.data(), M_unique_global, MPI_INT, MPI_MAX, emi_surfmesh_w_counter_face.comm);
428  MPI_Allreduce(MPI_IN_PLACE, second_idx.data(), M_unique_global, MPI_INT, MPI_MAX, emi_surfmesh_w_counter_face.comm);
429 
430  auto assemble_unique_to_both = [&]() {
431  SF::vector<SF_int> row_idx(1), col_idx(1);
432  SF::dmat<SF_real> ebuff(1, 1);
433  ebuff.assign(1, 1, 1.0);
434 
435  for (int gid = 0; gid < M_unique_global; gid++) {
436  col_idx[0] = gid;
437  if (first_rank[gid] == rank && first_idx[gid] >= 0) {
438  row_idx[0] = static_cast<SF_int>(layout[rank] + first_idx[gid]);
439  operator_unique_to_both_faces->set_values(row_idx, col_idx, ebuff.data(), false);
440  }
441  if (second_rank[gid] == rank && second_idx[gid] >= 0) {
442  row_idx[0] = static_cast<SF_int>(layout[rank] + second_idx[gid]);
443  operator_unique_to_both_faces->set_values(row_idx, col_idx, ebuff.data(), false);
444  }
445  }
446  operator_unique_to_both_faces->finish_assembly();
447  };
448 
449  if (operator_unique_to_both_faces->begin_exact_preallocation()) {
450  assemble_unique_to_both();
451  operator_unique_to_both_faces->finalize_exact_preallocation();
452  }
453  assemble_unique_to_both();
454 
455  // Assemble the restriction/averaging operator both -> unique from the direct map.
456  SF::init_matrix(&operator_both_to_unique_face);
457  operator_both_to_unique_face->init(M_unique_face, M, m_unique_face, m, m_unique_face_l, 2);
458  operator_both_to_unique_face->zero();
459  if (operator_both_to_unique_face->begin_exact_preallocation()) {
460  assemble_map_both_to_unique(*operator_both_to_unique_face,
461  map_elem_uniqueFace_to_elem_bothface,
462  emi_surfmesh_unique_face,
463  emi_surfmesh_w_counter_face);
464  operator_both_to_unique_face->finalize_exact_preallocation();
465  }
466  assemble_map_both_to_unique(*operator_both_to_unique_face,
467  map_elem_uniqueFace_to_elem_bothface,
468  emi_surfmesh_unique_face,
469  emi_surfmesh_w_counter_face);
470 
471 #ifdef EMI_DEBUG_MESH
472  {
473  // Validate mapping coverage and check whether the direct operators compose
474  // to the identity on a constant unique-face field.
475  mesh_int_t local_valid_first = 0, local_valid_second = 0;
476  mesh_int_t local_same_column = 0;
477  SF::vector<long int> layout_both_dbg;
478  SF::layout_from_count<long int>(emi_surfmesh_w_counter_face.l_numelem, layout_both_dbg, emi_surfmesh_w_counter_face.comm);
479  SF::vector<long int> layout_unique_dbg;
480  SF::layout_from_count<long int>(emi_surfmesh_unique_face.l_numelem, layout_unique_dbg, emi_surfmesh_unique_face.comm);
481 
482  for (const auto& [uidx, both_pair] : map_elem_uniqueFace_to_elem_bothface) {
483  if (both_pair.first.index >= 0) local_valid_first++;
484  if (both_pair.second.index >= 0) local_valid_second++;
485 
486  if (both_pair.first.index >= 0 && both_pair.second.index >= 0 &&
487  both_pair.first.rank >= 0 && both_pair.second.rank >= 0) {
488  mesh_int_t global_first = layout_both_dbg[both_pair.first.rank] + both_pair.first.index;
489  mesh_int_t global_second = layout_both_dbg[both_pair.second.rank] + both_pair.second.index;
490  if (global_first == global_second) local_same_column++;
491  }
492  }
493  mesh_int_t global_valid_first = local_valid_first;
494  mesh_int_t global_valid_second = local_valid_second;
495  mesh_int_t global_same_column = local_same_column;
496  const MPI_Datatype mesh_mpi_t = opencarp::mpi_datatype<mesh_int_t>();
497  MPI_Allreduce(MPI_IN_PLACE, &global_valid_first, 1, mesh_mpi_t, MPI_SUM, emi_surfmesh_w_counter_face.comm);
498  MPI_Allreduce(MPI_IN_PLACE, &global_valid_second, 1, mesh_mpi_t, MPI_SUM, emi_surfmesh_w_counter_face.comm);
499  MPI_Allreduce(MPI_IN_PLACE, &global_same_column, 1, mesh_mpi_t, MPI_SUM, emi_surfmesh_w_counter_face.comm);
500 
501  int dbg_rank = -1;
502  MPI_Comm_rank(emi_surfmesh_w_counter_face.comm, &dbg_rank);
503  if (dbg_rank == 0) {
504  log_msg(NULL, 0, 0, "DEBUG map_unique_to_both: valid_first=%jd valid_second=%jd (global M=%zu, M_unique=%zu)",
505  opencarp::printable_int(global_valid_first),
506  opencarp::printable_int(global_valid_second),
507  (size_t)emi_surfmesh_w_counter_face.g_numelem,
508  (size_t)emi_surfmesh_unique_face.g_numelem);
509  log_msg(NULL, 0, 0, "DEBUG map_unique_to_both: same_column=%jd", opencarp::printable_int(global_same_column));
510  }
511 
512  {
513  std::vector<char> present(emi_surfmesh_unique_face.l_numelem, 0);
514  for (const auto& [uidx, both_pair] : map_elem_uniqueFace_to_elem_bothface) {
515  if (uidx >= 0 && uidx < (mesh_int_t)present.size()) present[uidx] = 1;
516  }
517  std::vector<int> missing;
518  for (mesh_int_t i = 0; i < (mesh_int_t)present.size(); i++) {
519  if (!present[i]) missing.push_back((int)i);
520  }
521  int local_missing = (int)missing.size();
522  int comm_size_dbg = 0;
523  MPI_Comm_size(emi_surfmesh_w_counter_face.comm, &comm_size_dbg);
524  std::vector<int> counts(comm_size_dbg, 0), displs(comm_size_dbg, 0);
525  MPI_Gather(&local_missing, 1, MPI_INT,
526  dbg_rank == 0 ? counts.data() : nullptr, 1, MPI_INT,
527  0, emi_surfmesh_w_counter_face.comm);
528  if (dbg_rank == 0) {
529  int total = 0;
530  for (int r = 0; r < comm_size_dbg; r++) {
531  displs[r] = total;
532  total += counts[r];
533  }
534  std::vector<int> all_missing(total, -1);
535  MPI_Gatherv(missing.data(), local_missing, MPI_INT,
536  all_missing.data(), counts.data(), displs.data(), MPI_INT,
537  0, emi_surfmesh_w_counter_face.comm);
538  int offset = 0;
539  bool any = false;
540  for (int r = 0; r < comm_size_dbg; r++) {
541  if (counts[r] == 0) {
542  offset += counts[r];
543  continue;
544  }
545  any = true;
546  log_msg(NULL, 0, 0, "DEBUG unique missing: rank=%d count=%d", r, counts[r]);
547  int to_print = counts[r] < 10 ? counts[r] : 10;
548  for (int i = 0; i < to_print; i++) {
549  log_msg(NULL, 0, 0, "DEBUG unique missing: rank=%d local_unique=%d", r, all_missing[offset + i]);
550  }
551  offset += counts[r];
552  }
553  if (!any) log_msg(NULL, 0, 0, "DEBUG unique missing: none");
554  } else {
555  MPI_Gatherv(missing.data(), local_missing, MPI_INT,
556  nullptr, nullptr, nullptr, MPI_INT,
557  0, emi_surfmesh_w_counter_face.comm);
558  }
559  }
560 
561  SF::abstract_vector<T, S>* dbg_unique = nullptr;
562  SF::abstract_vector<T, S>* dbg_both = nullptr;
563  SF::abstract_vector<T, S>* dbg_back = nullptr;
564  SF::init_vector(&dbg_unique, emi_surfmesh_unique_face, dpn, alg_surface_type);
565  SF::init_vector(&dbg_both, emi_surfmesh_w_counter_face, dpn, alg_surface_type);
566  SF::init_vector(&dbg_back, emi_surfmesh_unique_face, dpn, alg_surface_type);
567  dbg_unique->set(1.0);
568  operator_unique_to_both_faces->mult(*dbg_unique, *dbg_both);
569  operator_both_to_unique_face->mult(*dbg_both, *dbg_back);
570 
571  SF_real local_max_err = 0.0;
572  mesh_int_t local_half_count = 0;
573  SF_real* p = dbg_back->ptr();
574  for (mesh_int_t i = 0; i < dbg_back->lsize(); i++) {
575  SF_real err = std::abs(p[i] - 1.0);
576  if (err > local_max_err) local_max_err = err;
577  if (std::abs(p[i] - 0.5) < 1e-12) local_half_count++;
578  }
579  dbg_back->release_ptr(p);
580  SF_real global_max_err = 0.0;
581  mesh_int_t global_half_count = 0;
582  MPI_Allreduce(&local_max_err, &global_max_err, 1, opencarp::mpi_datatype<SF_real>(),
583  MPI_MAX, emi_surfmesh_w_counter_face.comm);
584  global_half_count = local_half_count;
585  MPI_Allreduce(MPI_IN_PLACE, &global_half_count, 1, mesh_mpi_t, MPI_SUM,
586  emi_surfmesh_w_counter_face.comm);
587  if (dbg_rank == 0) {
588  log_msg(NULL, 0, 0, "DEBUG map_unique_to_both consistency: max_err=%.6e half_count=%jd",
589  (double)global_max_err, opencarp::printable_int(global_half_count));
590  }
591 
592  {
593  const int max_print = 5;
594  const int fields = 9;
595  std::vector<int> local_buf(max_print * fields, -1);
596  int filled = 0;
597 
598  for (const auto& [local_unique_idx, both_pair] : map_elem_uniqueFace_to_elem_bothface) {
599  if (filled >= max_print) break;
600  const auto& first_both = both_pair.first;
601  const auto& second_both = both_pair.second;
602 
603  const bool first_valid = (first_both.index >= 0 && first_both.rank >= 0);
604  const bool second_valid = (second_both.index >= 0 && second_both.rank >= 0);
605  int count = (first_valid ? 1 : 0) + (second_valid ? 1 : 0);
606  if (count != 1) continue;
607 
608  mesh_int_t global_unique = layout_unique_dbg[dbg_rank] + local_unique_idx;
609  mesh_int_t global_first = first_valid ? (layout_both_dbg[first_both.rank] + first_both.index) : -1;
610  mesh_int_t global_second = second_valid ? (layout_both_dbg[second_both.rank] + second_both.index) : -1;
611 
612  int base = filled * fields;
613  local_buf[base + 0] = dbg_rank;
614  local_buf[base + 1] = (int)local_unique_idx;
615  local_buf[base + 2] = (int)global_unique;
616  local_buf[base + 3] = (int)first_both.rank;
617  local_buf[base + 4] = (int)first_both.index;
618  local_buf[base + 5] = (int)global_first;
619  local_buf[base + 6] = (int)second_both.rank;
620  local_buf[base + 7] = (int)second_both.index;
621  local_buf[base + 8] = (int)global_second;
622  filled++;
623  }
624 
625  int comm_size = 0;
626  MPI_Comm_size(emi_surfmesh_w_counter_face.comm, &comm_size);
627  std::vector<int> all_buf;
628  if (dbg_rank == 0) all_buf.resize(comm_size * max_print * fields, -1);
629 
630  MPI_Gather(local_buf.data(), max_print * fields, MPI_INT,
631  dbg_rank == 0 ? all_buf.data() : nullptr, max_print * fields, MPI_INT,
632  0, emi_surfmesh_w_counter_face.comm);
633 
634  if (dbg_rank == 0) {
635  for (int r = 0; r < comm_size; r++) {
636  for (int i = 0; i < max_print; i++) {
637  int base = (r * max_print + i) * fields;
638  if (all_buf[base + 1] < 0) continue;
639  log_msg(NULL, 0, 0,
640  "DEBUG map_unique_to_both bad: rank=%d local_unique=%d global_unique=%d "
641  "first(rank=%d idx=%d glob=%d) second(rank=%d idx=%d glob=%d)",
642  all_buf[base + 0], all_buf[base + 1], all_buf[base + 2],
643  all_buf[base + 3], all_buf[base + 4], all_buf[base + 5],
644  all_buf[base + 6], all_buf[base + 7], all_buf[base + 8]);
645  }
646  }
647  }
648  }
649 
650  delete dbg_unique;
651  delete dbg_both;
652  delete dbg_back;
653  }
654 #endif
655 }
656 
679 template<class tuple_key, class tuple_value, class tri_key, class tri_value, class quad_key, class quad_value, class T, class S>
680 inline void assemble_restrict_operator( abstract_matrix<T,S> & B,
681  abstract_matrix<T,S> & Bi,
682  abstract_matrix<T,S> & BsM,
683  const SF::vector<mesh_int_t> & elemTag_surface_mesh,
684  const hashmap::unordered_map<std::pair<mesh_int_t,mesh_int_t>,
685  std::pair<mesh_int_t,mesh_int_t>> & map_vertex_tag_to_dof_petsc,
686  const hashmap::unordered_map<tuple_key,
687  std::pair<tuple_value, tuple_value>> & line_face,
688  const hashmap::unordered_map<tri_key,
689  std::pair<tri_value, tri_value>> & tri_face,
690  const hashmap::unordered_map<quad_key,
691  std::pair<quad_value, quad_value>> & quad_face,
692  const meshdata<mesh_int_t,mesh_real_t> & surface_mesh,
693  const meshdata<mesh_int_t,mesh_real_t> & emi_mesh,
694  S mass_scale)
695 {
696  // we want to make sure that the element integrator fits the matrix
697  T row_dpn = 1; T col_dpn = 1;
698 
699  const SF::vector<mesh_int_t> & rnod_emi = emi_mesh.get_numbering(SF::NBR_REF);
701  g2l_emi.reserve(rnod_emi.size());
702  for(size_t i=0; i<rnod_emi.size(); i++){
703  g2l_emi[rnod_emi[i]] = i;
704  }
705 
706 
707  const SF::vector<mesh_int_t> & rnod = surface_mesh.get_numbering(SF::NBR_REF);
708 
709  auto find_petsc_index = [&](const std::pair<mesh_int_t,mesh_int_t>& key) -> const std::pair<mesh_int_t,mesh_int_t>& {
710  auto it = map_vertex_tag_to_dof_petsc.find(key);
711  if (it == map_vertex_tag_to_dof_petsc.end()) {
712  std::cerr << "ERROR: PETSc index not found for vertex=" << key.first
713  << " tag=" << key.second << std::endl;
714  throw std::runtime_error("PETSc index not found for vertex/tag pair");
715  }
716  return it->second;
717  };
718 
719  // allocate row / col index buffers
720  vector<SF_int> row_idx(SF_MAX_ELEM_NODES * row_dpn), row_idx_counter(SF_MAX_ELEM_NODES * row_dpn), col_idx(SF_MAX_ELEM_NODES * col_dpn), col_idx_counter(SF_MAX_ELEM_NODES * col_dpn);
721 
722  const SF::vector<mesh_int_t> & emi_surfmesh_elem = surface_mesh.get_numbering(SF::NBR_ELEM_REF);
723 
724  // allocate elem index buffer
725  dmat<SF_real> ebuff(SF_MAX_ELEM_NODES * row_dpn, SF_MAX_ELEM_NODES * col_dpn);
726  dmat<SF_real> ebuff_s(SF_MAX_ELEM_NODES * row_dpn, SF_MAX_ELEM_NODES * col_dpn);
727  dmat<SF_real> ebuff_counter(SF_MAX_ELEM_NODES * row_dpn, SF_MAX_ELEM_NODES * col_dpn);
728 
729  std::vector<mesh_int_t> elem_nodes;
730  std::vector<mesh_int_t> elem_nodes_old;
731  std::vector<mesh_int_t> petsc_first;
732  std::vector<mesh_int_t> petsc_second;
733  vector<SF::Point> face_coordinates;
734  elem_nodes.reserve(SF_MAX_ELEM_NODES);
735  elem_nodes_old.reserve(SF_MAX_ELEM_NODES);
736  petsc_first.reserve(SF_MAX_ELEM_NODES);
737  petsc_second.reserve(SF_MAX_ELEM_NODES);
738  face_coordinates.reserve(SF_MAX_ELEM_NODES);
739 
740  // start with assembly
741  for(size_t eidx=0; eidx < surface_mesh.l_numelem; eidx++)
742  {
743  T tag = surface_mesh.tag[eidx];
744 
745  // get the counter part
746  elem_nodes.clear();
747  elem_nodes_old.clear();
748  petsc_first.clear();
749  petsc_second.clear();
750 
751  T tag_first = 0;
752  T tag_second = 0;
753 
754  T mem_first = 0;
755  T mem_second = 0;
756 
757  T sign = 1.0;
758 
759  for (int n = surface_mesh.dsp[eidx]; n < surface_mesh.dsp[eidx+1];n++)
760  {
761  T l_idx = surface_mesh.con[n];
762 
763  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
764  Index_tag_old = std::make_pair(rnod[l_idx],tag);
765  mesh_int_t Index_new = find_petsc_index(Index_tag_old).first;
766  elem_nodes.push_back(Index_new);
767  elem_nodes_old.push_back(rnod[l_idx]);
768  }
769 
770  std::sort(elem_nodes.begin(),elem_nodes.end());
771  if(elem_nodes.size()==2){
772  tuple_key key;
773 
774  key.v1 = elem_nodes[0];
775  key.v2 = elem_nodes[1];
776  auto it = line_face.find(key);
777  if (it == line_face.end()) throw std::runtime_error("Line interface face not found");
778  const std::pair<tuple_value, tuple_value> & value = it->second;
779 
780  tag_first = value.first.tag;
781  tag_second = value.second.tag;
782 
783  mem_first = value.first.mem;
784  mem_second = value.second.mem;
785 
786  }
787  else if(elem_nodes.size()==3){
788  tri_key key;
789  key.v1 = elem_nodes[0];
790  key.v2 = elem_nodes[1];
791  key.v3 = elem_nodes[2];
792  auto it = tri_face.find(key);
793  if (it == tri_face.end()) throw std::runtime_error("Triangular interface face not found");
794  const std::pair<tri_value, tri_value> & value = it->second;
795 
796  tag_first = value.first.tag;
797  tag_second = value.second.tag;
798 
799  mem_first = value.first.mem;
800  mem_second = value.second.mem;
801 
802  }
803  else if(elem_nodes.size()==4){
804  quad_key key;
805  key.v1 = elem_nodes[0];
806  key.v2 = elem_nodes[1];
807  key.v3 = elem_nodes[2];
808  key.v4 = elem_nodes[3];
809  auto it = quad_face.find(key);
810  if (it == quad_face.end()) throw std::runtime_error("Quadrilateral interface face not found");
811  const std::pair<quad_value, quad_value> & value = it->second;
812 
813  tag_first = value.first.tag;
814  tag_second = value.second.tag;
815 
816  mem_first = value.first.mem;
817  mem_second = value.second.mem;
818 
819  }
820 
821  if(tag != tag_first) {
822  std::swap(tag_first, tag_second);
823  std::swap(mem_first, mem_second);
824  }
825 
826  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
827 
828  for (size_t indx = 0; indx < elem_nodes_old.size(); ++indx)
829  {
830  std::pair <mesh_int_t,mesh_int_t> Index_tag_old_first;
831  Index_tag_old_first = std::make_pair(elem_nodes_old[indx],tag_first);
832 
833  auto it_first = map_vertex_tag_to_dof_petsc.find(Index_tag_old_first);
834  if (it_first == map_vertex_tag_to_dof_petsc.end()) {
835  std::cerr << "ERROR: tag_first=" << tag_first << " not found for vertex=" << elem_nodes_old[indx] << std::endl;
836  throw std::runtime_error("PETSc index not found for first tag");
837  }
838  std::pair <mesh_int_t,mesh_int_t> newIndex_petsc_first = it_first->second;
839  mesh_int_t newIndex_first = newIndex_petsc_first.first;
840  petsc_first.push_back(newIndex_petsc_first.second);
841 
842  std::pair <mesh_int_t,mesh_int_t> Index_tag_old_second;
843  Index_tag_old_second = std::make_pair(elem_nodes_old[indx],tag_second);
844 
845  auto it_second = map_vertex_tag_to_dof_petsc.find(Index_tag_old_second);
846  if (it_second == map_vertex_tag_to_dof_petsc.end()) {
847  std::cerr << "ERROR: tag_second=" << tag_second << " not found for vertex=" << elem_nodes_old[indx] << std::endl;
848  throw std::runtime_error("PETSc index not found for counter-face tag");
849  }
850  std::pair <mesh_int_t,mesh_int_t> newIndex_petsc_second = it_second->second;
851  mesh_int_t newIndex_second = newIndex_petsc_second.first;
852  petsc_second.push_back(newIndex_petsc_second.second);
853 
854  // elemTag_surface_mesh[eidx] == 1 means that the tag belongs to the extracellular region
855  if(mem_first==1 and elemTag_surface_mesh[eidx]==1) // condition for the membrane which the sign is always negative on extracellular side
856  sign = -1.0;
857  else if (mem_first==2 and tag<tag_second) // condition for the gap juntion which the sign is always negative for smaller tag
858  sign = -1.0;
859  }
860 
861  SF_int nnodes = elem_nodes.size();
862  face_coordinates.resize(nnodes);
863  // calculate row/col indices of entries
864  row_idx.resize(nnodes*row_dpn);
865  row_idx_counter.resize(nnodes*row_dpn);
866  col_idx.resize(nnodes*col_dpn);
867  col_idx_counter.resize(nnodes*col_dpn);
868 
869  {
870  for(SF_int i=0; i<nnodes; i++){
871  // all the row sets with element index
872  for(short j=0; j<row_dpn; j++){
873  row_idx.data()[i*row_dpn + j] = emi_surfmesh_elem[eidx]*row_dpn;
874  }
875 
876  for(short j=0; j<col_dpn; j++){
877  col_idx[i*col_dpn + j] = (petsc_first[i])*col_dpn + j;
878  col_idx_counter[i*col_dpn + j] = (petsc_second[i])*col_dpn + j;
879  }
880  }
881  }
882 
883  // assign ebuff and ebuff_s
884  ebuff.assign(nnodes, nnodes, 0.0);
885  ebuff_s.assign(nnodes, nnodes, 0.0);
886 
887  ebuff_counter.assign(nnodes, nnodes, 0.0);
888 
889  for (int n = surface_mesh.dsp[eidx], i = 0; n < surface_mesh.dsp[eidx+1];n++,i++)
890  {
891  T l_idx = surface_mesh.con[n];
892 
893  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
894  Index_tag_old = std::make_pair(rnod[l_idx],tag);
895  mesh_int_t Index_new = find_petsc_index(Index_tag_old).first;
896 
897  double x = emi_mesh.xyz[g2l_emi[Index_new]*3+0];
898  double y = emi_mesh.xyz[g2l_emi[Index_new]*3+1];
899  double z = emi_mesh.xyz[g2l_emi[Index_new]*3+2];
900 
901  face_coordinates[i].x = x;
902  face_coordinates[i].y = y;
903  face_coordinates[i].z = z;
904  }
905  compute_integrate_matrix_barycentric(face_coordinates, nnodes, ebuff, ebuff_s, ebuff_counter, mass_scale);
906 
907  // add values into system matrix
908  const bool add = true;
909 
910  // we simply build Bi in the same way as B, but without the sign change, so we end up with Bi*Iij_stim = Ib_stim = Ii + Ij
911  Bi.set_values(row_idx, col_idx, ebuff.data(), add);
912  Bi.set_values(row_idx, col_idx_counter, ebuff_counter.data(), add);
913  // the operator matrix in order to project potential of dofs to the barycenteric of each face as transmembrane (vm = ui - uj)
914  ebuff*=(sign);
915  B.set_values(row_idx, col_idx, ebuff.data(), add);
916  ebuff_counter*=(-sign);
917  B.set_values(row_idx, col_idx_counter, ebuff_counter.data(), add); // counter face
918 
919  // BsM is a volume-row / face-column scatter. Insert entries explicitly
920  // because row_idx contains the same face row repeated for B/Bi assembly.
921  ebuff_s*=(sign);
922  for(SF_int i = 0; i < nnodes; i++) {
923  BsM.set_value(col_idx[i], row_idx[0], ebuff_s[0][i], add);
924  }
925  }
926  // finish assembly and progress output
927  B.finish_assembly();
928  Bi.finish_assembly();
929  BsM.finish_assembly();
930 }
931 
952 template<class tuple_key, class tuple_value, class tri_key, class tri_value, class quad_key, class quad_value, class T, class S>
953 inline void assemble_lhs_emi(abstract_matrix<T,S> & mat,
954  abstract_matrix<T,S> & mat_surf,
955  const meshdata<mesh_int_t,mesh_real_t> & emi_mesh,
956  const meshdata<mesh_int_t,mesh_real_t> & surface_mesh,
957  const 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,
958  const hashmap::unordered_map<tuple_key,
959  std::pair<tuple_value, tuple_value>> & line_face,
960  const hashmap::unordered_map<tri_key,
961  std::pair<tri_value, tri_value>> & tri_face,
962  const hashmap::unordered_map<quad_key,
963  std::pair<quad_value, quad_value>> & quad_face,
964  matrix_integrator<mesh_int_t,mesh_real_t> & stiffness_integrator,
965  matrix_integrator<mesh_int_t, mesh_real_t> & mass_integrator,
966  S stiffness_scale,
967  S mass_scale)
968 {
969  // we want to make sure that the element integrator fits the matrix
970  T row_dpn = 1; T col_dpn = 1;
971 
972  // allocate row / col index buffers
973  vector<SF_int> idx(SF_MAX_ELEM_NODES * col_dpn), idx_counter(SF_MAX_ELEM_NODES * col_dpn);
974  vector<T> row_idx(SF_MAX_ELEM_NODES * row_dpn), col_idx(SF_MAX_ELEM_NODES * col_dpn);
975 
976  // allocate elem index buffer
977  dmat<SF_real> ebuff(SF_MAX_ELEM_NODES * col_dpn, SF_MAX_ELEM_NODES * col_dpn);
978  dmat<SF_real> ebuff_mass(SF_MAX_ELEM_NODES * col_dpn, SF_MAX_ELEM_NODES * col_dpn);
979  // Assemble stiffness matrix (K)
980  const vector<mesh_int_t> & stiffness_petsc_nbr = emi_mesh.get_numbering(NBR_PETSC);
981  element_view<mesh_int_t, mesh_real_t> stiffness_view(emi_mesh, NBR_PETSC);
982 
983  // Assembly of global stiffness matrix into lhs matrix
984  for(size_t eidx=0; eidx < emi_mesh.l_numelem; eidx++)
985  {
986  // set element view to current element
987  stiffness_view.set_elem(eidx);
988 
989  // calculate row/col indices of entries
990  mesh_int_t nnodes = stiffness_view.num_nodes();
991 
992  row_idx.resize(nnodes*row_dpn);
993  col_idx.resize(nnodes*col_dpn);
994  canonic_indices<mesh_int_t,SF_int>(stiffness_view.nodes(), stiffness_petsc_nbr.data(), nnodes, row_dpn, row_idx.data());
995  canonic_indices<mesh_int_t,SF_int>(stiffness_view.nodes(), stiffness_petsc_nbr.data(), nnodes, col_dpn, col_idx.data());
996 
997  // call integrator
998  stiffness_integrator(stiffness_view, ebuff);
999  ebuff *= stiffness_scale;
1000 
1001  // add values into system matrix
1002  const bool add = true;
1003  mat.set_values(row_idx, col_idx, ebuff.data(), add);
1004  }
1005 
1006  // Assemble the global surface mass matrix (M).
1007  // Each local mass matrix computed for a surface element is mapped into the global system
1008  // using the corresponding global row and column indices of that element’s nodes.
1009 
1010  const SF::vector<mesh_int_t> & rnod = surface_mesh.get_numbering(SF::NBR_REF);
1011 
1012  auto find_petsc_index = [&](const std::pair<mesh_int_t,mesh_int_t>& key) -> const std::pair<mesh_int_t,mesh_int_t>& {
1013  auto it = map_vertex_tag_to_dof_petsc.find(key);
1014  if (it == map_vertex_tag_to_dof_petsc.end()) {
1015  std::cerr << "ERROR: PETSc index not found for vertex=" << key.first
1016  << " tag=" << key.second << std::endl;
1017  throw std::runtime_error("PETSc index not found for vertex/tag pair");
1018  }
1019  return it->second;
1020  };
1021 
1022  // start with assembly mass matrix into lhs matrix
1023  element_view<mesh_int_t, mesh_real_t> view(surface_mesh, NBR_PETSC);
1024  for(size_t eidx=0; eidx < surface_mesh.l_numelem; eidx++)
1025  {
1026  // set element view to current element
1027  view.set_elem(eidx);
1028  mesh_int_t tag = surface_mesh.tag[eidx]; // selected face
1029 
1030  // get the counter part
1031  std::vector<mesh_int_t> elem_nodes;
1032  std::vector<mesh_int_t> elem_nodes_old;
1033  std::vector<mesh_int_t> elem_nodes_first;
1034  std::vector<mesh_int_t> elem_nodes_second;
1035 
1036  std::vector<mesh_int_t> petsc_first;
1037  std::vector<mesh_int_t> petsc_second;
1038 
1039  T tag_first = 0;
1040  T tag_second = 0;
1041 
1042  for (int n = surface_mesh.dsp[eidx]; n < surface_mesh.dsp[eidx+1];n++)
1043  {
1044  T l_idx = surface_mesh.con[n];
1045 
1046  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
1047  Index_tag_old = std::make_pair(rnod[l_idx],tag);
1048  mesh_int_t Index_new = find_petsc_index(Index_tag_old).first;
1049  elem_nodes.push_back(Index_new);
1050  elem_nodes_old.push_back(rnod[l_idx]);
1051  }
1052 
1053  std::sort(elem_nodes.begin(),elem_nodes.end());
1054  if(elem_nodes.size()==2){
1055  tuple_key key;
1056 
1057  key.v1 = elem_nodes[0];
1058  key.v2 = elem_nodes[1];
1059  auto it = line_face.find(key);
1060  if (it == line_face.end()) throw std::runtime_error("Line interface face not found");
1061  const std::pair<tuple_value, tuple_value> & value = it->second;
1062 
1063  tag_first = value.first.tag;
1064  tag_second = value.second.tag;
1065  }
1066  else if(elem_nodes.size()==3){
1067  tri_key key;
1068  key.v1 = elem_nodes[0];
1069  key.v2 = elem_nodes[1];
1070  key.v3 = elem_nodes[2];
1071  auto it = tri_face.find(key);
1072  if (it == tri_face.end()) throw std::runtime_error("Triangular interface face not found");
1073  const std::pair<tri_value, tri_value> & value = it->second;
1074 
1075  tag_first = value.first.tag;
1076  tag_second = value.second.tag;
1077  }
1078  else if(elem_nodes.size()==4){
1079  quad_key key;
1080  key.v1 = elem_nodes[0];
1081  key.v2 = elem_nodes[1];
1082  key.v3 = elem_nodes[2];
1083  key.v4 = elem_nodes[3];
1084  auto it = quad_face.find(key);
1085  if (it == quad_face.end()) throw std::runtime_error("Quadrilateral interface face not found");
1086  const std::pair<quad_value, quad_value> & value = it->second;
1087 
1088  tag_first = value.first.tag;
1089  tag_second = value.second.tag;
1090  }
1091 
1092  if(tag != tag_first) std::swap(tag_first, tag_second);
1093 
1094  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
1095 
1096  for (size_t indx = 0; indx < elem_nodes_old.size(); ++indx)
1097  {
1098  std::pair <mesh_int_t,mesh_int_t> Index_tag_old_first;
1099  Index_tag_old_first = std::make_pair(elem_nodes_old[indx],tag_first);
1100  std::pair <mesh_int_t,mesh_int_t> newIndex_petsc_first = find_petsc_index(Index_tag_old_first);
1101  mesh_int_t newIndex_first = newIndex_petsc_first.first;
1102  petsc_first.push_back(newIndex_petsc_first.second);
1103 
1104  std::pair <mesh_int_t,mesh_int_t> Index_tag_old_second;
1105  Index_tag_old_second = std::make_pair(elem_nodes_old[indx],tag_second);
1106  std::pair <mesh_int_t,mesh_int_t> newIndex_petsc_second = find_petsc_index(Index_tag_old_second);
1107  mesh_int_t newIndex_second = newIndex_petsc_second.first;
1108  petsc_second.push_back(newIndex_petsc_second.second);
1109  }
1110 
1111  // calculate row/col indices of entries
1112  SF_int nnodes = view.num_nodes();
1113  idx.resize(nnodes*row_dpn);
1114  idx_counter.resize(nnodes*col_dpn);
1115 
1116  {
1117  for(T i=0; i<nnodes; i++)
1118  {
1119  for(short j=0; j<row_dpn; j++){
1120  idx[i*row_dpn + j] = (petsc_first[i])*row_dpn + j;
1121  }
1122  for(short j=0; j<col_dpn; j++){
1123  idx_counter[i*col_dpn + j] = (petsc_second[i])*col_dpn + j;
1124  }
1125  }
1126  }
1127 
1128  // add values into system matrix
1129  const bool add = true;
1130 
1131  // call integrator
1132  mass_integrator(view, ebuff);
1133  ebuff_mass.assign(nnodes, nnodes, 0.0);
1134  ebuff_mass = ebuff;
1135  // Note: surface mesh by default has both face and counter face;
1136  // to avoid the dublication, factor 0.5 is necessary
1137  ebuff*=0.5 * mass_scale;
1138  ebuff_mass*=0.5;
1139 
1140  mat.set_values(idx, idx, ebuff.data(), add);
1141  mat.set_values(idx_counter, idx_counter, ebuff.data(), add);
1142 
1143  mat_surf.set_values(idx, idx, ebuff_mass.data(), add);
1144  mat_surf.set_values(idx_counter, idx_counter, ebuff_mass.data(), add);
1145 
1146  ebuff*=(-1.0);
1147  ebuff_mass*=(-1.0);
1148 
1149  mat.set_values(idx, idx_counter, ebuff.data(), add);
1150  mat.set_values(idx_counter, idx, ebuff.data(), add);
1151 
1152  mat_surf.set_values(idx, idx_counter, ebuff_mass.data(), add);
1153  mat_surf.set_values(idx_counter, idx, ebuff_mass.data(), add);
1154 
1155  // Each face contributes the following 2x2 block structure:
1156  //
1157  // s* [ + M - M ;
1158  // - M + M ]
1159  //
1160  // with s = 0.5 * alpha,
1161  // where M is the local face mass matrix.
1162 
1163  }
1164  // finish assembly and progress output
1165  mat.finish_assembly();
1166  mat_surf.finish_assembly();
1167 }
1168 
1187 template<class tuple_key, class tuple_value, class tri_key, class tri_value, class quad_key, class quad_value, class T, class S>
1188 inline void assign_resting_potential_from_ionic_models_on_myocyte(abstract_vector<T,S> & ui,
1189  abstract_vector<T, S>* vb,
1190  const SF::vector<mesh_int_t> & elemTag_emi_mesh,
1191  const hashmap::unordered_map<std::pair<T,T>,
1192  std::pair<T,T>> & map_vertex_tag_to_dof_petsc,
1193  const hashmap::unordered_map<tuple_key,
1194  std::pair<tuple_value, tuple_value>> & line_face,
1195  const hashmap::unordered_map<tri_key,
1196  std::pair<tri_value, tri_value>> & tri_face,
1197  const hashmap::unordered_map<quad_key,
1198  std::pair<quad_value, quad_value>> & quad_face,
1199  const meshdata<T,mesh_real_t> & surface_mesh,
1200  const meshdata<T,mesh_real_t> & emi_mesh)
1201 {
1202  T row_dpn = 1;
1203 
1204  const SF::vector<T> & rnod = surface_mesh.get_numbering(SF::NBR_REF);
1205 
1207 
1208  auto find_petsc_index = [&](const std::pair<T,T>& key) -> const std::pair<T,T>& {
1209  auto it = map_vertex_tag_to_dof_petsc.find(key);
1210  if (it == map_vertex_tag_to_dof_petsc.end()) {
1211  std::cerr << "ERROR: PETSc index not found for vertex=" << key.first
1212  << " tag=" << key.second << std::endl;
1213  throw std::runtime_error("PETSc index not found for vertex/tag pair");
1214  }
1215  return it->second;
1216  };
1217 
1218  auto vb_data = vb->const_ptr();
1219 
1220  // start with assembly
1221  for(size_t eidx=0; eidx < surface_mesh.l_numelem; eidx++)
1222  {
1223  T tag = surface_mesh.tag[eidx];
1224 
1225  // get the counter part
1226  std::vector<mesh_int_t> elem_nodes;
1227  T tag_first = 0;
1228  T tag_second = 0;
1229  T mem_first = 0;
1230  T mem_second = 0;
1231 
1232  for (int n = surface_mesh.dsp[eidx], i = 0; n < surface_mesh.dsp[eidx+1];n++,i++)
1233  {
1234  T l_idx = surface_mesh.con[n];
1235  std::pair <T,T> Index_tag_old;
1236  Index_tag_old = std::make_pair(rnod[l_idx],tag);
1237  mesh_int_t Index_new = find_petsc_index(Index_tag_old).first;
1238  elem_nodes.push_back(Index_new);
1239  }
1240 
1241  std::sort(elem_nodes.begin(),elem_nodes.end());
1242  if(elem_nodes.size()==2){
1243  tuple_key key;
1244 
1245  key.v1 = elem_nodes[0];
1246  key.v2 = elem_nodes[1];
1247  auto it = line_face.find(key);
1248  if (it == line_face.end()) throw std::runtime_error("Line interface face not found");
1249  const std::pair<tuple_value, tuple_value> & value = it->second;
1250 
1251  tag_first = value.first.tag;
1252  tag_second = value.second.tag;
1253 
1254  mem_first = value.first.mem;
1255  mem_second = value.second.mem;
1256  }
1257  else if(elem_nodes.size()==3){
1258  tri_key key;
1259  key.v1 = elem_nodes[0];
1260  key.v2 = elem_nodes[1];
1261  key.v3 = elem_nodes[2];
1262  auto it = tri_face.find(key);
1263  if (it == tri_face.end()) throw std::runtime_error("Triangular interface face not found");
1264  const std::pair<tri_value, tri_value> & value = it->second;
1265 
1266  tag_first = value.first.tag;
1267  tag_second = value.second.tag;
1268 
1269  mem_first = value.first.mem;
1270  mem_second = value.second.mem;
1271  }
1272  else if(elem_nodes.size()==4){
1273  quad_key key;
1274  key.v1 = elem_nodes[0];
1275  key.v2 = elem_nodes[1];
1276  key.v3 = elem_nodes[2];
1277  key.v4 = elem_nodes[3];
1278  auto it = quad_face.find(key);
1279  if (it == quad_face.end()) throw std::runtime_error("Quadrilateral interface face not found");
1280  const std::pair<quad_value, quad_value> & value = it->second;
1281 
1282  tag_first = value.first.tag;
1283  tag_second = value.second.tag;
1284 
1285  mem_first = value.first.mem;
1286  mem_second = value.second.mem;
1287  }
1288 
1289  SF_int nnodes = elem_nodes.size();
1290  for (int i = 0; i < nnodes; ++i)
1291  {
1292  if(mem_first==1 || mem_second==1){
1293  tag_2_vm[tag_first] = vb_data[eidx];
1294  tag_2_vm[tag_second] = vb_data[eidx];
1295  }
1296  }
1297  }
1298 
1299  vb->const_release_ptr(vb_data);
1300 
1301  // Assign resting potentials directly into the owned ui entries. This replaces
1302  // the old diagonal Vm_myocyte_emi matrix, whose diagonal was immediately
1303  // copied into ui and never used as a real operator.
1304  {
1305  vector<T> row_idx(SF_MAX_ELEM_NODES * row_dpn);
1306  const vector<mesh_int_t> & petsc_nbr = emi_mesh.get_numbering(NBR_PETSC);
1307  T start = 0, stop = 0;
1308  ui.get_ownership_range(start, stop);
1309  ui.set(0.0);
1310  S* ui_data = ui.ptr();
1311 
1312  element_view<mesh_int_t, mesh_real_t> view(emi_mesh, NBR_PETSC);
1313  for(size_t eidx=0; eidx < emi_mesh.l_numelem; eidx++)
1314  {
1315  view.set_elem(eidx);
1316  T tag = emi_mesh.tag[eidx];
1317  mesh_int_t nnodes = view.num_nodes();
1318 
1319  row_idx.resize(nnodes*row_dpn);
1320  canonic_indices<mesh_int_t,SF_int>(view.nodes(), petsc_nbr.data(), nnodes, row_dpn, row_idx.data());
1321 
1322  for (int i = 0; i < nnodes; ++i)
1323  {
1324  // elemTag_emi_mesh[eidx]==2 means that the tag belongs to the intracellular region
1325  if(elemTag_emi_mesh[eidx]==2){
1326  if(row_idx[i] >= start && row_idx[i] < stop) {
1327  auto vm_it = tag_2_vm.find(tag);
1328  if (vm_it != tag_2_vm.end()) ui_data[row_idx[i] - start] = vm_it->second;
1329  }
1330  }
1331  }
1332  }
1333  ui.release_ptr(ui_data);
1334  ui.finish_assembly();
1335  }
1336 }
1337 
1338 }
1339 
1340 #endif
1341 #endif
#define sign(x)
Definition: ION_IF.h:62
Basic containers.
opencarp::local_index_t mesh_int_t
Definition: SF_container.h:31
FEM utilities.
#define SF_MAX_ELEM_NODES
max #nodes defining an element
Definition: SF_fem_utils.h:25
opencarp::real_t SF_real
Global scalar type.
Definition: SF_globals.h:18
opencarp::global_index_t SF_int
Global algebraic index type.
Definition: SF_globals.h:17
virtual void mult(const abstract_vector< T, S > &x, abstract_vector< T, S > &b) const =0
virtual void finish_assembly()=0
virtual void zero()=0
virtual void finalize_exact_preallocation()
virtual bool begin_exact_preallocation()
virtual void init(T iNRows, T iNCols, T ilrows, T ilcols, T loc_offset, T mxent)
virtual void set_values(const vector< T > &row_idx, const vector< T > &col_idx, const vector< S > &vals, bool add)=0
virtual S * ptr()=0
virtual void release_ptr(S *&p)=0
virtual T lsize() const =0
virtual void set(const vector< T > &idx, const vector< S > &vals, const bool additive=false, const bool local=false)=0
Dense matrix class.
Definition: dense_mat.hpp:28
size_t l_numelem
local number of elements
Definition: SF_container.h:384
size_t g_numelem
global number of elements
Definition: SF_container.h:383
MPI_Comm comm
the parallel mesh is defined on a MPI world
Definition: SF_container.h:389
size_t size() const
The current size of the vector.
Definition: SF_vector.h:89
void assign(InputIterator s, InputIterator e)
Assign a memory range.
Definition: SF_vector.h:146
T * data()
Pointer to the vector's start.
Definition: SF_vector.h:76
iterator find(const K &key)
Search for key. Return iterator.
Definition: hashmap.hpp:626
void reserve(size_t n)
Definition: hashmap.hpp:719
#define log_msg(F, L, O,...)
Definition: filament.h:8
Definition: dense_mat.hpp:19
double mag(const Point &vect)
vector magnitude
Definition: SF_container.h:96
double inner_prod(const Point &a, const Point &b)
Definition: SF_container.h:75
void count(const vector< T > &data, vector< S > &cnt)
Count number of occurrences of indices.
Definition: SF_vector.h:317
void init_vector(SF::abstract_vector< T, S > **vec)
Definition: SF_init.h:110
void init_matrix(SF::abstract_matrix< T, S > **mat)
Definition: SF_init.h:211
Point cross(const Point &a, const Point &b)
cross product
Definition: SF_container.h:69
V clamp(const V val, const W start, const W end)
Clamp a value into an interval [start, end].
Definition: SF_base.h:48
@ NBR_PETSC
PETSc numbering of nodes.
Definition: SF_container.h:188
@ NBR_ELEM_REF
The element numbering of the reference mesh (the one stored on HD).
Definition: SF_container.h:189
@ NBR_REF
The nodal numbering of the reference mesh (the one stored on HD).
Definition: SF_container.h:186
constexpr T max(T a, T b)
Definition: ion_type.h:16
std::intmax_t printable_int(T value)
Definition: mpi_utils.h:115
vec3< POINT_REAL > Point
Definition: vect.h:78
Point and vector struct.
Definition: SF_container.h:50
double y
Definition: SF_container.h:52
double z
Definition: SF_container.h:53
double x
Definition: SF_container.h:51