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