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