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