openCARP
Doxygen code documentation for the open cardiac electrophysiology simulator openCARP
SF_mesh_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 
26 #if WITH_EMI_MODEL
27 
28 #ifndef _SF_MESH_UTILS_EMI_H
29 #define _SF_MESH_UTILS_EMI_H
30 
31 #include <algorithm>
32 #include <cstdio>
33 #include <cstdlib>
34 
35 #include "SF_mesh_utils.h"
36 #include "SF_mesh_io_emi.h"
37 
38 namespace SF {
39 
40 
41 const int MAX_INTERSECTIONS = 20;
42 // Adaptive merge-rank settings for EMI mesh merging.
43 // For communicator sizes up to the node-aware cap, we preserve the original
44 // all-ranks behavior. Once size > merge_rank_cap, we reduce the number of
45 // merge ranks based on the estimated global communication volume, bounded by
46 // that cap, and spread those merge ranks across the communicator. The lower
47 // bound avoids concentrating all merge work on too few ranks. These values are
48 // heuristics and may need retuning on different machines.
49 const int EMI_MIN_MERGE_RANKS = 8;
50 const int EMI_MAX_MERGE_RANKS_PER_NODE = 2;
51 const unsigned long long EMI_TARGET_BYTES_PER_MERGE_RANK = 8ull * 1024ull * 1024ull;
52 
64 inline int emi_node_aware_merge_rank_cap(MPI_Comm comm)
65 {
66  int size = 0;
67  MPI_Comm_size(comm, &size);
68 
69  // Split the communicator into shared-memory subcommunicators, one per node.
70  MPI_Comm node_comm = MPI_COMM_NULL;
71  MPI_Comm_split_type(comm, MPI_COMM_TYPE_SHARED, 0, MPI_INFO_NULL, &node_comm);
72 
73  // Rank 0 in each node subcommunicator acts as the node representative.
74  int node_rank = 0;
75  MPI_Comm_rank(node_comm, &node_rank);
76 
77  int local_leader = (node_rank == 0) ? 1 : 0;
78  int num_nodes = 0;
79  // Count how many node representatives exist globally, which gives the number of nodes.
80  MPI_Allreduce(&local_leader, &num_nodes, 1, MPI_INT, MPI_SUM, comm);
81 
82  MPI_Comm_free(&node_comm);
83 
84  // Allow only a small number of merge ranks per node, bounded by the communicator size.
85  return std::min(size, std::max(EMI_MIN_MERGE_RANKS, num_nodes * EMI_MAX_MERGE_RANKS_PER_NODE));
86 }
87 
106 template<class K, class TokenFn>
107 inline int emi_select_merge_destinations(const vector<K>& key_vec,
108  size_t dsize,
109  size_t bytes_per_entry,
110  vector<int>& dest,
111  MPI_Comm comm,
112  const char* label,
113  TokenFn token_fn)
114 {
115  int size = 0, rank = 0;
116  MPI_Comm_size(comm, &size);
117  MPI_Comm_rank(comm, &rank);
118 
119  // Estimate how many bytes this helper exchanges locally and globally.
120  const unsigned long long local_bytes =
121  static_cast<unsigned long long>(dsize) * static_cast<unsigned long long>(bytes_per_entry);
122  unsigned long long global_bytes = 0;
123  MPI_Allreduce(&local_bytes, &global_bytes, 1, MPI_UNSIGNED_LONG_LONG, MPI_SUM, comm);
124 
125  // Compute the node-aware upper bound on how many merge ranks we allow.
126  const int merge_rank_cap = emi_node_aware_merge_rank_cap(comm);
127  int nmerge = size;
128  if (size > merge_rank_cap) {
129  // For large runs, estimate how many merge ranks are needed from the total data volume.
130  const int nmerge_floor = EMI_MIN_MERGE_RANKS;
131  const int nmerge_cap = merge_rank_cap;
132  const unsigned long long nmerge_est_ull =
133  std::max(1ull, (global_bytes + EMI_TARGET_BYTES_PER_MERGE_RANK - 1ull) /
134  EMI_TARGET_BYTES_PER_MERGE_RANK);
135  const int nmerge_est = static_cast<int>(std::min<unsigned long long>(nmerge_cap, nmerge_est_ull));
136  // Clamp the estimate to the allowed range for this communicator.
137  nmerge = std::max(nmerge_floor, nmerge_est);
138  }
139 
140  dest.resize(dsize);
141  for (size_t i = 0; i < dsize; ++i) {
142  if (nmerge == size) {
143  // Preserve the original all-ranks merge behavior for small and medium runs.
144  dest[i] = static_cast<int>(token_fn(key_vec[i]) % static_cast<size_t>(size));
145  } else {
146  // First pick one logical merge bucket in [0, nmerge).
147  const size_t bucket = token_fn(key_vec[i]) % static_cast<size_t>(nmerge);
148  // Then spread those buckets over the full communicator instead of packing them
149  // into ranks [0, nmerge), which reduces concentration on low ranks.
150  dest[i] = static_cast<int>((bucket * static_cast<size_t>(size)) / static_cast<size_t>(nmerge));
151  }
152  }
153 
154  if (rank == 0 && std::getenv("OPENCARP_EMI_LOG_MERGE_RANKS") != NULL) {
155  const char* mode = (nmerge == size) ? "all-ranks" : "limited";
156  std::fprintf(stdout,
157  "[EMI merge ranks] %s: size=%d dsize=%zu global_bytes=%llu cap=%d nmerge=%d mode=%s\n",
158  label, size, dsize, global_bytes, merge_rank_cap, nmerge, mode);
159  std::fflush(stdout);
160  }
161 
162  return nmerge;
163 }
164 
173 struct intersection_tags {
181  int tags[MAX_INTERSECTIONS];
182  intersection_tags() {
183  std::fill_n(tags, MAX_INTERSECTIONS, -1);
184  }
185 
186 };
187 
188 struct intersection_data {
189  int tags[MAX_INTERSECTIONS];
190  int data[MAX_INTERSECTIONS];
191  int ranks[MAX_INTERSECTIONS];
192 
193  intersection_data() {
194  std::fill_n(tags, MAX_INTERSECTIONS, -1);
195  std::fill_n(data, MAX_INTERSECTIONS, -1);
196  std::fill_n(ranks, MAX_INTERSECTIONS, -1);
197  }
198 };
199 
203 struct intersection_indices {
204  mesh_int_t indices[MAX_INTERSECTIONS];
205  intersection_indices() {
206  std::fill_n(indices, MAX_INTERSECTIONS, -1);
207  }
208 };
221 template<class K, class V> inline
222 void assign_counter_face(hashmap::unordered_map<K, V> & map, const MPI_Comm comm)
223 {
224  size_t dsize = map.size();
225  vector<K> key_vec (dsize);
226  vector<V> value_vec (dsize);
227 
228  // make key and value vector of elements without counterparts
229  // which are from different ranks
230  size_t idx=0;
231  for(const auto & v : map) {
232  if (v.second.second.eidx == -1){
233  key_vec[idx] = v.first;
234  value_vec[idx] = v.second;
235  idx++;
236  }
237  }
238  dsize = idx;
239  key_vec.resize(dsize);
240  value_vec.resize(dsize);
241 
242  vector<int> perm, dest;
243  emi_select_merge_destinations(key_vec, dsize, sizeof(K) + sizeof(V), dest, comm,
244  "assign_counter_face",
245  [](const K& key) { return hashmap::hash_ops<K>::hash(key); });
246 
247  commgraph<size_t> grph;
248  grph.configure(dest, comm);
249  size_t nrecv = sum(grph.rcnt);
250 
251  interval(perm, 0, dsize);
252  binary_sort_copy(dest, perm);
253 
254  // fill send buffer and communicate
255  vector<K> sbuff_key(dsize), rbuff_key(nrecv);
256  vector<V> sbuff_value(dsize), ibuff_value(dsize), rbuff_value(nrecv);
257  for(size_t i=0; i<dsize; i++) {
258  sbuff_key[i] = key_vec[perm[i]];
259  sbuff_value[i] = value_vec[perm[i]];
260  }
261  MPI_Exchange(grph, sbuff_key, rbuff_key, comm);
262  MPI_Exchange(grph, sbuff_value, rbuff_value, comm);
263 
264  // Merge values for identical keys on the receiving side.
265  // Each merged entry should contain both sides and preserve owner index_unique.
267 
268  // Merge all received entries for the same key.
269  for (size_t i = 0; i < nrecv; i++) {
270  auto it = rmap.find(rbuff_key[i]);
271  if(it != rmap.end()) {
272  // Add counter faces only if the tag numbers of the two faces are different.
273  // That is, the counter face must have a different tag number from the current face.
274  // However, in some special cases, both faces may belong to the extracellular domain,
275  // which are removed later.
276  if (it->second.first.tag != rbuff_value[i].first.tag) {
277  it->second.second = rbuff_value[i].first;
278  }
279  } else {
280  rmap.insert({rbuff_key[i], rbuff_value[i]});
281  }
282  }
283 
284  for (size_t i = 0; i < nrecv; i++ ) {
285  auto it = rmap.find(rbuff_key[i]);
286  if(it != rmap.end()) rbuff_value[i] = it->second;
287  }
288 
289  // Send merged values back to the originating ranks.
290  grph.transpose();
291  MPI_Exchange(grph, rbuff_value, ibuff_value, comm);
292 
293  for (size_t i = 0; i < dsize; i++ ) {
294  auto it = map.find(sbuff_key[i]);
295  if (it != map.end()) it->second = ibuff_value[i];
296  }
297 }
298 
311 template<class K, class V> inline
312 void assign_unique_first_face(hashmap::unordered_map<K, V> & map, const MPI_Comm comm)
313 {
314  // Exchange and merge interface-face pairs across ranks.
315  // Each key represents one physical interface; after this call, both sides
316  // (first/second) carry rank/index data, and the owner side has index_unique set.
317  //
318  // Steps:
319  // 1) Collect only partial entries (missing counter-face) that need MPI exchange.
320  // 2) Hash-partition these keys to a merge owner rank.
321  // 3) Exchange keys/values to owners and merge both sides for each key.
322  // 4) Send merged entries back so every rank has complete data for its keys.
323  size_t dsize = map.size();
324  vector<K> key_vec (dsize);
325  vector<V> value_vec (dsize);
326 
327  // Collect only entries without counterparts (cross-rank interfaces).
328  size_t idx=0;
329  for(const auto & v : map) {
330  if (v.second.second.index_unique == -1 && v.second.second.index_one == -1){
331  key_vec[idx] = v.first;
332  value_vec[idx] = v.second;
333  idx++;
334  }
335  }
336  dsize = idx;
337  key_vec.resize(dsize);
338  value_vec.resize(dsize);
339 
340  vector<int> perm, dest;
341  emi_select_merge_destinations(key_vec, dsize, sizeof(K) + sizeof(V), dest, comm,
342  "assign_unique_first_face",
343  [](const K& key) { return hashmap::hash_ops<K>::hash(key); });
344 
345  commgraph<size_t> grph;
346  grph.configure(dest, comm);
347  size_t nrecv = sum(grph.rcnt);
348 
349  interval(perm, 0, dsize);
350  binary_sort_copy(dest, perm);
351 
352  // Send keys and partial values to the owner rank for each key.
353  vector<K> sbuff_key(dsize), rbuff_key(nrecv);
354  vector<V> sbuff_value(dsize), ibuff_value(dsize), rbuff_value(nrecv);
355  for(size_t i=0; i<dsize; i++) {
356  sbuff_key[i] = key_vec[perm[i]];
357  sbuff_value[i] = value_vec[perm[i]];
358  }
359  MPI_Exchange(grph, sbuff_key, rbuff_key, comm);
360  MPI_Exchange(grph, sbuff_value, rbuff_value, comm);
361 
362  // rmap accumulates merged entries per key on the receiving rank.
363  // Multiple ranks may send the same key; we combine them into one pair.
365 
366  // Merge all received entries for the same key.
367  for (size_t i = 0; i < nrecv; i++) {
368  auto it = rmap.find(rbuff_key[i]);
369  if(it != rmap.end()) {
370  // Merge entries without losing existing owner info.
371  auto& existing = it->second;
372  using Face = std::decay_t<decltype(existing.first)>;
373 
374  auto update_slot = [&](Face& slot, const Face& src) {
375  // Merge strategy for one side:
376  // - index_unique is only set by the owner and must never be overwritten by a non-owner.
377  // - rank/index_one are filled lazily when missing.
378  if (src.index_unique >= 0) {
379  slot.index_unique = src.index_unique;
380  slot.rank = src.rank;
381  } else {
382  if (slot.rank < 0) slot.rank = src.rank;
383  }
384  if (slot.index_one < 0 && src.index_one >= 0) slot.index_one = src.index_one;
385  };
386 
387  auto same_face = [&](const Face& a, const Face& b) {
388  // Use (rank,index_one) to identify a side uniquely (index_one is local).
389  return (a.rank >= 0 && b.rank >= 0 &&
390  a.index_one >= 0 && b.index_one >= 0 &&
391  a.rank == b.rank && a.index_one == b.index_one);
392  };
393 
394  auto ingest = [&](const Face& incoming) {
395  // Ignore empty placeholders (no useful info).
396  if (incoming.index_one < 0 && incoming.index_unique < 0 && incoming.rank < 0) return;
397  // Try to match incoming with an existing side (same rank or same index_one).
398  if (same_face(existing.first, incoming) || existing.first.rank == incoming.rank) {
399  update_slot(existing.first, incoming);
400  } else if (same_face(existing.second, incoming) || existing.second.rank == incoming.rank) {
401  update_slot(existing.second, incoming);
402  } else if (existing.second.index_one == -1 && existing.second.index_unique == -1) {
403  // If the second slot is empty, place incoming there (new side).
404  existing.second = incoming;
405  } else if (existing.first.index_one == -1 && existing.first.index_unique == -1) {
406  // If the first slot is empty, place incoming there (new side).
407  existing.first = incoming;
408  } else {
409  // Both slots are filled. Only upgrade missing owner info.
410  // (Do not overwrite an existing owner assignment.)
411  if (existing.first.index_unique < 0 && incoming.index_unique >= 0) {
412  existing.first.index_unique = incoming.index_unique;
413  existing.first.rank = incoming.rank;
414  } else if (existing.second.index_unique < 0 && incoming.index_unique >= 0) {
415  existing.second.index_unique = incoming.index_unique;
416  existing.second.rank = incoming.rank;
417  }
418  }
419  };
420 
421  // Merge both sides from the received pair.
422  ingest(rbuff_value[i].first);
423  ingest(rbuff_value[i].second);
424  } else {
425  // First time we see this key on the receiving rank.
426  rmap.insert({rbuff_key[i], rbuff_value[i]});
427  }
428  }
429 
430  for (size_t i = 0; i < nrecv; i++ ) {
431  auto it = rmap.find(rbuff_key[i]);
432  if(it != rmap.end()) rbuff_value[i] = it->second;
433  }
434 
435  // sending the assigned data back to original rank
436  grph.transpose();
437  MPI_Exchange(grph, rbuff_value, ibuff_value, comm);
438 
439  for (size_t i = 0; i < dsize; i++ ) {
440  auto it = map.find(sbuff_key[i]);
441  if (it != map.end()) {
442  // Overwrite with merged entry from exchange (rmap already combined both sides)
443  it->second = ibuff_value[i];
444  } else {
445  // Insert new entry received from another rank
446  // This ensures non-owner ranks get the complete mapping data
447  map.insert({sbuff_key[i], ibuff_value[i]});
448  }
449  }
450 }
451 
462 template<class K> inline
463 void assign_counter_vertices_tuple(hashmap::unordered_map<K, intersection_data>& map, const MPI_Comm comm)
464 {
465  size_t dsize = map.size();
466  vector<K> key_vec(dsize);
467  vector<intersection_data> value_vec(dsize);
468 
469  size_t idx = 0;
470  for (const auto& v : map) {
471  key_vec[idx] = v.first;
472  value_vec[idx] = v.second;
473  idx++;
474  }
475 
476  vector<int> perm, dest;
477  emi_select_merge_destinations(key_vec, dsize, sizeof(K) + sizeof(intersection_data), dest, comm,
478  "assign_counter_vertices_tuple",
479  [](const K& key) { return hashmap::hash_ops<K>::hash(key); });
480 
481  commgraph<size_t> grph;
482  grph.configure(dest, comm);
483  size_t nrecv = sum(grph.rcnt);
484 
485  interval(perm, 0, dsize);
486  binary_sort_copy(dest, perm);
487 
488  // fill send buffer and communicate
489  vector<K> sbuff_key(dsize), rbuff_key(nrecv);
490  vector<intersection_data> sbuff_value(dsize), ibuff_value(dsize), rbuff_value(nrecv);
491  for (size_t i = 0; i < dsize; i++) {
492  sbuff_key[i] = key_vec[perm[i]];
493  sbuff_value[i] = value_vec[perm[i]];
494  }
495  MPI_Exchange(grph, sbuff_key, rbuff_key, comm);
496  MPI_Exchange(grph, sbuff_value, rbuff_value, comm);
497 
499  for (size_t i = 0; i < nrecv; i++) {
500  auto it = rmap.find(rbuff_key[i]);
501  if (it != rmap.end()) {
502  intersection_data& map_val = it->second;
503  intersection_data& r_val = rbuff_value[i];
504 
505  int first_index = -1;
506  for (int j = 0; j < MAX_INTERSECTIONS; ++j) {
507  if (map_val.tags[j] == -1 && map_val.ranks[j] == -1) {
508  first_index = j;
509  break;
510  }
511  }
512 
513  if (first_index == -1) continue; // This means all possible intersections have already been assigned.
514 
515  for (int j = 0; j < MAX_INTERSECTIONS; ++j) {
516  if (r_val.tags[j] == -1) continue;
517 
518  int rtag = r_val.tags[j];
519  int rdata = r_val.data[j];
520  int rrank = r_val.ranks[j];
521 
522  bool exist_rtag = false;
523  for (int k = 0; k < first_index; ++k) {
524  if (map_val.tags[k] == rtag && map_val.ranks[k] == rrank) {
525  exist_rtag = true;
526  break;
527  }
528  }
529 
530  if (!exist_rtag && first_index < MAX_INTERSECTIONS) {
531  map_val.tags[first_index] = rtag;
532  map_val.data[first_index] = rdata;
533  map_val.ranks[first_index] = rrank;
534  first_index++;
535  }
536  }
537  }
538  else {
539  rmap.insert({ rbuff_key[i], rbuff_value[i] });
540  }
541  }
542 
543  for (size_t i = 0; i < nrecv; i++) {
544  auto it = rmap.find(rbuff_key[i]);
545  if (it != rmap.end()) rbuff_value[i] = it->second;
546  }
547 
548  // sending the assigned data back to original rank
549  grph.transpose();
550  MPI_Exchange(grph, rbuff_value, ibuff_value, comm);
551 
552  for (size_t i = 0; i < dsize; i++) {
553  auto it = map.find(sbuff_key[i]);
554  if (it != map.end()) it->second = ibuff_value[i];
555  }
556 }
557 
569 template<class K, class V> inline
570 void assign_dof_on_counter_face(hashmap::unordered_map<K, V> & map, const MPI_Comm comm)
571 {
572  size_t dsize = map.size();
573  vector<K> key_vec (dsize);
574  vector<V> value_vec (dsize);
575 
576  // make key and value vector of elements without counterparts
577  // which are from different ranks
578  size_t idx=0;
579  for(const auto & v : map) {
580  key_vec[idx] = v.first;
581  value_vec[idx] = v.second;
582  idx++;
583  }
584  dsize = idx;
585  key_vec.resize(dsize);
586  value_vec.resize(dsize);
587 
588  vector<int> perm, dest;
589  emi_select_merge_destinations(key_vec, dsize, sizeof(K) + sizeof(V), dest, comm,
590  "assign_dof_on_counter_face",
591  [](const K& key) { return key.first; });
592 
593  commgraph<size_t> grph;
594  grph.configure(dest, comm);
595  size_t nrecv = sum(grph.rcnt);
596 
597  interval(perm, 0, dsize);
598  binary_sort_copy(dest, perm);
599 
600  // fill send buffer and communicate
601  vector<K> sbuff_key(dsize), rbuff_key(nrecv);
602  vector<V> sbuff_value(dsize), ibuff_value(dsize), rbuff_value(nrecv);
603  for(size_t i=0; i<dsize; i++) {
604  sbuff_key[i] = key_vec[perm[i]];
605  sbuff_value[i] = value_vec[perm[i]];
606  }
607  MPI_Exchange(grph, sbuff_key, rbuff_key, comm);
608  MPI_Exchange(grph, sbuff_value, rbuff_value, comm);
609 
611 
612  for (size_t i = 0; i < nrecv; i++) {
613  auto it = rmap.find(rbuff_key[i]);
614  if(it != rmap.end()) {
615  if(rbuff_value[i] != -1 and it->second == -1){
616  it->second = rbuff_value[i];// add new indices
617  }
618  } else {
619  rmap.insert({rbuff_key[i], rbuff_value[i]});
620  }
621  }
622 
623  for (size_t i = 0; i < nrecv; i++ ) {
624  auto it = rmap.find(rbuff_key[i]);
625  if(it != rmap.end()) {
626  if(rbuff_value[i] == -1)
627  rbuff_value[i] = it->second;
628  }
629  }
630 
631  // sending the assigned data back to original rank
632  grph.transpose();
633  MPI_Exchange(grph, rbuff_value, ibuff_value, comm);
634 
635  for (size_t i = 0; i < dsize; i++ ) {
636  auto it = map.find(sbuff_key[i]);
637  if (it != map.end()) it->second = ibuff_value[i];
638  }
639 }
640 
652 template<class K, class V> inline
653 void assign_petsc_on_counter_face(hashmap::unordered_map<K, V> & map, const MPI_Comm comm)
654 {
655  size_t dsize = map.size();
656  vector<K> key_vec (dsize);
657  vector<V> value_vec (dsize);
658 
659  // make key and value vector of elements without counterparts
660  // which are from different ranks
661  size_t idx=0;
662  for(const auto & v : map) {
663  key_vec[idx] = v.first;
664  value_vec[idx] = v.second;
665  idx++;
666  }
667  dsize = idx;
668  key_vec.resize(dsize);
669  value_vec.resize(dsize);
670 
671  vector<int> perm, dest;
672  emi_select_merge_destinations(key_vec, dsize, sizeof(K) + sizeof(V), dest, comm,
673  "assign_petsc_on_counter_face",
674  [](const K& key) { return key.first; });
675 
676  commgraph<size_t> grph;
677  grph.configure(dest, comm);
678  size_t nrecv = sum(grph.rcnt);
679 
680  interval(perm, 0, dsize);
681  binary_sort_copy(dest, perm);
682 
683  // fill send buffer and communicate
684  vector<K> sbuff_key(dsize), rbuff_key(nrecv);
685  vector<V> sbuff_value(dsize), ibuff_value(dsize), rbuff_value(nrecv);
686  for(size_t i=0; i<dsize; i++) {
687  sbuff_key[i] = key_vec[perm[i]];
688  sbuff_value[i] = value_vec[perm[i]];
689  }
690  MPI_Exchange(grph, sbuff_key, rbuff_key, comm);
691  MPI_Exchange(grph, sbuff_value, rbuff_value, comm);
692 
694 
695  for (size_t i = 0; i < nrecv; i++) {
696  auto it = rmap.find(rbuff_key[i]);
697  if(it != rmap.end()) {
698  if(rbuff_value[i].second != -1 and it->second.second == -1){
699  it->second = rbuff_value[i];// add new indices
700  }
701  } else {
702  rmap.insert({rbuff_key[i], rbuff_value[i]});
703  }
704  }
705 
706  for (size_t i = 0; i < nrecv; i++ ) {
707  auto it = rmap.find(rbuff_key[i]);
708  if(it != rmap.end()) {
709  if(rbuff_value[i].second == -1)
710  rbuff_value[i].second = it->second.second;
711  }
712  }
713 
714  // sending the assigned data back to original rank
715  grph.transpose();
716  MPI_Exchange(grph, rbuff_value, ibuff_value, comm);
717 
718  for (size_t i = 0; i < dsize; i++ ) {
719  auto it = map.find(sbuff_key[i]);
720  if (it != map.end()) it->second = ibuff_value[i];
721  }
722 }
723 
731 template<class T>
732 void sort_surf_local_indices(tuple<T> & ref, tuple<T> & target)
733 {
734  vector<T> buff(2);
735 
736  buff[0] = ref.v1, buff[1] = ref.v2;
737  binary_sort(buff);
738 
739  target.v1 = buff[0], target.v2 = buff[1];
740 }
741 
749 template<class T>
750 void sort_surf_local_indices(triple<T> & ref, triple<T> & target)
751 {
752  vector<T> buff(3);
753 
754  buff[0] = ref.v1, buff[1] = ref.v2, buff[2] = ref.v3;
755  binary_sort(buff);
756 
757  target.v1 = buff[0], target.v2 = buff[1], target.v3 = buff[2];
758 }
759 
767 template<class T>
768 void sort_surf_local_indices(quadruple<T> & ref, quadruple<T> & target)
769 {
770  vector<T> buff(4);
771 
772  buff[0] = ref.v1, buff[1] = ref.v2, buff[2] = ref.v3, buff[3] = ref.v4;
773  binary_sort(buff);
774 
775  target.v1 = buff[0], target.v2 = buff[1], target.v3 = buff[2], target.v4 = buff[3];
776 }
777 
789 template<class K, class V> inline
790 void insert_surf_based_Tag(V & ref,
791  hashmap::unordered_map<K, std::pair<V, V>> & surfmap)
792 {
793  K surf;
794 
795  sort_surf_local_indices(ref.points, surf);
796 
797  auto it = surfmap.find(surf);
798  if (it != surfmap.end()) {
799  // Add as a counter face only if this face exists in surfMap and its tag number
800  // differs from the tag number of the first saved pair.
801  if ( it->second.first.tag != ref.tag )
802  it->second.second = ref;
803  } else {
804  std::pair<V, V> face;
805  face.first = ref;
806  surfmap.insert({surf,face});
807  }
808 }
809 
829 template<class T, class W, class V, class U> inline
830 void insert_surf_emi( int rank,
831  SF::vector<T> const & ref_con,
832  SF::vector<T> const & ptsData,
833  const T eidx, const T tag,
834  const std::vector<T> & line_con, const std::vector<T> & surf_con, const std::vector<T> & qsurf_con,
835  hashmap::unordered_map<tuple<T>, std::pair<W, W> > & line_face,
836  hashmap::unordered_map<triple<T>, std::pair<V, V> > & surfmap,
837  hashmap::unordered_map<quadruple<T>, std::pair<U, U> > & qsurfmap)
838 {
839  W line;
840  line.eidx = eidx;
841  line.tag = tag;
842  V face;
843  face.eidx = eidx;
844  face.tag = tag;
845  U qface;
846  qface.eidx = eidx;
847  qface.tag = tag;
848 
849  // Mark faces as potential candidates if all of their vertices lie on the interface.
850  for (size_t i = 0; i < line_con.size(); i += 2) {
851  if (ptsData[line_con[i ] - 1] > 0 &&
852  ptsData[line_con[i + 1] - 1] > 0 ) {
853  line.points.v1 = ref_con[line_con[i ] - 1];
854  line.points.v2 = ref_con[line_con[i + 1] - 1];
855  line.rank = rank;
856  insert_surf_based_Tag(line, line_face);
857  }
858  }
859 
860  for (size_t i = 0; i < surf_con.size(); i += 3) {
861  if (ptsData[surf_con[i ] - 1] > 0 &&
862  ptsData[surf_con[i + 1] - 1] > 0 &&
863  ptsData[surf_con[i + 2] - 1] > 0 ) {
864  face.points.v1 = ref_con[surf_con[i ] - 1];
865  face.points.v2 = ref_con[surf_con[i + 1] - 1];
866  face.points.v3 = ref_con[surf_con[i + 2] - 1];
867  face.rank = rank;
868  insert_surf_based_Tag(face, surfmap);
869  }
870  }
871 
872  for (size_t i = 0; i < qsurf_con.size(); i += 4) {
873  if (ptsData[qsurf_con[i ] - 1] > 0 &&
874  ptsData[qsurf_con[i + 1] - 1] > 0 &&
875  ptsData[qsurf_con[i + 2] - 1] > 0 &&
876  ptsData[qsurf_con[i + 3] - 1] > 0) {
877  qface.points.v1 = ref_con[qsurf_con[i ] - 1];
878  qface.points.v2 = ref_con[qsurf_con[i + 1] - 1];
879  qface.points.v3 = ref_con[qsurf_con[i + 2] - 1];
880  qface.points.v4 = ref_con[qsurf_con[i + 3] - 1];
881  qface.rank = rank;
882  insert_surf_based_Tag(qface, qsurfmap);
883  }
884  }
885 }
886 
893 template<class T, class V>
894 struct emi_face {
895  V points;
896  T eidx = -1;
897  T tag = 0;
898  T rank = -1;
899  T mem = 1; // default the face on membrane, otherwise will be asssign to 2 for gap-junction face
900  bool mark_to_take = false; // if the face is selected on one of the ranks on the surface mesh with unique face, it will be true
901  // we assume that the face will be selected on the smaller rank. If both has the same rank, we choose the first pair.
902  T index_unique = -1; // index of face on emi_surface_unique_face_msh
903  T index_one = -1; // index of face on emi_surface_msh
904 };
905 
906 template<class T>
907 struct emi_unique_face {
908  T index_unique = -1;
909  T index_one = -1;
910  T rank = -1;
911 };
912 
939 template<class T>
940 struct emi_index_rank {
941  T index = -1;
942  T rank = -1;
943 };
961 template<class T, class S> inline
962 void compute_surface_with_tags(meshdata<T,S> & mesh,
963  const SF_nbr numbering,
964  hashmap::unordered_map<T,T> & vertex2ptsdata,
965  hashmap::unordered_set<int> & extra_tags,
966  hashmap::unordered_map<tuple<T>,
967  std::pair<emi_face<T,tuple<T>>, emi_face<T,tuple<T>>>> & line_face,
968  hashmap::unordered_map<triple<T>,
969  std::pair<emi_face<T,triple<T>>, emi_face<T,triple<T>>>> & tri_surf,
970  hashmap::unordered_map<quadruple<T>,
971  std::pair<emi_face<T,quadruple<T>>, emi_face<T,quadruple<T>>>> & quad_surf)
972 {
973  MPI_Comm comm = SF_COMM;
974  int size, rank;
975  MPI_Comm_size(comm, &size);
976  MPI_Comm_rank(comm, &rank);
977 
978  const T* con = mesh.con.data();
979  const T* nbr = mesh.get_numbering(numbering).data();
980  const SF::vector<mesh_int_t> & rnod = mesh.get_numbering(numbering);
981 
983 
984  for(size_t i=0; i<mesh.con.size(); i++){
985  g2ptsData[rnod[con[i]]] = vertex2ptsdata[rnod[con[i]]];
986  }
987 
988  const vector<T> & ref_eidx = mesh.get_numbering(NBR_ELEM_REF);
989 
990  // Collect potential faces whose three vertices lie on the EMI interfaces.
991  // The ptsData, computed earlier from the input mesh, is used to check
992  // the intersection of each vertex with different tag numbers.
993  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++) {
994  T tag = mesh.tag[eidx];
995  T size_elem = mesh.dsp[eidx+1]-mesh.dsp[eidx];
996 
997  vector<T> dofvec(size_elem); // reference nodes of each element
998  vector<T> ptsDatavec(size_elem); // point data on the nodes of each element
999 
1000  for (int n = mesh.dsp[eidx], i = 0; n < mesh.dsp[eidx+1];n++,i++)
1001  {
1002  dofvec[i] = rnod[con[n]];
1003  ptsDatavec[i] = g2ptsData[rnod[con[n]]];
1004  }
1005  std::vector<T> surf_con ;
1006  std::vector<T> qsurf_con;
1007  std::vector<T> line_con;
1008  switch(mesh.type[eidx]) {
1009  // I need to find the right order of lines in 2D
1010 
1011  // I guess that in the 2D case we would define the interfaces as
1012  // lines instead of surfaces.
1013  case Tri: {
1014  line_con = {1, 2,
1015  2, 3,
1016  3, 1};
1017  surf_con = {};
1018  qsurf_con = {};
1019  break;
1020  }
1021 
1022  case Quad: {
1023  line_con = {1, 2,
1024  2, 3,
1025  3, 4,
1026  4, 1};
1027  surf_con = {};
1028  qsurf_con = {};
1029  break;
1030  }
1031 
1032  case Tetra: {
1033  // surfaces are (2,3,1) , (1,4,2) , (2,4,3) , (1,3,4)
1034  line_con = {};
1035  surf_con = {2,3,1,
1036  1,4,2,
1037  2,4,3,
1038  1,3,4};
1039  qsurf_con = {};
1040  break;
1041  }
1042  case Pyramid: {
1043  // surfaces are (1,5,2) , (2,5,3) , (3,5,4) , (4,5,1) , (1,2,3,4)
1044  line_con = {};
1045  surf_con = {1,5,2,
1046  2,5,3,
1047  3,5,4,
1048  4,5,1};
1049  qsurf_con = {1,2,3,4};
1050  break;
1051  }
1052  case Prism: {
1053  // surfaces are (1,2,3) , (4,5,6) , (1,2,6,4) , (2,3,5,6) , (3,1,4,5)
1054  line_con = {};
1055  surf_con = {1,2,3,
1056  4,5,6};
1057  qsurf_con = {1,2,6,4,
1058  2,3,5,6,
1059  3,1,4,5};
1060  break;
1061  }
1062  case Hexa: {
1063  // surfaces are (1,2,3,4) , (3,2,8,7) , (4,3,7,6) , (1,4,6,5) , (2,1,5,8), (6,7,8,5)
1064  line_con = {};
1065  surf_con = {};
1066  qsurf_con = {1,2,3,4,
1067  3,2,8,7,
1068  4,3,7,6,
1069  1,4,6,5,
1070  2,1,5,8,
1071  6,7,8,5 };
1072  break;
1073  }
1074  default:
1075  fprintf(stderr, "%s error: Unsupported element in surface computation!\n", __func__);
1076  exit(1);
1077  }
1078  insert_surf_emi(rank, dofvec, ptsDatavec, ref_eidx[eidx], mesh.tag[eidx], line_con, surf_con, qsurf_con, line_face, tri_surf, quad_surf);
1079  }
1080 
1081  // Find the counter faces corresponding to the selected potential faces.
1082  assign_counter_face(line_face, mesh.comm);
1083  assign_counter_face(tri_surf, mesh.comm);
1084  assign_counter_face(quad_surf, mesh.comm);
1085 
1086  // Removed potential faces without matching counter faces and
1087  // Removed potential faces where both associated tags belong to the extracellular domain.
1088  for(auto it = line_face.begin(); it != line_face.end(); ) {
1089  if( it->second.second.eidx == -1 ||(extra_tags.find(it->second.first.tag) != extra_tags.end() && extra_tags.find(it->second.second.tag) != extra_tags.end()))
1090  it = line_face.erase(it);
1091  else
1092  ++it;
1093  }
1094 
1095  for(auto it = tri_surf.begin(); it != tri_surf.end(); ) {
1096  if( it->second.second.eidx == -1 ||(extra_tags.find(it->second.first.tag) != extra_tags.end() && extra_tags.find(it->second.second.tag) != extra_tags.end()))
1097  it = tri_surf.erase(it);
1098  else
1099  ++it;
1100  }
1101 
1102  for(auto it = quad_surf.begin(); it != quad_surf.end(); ) {
1103  if( it->second.second.eidx == -1 ||(extra_tags.find(it->second.first.tag) != extra_tags.end() && extra_tags.find(it->second.second.tag) != extra_tags.end()))
1104  it = quad_surf.erase(it);
1105  else
1106  ++it;
1107  }
1108 }
1109 
1123 inline bool should_take_first(int tag1, int tag2,
1124  const hashmap::unordered_set<int>& extra_tags,
1125  const hashmap::unordered_set<int>& intra_tags)
1126 {
1127  bool tag1_is_intra = (intra_tags.find(tag1) != intra_tags.end());
1128  bool tag2_is_intra = (intra_tags.find(tag2) != intra_tags.end());
1129  bool tag1_is_extra = (extra_tags.find(tag1) != extra_tags.end());
1130  bool tag2_is_extra = (extra_tags.find(tag2) != extra_tags.end());
1131 
1132  // Case 1: Both are intra tags (gap junction) - take smaller tag
1133  if(tag1_is_intra && tag2_is_intra) {
1134  return tag1 < tag2;
1135  }
1136 
1137  // Case 2: One is extra, one is intra (membrane) - take intra
1138  if(tag1_is_intra && tag2_is_extra) {
1139  return true; // take tag1 (intra)
1140  }
1141  if(tag2_is_intra && tag1_is_extra) {
1142  return false; // take tag2 (intra)
1143  }
1144 
1145  // Default fallback: take smaller tag
1146  return tag1 < tag2;
1147 }
1148 
1170 inline
1171 void assign_map_between_elem_oneface_and_elem_uniqueFace(int rank,
1172  emi_unique_face <mesh_int_t> first,
1173  emi_unique_face <mesh_int_t> second,
1174  hashmap::unordered_map<mesh_int_t, std::pair<emi_index_rank<mesh_int_t>, emi_index_rank<mesh_int_t>>> & map_elem_uniqueFace_to_elem_oneface,
1175  hashmap::unordered_map<mesh_int_t, emi_index_rank<mesh_int_t>> & map_elem_oneface_to_elem_uniqueFace)
1176 {
1177  // Case 1: Both face and counter face belong to the same rank
1178  if(second.index_one==-1 && second.index_unique==-1){
1179  second.index_one = first.index_one;
1180  second.index_unique = first.index_unique;
1181  }
1182  // Case 2: Cross-rank faces - set up mapping for non-owner ranks
1183  else if(first.rank != second.rank) {
1184  // Find our local index_one (from whichever field matches our rank)
1185  int our_index_one = (first.rank == rank) ? first.index_one :
1186  (second.rank == rank) ? second.index_one : -1;
1187  // Find owner's index_unique (from whichever field has valid index_unique)
1188  int owner_index_unique = (first.index_unique >= 0) ? first.index_unique :
1189  (second.index_unique >= 0) ? second.index_unique : -1;
1190  int owner_rank = (first.index_unique >= 0) ? first.rank :
1191  (second.index_unique >= 0) ? second.rank : -1;
1192 
1193  // Only set mapping for non-owner ranks (owner already set it in STEP 3)
1194  if(our_index_one >= 0 && owner_index_unique >= 0 && owner_rank >= 0 && owner_rank != rank) {
1195  map_elem_oneface_to_elem_uniqueFace[our_index_one].index = owner_index_unique;
1196  map_elem_oneface_to_elem_uniqueFace[our_index_one].rank = owner_rank;
1197  }
1198  }
1199 
1200  // Populate map_elem_uniqueFace_to_elem_oneface (only on owner rank where index_unique >= 0)
1201  // Checked both first and second to find which one represents this rank as owner
1202  // After MPI exchange, the owner info could be in either field
1203  int local_index_unique = -1;
1204  emi_index_rank<mesh_int_t> value1, value2;
1205 
1206  if (first.index_unique >= 0 && first.rank == rank) {
1207  // This rank owns the unique face, info is in 'first'
1208  local_index_unique = first.index_unique;
1209  value1.index = first.index_one;
1210  value1.rank = first.rank;
1211  value2.index = second.index_one;
1212  value2.rank = second.rank;
1213  } else if (second.index_unique >= 0 && second.rank == rank) {
1214  // This rank owns the unique face, info is in 'second'
1215  local_index_unique = second.index_unique;
1216  value1.index = second.index_one;
1217  value1.rank = second.rank;
1218  value2.index = first.index_one;
1219  value2.rank = first.rank;
1220  }
1221 
1222  if (local_index_unique >= 0) {
1223  auto itr = map_elem_uniqueFace_to_elem_oneface.find(local_index_unique);
1224  if (itr == map_elem_uniqueFace_to_elem_oneface.end()) {
1225  std::pair <emi_index_rank<mesh_int_t>,emi_index_rank<mesh_int_t>> value;
1226  value = std::make_pair(value1, value2);
1227  map_elem_uniqueFace_to_elem_oneface.insert({local_index_unique, value});
1228  }
1229  }
1230 }
1231 
1254 template <class T, class Key, class PointsKey>
1255 inline void assign_ownership_rank_on_faces( const Key& key,
1256  std::pair<emi_face<T, PointsKey>, emi_face<T, PointsKey>>& v, // (face, counter-face)
1257  int rank,
1258  mesh_int_t idx_oneface,
1259  mesh_int_t& lsize_unique,
1260  const hashmap::unordered_set<int>& extra_tags,
1261  const hashmap::unordered_set<int>& intra_tags,
1262  hashmap::unordered_map<Key,std::pair<emi_unique_face<mesh_int_t>, emi_unique_face<mesh_int_t>>>& unique_face_to_elements,
1263  hashmap::unordered_map<mesh_int_t, emi_index_rank<mesh_int_t>>& map_elem_oneface_to_elem_uniqueFace)
1264 {
1265  const bool same_rank = (v.first.rank == v.second.rank);
1266 
1267  // Decide owner rank in an order-independent way:
1268  // - If one side is intra, always take the intra side
1269  // - If both intra, take smaller tag (tie -> smaller rank)
1270  // - Otherwise, take smaller tag (tie -> smaller rank)
1271  const bool first_intra = (intra_tags.find(v.first.tag) != intra_tags.end());
1272  const bool second_intra = (intra_tags.find(v.second.tag) != intra_tags.end());
1273 
1274  // Decide which side to take based purely on tags/intra (order-independent)
1275  bool take_first_by_tags = true;
1276  if (first_intra != second_intra) {
1277  take_first_by_tags = first_intra; // take intra side
1278  } else if (v.first.tag != v.second.tag) {
1279  take_first_by_tags = (v.first.tag < v.second.tag);
1280  } else {
1281  // Same tag (should be rare for interfaces); fall back to smaller rank to be deterministic
1282  take_first_by_tags = (v.first.rank <= v.second.rank);
1283  }
1284 
1285  int owner_rank = take_first_by_tags ? v.first.rank : v.second.rank;
1286 
1287  const bool i_am_owner = (rank == owner_rank);
1288 
1289  // If I'm the owner, mark the chosen side and set mapping now.
1290  if (i_am_owner) {
1291  auto& chosen = take_first_by_tags ? v.first : v.second;
1292 
1293  chosen.mark_to_take = true;
1294  chosen.index_unique = lsize_unique;
1295  chosen.index_one = idx_oneface;
1296 
1297  map_elem_oneface_to_elem_uniqueFace[idx_oneface].index = lsize_unique;
1298  map_elem_oneface_to_elem_uniqueFace[idx_oneface].rank = rank;
1299  }
1300 
1301  // Insert per-face record once:
1302  // first = "my side if I'm owner else -1" + rank info
1303  // second = placeholder for the other rank (or same rank if same_rank)
1304  auto it = unique_face_to_elements.find(key);
1305  if (it == unique_face_to_elements.end()) {
1306  emi_unique_face<mesh_int_t> first;
1307  emi_unique_face<mesh_int_t> second;
1308 
1309  if (i_am_owner) {
1310  // Owner knows its local unique index.
1311  first.index_unique = lsize_unique;
1312  first.index_one = idx_oneface;
1313  first.rank = rank;
1314 
1315  // "other side" rank:
1316  second.index_unique = -1;
1317  second.index_one = -1;
1318  second.rank = same_rank ? rank : (rank == v.first.rank ? v.second.rank : v.first.rank);
1319  } else {
1320  // Non-owner does not know unique index yet; MPI step will fill it.
1321  first.index_unique = -1;
1322  first.index_one = idx_oneface;
1323  first.rank = rank;
1324 
1325  second.index_unique = -1;
1326  second.index_one = -1;
1327  second.rank = owner_rank; // this is the rank that will eventually provide index_unique
1328  }
1329 
1330  unique_face_to_elements.insert({ key, std::make_pair(first, second) });
1331  } else {
1332  // Update existing entry to avoid losing owner info when non-owner inserted first.
1333  auto& existing = it->second;
1334 
1335  // Choose a slot to update for this rank.
1336  emi_unique_face<mesh_int_t>* slot = nullptr;
1337  if (existing.first.rank == rank || existing.first.rank < 0) {
1338  slot = &existing.first;
1339  } else if (existing.second.rank == rank || existing.second.rank < 0) {
1340  slot = &existing.second;
1341  } else if (existing.first.index_one == idx_oneface) {
1342  slot = &existing.first;
1343  } else if (existing.second.index_one == idx_oneface) {
1344  slot = &existing.second;
1345  } else if (existing.first.index_unique < 0) {
1346  slot = &existing.first;
1347  } else if (existing.second.index_unique < 0) {
1348  slot = &existing.second;
1349  }
1350 
1351  if (slot) {
1352  if (slot->rank < 0) slot->rank = rank;
1353  if (slot->index_one < 0) slot->index_one = idx_oneface;
1354  if (i_am_owner && slot->index_unique < 0) {
1355  slot->index_unique = lsize_unique;
1356  }
1357  }
1358  }
1359 
1360  // Only the owning rank increments lsize_unique
1361  if (i_am_owner) {
1362  lsize_unique += 1;
1363  }
1364 }
1365 
1366 
1425 template<class T, class S> inline
1426 void extract_face_based_tags(meshdata<T,S> & mesh,
1427  const SF_nbr numbering,
1428  hashmap::unordered_map<T,T> & vertex2ptsdata,
1429  hashmap::unordered_map<tuple<T> ,
1430  std::pair<emi_face<T,tuple<T>>, emi_face<T,tuple<T>>>> & line_face,
1431  hashmap::unordered_map<triple<T>,
1432  std::pair<emi_face<T,triple<T>>, emi_face<T,triple<T>>>> & tri_face,
1433  hashmap::unordered_map<quadruple<T>,
1434  std::pair<emi_face<T,quadruple<T>>, emi_face<T,quadruple<T>>>> & quad_face,
1435  hashmap::unordered_set<int> & extra_tags,
1436  hashmap::unordered_set<int> & intra_tags,
1437  meshdata<T,S> & surfmesh,
1438  meshdata<T,S> & surfmesh_w_counter,
1439  meshdata<T,S> & surfmesh_unique_face,
1440  hashmap::unordered_map<mesh_int_t, std::pair<emi_index_rank<mesh_int_t>, emi_index_rank<mesh_int_t>>> & map_elem_uniqueFace_to_elem_oneface,
1441  hashmap::unordered_map<mesh_int_t, emi_index_rank<mesh_int_t>> & map_elem_oneface_to_elem_uniqueFace)
1442 {
1443  // ============================================================================
1444  // STEP 1: Initialize surface meshes and extract interface faces
1445  // ============================================================================
1446  surfmesh.register_numbering(SF::NBR_REF);
1447  surfmesh_w_counter.register_numbering(SF::NBR_REF);
1448  surfmesh_unique_face.register_numbering(SF::NBR_REF);
1449 
1450  // Extract faces with counter faces on the interfaces to build surface mesh
1451 
1452  compute_surface_with_tags(mesh, numbering, vertex2ptsdata, extra_tags, line_face, tri_face, quad_face);
1453 
1454  int size, rank;
1455  MPI_Comm_size(mesh.comm, &size);
1456  MPI_Comm_rank(mesh.comm, &rank);
1457 
1458  long int g_num_line = line_face.size();
1459  long int g_num_tri = tri_face.size();
1460  long int g_num_quad = quad_face.size();
1461  MPI_Allreduce(MPI_IN_PLACE, &g_num_line, 1, MPI_LONG, MPI_SUM, mesh.comm);
1462  MPI_Allreduce(MPI_IN_PLACE, &g_num_tri, 1, MPI_LONG, MPI_SUM, mesh.comm);
1463  MPI_Allreduce(MPI_IN_PLACE, &g_num_quad, 1, MPI_LONG, MPI_SUM, mesh.comm);
1464 
1465  // Warn if any extra-extra faces remain (should be none after filtering)
1466  {
1467  long int local_extra_extra = 0;
1468  for (const auto& [key, v] : line_face) {
1469  if (extra_tags.find(v.first.tag) != extra_tags.end() &&
1470  extra_tags.find(v.second.tag) != extra_tags.end()) {
1471  local_extra_extra++;
1472  }
1473  }
1474  for (const auto& [key, v] : tri_face) {
1475  if (extra_tags.find(v.first.tag) != extra_tags.end() &&
1476  extra_tags.find(v.second.tag) != extra_tags.end()) {
1477  local_extra_extra++;
1478  }
1479  }
1480  for (const auto& [key, v] : quad_face) {
1481  if (extra_tags.find(v.first.tag) != extra_tags.end() &&
1482  extra_tags.find(v.second.tag) != extra_tags.end()) {
1483  local_extra_extra++;
1484  }
1485  }
1486  long int global_extra_extra = 0;
1487  MPI_Allreduce(&local_extra_extra, &global_extra_extra, 1, MPI_LONG, MPI_SUM, mesh.comm);
1488  if (global_extra_extra > 0 && rank == 0) {
1489  fprintf(stderr, "WARN: extra-extra faces detected before filtering: %ld\n", global_extra_extra);
1490  }
1491  }
1492 
1493  surfmesh.g_numelem = g_num_line + g_num_tri + g_num_quad;
1494  surfmesh.l_numelem = line_face.size() + tri_face.size() + quad_face.size();
1495 
1496  vector<T> cnt(surfmesh.l_numelem, 0);
1497  surfmesh.tag.resize(surfmesh.l_numelem);
1498  surfmesh.type.resize(surfmesh.l_numelem);
1499  surfmesh.con.resize(line_face.size() * 2 + tri_face.size() * 3 + quad_face.size() * 4);
1500 
1501  // ============================================================================
1502  // STEP 2: Sort face keys for deterministic ordering across MPI ranks
1503  // ============================================================================
1504  // To ensure deterministic order, we must sort the keys of the hashmaps before iterating.
1505  // This is crucial for MPI consistency - all ranks must process faces in the same order.
1506  vector<tuple<T>> line_keys;
1507  for(auto const& [key, val] : line_face) line_keys.push_back(key);
1508  std::sort(line_keys.begin(), line_keys.end());
1509 
1510  vector<triple<T>> tri_keys;
1511  for(auto const& [key, val] : tri_face) tri_keys.push_back(key);
1512  std::sort(tri_keys.begin(), tri_keys.end());
1513 
1514  vector<quadruple<T>> quad_keys;
1515  for(auto const& [key, val] : quad_face) quad_keys.push_back(key);
1516  std::sort(quad_keys.begin(), quad_keys.end());
1517 
1518  // Temporary hashmaps to track unique-face assignments before MPI communication
1519  hashmap::unordered_map<SF::tuple<mesh_int_t>, std::pair<emi_unique_face <mesh_int_t>,emi_unique_face<mesh_int_t>>> line_unique_face_to_elements;
1520  hashmap::unordered_map<SF::triple<mesh_int_t>, std::pair<emi_unique_face<mesh_int_t>,emi_unique_face<mesh_int_t>>> tri_unique_face_to_elements;
1521  hashmap::unordered_map<SF::quadruple<mesh_int_t>, std::pair<emi_unique_face<mesh_int_t>,emi_unique_face<mesh_int_t>>> quad_unique_face_to_elements;
1522 
1523  // Counters for local element indices:
1524  // - idx: index into surfmesh (one-side mesh)
1525  // - lsize_*: count of elements in surfmesh_w_counter
1526  // - lsize_unique_*: count of elements in surfmesh_unique_face
1527  mesh_int_t idx = 0, cidx = 0;
1528  mesh_int_t lsize_line = 0;
1529  mesh_int_t lsize_tri = 0;
1530  mesh_int_t lsize_quad = 0;
1531 
1532  mesh_int_t lsize_unique_line = 0;
1533  mesh_int_t lsize_unique_tri = 0;
1534  mesh_int_t lsize_unique_quad = 0;
1535 
1536  // ============================================================================
1537  // STEP 3: Process each face and determine unique-face ownership
1538  // ============================================================================
1539  // For each interface face, we:
1540  // 1. Add it to surfmesh
1541  for(auto const& key : line_keys) {
1542  auto& v = line_face.at(key);
1543  const bool local_involved = (v.first.rank == rank) || (v.second.rank == rank);
1544  if (!local_involved) {
1545  continue;
1546  }
1547  cnt[idx] = 2;
1548 
1549  // Mark gap-junction faces explicitly; membrane is the default.
1550  if ((intra_tags.find(v.first.tag) != intra_tags.end()) && (intra_tags.find(v.second.tag) != intra_tags.end())) {
1551  v.first.mem = 2; // to set the face located on gap-junction
1552  v.second.mem = 2; // to set the face located on gap-junction
1553  }
1554 
1555  // changing the strategy to select the face with the one with the same rank
1556  emi_face<T,tuple<T>> surf_neighbor =
1557  (v.first.rank == rank) ? v.first : v.second;
1558 
1559  surfmesh.type[idx] = Line;
1560  surfmesh.tag[idx] = surf_neighbor.tag;
1561  surfmesh.con[cidx + 0] = surf_neighbor.points.v1;
1562  surfmesh.con[cidx + 1] = surf_neighbor.points.v2;
1563 
1564  // If the ranks are equal, both faces will be added later.
1565  if(v.first.rank == v.second.rank)
1566  lsize_line+=2;
1567  else
1568  lsize_line+=1;
1569 
1570  assign_ownership_rank_on_faces<T, tuple<T>, tuple<T>>( key, v, rank,
1571  idx,
1572  lsize_unique_line,
1573  extra_tags, intra_tags,
1574  line_unique_face_to_elements,
1575  map_elem_oneface_to_elem_uniqueFace);
1576  idx += 1;
1577  cidx += 2;
1578  }
1579 
1580  for(auto const& key : tri_keys) {
1581  auto& v = tri_face.at(key);
1582  const bool local_involved = (v.first.rank == rank) || (v.second.rank == rank);
1583  if (!local_involved) {
1584  continue;
1585  }
1586  cnt[idx] = 3;
1587 
1588  if ((intra_tags.find(v.first.tag) != intra_tags.end()) && (intra_tags.find(v.second.tag) != intra_tags.end())) {
1589  v.first.mem = 2; // to set the face located on gap-junction
1590  v.second.mem = 2; // to set the face located on gap-junction
1591  }
1592 
1593  // changing the strategy to select the face with the one with the same rank
1594  emi_face<T,triple<T>> surf_neighbor =
1595  (v.first.rank == rank) ? v.first : v.second;
1596 
1597  surfmesh.type[idx] = Tri;
1598  surfmesh.tag[idx] = surf_neighbor.tag;
1599  surfmesh.con[cidx + 0] = surf_neighbor.points.v1;
1600  surfmesh.con[cidx + 1] = surf_neighbor.points.v2;
1601  surfmesh.con[cidx + 2] = surf_neighbor.points.v3;
1602 
1603  // If the ranks are equal, both faces will be added later.
1604  if(v.first.rank == v.second.rank)
1605  lsize_tri+=2;
1606  else
1607  lsize_tri+=1;
1608 
1609  assign_ownership_rank_on_faces<T, triple<T>, triple<T>>( key, v, rank,
1610  idx,
1611  lsize_unique_tri,
1612  extra_tags, intra_tags,
1613  tri_unique_face_to_elements,
1614  map_elem_oneface_to_elem_uniqueFace);
1615  idx += 1;
1616  cidx += 3;
1617  }
1618 
1619  for(auto const& key : quad_keys) {
1620  auto& v = quad_face.at(key);
1621  const bool local_involved = (v.first.rank == rank) || (v.second.rank == rank);
1622  if (!local_involved) {
1623  continue;
1624  }
1625  cnt[idx] = 4;
1626 
1627  if ((intra_tags.find(v.first.tag) != intra_tags.end()) && (intra_tags.find(v.second.tag) != intra_tags.end())) {
1628  v.first.mem = 2; // to set the face located on gap-junction
1629  v.second.mem = 2; // to set the face located on gap-junction
1630  }
1631 
1632  // changing the strategy to select the face with the one with the same rank
1633  emi_face<T,quadruple<T>> qsurf_neighbor =
1634  (v.first.rank == rank) ? v.first : v.second;
1635 
1636  surfmesh.type[idx] = Quad;
1637  surfmesh.tag[idx] = qsurf_neighbor.tag;
1638  surfmesh.con[cidx + 0] = qsurf_neighbor.points.v1;
1639  surfmesh.con[cidx + 1] = qsurf_neighbor.points.v2;
1640  surfmesh.con[cidx + 2] = qsurf_neighbor.points.v3;
1641  surfmesh.con[cidx + 3] = qsurf_neighbor.points.v4;
1642 
1643  // If the ranks are equal, both faces will be added later.
1644  if(v.first.rank == v.second.rank)
1645  lsize_quad+=2;
1646  else
1647  lsize_quad+=1;
1648 
1649  assign_ownership_rank_on_faces<T, quadruple<T>, quadruple<T>>( key, v, rank,
1650  idx,
1651  lsize_unique_quad,
1652  extra_tags, intra_tags,
1653  quad_unique_face_to_elements,
1654  map_elem_oneface_to_elem_uniqueFace);
1655  idx += 1;
1656  cidx += 4;
1657  }
1658 
1659  surfmesh.l_numelem = idx;
1660  surfmesh.tag.resize(idx);
1661  surfmesh.type.resize(idx);
1662  cnt.resize(idx);
1663  dsp_from_cnt(cnt, surfmesh.dsp);
1664  surfmesh.con.resize(cidx);
1665 
1666  long int g_num_surf = surfmesh.l_numelem;
1667  MPI_Allreduce(MPI_IN_PLACE, &g_num_surf, 1, MPI_LONG, MPI_SUM, mesh.comm);
1668  surfmesh.g_numelem = g_num_surf;
1669 
1670  if(rank ==0){
1671  std::cout << "surfmesh.g_numelem: " << surfmesh.g_numelem <<std::endl;
1672  std::cout << "surfmesh.l_numelem: " << surfmesh.l_numelem <<std::endl;
1673  }
1674 
1675  {
1676  long int g_num_w_counter_line = lsize_line;
1677  long int g_num_w_counter_tri = lsize_tri;
1678  long int g_num_w_counter_quad = lsize_quad;
1679 
1680  MPI_Allreduce(MPI_IN_PLACE, &g_num_w_counter_line, 1, MPI_LONG, MPI_SUM, SF_COMM);
1681  MPI_Allreduce(MPI_IN_PLACE, &g_num_w_counter_tri, 1, MPI_LONG, MPI_SUM, SF_COMM);
1682  MPI_Allreduce(MPI_IN_PLACE, &g_num_w_counter_quad, 1, MPI_LONG, MPI_SUM, SF_COMM);
1683 
1684  // Assign global and local elements for the surface mesh used in ionic computation.
1685  surfmesh_w_counter.g_numelem = g_num_w_counter_line+g_num_w_counter_tri+g_num_w_counter_quad;
1686  surfmesh_w_counter.l_numelem = lsize_line + lsize_tri + lsize_quad;
1687 
1688  surfmesh_w_counter.tag.resize(surfmesh_w_counter.l_numelem);
1689  surfmesh_w_counter.type.resize(surfmesh_w_counter.l_numelem);
1690  surfmesh_w_counter.con.resize(lsize_line * 2 + lsize_tri * 3 + lsize_quad * 4);
1691 
1692  if(rank ==0){
1693  std::cout << "surfmesh_w_counter.g_numelem: " << surfmesh_w_counter.g_numelem <<std::endl;
1694  std::cout << "surfmesh_w_counter.l_numelem: " << surfmesh_w_counter.l_numelem <<std::endl;
1695  }
1696  }
1697 
1698  {
1699  long int g_num_w_unique_line = lsize_unique_line;
1700  long int g_num_w_unique_tri = lsize_unique_tri;
1701  long int g_num_w_unique_quad = lsize_unique_quad;
1702 
1703  MPI_Allreduce(MPI_IN_PLACE, &g_num_w_unique_line, 1, MPI_LONG, MPI_SUM, SF_COMM);
1704  MPI_Allreduce(MPI_IN_PLACE, &g_num_w_unique_tri, 1, MPI_LONG, MPI_SUM, SF_COMM);
1705  MPI_Allreduce(MPI_IN_PLACE, &g_num_w_unique_quad, 1, MPI_LONG, MPI_SUM, SF_COMM);
1706 
1707  // Assign global and local elements for the surface mesh used in ionic computation.
1708  surfmesh_unique_face.g_numelem = g_num_w_unique_line+g_num_w_unique_tri+g_num_w_unique_quad;
1709  surfmesh_unique_face.l_numelem = lsize_unique_line + lsize_unique_tri + lsize_unique_quad;
1710 
1711  surfmesh_unique_face.tag.resize(surfmesh_unique_face.l_numelem);
1712  surfmesh_unique_face.type.resize(surfmesh_unique_face.l_numelem);
1713  surfmesh_unique_face.con.resize(lsize_unique_line * 2 + lsize_unique_tri * 3 + lsize_unique_quad * 4);
1714 
1715  if(rank ==0){
1716  std::cout << "surfmesh_unique_face.g_numelem: " << surfmesh_unique_face.g_numelem <<std::endl;
1717  std::cout << "surfmesh_unique_face.l_numelem: " << surfmesh_unique_face.l_numelem <<std::endl;
1718  }
1719  }
1720 
1721  // ============================================================================
1722  // STEP 4: MPI communication to share unique-face ownership between ranks
1723  // ============================================================================
1724  // When faces span multiple ranks, only one rank "owns" the unique-face.
1725  // assign_unique_first_face communicates the owner's index_unique to non-owner ranks
1726  // so they can set up their map_elem_oneface_to_elem_uniqueFace mappings correctly.
1727 
1728  // DEBUG: Before exchange
1729  #ifdef EMI_DEBUG_MESH
1730  {
1731  fprintf(stderr, "RANK %d BEFORE exchange: line_hash=%zu, tri_hash=%zu, quad_hash=%zu (expected unique total=%ld)\n",
1732  rank, line_unique_face_to_elements.size(), tri_unique_face_to_elements.size(),
1733  quad_unique_face_to_elements.size(), lsize_unique_line + lsize_unique_tri + lsize_unique_quad);
1734  fflush(stderr);
1735  }
1736  #endif
1737 
1738  if(size>1)
1739  {
1740  // Find the counter faces corresponding to the selected potential faces.
1741  assign_unique_first_face(line_unique_face_to_elements, mesh.comm);
1742  assign_unique_first_face(tri_unique_face_to_elements, mesh.comm);
1743  assign_unique_first_face(quad_unique_face_to_elements, mesh.comm);
1744  }
1745 
1746  // DEBUG: After exchange
1747  #ifdef EMI_DEBUG_MESH
1748  {
1749  int valid_line = 0, valid_tri = 0, valid_quad = 0;
1750  for (const auto& [key, val] : line_unique_face_to_elements) {
1751  if (val.first.index_unique >= 0 || val.second.index_unique >= 0) valid_line++;
1752  }
1753  for (const auto& [key, val] : tri_unique_face_to_elements) {
1754  if (val.first.index_unique >= 0 || val.second.index_unique >= 0) valid_tri++;
1755  }
1756  for (const auto& [key, val] : quad_unique_face_to_elements) {
1757  if (val.first.index_unique >= 0 || val.second.index_unique >= 0) valid_quad++;
1758  }
1759  fprintf(stderr, "RANK %d AFTER exchange: line_hash=%zu, tri_hash=%zu, quad_hash=%zu\n",
1760  rank, line_unique_face_to_elements.size(), tri_unique_face_to_elements.size(),
1761  quad_unique_face_to_elements.size());
1762  fprintf(stderr, "RANK %d VALID entries (index_unique>=0): line=%d, tri=%d, quad=%d\n",
1763  rank, valid_line, valid_tri, valid_quad);
1764  fflush(stderr);
1765  }
1766  #endif
1767 
1768  // ============================================================================
1769  // STEP 5: Finalize mappings after MPI communication
1770  // ============================================================================
1771  for(auto it = line_unique_face_to_elements.begin(); it != line_unique_face_to_elements.end(); ++it) {
1772 
1773  auto& first = it->second.first;
1774  auto& second = it->second.second;
1775 
1776  assign_map_between_elem_oneface_and_elem_uniqueFace(rank, first, second, map_elem_uniqueFace_to_elem_oneface, map_elem_oneface_to_elem_uniqueFace);
1777  }
1778 
1779  for(auto it = tri_unique_face_to_elements.begin(); it != tri_unique_face_to_elements.end(); ++it) {
1780  auto& first = it->second.first;
1781  auto& second = it->second.second;
1782 
1783  assign_map_between_elem_oneface_and_elem_uniqueFace(rank, first, second, map_elem_uniqueFace_to_elem_oneface, map_elem_oneface_to_elem_uniqueFace);
1784  }
1785 
1786  for(auto it = quad_unique_face_to_elements.begin(); it != quad_unique_face_to_elements.end(); ++it) {
1787  auto& first = it->second.first;
1788  auto& second = it->second.second;
1789 
1790  assign_map_between_elem_oneface_and_elem_uniqueFace(rank, first, second, map_elem_uniqueFace_to_elem_oneface, map_elem_oneface_to_elem_uniqueFace);
1791  }
1792 }
1793 
1803 template<class T> inline
1804 void complete_map_vertex_to_dof_with_counter_face(hashmap::unordered_map<tuple<T>,
1805  std::pair<emi_face<T,tuple<T>>, emi_face<T,tuple<T>>>> & line_face,
1806  hashmap::unordered_map<triple<T>,
1807  std::pair<emi_face<T,triple<T>>, emi_face<T,triple<T>>>> & tri_face,
1808  hashmap::unordered_map<quadruple<T>,
1809  std::pair<emi_face<T,quadruple<T>>, emi_face<T,quadruple<T>>>> & quad_face,
1810  hashmap::unordered_map<std::pair<mesh_int_t,mesh_int_t>,
1811  mesh_int_t> & map_vertex_tag_to_dof)
1812 {
1813  MPI_Comm comm = SF_COMM;
1814  int size, rank;
1815  MPI_Comm_size(comm, &size);
1816  MPI_Comm_rank(comm, &rank);
1817 
1818  // look for counter part line_inteface to add to the map key<vertex,tag> -> value<dof>
1819  for(const auto & v : line_face) {
1820 
1821  // if the ranks are equal, then it has already added to the map
1822  if(v.second.first.rank == v.second.second.rank){
1823  continue;
1824  }
1825  // otherwise we look for the counter part with different rank
1826  emi_face<T,tuple<T>> surf_neighbor =
1827  (v.second.first.rank != rank) ? v.second.first : v.second.second;
1828 
1829  {
1830  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
1831  Index_tag_old = std::make_pair(surf_neighbor.points.v1,surf_neighbor.tag);
1832  map_vertex_tag_to_dof.insert({Index_tag_old,-1});
1833  }
1834 
1835  {
1836  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
1837  Index_tag_old = std::make_pair(surf_neighbor.points.v2,surf_neighbor.tag);
1838  map_vertex_tag_to_dof.insert({Index_tag_old,-1});
1839  }
1840  }
1841 
1842  // look for counter part line_inteface to add to the map key<vertex,tag> -> value<dof>
1843  for(const auto & v : tri_face) {
1844 
1845  // if the ranks are equal, then it has already added to the map
1846  if(v.second.first.rank == v.second.second.rank){
1847  continue;
1848  }
1849  // otherwise we look for the counter part with different rank
1850  emi_face<T,triple<T>> surf_neighbor =
1851  (v.second.first.rank != rank) ? v.second.first : v.second.second;
1852 
1853  {
1854  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
1855  Index_tag_old = std::make_pair(surf_neighbor.points.v1,surf_neighbor.tag);
1856  map_vertex_tag_to_dof.insert({Index_tag_old,-1});
1857  }
1858 
1859  {
1860  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
1861  Index_tag_old = std::make_pair(surf_neighbor.points.v2,surf_neighbor.tag);
1862  map_vertex_tag_to_dof.insert({Index_tag_old,-1});
1863  }
1864 
1865  {
1866  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
1867  Index_tag_old = std::make_pair(surf_neighbor.points.v3,surf_neighbor.tag);
1868  map_vertex_tag_to_dof.insert({Index_tag_old,-1});
1869  }
1870  }
1871 
1872  // look for counter part line_inteface to add to the map key<vertex,tag> -> value<dof>
1873  for(const auto & v : quad_face) {
1874 
1875  // if the ranks are equal, then it has already added to the map
1876  if(v.second.first.rank == v.second.second.rank){
1877  continue;
1878  }
1879  // otherwise we look for the counter part with different rank
1880  emi_face<T,quadruple<T>> qsurf_neighbor =
1881  (v.second.first.rank != rank) ? v.second.first : v.second.second;
1882 
1883  {
1884  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
1885  Index_tag_old = std::make_pair(qsurf_neighbor.points.v1,qsurf_neighbor.tag);
1886  map_vertex_tag_to_dof.insert({Index_tag_old,-1});
1887  }
1888 
1889  {
1890  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
1891  Index_tag_old = std::make_pair(qsurf_neighbor.points.v2,qsurf_neighbor.tag);
1892  map_vertex_tag_to_dof.insert({Index_tag_old,-1});
1893  }
1894 
1895  {
1896  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
1897  Index_tag_old = std::make_pair(qsurf_neighbor.points.v3,qsurf_neighbor.tag);
1898  map_vertex_tag_to_dof.insert({Index_tag_old,-1});
1899  }
1900 
1901  {
1902  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
1903  Index_tag_old = std::make_pair(qsurf_neighbor.points.v4,qsurf_neighbor.tag);
1904  map_vertex_tag_to_dof.insert({Index_tag_old,-1});
1905  }
1906  }
1907 
1908  // assign the counter faces which we haven't assigned it yet the new dof
1909  assign_dof_on_counter_face(map_vertex_tag_to_dof,comm);
1910 }
1911 
1941 template<class T, class S> inline
1942 void compute_map_vertex_to_dof(meshdata<T,S> & mesh,
1943  const SF_nbr numbering,
1944  hashmap::unordered_map<T,T> & vertex2ptsdata,
1945  hashmap::unordered_set<int> & extra_tags,
1946  hashmap::unordered_map<std::pair<mesh_int_t,mesh_int_t>,
1947  mesh_int_t> & map_vertex_tag_to_dof)
1948 {
1949  MPI_Comm comm = mesh.comm;
1950  int size, rank;
1951  MPI_Comm_size(comm, &size);
1952  MPI_Comm_rank(comm, &rank);
1953 
1954  hashmap::unordered_map<mesh_int_t, intersection_data> map_vertex_to_tags_data_ranks;
1955 
1956  const T* con = mesh.con.data();
1957  const T* nbr = mesh.get_numbering(numbering).data();
1958  const SF::vector<T> & rnod = mesh.get_numbering(numbering);
1959 
1960  hashmap::unordered_map<T,T> g2ptsData;
1961 
1962  for(size_t i=0; i<mesh.con.size(); i++)
1963  {
1964  g2ptsData[rnod[con[i]]] = vertex2ptsdata[rnod[con[i]]];
1965  }
1966 
1967  //-----------------------------------------------------------------
1968  // Step 1: Local Information Gathering
1969  //-----------------------------------------------------------------
1970  // - Identify Simple Cases: For any vertex that is not on an interface (i.e., it's completely inside the extracellular space or completely
1971  // inside a unique myocyte), the mapping is simple. The new DOF index is the same as the original vertex index. The function populates the
1972  // output map, map_vertex_tag_to_dof, with these direct mappings, e.g., (vertex {1}, tag_3) -> 1.
1973  // - Collect Intersection Data: This is the most important part of this step. For every vertex, it builds a list of all the regions (tags) and
1974  // MPI ranks that share it, based on the elements the current process knows about. This information is stored in a temporary map called
1975  // map_vertex_to_tags_data_ranks.
1976  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++)
1977  {
1978  T tag = mesh.tag[eidx];
1979  auto pos_extra = extra_tags.find(tag);
1980  for (int n = mesh.dsp[eidx]; n < mesh.dsp[eidx+1];n++)
1981  {
1982  T gIndex = rnod[con[n]];
1983  T data_on_gIndex = g2ptsData[rnod[con[n]]];
1984 
1985  // This handles inner DoFs, either within the extracellular domain or vertices belonging to the intracellular domain.
1986  // and marked as a visited dofs and saved key<gIndex,tag)> -> val<gIndex>
1987  if((pos_extra != extra_tags.end()) || data_on_gIndex==0){
1988  std::pair <mesh_int_t,mesh_int_t> Index_tag_old = std::make_pair(gIndex,tag);
1989  if (map_vertex_tag_to_dof.find(Index_tag_old) == map_vertex_tag_to_dof.end() )
1990  {
1991  map_vertex_tag_to_dof.insert({Index_tag_old,gIndex});
1992  }
1993  }
1994 
1995  // When gIndex is located on an interface — membrane, gap junction, or their intersection.
1996  auto it = map_vertex_to_tags_data_ranks.find(gIndex);
1997  if (it != map_vertex_to_tags_data_ranks.end() )
1998  {
1999  intersection_data& value = it->second;
2000  bool check_tag_rank = true;
2001  for(int i=0; i<MAX_INTERSECTIONS; ++i) {
2002  if(value.tags[i] == tag && value.ranks[i] == rank) {
2003  check_tag_rank = false;
2004  break;
2005  }
2006  }
2007 
2008  // Insert the tag and corresponding rank if they haven’t been inserted yet.
2009  if(check_tag_rank){
2010  for(int i=0; i<MAX_INTERSECTIONS; ++i) {
2011  if(value.tags[i] == -1 && value.ranks[i] == -1) {
2012  value.tags[i] = tag;
2013  value.data[i] = data_on_gIndex;
2014  value.ranks[i] = rank;
2015  break;
2016  }
2017  }
2018  }
2019  }
2020  else
2021  {
2022  // insert gIndex to the map_vertex_to_tags_data_ranks
2023  intersection_data value;
2024  value.tags[0] = tag;
2025  value.data[0] = data_on_gIndex;
2026  value.ranks[0] = rank;
2027  map_vertex_to_tags_data_ranks.insert({gIndex,value});
2028  }
2029  }
2030  }
2031  // until here: map_vertex_to_tags_data_ranks knows about the interfaces for the elements it owns,
2032  // but it doesn't know if a vertex it owns is also part of an interface on another process.
2033 
2034  //-----------------------------------------------------------------
2035  // Step 2: Globalize Information with MPI
2036  //-----------------------------------------------------------------
2037  // - assign_counter_vertices_tuple: This helper function is called to manage the communication. It takes the partial
2038  // map_vertex_to_tags_data_ranks from each process and uses MPI (specifically MPI_Exchange, which is likely a wrapper around routines like
2039  // MPI_Alltoallv) to send and receive data.
2040  // - Build Global Map: After the communication, the partial maps are merged. The result is that the map_vertex_to_tags_data_ranks on every
2041  // process now contains the complete sharing information for every vertex in the entire mesh. Each process now knows the full list of tags
2042  // and ranks associated with any given vertex.
2043  assign_counter_vertices_tuple(map_vertex_to_tags_data_ranks, comm);
2044 
2045  //-----------------------------------------------------------------
2046  // Step 3: Identify and Count DOFs to Be Created
2047  //-----------------------------------------------------------------
2048  // Now that every process has the same global information, they can independently and deterministically decide which new DOFs to create.
2049  // - Analyze Intersections: The code iterates through the now-global map_vertex_to_tags_data_ranks. For each vertex, it inspects the list of
2050  // tags that share it to classify the type of interface.
2051  // - Apply Rules: Based on the interface type, it decides which side needs a new, unique DOF index.
2052  // * Membrane (Myocyte-Extracellular): The extracellular DOF keeps the original vertex index. The myocyte DOF is marked as needing a new, unique index.
2053  // * Gap Junction (Myocyte-Myocyte): To separate the two myocytes, one of them needs a new index. A deterministic rule (e.g., the myocyte
2054  // with the higher tag number gets the new index) is applied to ensure all processes make the same decision.
2055  // * Complex Junctions: For even more complex intersections (e.g., where multiple myocytes and the extracellular space meet), similar
2056  // rules are applied to create the necessary new DOFs.
2057  // - Count Local Contribution: Each process counts how many new DOFs (shift) it is responsible for creating based on these rules.
2058  T shift = 0;
2060  for(const auto & key : map_vertex_to_tags_data_ranks)
2061  {
2062  T gIndex = key.first;
2063  const intersection_data& value = key.second;
2064  T data_on_gIndex = g2ptsData[gIndex];
2065 
2066  // find the first -1 in value
2067  int first_index = 0;
2068  for (int i = 0; i < MAX_INTERSECTIONS; ++i) {
2069  if(value.tags[i] == -1 && value.ranks[i] == -1) {
2070  first_index = i;
2071  break;
2072  }else{
2073  // check if all the intersection_data has the same g2ptsData
2074  if(value.data[i]!=data_on_gIndex)
2075  {
2076  // Error for unhandled cases.
2077  fprintf(stderr,
2078  "WARN: Due to an inconsistency in ptsData computation at gIndex %lld with tag %lld, "
2079  "the program is stopped while preparing the data structure to introduce DOFs.\n",
2080  static_cast<long long>(gIndex), static_cast<long long>(value.tags[i]));
2081  fprintf(stderr, "\n");
2082  EXIT(1);
2083  }
2084  }
2085  if (i == MAX_INTERSECTIONS -1) first_index = MAX_INTERSECTIONS;
2086  }
2087 
2088  if(data_on_gIndex==1){ // membrane
2089  // check the mumber of tag numbers,
2090  int count_intra_tags = 0;
2091  int count_extra_tags = 0;
2092  for (int i = 0; i < first_index; ++i) {
2093  if(value.tags[i]!=-1 && extra_tags.find(value.tags[i])==extra_tags.end()){
2094  count_intra_tags += 1;
2095  }else if(value.tags[i]!=-1 && extra_tags.find(value.tags[i])!=extra_tags.end()){
2096  count_extra_tags += 1;
2097  }
2098  }
2099 
2100  // Check if the collected data at gIndex is indeed on the membrane.
2101  // A valid membrane vertex must intersect exactly one intracellular tag and one or more extracellular tags.
2102  if(count_intra_tags>1 || count_extra_tags == 0)
2103  {
2104  // Error for unhandled cases.
2105  fprintf(stderr,
2106  "WARN: Due to an issue in defining ptsData for gIndex %lld, , on the membrane"
2107  "the program is stopped while preparing the data structure to introduce DOFs.\n",
2108  static_cast<long long>(gIndex));
2109  fprintf(stderr, "\n");
2110  EXIT(1);
2111  }
2112 
2113  int index_intra = -1;
2114  // Find the intracellular tag number. Since data_on_gIndex == 1, there will be only one tag corresponding to the intracellular domain.
2115  for (int i = 0; i < first_index; ++i) {
2116  if(value.tags[i]!=-1 && extra_tags.find(value.tags[i])==extra_tags.end()){
2117  index_intra = i;
2118  break;
2119  }
2120  }
2121  if(index_intra == -1) {
2122  fprintf(stderr, "WARN: unhandled case in membrane while decoupling: gIndex %lld",
2123  static_cast<long long>(gIndex));
2124  fprintf(stderr, "\n");
2125  EXIT(1);
2126  }
2127  if(index_intra != -1) {
2128  T tag_myocyte = value.tags[index_intra];
2129  T rank_myocyte = value.ranks[index_intra];
2130 
2131  if(rank_myocyte == rank) { // mark intracellular tag with gIndex
2132  std::pair <mesh_int_t,mesh_int_t> Index_tag_next = std::make_pair(gIndex,tag_myocyte);
2133  if (map_mark_new_dofs.find(Index_tag_next) == map_mark_new_dofs.end()) {
2134  map_mark_new_dofs.insert({Index_tag_next,true}); //mark a new index
2135  shift++;
2136  }
2137  }
2138  }
2139  }
2140  else if(data_on_gIndex==2){ // gap junction
2141  // count the number of extra tags and intra tags
2142  int count_intra_tags = 0;
2143  int count_extra_tags = 0;
2144  for (int i = 0; i < first_index; ++i) {
2145  if(value.tags[i]!=-1 && extra_tags.find(value.tags[i])==extra_tags.end()){
2146  count_intra_tags += 1;
2147  }else if(value.tags[i]!=-1 && extra_tags.find(value.tags[i])!=extra_tags.end()){
2148  count_extra_tags += 1;
2149  }
2150  }
2151  if(count_intra_tags!=2 || count_extra_tags != 0){
2152  // Error for unhandled cases.
2153  fprintf(stderr,
2154  "WARN: Due to an issue in defining ptsData for gIndex %lld, on the gapjunction"
2155  "the program is stopped while preparing the data structure to introduce DOFs.\n",
2156  static_cast<long long>(gIndex));
2157  fprintf(stderr, "\n");
2158  EXIT(1);
2159  }
2160 
2161  T tag1 = -1;
2162  T tag2 = -1;
2163  T rank1 = -1;
2164  T rank2 = -1;
2165  bool tag1_checked = false;
2166  bool tag2_checked = false;
2167  // find the tag1 and tag2 on the gap junction
2168  for (int i = 0; i < first_index; ++i)
2169  {
2170  auto pos_extra = extra_tags.find(value.tags[i]);
2171  if(pos_extra == extra_tags.end()) {
2172  if(tag1==-1){
2173  tag1 = value.tags[i];
2174  rank1 = value.ranks[i];
2175  tag1_checked = true;
2176  }else if(tag1!=-1){
2177  tag2 = value.tags[i];
2178  rank2 = value.ranks[i];
2179  tag2_checked = true;
2180  break;
2181  }
2182  }
2183  }
2184 
2185  if(!tag1_checked && !tag2_checked || (rank1==rank2 && rank1!=rank) ) {
2186  fprintf(stderr,
2187  "WARN: unhandled case in gap junction while decoupling: gIndex %lld with tags %lld:%lld ",
2188  static_cast<long long>(gIndex),
2189  static_cast<long long>(tag1),
2190  static_cast<long long>(tag2));
2191  fprintf(stderr, "\n");
2192  EXIT(1);
2193  }
2194 
2195  if(rank1==rank2 && (rank1 == rank)){ // If both tag numbers belong to the same rank, keep the smaller tag’s vertex index and reserve a new index for the larger tag.
2196  T smallest_tag = tag1 < tag2? tag1:tag2;
2197  std::pair <mesh_int_t,mesh_int_t> Index_tag_old = std::make_pair(gIndex,smallest_tag);
2198  if (map_vertex_tag_to_dof.find(Index_tag_old) == map_vertex_tag_to_dof.end() ) {
2199  map_vertex_tag_to_dof.insert({Index_tag_old,gIndex});
2200  }
2201 
2202  T bigger_tag = tag1 < tag2? tag2:tag1;
2203  std::pair <mesh_int_t,mesh_int_t> Index_tag_new = std::make_pair(gIndex,bigger_tag);
2204  if (map_mark_new_dofs.find(Index_tag_new) == map_mark_new_dofs.end() ) {
2205  map_mark_new_dofs.insert({Index_tag_new,true});
2206  shift++;
2207  }
2208  }
2209  else if(rank1!=rank2 && (rank1 == rank)) { // If one belong to the current rank, keep the smaller tag’s vertex index else reserve a new index for the larger tag.
2210  if(tag1<tag2) {
2211  std::pair <mesh_int_t,mesh_int_t> Index_tag_old = std::make_pair(gIndex,tag1);
2212  if (map_vertex_tag_to_dof.find(Index_tag_old) == map_vertex_tag_to_dof.end() ) {
2213  map_vertex_tag_to_dof.insert({Index_tag_old,gIndex});
2214  }
2215  } else {
2216  std::pair <mesh_int_t,mesh_int_t> Index_tag_new = std::make_pair(gIndex,tag1);
2217  if (map_mark_new_dofs.find(Index_tag_new) == map_mark_new_dofs.end() ) {
2218  map_mark_new_dofs.insert({Index_tag_new,true});
2219  shift++;
2220  }
2221  }
2222  }
2223  else if(rank1!=rank2 && (rank2 == rank)) { // If one belong to the current rank, keep the smaller tag’s vertex index else reserve a new index for the larger tag.
2224  if(tag2<tag1) {
2225  std::pair <mesh_int_t,mesh_int_t> Index_tag_old = std::make_pair(gIndex,tag2);
2226  if (map_vertex_tag_to_dof.find(Index_tag_old) == map_vertex_tag_to_dof.end() ) {
2227  map_vertex_tag_to_dof.insert({Index_tag_old,gIndex});
2228  }
2229  } else {
2230  std::pair <mesh_int_t,mesh_int_t> Index_tag_new = std::make_pair(gIndex,tag2);
2231  if (map_mark_new_dofs.find(Index_tag_new) == map_mark_new_dofs.end() ) {
2232  map_mark_new_dofs.insert({Index_tag_new,true});
2233  shift++;
2234  }
2235  }
2236  }
2237  }
2238  else if(data_on_gIndex==3)
2239  {
2240  // count the number of extra tags and intra tags
2241  int count_intra_tags = 0;
2242  int count_extra_tags = 0;
2243  for (int i = 0; i < first_index; ++i) {
2244  if(value.tags[i]!=-1 && extra_tags.find(value.tags[i])==extra_tags.end()){
2245  count_intra_tags += 1;
2246  }else if(value.tags[i]!=-1 && extra_tags.find(value.tags[i])!=extra_tags.end()){
2247  count_extra_tags += 1;
2248  }
2249  }
2250  if(count_intra_tags!=2 || count_extra_tags == 0){
2251  // Error for unhandled cases.
2252  fprintf(stderr,
2253  "WARN: Due to an issue in defining ptsData for gIndex %lld, on intersection between membrane and gapjunction"
2254  "the program is stopped while preparing the data structure to introduce DOFs.\n",
2255  static_cast<long long>(gIndex));
2256  fprintf(stderr, "\n");
2257  EXIT(1);
2258  }
2259 
2260  // All extracellular tags have already been added in the previous loop that iterated over the mesh.
2261  // on the interface between gap juntion and membarne
2262  for (int i = 0; i < first_index; ++i) {
2263  // find the first two tag number which belong to itracellular tags, add for indexing
2264  auto pos_extra = extra_tags.find(value.tags[i]);
2265  if(value.tags[i]!=-1 && value.ranks[i] == rank && pos_extra == extra_tags.end()) { // myocytes, for sure we should have two myocytes
2266  std::pair <mesh_int_t,mesh_int_t> Index_tag_new = std::make_pair(gIndex,value.tags[i]);
2267  if (map_mark_new_dofs.find(Index_tag_new) == map_mark_new_dofs.end() )
2268  {
2269  map_mark_new_dofs.insert({Index_tag_new,true}); //mark a new index
2270  shift++;
2271  }
2272  }
2273  }
2274  }
2275  }
2276 
2277  vector<T> dsp_dof(size);
2278  MPI_Allgather(&shift, sizeof(T), MPI_BYTE, dsp_dof.data(), sizeof(T), MPI_BYTE, comm);
2279 
2280  //-----------------------------------------------------------------
2281  // Step 4: Assign Unique Global Indices in Parallel
2282  //-----------------------------------------------------------------
2283  // The final step is to assign the new indices without any conflicts between processes.
2284  // - Calculate Global Offset (`start`): An MPI_Allgather is used to share the shift counts among all processes. Each process then calculates
2285  // its own unique start index for the block of new DOFs it will create. This is done by taking the total number of vertices in the original
2286  // mesh and adding the shift counts from all processes with a smaller rank. This is a parallel prefix sum, and it guarantees that the ranges
2287  // of new indices created by different processes will not overlap.
2288  // - Assign New Indices: Finally, the function loops through the vertices it marked for new DOF creation and assigns them a unique global
2289  // index (newIndex = start + count). This final mapping, (original_vertex_index, tag) -> new_unique_dof_index, is inserted into the output
2290  // map map_vertex_tag_to_dof.
2291  T start;
2292  if(rank==0){
2293  start = mesh.g_numpts;
2294  }
2295  else
2296  {
2297  start = mesh.g_numpts;
2298  for (int r = 0; r < rank; ++r)
2299  {
2300  start+= dsp_dof[r];
2301  }
2302  }
2303 
2304  T count = 0;
2305  auto lexicographic_comp_pair = [](const std::pair<mesh_int_t,mesh_int_t> & a, const std::pair<mesh_int_t,mesh_int_t> & b)
2306  {
2307  if (a.second == b.second) return a.first < b.first;
2308  return a.second < b.second;
2309  };
2310 
2311  map_mark_new_dofs.sort(lexicographic_comp_pair);
2312 
2313  for(const auto & entry : map_mark_new_dofs)
2314  {
2315  T newIndex = start+count;
2316  map_vertex_tag_to_dof.insert({entry.first,newIndex});
2317  count++;
2318  }
2319 }
2320 
2332 template<class K, class IntersectionIndices> inline
2333 void assign_counter_dofs(hashmap::unordered_map<K, IntersectionIndices>& map, const MPI_Comm comm)
2334 {
2335  size_t dsize = map.size();
2336  vector<K> key_vec(dsize);
2337  vector<IntersectionIndices> value_vec(dsize);
2338  IntersectionIndices indices;
2339  size_t idx = 0;
2340  for (const auto& v : map) {
2341  key_vec[idx] = v.first;
2342  value_vec[idx] = v.second;
2343  idx++;
2344  }
2345 
2346  vector<int> perm, dest;
2347  emi_select_merge_destinations(key_vec, dsize, sizeof(K) + sizeof(intersection_indices), dest, comm,
2348  "assign_counter_dofs",
2349  [](const K& key) { return hashmap::hash_ops<K>::hash(key); });
2350 
2351  commgraph<size_t> grph;
2352  grph.configure(dest, comm);
2353  size_t nrecv = sum(grph.rcnt);
2354 
2355  interval(perm, 0, dsize);
2356  binary_sort_copy(dest, perm);
2357 
2358  // fill send buffer and communicate
2359  vector<K> sbuff_key(dsize), rbuff_key(nrecv);
2360  vector<IntersectionIndices> sbuff_value(dsize), ibuff_value(dsize), rbuff_value(nrecv);
2361  for (size_t i = 0; i < dsize; i++) {
2362  sbuff_key[i] = key_vec[perm[i]];
2363  sbuff_value[i] = value_vec[perm[i]];
2364  }
2365  MPI_Exchange(grph, sbuff_key, rbuff_key, comm);
2366  MPI_Exchange(grph, sbuff_value, rbuff_value, comm);
2367 
2369  for (size_t i = 0; i < nrecv; i++)
2370  {
2371  auto it = rmap.find(rbuff_key[i]);
2372  if (it != rmap.end())
2373  {
2374  IntersectionIndices& map_indices = it->second;
2375  IntersectionIndices& received_indices = rbuff_value[i];
2376 
2377  for (mesh_int_t received_index : received_indices.indices)
2378  {
2379  if (received_index == -1) continue;
2380  bool found = false;
2381  for (mesh_int_t map_index : map_indices.indices)
2382  {
2383  if (map_index == received_index) {
2384  found = true;
2385  break;
2386  }
2387  }
2388  if (!found)
2389  {
2390  for (size_t k = 0; k < MAX_INTERSECTIONS; ++k) {
2391  if (map_indices.indices[k] == -1) {
2392  map_indices.indices[k] = received_index;
2393  break;
2394  }
2395  }
2396  }
2397  }
2398  }
2399  else
2400  {
2401  rmap.insert({ rbuff_key[i], rbuff_value[i] });
2402  }
2403  }
2404 
2405  for (size_t i = 0; i < nrecv; i++) {
2406  auto it = rmap.find(rbuff_key[i]);
2407  if (it != rmap.end()) rbuff_value[i] = it->second;
2408  }
2409 
2410  // sending the assigned data back to original rank
2411  grph.transpose();
2412  MPI_Exchange(grph, rbuff_value, ibuff_value, comm);
2413 
2414  for (size_t i = 0; i < dsize; i++) {
2415  auto it = map.find(sbuff_key[i]);
2416  if (it != map.end()) it->second = ibuff_value[i];
2417  }
2418 }
2429 template<class T, class S> inline
2430 void update_emi_mesh_with_dofs(meshdata<T,S> & mesh,
2431  const SF_nbr numbering,
2432  hashmap::unordered_map<std::pair<mesh_int_t,mesh_int_t>,
2433  mesh_int_t> & map_vertex_tag_to_dof,
2435 {
2436  // map between old index to set of new indices
2438  MPI_Comm comm = mesh.comm;
2439  mesh.globalize(numbering);
2440  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++) {
2441  T tag = mesh.tag[eidx];
2442  T size_elem = mesh.dsp[eidx+1]-mesh.dsp[eidx];
2443 
2444  for (int n = mesh.dsp[eidx]; n < mesh.dsp[eidx+1];n++)
2445  {
2446  T gIndex = mesh.con[n];
2447 
2448  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
2449  Index_tag_old = std::make_pair(gIndex,tag);
2450 
2451  auto it_new = map_vertex_tag_to_dof.find(Index_tag_old);
2452  if (it_new != map_vertex_tag_to_dof.end() )
2453  {
2454  mesh_int_t dof_new = (*it_new).second;
2455  dof2vertex.insert({dof_new, gIndex});
2456  mesh.con[n] = dof_new;
2457 
2458  auto it = vertex2dof.find(gIndex);
2459  if (it != vertex2dof.end())
2460  {
2461  intersection_indices& indices = it->second;
2462  bool found = false;
2463  for(T t : indices.indices) {
2464  if(t == dof_new) {
2465  found = true;
2466  break;
2467  }
2468  }
2469  if(!found) {
2470  // the default size is 3, since the maximum intersection shoudl be on the intersection between the gap junction and memeranre, and since the 2 indiecs for myocyesand one indices for extracellular.
2471  for(size_t i=0; i<MAX_INTERSECTIONS; ++i) {
2472  if(indices.indices[i] == -1) {
2473  indices.indices[i] = dof_new;
2474  break;
2475  }
2476  }
2477  }
2478  }else{
2479  intersection_indices indices;
2480  indices.indices[0] = dof_new;
2481  vertex2dof.insert({gIndex,indices});
2482  }
2483  }
2484  }
2485  }
2486 
2487  assign_counter_dofs(vertex2dof, comm);
2488 
2489  // Now update dof2vertex using vertex2dof
2490  for (const auto& [old_idx, indices] : vertex2dof) {
2491  for (mesh_int_t new_idx : indices.indices) {
2492  if (new_idx != -1) {
2493  // Avoid overwriting unless necessary
2494  if (dof2vertex.find(new_idx) == dof2vertex.end()) {
2495  dof2vertex.insert({new_idx, old_idx});
2496  }
2497  }
2498  }
2499  }
2500 
2501  mesh.localize(numbering);
2502 }
2503 
2517 template<class T, class S> inline
2518 void update_map_indices_to_petsc(meshdata<T,S> & mesh,
2519  const SF_nbr numbering_ref,const SF_nbr numbering_petsc,
2520  const hashmap::unordered_set<int> & extra_tags,
2521  hashmap::unordered_map<std::pair<mesh_int_t,mesh_int_t>,
2522  std::pair<mesh_int_t,mesh_int_t>> & map_vertex_tag_to_dof_petsc,
2524  SF::vector<mesh_int_t> & elemTag_emi_mesh)
2525 {
2526  const SF::vector<T> & ref_nbr = mesh.get_numbering(numbering_ref);
2527  const SF::vector<T> & petsc_nbr = mesh.get_numbering(numbering_petsc);
2528 
2529  elemTag_emi_mesh.resize(mesh.l_numelem);
2530  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++) {
2531  T tag = mesh.tag[eidx];
2532  elemTag_emi_mesh[eidx] = 1; // assigned 1 for for extracellular located on extracellular side
2533  if(extra_tags.find(tag) == extra_tags.end())
2534  elemTag_emi_mesh[eidx] = 2; // assigned 2 for for intracellular located on myocyte
2535  for (int n = mesh.dsp[eidx]; n < mesh.dsp[eidx+1];n++)
2536  {
2537  T l_Indx = mesh.con[n];
2538  T petsc_Idx = petsc_nbr[l_Indx];
2539  T n_Indx = ref_nbr[l_Indx];
2540  T o_Indx = dof2vertex[n_Indx];
2541 
2542  std::pair <mesh_int_t,mesh_int_t> oIndx_tag;
2543  oIndx_tag = std::make_pair(o_Indx,tag);
2544 
2545  auto it_new = map_vertex_tag_to_dof_petsc.find(oIndx_tag);
2546  if (it_new != map_vertex_tag_to_dof_petsc.end() )
2547  {
2548  std::pair <mesh_int_t,mesh_int_t> nIndx_petsc;
2549  nIndx_petsc = std::make_pair(n_Indx,petsc_Idx);
2550 
2551  (*it_new).second = nIndx_petsc;
2552  }
2553  }
2554  }
2555 }
2556 
2568 template<class T, class S>
2569 inline void update_faces_on_surface_mesh_after_decoupling_with_dofs(meshdata<T, S> & mesh,
2570  hashmap::unordered_map<std::pair<mesh_int_t,mesh_int_t>,
2571  mesh_int_t> & map_vertex_tag_to_dof,
2572  hashmap::unordered_map<tuple<T>,
2573  std::pair<emi_face<T,tuple<T>>, emi_face<T,tuple<T>>>> & line_face,
2574  hashmap::unordered_map<triple<T>,
2575  std::pair<emi_face<T,triple<T>>, emi_face<T,triple<T>>>> & tri_face,
2576  hashmap::unordered_map<quadruple<T>,
2577  std::pair<emi_face<T,quadruple<T>>, emi_face<T,quadruple<T>>>> & quad_face)
2578 {
2579  MPI_Comm comm = SF_COMM;
2580  int size, rank;
2581  MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank);
2582 
2583  SF::vector<mesh_int_t> idxbuff(mesh.con);
2584  binary_sort(idxbuff); unique_resize(idxbuff);
2585 
2588  for(size_t i=0; i<idxbuff.size(); i++){
2589  g2l[idxbuff[i]] = i;
2590  l2g[i] = idxbuff[i];
2591  }
2592 
2593  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++) {
2594  T tag = mesh.tag[eidx];
2595  T size_elem = mesh.dsp[eidx+1]-mesh.dsp[eidx];
2596  std::vector<mesh_int_t> elem_nodes;
2597  for (int n = mesh.dsp[eidx]; n < mesh.dsp[eidx+1];n++)
2598  {
2599  T gIndex = mesh.con[n];
2600  elem_nodes.push_back(gIndex);
2601  }
2602  std::sort(elem_nodes.begin(),elem_nodes.end());
2603  if(elem_nodes.size()==2){
2604  tuple<T> key;
2605  key.v1 = elem_nodes[0];
2606  key.v2 = elem_nodes[1];
2607  std::pair<emi_face<T,tuple<T>>, emi_face<T,tuple<T>>> value = line_face[key];
2608 
2609  line_face.erase(key);
2610  std::vector<mesh_int_t> new_nodes(2);
2611 
2612  auto tag_key = (value.first.rank == rank) ? value.first.tag : value.second.tag;
2613  {
2614  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
2615  Index_tag_old = std::make_pair(elem_nodes[0],tag_key);
2616  mesh_int_t Index_new = map_vertex_tag_to_dof[Index_tag_old];
2617  new_nodes[0] = Index_new;
2618  }
2619  {
2620  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
2621  Index_tag_old = std::make_pair(elem_nodes[1],tag_key);
2622  mesh_int_t Index_new = map_vertex_tag_to_dof[Index_tag_old];
2623  new_nodes[1] = Index_new;
2624  }
2625  std::sort(new_nodes.begin(),new_nodes.end());
2626  // update key
2627  tuple<T> new_key;
2628  new_key.v1 = new_nodes[0];
2629  new_key.v2 = new_nodes[1];
2630 
2631  // first
2632  {
2633  mesh_int_t Index_new1 = map_vertex_tag_to_dof[std::make_pair(value.first.points.v1, value.first.tag)];
2634  mesh_int_t Index_new2 = map_vertex_tag_to_dof[std::make_pair(value.first.points.v2, value.first.tag)];
2635  value.first.points.v1 = Index_new1;
2636  value.first.points.v2 = Index_new2;
2637  }
2638  // second
2639  {
2640  mesh_int_t Index_new1 = map_vertex_tag_to_dof[std::make_pair(value.second.points.v1, value.second.tag)];
2641  mesh_int_t Index_new2 = map_vertex_tag_to_dof[std::make_pair(value.second.points.v2, value.second.tag)];
2642  value.second.points.v1 = Index_new1;
2643  value.second.points.v2 = Index_new2;
2644  }
2645  line_face.insert({new_key,value});
2646  }
2647  if(elem_nodes.size()==3){
2648  triple<T> key;
2649  key.v1 = elem_nodes[0];
2650  key.v2 = elem_nodes[1];
2651  key.v3 = elem_nodes[2];
2652  std::pair<emi_face<T,triple<T>>, emi_face<T,triple<T>>> value = tri_face[key];
2653  tri_face.erase(key);
2654  std::vector<mesh_int_t> new_nodes(3);
2655 
2656  auto tag_key = (value.first.rank == rank) ? value.first.tag : value.second.tag;
2657  {
2658  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
2659  Index_tag_old = std::make_pair(elem_nodes[0],tag_key);
2660  mesh_int_t Index_new = map_vertex_tag_to_dof[Index_tag_old];
2661  new_nodes[0] = Index_new;
2662  }
2663  {
2664  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
2665  Index_tag_old = std::make_pair(elem_nodes[1],tag_key);
2666  mesh_int_t Index_new = map_vertex_tag_to_dof[Index_tag_old];
2667  new_nodes[1] = Index_new;
2668  }
2669  {
2670  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
2671  Index_tag_old = std::make_pair(elem_nodes[2],tag_key);
2672  mesh_int_t Index_new = map_vertex_tag_to_dof[Index_tag_old];
2673  new_nodes[2] = Index_new;
2674  }
2675  std::sort(new_nodes.begin(),new_nodes.end());
2676  // update key
2677  triple<T> new_key;
2678  new_key.v1 = new_nodes[0];
2679  new_key.v2 = new_nodes[1];
2680  new_key.v3 = new_nodes[2];
2681 
2682  // first
2683  {
2684  mesh_int_t Index_new1 = map_vertex_tag_to_dof[std::make_pair(value.first.points.v1, value.first.tag)];
2685  mesh_int_t Index_new2 = map_vertex_tag_to_dof[std::make_pair(value.first.points.v2, value.first.tag)];
2686  mesh_int_t Index_new3 = map_vertex_tag_to_dof[std::make_pair(value.first.points.v3, value.first.tag)];
2687  value.first.points.v1 = Index_new1;
2688  value.first.points.v2 = Index_new2;
2689  value.first.points.v3 = Index_new3;
2690  }
2691  // second
2692  {
2693  mesh_int_t Index_new1 = map_vertex_tag_to_dof[std::make_pair(value.second.points.v1, value.second.tag)];
2694  mesh_int_t Index_new2 = map_vertex_tag_to_dof[std::make_pair(value.second.points.v2, value.second.tag)];
2695  mesh_int_t Index_new3 = map_vertex_tag_to_dof[std::make_pair(value.second.points.v3, value.second.tag)];
2696  value.second.points.v1 = Index_new1;
2697  value.second.points.v2 = Index_new2;
2698  value.second.points.v3 = Index_new3;
2699  }
2700  tri_face.insert({new_key,value});
2701  }
2702  if(elem_nodes.size()==4){
2703  quadruple<T> key;
2704  key.v1 = elem_nodes[0];
2705  key.v2 = elem_nodes[1];
2706  key.v3 = elem_nodes[2];
2707  key.v4 = elem_nodes[3];
2708  std::pair<emi_face<T,quadruple<T>>, emi_face<T,quadruple<T>>> value = quad_face[key];
2709  quad_face.erase(key);
2710  std::vector<mesh_int_t> new_nodes(4);
2711 
2712  auto tag_key = (value.first.rank == rank) ? value.first.tag : value.second.tag;
2713  {
2714  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
2715  Index_tag_old = std::make_pair(elem_nodes[0],tag_key);
2716  mesh_int_t Index_new = map_vertex_tag_to_dof[Index_tag_old];
2717  new_nodes[0] = Index_new;
2718  }
2719  {
2720  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
2721  Index_tag_old = std::make_pair(elem_nodes[1],tag_key);
2722  mesh_int_t Index_new = map_vertex_tag_to_dof[Index_tag_old];
2723  new_nodes[1] = Index_new;
2724  }
2725  {
2726  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
2727  Index_tag_old = std::make_pair(elem_nodes[2],tag_key);
2728  mesh_int_t Index_new = map_vertex_tag_to_dof[Index_tag_old];
2729  new_nodes[2] = Index_new;
2730  }
2731  {
2732  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
2733  Index_tag_old = std::make_pair(elem_nodes[3],tag_key);
2734  mesh_int_t Index_new = map_vertex_tag_to_dof[Index_tag_old];
2735  new_nodes[3] = Index_new;
2736  }
2737  std::sort(new_nodes.begin(),new_nodes.end());
2738  // update key
2739  quadruple<T> new_key;
2740  new_key.v1 = new_nodes[0];
2741  new_key.v2 = new_nodes[1];
2742  new_key.v3 = new_nodes[2];
2743  new_key.v4 = new_nodes[3];
2744 
2745  // first
2746  {
2747  mesh_int_t Index_new1 = map_vertex_tag_to_dof[std::make_pair(value.first.points.v1, value.first.tag)];
2748  mesh_int_t Index_new2 = map_vertex_tag_to_dof[std::make_pair(value.first.points.v2, value.first.tag)];
2749  mesh_int_t Index_new3 = map_vertex_tag_to_dof[std::make_pair(value.first.points.v3, value.first.tag)];
2750  mesh_int_t Index_new4 = map_vertex_tag_to_dof[std::make_pair(value.first.points.v4, value.first.tag)];
2751  value.first.points.v1 = Index_new1;
2752  value.first.points.v2 = Index_new2;
2753  value.first.points.v3 = Index_new3;
2754  value.first.points.v4 = Index_new4;
2755  }
2756  // second
2757  {
2758  mesh_int_t Index_new1 = map_vertex_tag_to_dof[std::make_pair(value.second.points.v1, value.second.tag)];
2759  mesh_int_t Index_new2 = map_vertex_tag_to_dof[std::make_pair(value.second.points.v2, value.second.tag)];
2760  mesh_int_t Index_new3 = map_vertex_tag_to_dof[std::make_pair(value.second.points.v3, value.second.tag)];
2761  mesh_int_t Index_new4 = map_vertex_tag_to_dof[std::make_pair(value.second.points.v4, value.second.tag)];
2762  value.second.points.v1 = Index_new1;
2763  value.second.points.v2 = Index_new2;
2764  value.second.points.v3 = Index_new3;
2765  value.second.points.v4 = Index_new4;
2766  }
2767  quad_face.insert({new_key,value});
2768  }
2769  }
2770 }
2771 
2783 template<class T, class S> inline
2784 void compute_surface_mesh_with_counter_face(meshdata<T, S> & mesh, const SF_nbr numbering,
2785  hashmap::unordered_map<tuple<T>,
2786  std::pair<emi_face<T,tuple<T>>, emi_face<T,tuple<T>>>> & line_face,
2787  hashmap::unordered_map<triple<T>,
2788  std::pair<emi_face<T,triple<T>>, emi_face<T,triple<T>>>> & tri_face,
2789  hashmap::unordered_map<quadruple<T>,
2790  std::pair<emi_face<T,quadruple<T>>, emi_face<T,quadruple<T>>>> & quad_face)
2791 {
2792  mesh.register_numbering(numbering);
2793 
2794  MPI_Comm comm = SF_COMM;
2795  int size, rank;
2796  MPI_Comm_size(comm, &size);
2797  MPI_Comm_rank(comm, &rank);
2798 
2799  // To ensure deterministic order, we must sort the keys of the hashmaps before iterating.
2800  vector<tuple<T>> line_keys;
2801  for(auto const& [key, val] : line_face) line_keys.push_back(key);
2802  std::sort(line_keys.begin(), line_keys.end());
2803 
2804  vector<triple<T>> tri_keys;
2805  for(auto const& [key, val] : tri_face) tri_keys.push_back(key);
2806  std::sort(tri_keys.begin(), tri_keys.end());
2807 
2808  vector<quadruple<T>> quad_keys;
2809  for(auto const& [key, val] : quad_face) quad_keys.push_back(key);
2810  std::sort(quad_keys.begin(), quad_keys.end());
2811 
2812  vector<T> cnt(mesh.l_numelem);
2813  size_t idx = 0, cidx = 0;
2814 
2815  // Add faces to the surface mesh used in ionic computation.
2816  // For each {line,tri,quad}_face, add faces only if one of the pair matches the current rank,
2817  // or if both faces have the same rank, add both.
2818  for(const auto & key : line_keys) {
2819  const auto & v = line_face.at(key);
2820  bool both_faces = (v.first.rank == v.second.rank) ? true: false;
2821 
2822  cnt[idx] = 2;
2823 
2824  emi_face<T,tuple<T>> surf_first;
2825  emi_face<T,tuple<T>> surf_second;
2826  if(v.first.rank == rank)
2827  {
2828  surf_first = v.first;
2829  surf_second = v.second;
2830  }
2831  else
2832  {
2833  surf_first = v.second;
2834  surf_second = v.first;
2835  }
2836 
2837  mesh.type[idx] = Line;
2838  mesh.tag[idx] = surf_first.tag;
2839  mesh.con[cidx + 0] = surf_first.points.v1;
2840  mesh.con[cidx + 1] = surf_first.points.v2;
2841  idx += 1;
2842  cidx += 2;
2843 
2844  if(both_faces){
2845 
2846  cnt[idx] = 2;
2847  mesh.type[idx] = Line;
2848  mesh.tag[idx] = surf_second.tag;
2849  mesh.con[cidx + 0] = surf_second.points.v1;
2850  mesh.con[cidx + 1] = surf_second.points.v2;
2851 
2852  idx += 1;
2853  cidx += 2;
2854  }
2855  }
2856 
2857  for(const auto & key : tri_keys) {
2858  const auto & v = tri_face.at(key);
2859  bool both_faces = (v.first.rank == v.second.rank) ? true: false;
2860 
2861  cnt[idx] = 3;
2862 
2863  emi_face<T,triple<T>> surf_first;
2864  emi_face<T,triple<T>> surf_second;
2865  if(v.first.rank == rank)
2866  {
2867  surf_first = v.first;
2868  surf_second = v.second;
2869  }
2870  else
2871  {
2872  surf_first = v.second;
2873  surf_second = v.first;
2874  }
2875 
2876  mesh.type[idx] = Tri;
2877  mesh.tag[idx] = surf_first.tag;
2878  mesh.con[cidx + 0] = surf_first.points.v1;
2879  mesh.con[cidx + 1] = surf_first.points.v2;
2880  mesh.con[cidx + 2] = surf_first.points.v3;
2881 
2882  idx += 1;
2883  cidx += 3;
2884 
2885  if(both_faces){
2886 
2887  cnt[idx] = 3;
2888 
2889  mesh.type[idx] = Tri;
2890  mesh.tag[idx] = surf_second.tag;
2891  mesh.con[cidx + 0] = surf_second.points.v1;
2892  mesh.con[cidx + 1] = surf_second.points.v2;
2893  mesh.con[cidx + 2] = surf_second.points.v3;
2894 
2895  idx += 1;
2896  cidx += 3;
2897  }
2898  }
2899 
2900  for(const auto & key : quad_keys) {
2901  const auto & v = quad_face.at(key);
2902  bool both_faces = (v.first.rank == v.second.rank) ? true: false;
2903  cnt[idx] = 4;
2904 
2905  emi_face<T,quadruple<T>> surf_first;
2906  emi_face<T,quadruple<T>> surf_second;
2907  if(v.first.rank == rank)
2908  {
2909  surf_first = v.first;
2910  surf_second = v.second;
2911  }
2912  else
2913  {
2914  surf_first = v.second;
2915  surf_second = v.first;
2916  }
2917 
2918  mesh.type[idx] = Quad;
2919  mesh.tag[idx] = surf_first.tag;
2920  mesh.con[cidx + 0] = surf_first.points.v1;
2921  mesh.con[cidx + 1] = surf_first.points.v2;
2922  mesh.con[cidx + 2] = surf_first.points.v3;
2923  mesh.con[cidx + 3] = surf_first.points.v4;
2924 
2925  idx += 1;
2926  cidx += 4;
2927 
2928  if(both_faces){
2929 
2930  cnt[idx] = 4;
2931 
2932  mesh.type[idx] = Quad;
2933  mesh.tag[idx] = surf_second.tag;
2934  mesh.con[cidx + 0] = surf_second.points.v1;
2935  mesh.con[cidx + 1] = surf_second.points.v2;
2936  mesh.con[cidx + 2] = surf_second.points.v3;
2937  mesh.con[cidx + 3] = surf_second.points.v4;
2938 
2939  idx += 1;
2940  cidx += 4;
2941  }
2942  }
2943  dsp_from_cnt(cnt, mesh.dsp);
2944 }
2945 
2977 template<class T, class S> inline
2978 void compute_surface_mesh_with_unique_face(meshdata<T, S> & mesh, const SF_nbr numbering,
2979  hashmap::unordered_map<tuple<T>,
2980  std::pair<emi_face<T,tuple<T>>, emi_face<T,tuple<T>>>> & line_face,
2981  hashmap::unordered_map<triple<T>,
2982  std::pair<emi_face<T,triple<T>>, emi_face<T,triple<T>>>> & tri_face,
2983  hashmap::unordered_map<quadruple<T>,
2984  std::pair<emi_face<T,quadruple<T>>, emi_face<T,quadruple<T>>>> & quad_face,
2985  hashmap::unordered_map<mesh_int_t, std::pair<mesh_int_t, mesh_int_t>> & map_elem_uniqueFace_to_tags)
2986 {
2987  mesh.register_numbering(numbering);
2988 
2989  MPI_Comm comm = SF_COMM;
2990  int size, rank;
2991  MPI_Comm_size(comm, &size);
2992  MPI_Comm_rank(comm, &rank);
2993 
2994  // To ensure deterministic order, we must sort the keys of the hashmaps before iterating.
2995  vector<tuple<T>> line_keys;
2996  for(auto const& [key, val] : line_face) line_keys.push_back(key);
2997  std::sort(line_keys.begin(), line_keys.end());
2998 
2999  vector<triple<T>> tri_keys;
3000  for(auto const& [key, val] : tri_face) tri_keys.push_back(key);
3001  std::sort(tri_keys.begin(), tri_keys.end());
3002 
3003  vector<quadruple<T>> quad_keys;
3004  for(auto const& [key, val] : quad_face) quad_keys.push_back(key);
3005  std::sort(quad_keys.begin(), quad_keys.end());
3006 
3007  vector<T> cnt(mesh.l_numelem);
3008  size_t idx = 0, cidx = 0;
3009 
3010  // Add faces to the surface mesh used in ionic computation.
3011  // For each {line,tri,quad}_face, add faces only if one of the pair matches the current rank,
3012  // or if both faces have the same rank, add both.
3013  for(const auto & key : line_keys) {
3014  const auto & v = line_face.at(key);
3015 
3016  bool to_take_face = false;
3017 
3018  emi_face<T,tuple<T>> surf_take;
3019  std::pair <mesh_int_t,mesh_int_t> tag_pairs_take;
3020  {
3021  if(v.first.rank == rank && v.first.mark_to_take == true)
3022  {
3023  surf_take = v.first;
3024  to_take_face = true;
3025  tag_pairs_take = std::make_pair(v.first.tag,v.second.tag);
3026  }
3027  else if(v.second.rank == rank && v.second.mark_to_take == true)
3028  {
3029  surf_take = v.second;
3030  to_take_face = true;
3031  tag_pairs_take = std::make_pair(v.second.tag,v.first.tag);
3032  }
3033 
3034  if(to_take_face)
3035  {
3036  map_elem_uniqueFace_to_tags.insert({idx,tag_pairs_take});
3037  cnt[idx] = 2;
3038  mesh.type[idx] = Line;
3039  mesh.tag[idx] = surf_take.tag;
3040  mesh.con[cidx + 0] = surf_take.points.v1;
3041  mesh.con[cidx + 1] = surf_take.points.v2;
3042  idx += 1;
3043  cidx += 2;
3044  }
3045  }
3046  }
3047 
3048  for(const auto & key : tri_keys) {
3049  const auto & v = tri_face.at(key);
3050 
3051  bool to_take_face = false;
3052 
3053  emi_face<T,triple<T>> surf_take;
3054  std::pair <mesh_int_t,mesh_int_t> tag_pairs_take;
3055  {
3056  if(v.first.rank == rank && v.first.mark_to_take == true)
3057  {
3058  surf_take = v.first;
3059  to_take_face = true;
3060  tag_pairs_take = std::make_pair(v.first.tag,v.second.tag);
3061  }
3062  else if(v.second.rank == rank && v.second.mark_to_take == true)
3063  {
3064  surf_take = v.second;
3065  to_take_face = true;
3066  tag_pairs_take = std::make_pair(v.second.tag,v.first.tag);
3067  }
3068 
3069  if(to_take_face)
3070  {
3071  map_elem_uniqueFace_to_tags.insert({idx,tag_pairs_take});
3072  cnt[idx] = 3;
3073  mesh.type[idx] = Tri;
3074  mesh.tag[idx] = surf_take.tag;
3075  mesh.con[cidx + 0] = surf_take.points.v1;
3076  mesh.con[cidx + 1] = surf_take.points.v2;
3077  mesh.con[cidx + 2] = surf_take.points.v3;
3078 
3079  idx += 1;
3080  cidx += 3;
3081  }
3082  }
3083  }
3084 
3085  for(const auto & key : quad_keys) {
3086  const auto & v = quad_face.at(key);
3087 
3088  bool to_take_face = false;
3089 
3090  emi_face<T,quadruple<T>> surf_take;
3091  std::pair <mesh_int_t,mesh_int_t> tag_pairs_take;
3092  {
3093  if(v.first.rank == rank && v.first.mark_to_take == true)
3094  {
3095  surf_take = v.first;
3096  to_take_face = true;
3097  tag_pairs_take = std::make_pair(v.first.tag,v.second.tag);
3098  }
3099  else if(v.second.rank == rank && v.second.mark_to_take == true)
3100  {
3101  surf_take = v.second;
3102  to_take_face = true;
3103  tag_pairs_take = std::make_pair(v.second.tag,v.first.tag);
3104  }
3105 
3106  if(to_take_face)
3107  {
3108  map_elem_uniqueFace_to_tags.insert({idx,tag_pairs_take});
3109  cnt[idx] = 4;
3110  mesh.type[idx] = Quad;
3111  mesh.tag[idx] = surf_take.tag;
3112  mesh.con[cidx + 0] = surf_take.points.v1;
3113  mesh.con[cidx + 1] = surf_take.points.v2;
3114  mesh.con[cidx + 2] = surf_take.points.v3;
3115  mesh.con[cidx + 3] = surf_take.points.v4;
3116 
3117  idx += 1;
3118  cidx += 4;
3119  }
3120  }
3121  }
3122  dsp_from_cnt(cnt, mesh.dsp);
3123 }
3124 
3144 template<class T> inline
3145 void create_reverse_elem_mapping_between_surface_meshes(
3146  hashmap::unordered_map<tuple<T>, std::pair<emi_face<T,tuple<T>>, emi_face<T,tuple<T>>>>& line_face,
3147  hashmap::unordered_map<triple<T>, std::pair<emi_face<T,triple<T>>, emi_face<T,triple<T>>>>& tri_face,
3148  hashmap::unordered_map<quadruple<T>, std::pair<emi_face<T,quadruple<T>>, emi_face<T,quadruple<T>>>>& quad_face,
3149  SF::vector<T>& vec_one_to_both_face,
3150  MPI_Comm comm)
3151 {
3152  int rank;
3153  MPI_Comm_rank(comm, &rank);
3154 
3155  // To ensure deterministic order, we must sort the keys of the hashmaps before iterating.
3156  vector<tuple<T>> line_keys;
3157  for(auto const& [key, val] : line_face) line_keys.push_back(key);
3158  std::sort(line_keys.begin(), line_keys.end());
3159 
3160  vector<triple<T>> tri_keys;
3161  for(auto const& [key, val] : tri_face) tri_keys.push_back(key);
3162  std::sort(tri_keys.begin(), tri_keys.end());
3163 
3164  vector<quadruple<T>> quad_keys;
3165  for(auto const& [key, val] : quad_face) quad_keys.push_back(key);
3166  std::sort(quad_keys.begin(), quad_keys.end());
3167 
3168  size_t surf_elem_idx = 0;
3169  size_t w_counter_elem_idx = 0;
3170 
3171  size_t lsize_line = 0;
3172  size_t lsize_tri = 0;
3173  size_t lsize_quad = 0;
3174 
3175  // Count how many both-face elements are local on this rank.
3176  // If both faces are on the same rank, the both-face mesh has two entries.
3177  for(const auto& key : line_keys) {
3178  const auto& v = line_face.at(key);
3179  const bool local_involved = (v.first.rank == rank) || (v.second.rank == rank);
3180  if (!local_involved) {
3181  continue;
3182  }
3183  if(v.first.rank == v.second.rank) lsize_line+=2;
3184  else lsize_line+=1;
3185  }
3186  for(const auto& key : tri_keys) {
3187  const auto& v = tri_face.at(key);
3188  const bool local_involved = (v.first.rank == rank) || (v.second.rank == rank);
3189  if (!local_involved) {
3190  continue;
3191  }
3192  if(v.first.rank == v.second.rank) lsize_tri+=2;
3193  else lsize_tri+=1;
3194  }
3195  for(const auto& key : quad_keys) {
3196  const auto& v = quad_face.at(key);
3197  const bool local_involved = (v.first.rank == rank) || (v.second.rank == rank);
3198  if (!local_involved) {
3199  continue;
3200  }
3201  if(v.first.rank == v.second.rank) lsize_quad+=2;
3202  else lsize_quad+=1;
3203  }
3204 
3205  vec_one_to_both_face.resize(lsize_line + lsize_tri + lsize_quad);
3206 
3207  // Fill mapping in deterministic order:
3208  // each both-face element gets the index of its corresponding one-face element.
3209  for (const auto& key : line_keys) {
3210  const auto& v = line_face.at(key);
3211  const bool local_involved = (v.first.rank == rank) || (v.second.rank == rank);
3212  if (!local_involved) {
3213  continue;
3214  }
3215  vec_one_to_both_face[w_counter_elem_idx++] = surf_elem_idx;
3216  if (v.first.rank == v.second.rank) {
3217  vec_one_to_both_face[w_counter_elem_idx++] = surf_elem_idx;
3218  }
3219  surf_elem_idx++;
3220  }
3221 
3222  for (const auto& key : tri_keys) {
3223  const auto& v = tri_face.at(key);
3224  const bool local_involved = (v.first.rank == rank) || (v.second.rank == rank);
3225  if (!local_involved) {
3226  continue;
3227  }
3228  vec_one_to_both_face[w_counter_elem_idx++] = surf_elem_idx;
3229  if (v.first.rank == v.second.rank) {
3230  vec_one_to_both_face[w_counter_elem_idx++] = surf_elem_idx;
3231  }
3232  surf_elem_idx++;
3233  }
3234 
3235  for (const auto& key : quad_keys) {
3236  const auto& v = quad_face.at(key);
3237  const bool local_involved = (v.first.rank == rank) || (v.second.rank == rank);
3238  if (!local_involved) {
3239  continue;
3240  }
3241  vec_one_to_both_face[w_counter_elem_idx++] = surf_elem_idx;
3242  if (v.first.rank == v.second.rank) {
3243  vec_one_to_both_face[w_counter_elem_idx++] = surf_elem_idx;
3244  }
3245  surf_elem_idx++;
3246  }
3247 }
3248 
3257 template<class T> inline
3258 void added_counter_faces_to_map(hashmap::unordered_map<tuple<T>,
3259  std::pair<emi_face<T,tuple<T>>, emi_face<T,tuple<T>>>> & line_face,
3260  hashmap::unordered_map<triple<T>,
3261  std::pair<emi_face<T,triple<T>>, emi_face<T,triple<T>>>> & tri_face,
3262  hashmap::unordered_map<quadruple<T>,
3263  std::pair<emi_face<T,quadruple<T>>, emi_face<T,quadruple<T>>>> & quad_face)
3264 {
3265  // Buffer inserts not to mutate an unordered_map while iterating over it and later add to unordered_map
3266  std::vector<std::pair<tuple<T>, std::pair<emi_face<T,tuple<T>>, emi_face<T,tuple<T>>>>> line_inserts;
3267  for(const auto & v : line_face) {
3268  SF::emi_face<mesh_int_t,SF::tuple<mesh_int_t>> surf_first = v.second.first;
3269  SF::emi_face<mesh_int_t,SF::tuple<mesh_int_t>> surf_second = v.second.second;
3270 
3271  std::vector<mesh_int_t> elem_nodes_first; elem_nodes_first.resize(2);
3272  std::vector<mesh_int_t> elem_nodes_second; elem_nodes_second.resize(2);
3273 
3274  SF::tuple<mesh_int_t> key_first;
3275  elem_nodes_first[0] = surf_first.points.v1;
3276  elem_nodes_first[1] = surf_first.points.v2;
3277  std::sort(elem_nodes_first.begin(),elem_nodes_first.end());
3278  key_first.v1 = elem_nodes_first[0];
3279  key_first.v2 = elem_nodes_first[1];
3280 
3281  SF::tuple<mesh_int_t> key_second;
3282  elem_nodes_second[0] = surf_second.points.v1;
3283  elem_nodes_second[1] = surf_second.points.v2;
3284  std::sort(elem_nodes_second.begin(),elem_nodes_second.end());
3285  key_second.v1 = elem_nodes_second[0];
3286  key_second.v2 = elem_nodes_second[1];
3287 
3288  const bool same_key_first = (v.first.v1 == key_first.v1 && v.first.v2 == key_first.v2);
3289  if (!same_key_first && line_face.find(key_first) == line_face.end()) {
3290  line_inserts.push_back({key_first, v.second});
3291  }
3292  const bool same_key_second = (v.first.v1 == key_second.v1 && v.first.v2 == key_second.v2);
3293  if (!same_key_second && line_face.find(key_second) == line_face.end()) {
3294  line_inserts.push_back({key_second, v.second});
3295  }
3296  }
3297  for (const auto & entry : line_inserts) line_face.insert(entry);
3298 
3299  std::vector<std::pair<triple<T>, std::pair<emi_face<T,triple<T>>, emi_face<T,triple<T>>>>> tri_inserts;
3300  for(const auto & v : tri_face) {
3301  SF::emi_face<mesh_int_t,SF::triple<mesh_int_t>> surf_first = v.second.first;
3302  SF::emi_face<mesh_int_t,SF::triple<mesh_int_t>> surf_second = v.second.second;
3303 
3304  std::vector<mesh_int_t> elem_nodes_first; elem_nodes_first.resize(3);
3305  std::vector<mesh_int_t> elem_nodes_second; elem_nodes_second.resize(3);
3306 
3307  SF::triple<mesh_int_t> key_first;
3308  elem_nodes_first[0] = surf_first.points.v1;
3309  elem_nodes_first[1] = surf_first.points.v2;
3310  elem_nodes_first[2] = surf_first.points.v3;
3311  std::sort(elem_nodes_first.begin(),elem_nodes_first.end());
3312  key_first.v1 = elem_nodes_first[0];
3313  key_first.v2 = elem_nodes_first[1];
3314  key_first.v3 = elem_nodes_first[2];
3315 
3316  SF::triple<mesh_int_t> key_second;
3317  elem_nodes_second[0] = surf_second.points.v1;
3318  elem_nodes_second[1] = surf_second.points.v2;
3319  elem_nodes_second[2] = surf_second.points.v3;
3320  std::sort(elem_nodes_second.begin(),elem_nodes_second.end());
3321  key_second.v1 = elem_nodes_second[0];
3322  key_second.v2 = elem_nodes_second[1];
3323  key_second.v3 = elem_nodes_second[2];
3324 
3325  const bool same_key_first = (v.first.v1 == key_first.v1 &&
3326  v.first.v2 == key_first.v2 &&
3327  v.first.v3 == key_first.v3);
3328  if (!same_key_first && tri_face.find(key_first) == tri_face.end()) {
3329  tri_inserts.push_back({key_first, v.second});
3330  }
3331  const bool same_key_second = (v.first.v1 == key_second.v1 &&
3332  v.first.v2 == key_second.v2 &&
3333  v.first.v3 == key_second.v3);
3334  if (!same_key_second && tri_face.find(key_second) == tri_face.end()) {
3335  tri_inserts.push_back({key_second, v.second});
3336  }
3337  }
3338  for (const auto & entry : tri_inserts) tri_face.insert(entry);
3339 
3340  std::vector<std::pair<quadruple<T>, std::pair<emi_face<T,quadruple<T>>, emi_face<T,quadruple<T>>>>> quad_inserts;
3341  for(const auto & v : quad_face) {
3342  SF::emi_face<mesh_int_t,SF::quadruple<mesh_int_t>> surf_first = v.second.first;
3343  SF::emi_face<mesh_int_t,SF::quadruple<mesh_int_t>> surf_second = v.second.second;
3344 
3345  std::vector<mesh_int_t> elem_nodes_first; elem_nodes_first.resize(4);
3346  std::vector<mesh_int_t> elem_nodes_second; elem_nodes_second.resize(4);
3347 
3348  SF::quadruple<mesh_int_t> key_first;
3349  elem_nodes_first[0] = surf_first.points.v1;
3350  elem_nodes_first[1] = surf_first.points.v2;
3351  elem_nodes_first[2] = surf_first.points.v3;
3352  elem_nodes_first[3] = surf_first.points.v4;
3353  std::sort(elem_nodes_first.begin(),elem_nodes_first.end());
3354  key_first.v1 = elem_nodes_first[0];
3355  key_first.v2 = elem_nodes_first[1];
3356  key_first.v3 = elem_nodes_first[2];
3357  key_first.v4 = elem_nodes_first[3];
3358 
3359  SF::quadruple<mesh_int_t> key_second;
3360  elem_nodes_second[0] = surf_second.points.v1;
3361  elem_nodes_second[1] = surf_second.points.v2;
3362  elem_nodes_second[2] = surf_second.points.v3;
3363  elem_nodes_second[3] = surf_second.points.v4;
3364  std::sort(elem_nodes_second.begin(),elem_nodes_second.end());
3365  key_second.v1 = elem_nodes_second[0];
3366  key_second.v2 = elem_nodes_second[1];
3367  key_second.v3 = elem_nodes_second[2];
3368  key_second.v4 = elem_nodes_second[3];
3369 
3370  const bool same_key_first = (v.first.v1 == key_first.v1 &&
3371  v.first.v2 == key_first.v2 &&
3372  v.first.v3 == key_first.v3 &&
3373  v.first.v4 == key_first.v4);
3374  if (!same_key_first && quad_face.find(key_first) == quad_face.end()) {
3375  quad_inserts.push_back({key_first, v.second});
3376  }
3377  const bool same_key_second = (v.first.v1 == key_second.v1 &&
3378  v.first.v2 == key_second.v2 &&
3379  v.first.v3 == key_second.v3 &&
3380  v.first.v4 == key_second.v4);
3381  if (!same_key_second && quad_face.find(key_second) == quad_face.end()) {
3382  quad_inserts.push_back({key_second, v.second});
3383  }
3384  }
3385  for (const auto & entry : quad_inserts) quad_face.insert(entry);
3386 }
3387 
3395 template<class K> inline
3396 void assign_counter_tags(hashmap::unordered_map<K, intersection_tags>& map, const MPI_Comm comm)
3397 {
3398  size_t dsize = map.size();
3399  vector<K> key_vec(dsize);
3400  vector<intersection_tags> value_vec(dsize);
3401 
3402  size_t idx = 0;
3403  for (const auto& v : map) {
3404  key_vec[idx] = v.first;
3405  value_vec[idx] = v.second;
3406  idx++;
3407  }
3408 
3409  vector<int> perm, dest;
3410  emi_select_merge_destinations(key_vec, dsize, sizeof(K) + sizeof(intersection_tags), dest, comm,
3411  "assign_counter_tags",
3412  [](const K& key) { return hashmap::hash_ops<K>::hash(key); });
3413 
3414  commgraph<size_t> grph;
3415  grph.configure(dest, comm);
3416  size_t nrecv = sum(grph.rcnt);
3417 
3418  interval(perm, 0, dsize);
3419  binary_sort_copy(dest, perm);
3420 
3421  // fill send buffer and communicate
3422  vector<K> sbuff_key(dsize), rbuff_key(nrecv);
3423  vector<intersection_tags> sbuff_value(dsize), ibuff_value(dsize), rbuff_value(nrecv);
3424  for (size_t i = 0; i < dsize; i++) {
3425  sbuff_key[i] = key_vec[perm[i]];
3426  sbuff_value[i] = value_vec[perm[i]];
3427  }
3428  MPI_Exchange(grph, sbuff_key, rbuff_key, comm);
3429  MPI_Exchange(grph, sbuff_value, rbuff_value, comm);
3430 
3432  for (size_t i = 0; i < nrecv; i++)
3433  {
3434  auto it = rmap.find(rbuff_key[i]);
3435  if (it != rmap.end())
3436  {
3437  intersection_tags& map_tags = it->second;
3438  intersection_tags& r_tags = rbuff_value[i];
3439 
3440  for (int r_tag : r_tags.tags)
3441  {
3442  if (r_tag == -1) continue;
3443  bool found = false;
3444  for (int m_tag : map_tags.tags)
3445  {
3446  if (m_tag == r_tag) {
3447  found = true;
3448  break;
3449  }
3450  }
3451  if (!found)
3452  {
3453  for (size_t k = 0; k < MAX_INTERSECTIONS; ++k) {
3454  if (map_tags.tags[k] == -1) {
3455  map_tags.tags[k] = r_tag;
3456  break;
3457  }
3458  }
3459  }
3460  }
3461  }
3462  else
3463  {
3464  rmap.insert({ rbuff_key[i], rbuff_value[i] });
3465  }
3466  }
3467 
3468  for (size_t i = 0; i < nrecv; i++) {
3469  auto it = rmap.find(rbuff_key[i]);
3470  if (it != rmap.end()) rbuff_value[i] = it->second;
3471  }
3472 
3473  // sending the assigned data back to original rank
3474  grph.transpose();
3475  MPI_Exchange(grph, rbuff_value, ibuff_value, comm);
3476 
3477  for (size_t i = 0; i < dsize; i++) {
3478  auto it = map.find(sbuff_key[i]);
3479  if (it != map.end()) it->second = ibuff_value[i];
3480  }
3481 }
3482 
3504 template<class T, class S> inline
3505 void compute_ptsdata_from_original_mesh(meshdata<T,S> & mesh,
3506  const SF_nbr numbering,
3507  hashmap::unordered_map<T,T> & vertex2ptsdata,
3508  hashmap::unordered_set<int> extra_tags,
3509  hashmap::unordered_set<int> intra_tags)
3510 {
3511  MPI_Comm comm = mesh.comm;
3512  int size, rank;
3513  MPI_Comm_size(comm, &size);
3514  MPI_Comm_rank(comm, &rank);
3515 
3516  const T* con = mesh.con.data();
3517  const SF::vector<T> & rnod = mesh.get_numbering(numbering);
3518 
3519  // Step 1: Gather all unique region tags associated with each vertex.
3520  // This map is local to each process and will store a list of tags for each vertex
3521  // based on the elements owned by the current process.
3523  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++)
3524  {
3525  T tag = mesh.tag[eidx];
3526  for (int n = mesh.dsp[eidx]; n < mesh.dsp[eidx+1];n++)
3527  {
3528  T gIndex = rnod[con[n]];
3529 
3530  auto it_new = map_index_to_tags.find(gIndex);
3531  if (it_new != map_index_to_tags.end())
3532  {
3533  // If vertex is already in the map, add the new tag if it's not already present.
3534  intersection_tags& tags = it_new->second;
3535  bool found = false;
3536  for(T t : tags.tags) {
3537  if(t == tag) {
3538  found = true;
3539  break;
3540  }
3541  }
3542  if(!found) {
3543  // This is an intracellular tag. Find an empty slot and add it.
3544  for(int i=0; i <MAX_INTERSECTIONS; ++i) {
3545  if (tags.tags[i] == -1) {
3546  tags.tags[i] = tag;
3547  break;
3548  }
3549  }
3550  }
3551  }
3552  else{
3553  // If vertex is not in the map, add it with the current tag.
3554  intersection_tags tags;
3555  tags.tags[0] = tag;
3556  map_index_to_tags.insert({gIndex,tags});
3557  }
3558  }
3559  }
3560 
3561  // Step 2: Globalize the tag information.
3562  // After this call, `map_index_to_tags` on every process will contain the
3563  // complete list of unique tags for every vertex in the entire mesh.
3564  assign_counter_tags(map_index_to_tags, comm);
3565 
3566  // Step 3: Classify each vertex based on its global list of tags.
3567  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++)
3568  {
3569  for (int n = mesh.dsp[eidx]; n < mesh.dsp[eidx+1];n++)
3570  {
3571  T gIndex = rnod[con[n]];
3572  intersection_tags& vec_tags = map_index_to_tags[gIndex];
3573  int count_intra_tags = 0;
3574  int count_extra_tags = 0;
3575 
3576  // Count the number of unique intracellular and extracellular tags for the vertex.
3577  int count_tags = 0;
3578  for(T t : vec_tags.tags) {
3579  if(intra_tags.count(t)) count_intra_tags++;
3580  if(extra_tags.count(t)) count_extra_tags++;
3581  }
3582 
3583  // Apply classification rules based on the tag counts.
3584  if(count_extra_tags>=1 && count_intra_tags==0){
3585  vertex2ptsdata[gIndex] = 0; // Interior to an extracellular region
3586  }else if(count_extra_tags==0 && count_intra_tags==1){
3587  vertex2ptsdata[gIndex] = 0; // Interior to a myocyte
3588  }else if(count_extra_tags>=1 && count_intra_tags==1){
3589  vertex2ptsdata[gIndex] = 1; // On a membrane (1 myocyte, 1 extracellular)
3590  }else if(count_extra_tags==0 && count_intra_tags==2){
3591  vertex2ptsdata[gIndex] = 2; // On a gap junction (2 myocytes)
3592  }else if(count_extra_tags>=1 && count_intra_tags==2){
3593  vertex2ptsdata[gIndex] = 3; // On a complex junction (2 myocytes, 1 extracellular)
3594  }
3595  else if(count_intra_tags>2){
3596  // Error for unhandled cases.
3597  fprintf(stderr, "More than two intracellular are connected %lld. Tags:",
3598  static_cast<long long>(gIndex));
3599  for(T tag : vec_tags.tags) if(tag != -1) fprintf(stderr, " %lld", static_cast<long long>(tag));
3600  fprintf(stderr, "\n");
3601  EXIT(1);
3602  }
3603  else{
3604  // Error for unhandled cases.
3605  fprintf(stderr, "WARN: unhandled case in ptsData computation for gIndex %lld. Tags:",
3606  static_cast<long long>(gIndex));
3607  for(T tag : vec_tags.tags) if(tag != -1) fprintf(stderr, " %lld", static_cast<long long>(tag));
3608  fprintf(stderr, "\n");
3609  EXIT(1);
3610  }
3611  }
3612  }
3613 }
3614 
3615 
3631 template <class T, class S, class V, class emi_index_rank>
3632 inline void assemble_map_both_to_unique(SF::abstract_matrix<T, S>& op,
3634  std::pair<emi_index_rank, emi_index_rank>>& map,
3635  const SF::meshdata<mesh_int_t, V>& unique_mesh,
3636  const SF::meshdata<mesh_int_t, V>& both_mesh)
3637 {
3638  // Recover global row/column ownership so locally assembled entries can be
3639  // inserted directly with global matrix indices.
3640  int rank;
3641  MPI_Comm_rank(both_mesh.comm, &rank);
3642 
3643  SF::vector<long int> layout_both;
3644  SF::layout_from_count<long int>(both_mesh.l_numelem, layout_both, both_mesh.comm);
3645 
3646  SF::vector<long int> layout_unique;
3647  SF::layout_from_count<long int>(unique_mesh.l_numelem, layout_unique, unique_mesh.comm);
3648 
3649  SF::vector<SF_int> row_idx(1), col_idx(1);
3650  SF::dmat<SF_real> ebuff(1, 1);
3651 
3652  // Assemble one restriction row per locally owned unique face.
3653  for (const auto& [local_unique_idx, both_pair] : map) {
3654  const auto& first_both = both_pair.first;
3655  const auto& second_both = both_pair.second;
3656 
3657  // The unique-face row is owned by this rank, so its global row index comes
3658  // from the unique-face layout plus the local unique-face element id.
3659  T global_unique_row = layout_unique[rank] + local_unique_idx;
3660  row_idx[0] = global_unique_row;
3661 
3662  // Resolve the candidate both-face columns and discard invalid sides.
3663  bool first_valid = (first_both.index >= 0 && first_both.rank >= 0);
3664  bool second_valid = (second_both.index >= 0 && second_both.rank >= 0);
3665 
3666  T global_both_col_first = -1;
3667  T global_both_col_second = -1;
3668  if (first_valid) {
3669  global_both_col_first = layout_both[first_both.rank] + first_both.index;
3670  }
3671  if (second_valid) {
3672  global_both_col_second = layout_both[second_both.rank] + second_both.index;
3673  }
3674 
3675  // If both sides collapse to the same global both-face column, do not insert
3676  // the same contribution twice; treat it as a single-sided restriction row.
3677  if (first_valid && second_valid && global_both_col_first == global_both_col_second) {
3678  second_valid = false;
3679  }
3680 
3681  // Count how many distinct both-face columns contribute to this unique face.
3682  int count = 0;
3683  if (first_valid) count++;
3684  if (second_valid) count++;
3685 
3686  if (count == 0) continue; // No data to average
3687 
3688  // Use arithmetic averaging when both sides exist; otherwise preserve the
3689  // single available value without halving it.
3690  double weight = 0.5;
3691  if (count == 1) weight = 1.0;
3692 
3693  ebuff.assign(1, 1, weight);
3694 
3695  // Insert the first contributing both-face column.
3696  if (first_valid) {
3697  col_idx[0] = global_both_col_first;
3698  op.set_values(row_idx, col_idx, ebuff.data(), false);
3699  }
3700 
3701  // Insert the second contributing both-face column when present.
3702  if (second_valid) {
3703  col_idx[0] = global_both_col_second;
3704  op.set_values(row_idx, col_idx, ebuff.data(), false);
3705  }
3706  }
3707 
3708  op.finish_assembly();
3709 }
3710 
3727 template<class T>
3728 inline void restrict_to_membrane(vector<T> & v,
3729  hashmap::unordered_map<T,T> & dof2ptsData,
3730  const meshdata<mesh_int_t, mesh_real_t> & mesh)
3731 {
3732  const SF::vector<SF_int> & rnod = mesh.get_numbering(SF::NBR_REF);
3735  for(size_t i=0; i<rnod.size(); i++){
3736  g2l[rnod[i]] = i;
3737  l2g[i] = rnod[i];
3738  }
3739 
3740  size_t widx = 0;
3741 
3742  for(size_t i=0; i<v.size(); i++){
3743  T g = l2g[v[i]];
3744 
3745  if (dof2ptsData[g] > 0) { // dof2ptsData requires global index
3746  v[widx++] = v[i];
3747  }
3748  }
3749 
3750  v.resize(widx);
3751 }
3752 
3753 }
3754 
3755 #endif
3756 #endif
opencarp::local_index_t mesh_int_t
Definition: SF_container.h:46
#define SF_COMM
the default SlimFem MPI communicator
Definition: SF_globals.h:28
Functions related to EMI mesh IO.
Functions handling a distributed mesh.
Definition: mesher.cc:245
virtual void finish_assembly()=0
virtual void set_values(const vector< T > &row_idx, const vector< T > &col_idx, const vector< S > &vals, bool add)=0
Dense matrix class.
Definition: dense_mat.hpp:43
The mesh storage class. It contains both element and vertex data.
Definition: SF_container.h:396
size_t l_numelem
local number of elements
Definition: SF_container.h:399
MPI_Comm comm
the parallel mesh is defined on a MPI world
Definition: SF_container.h:404
A vector storing arbitrary data.
Definition: SF_vector.h:43
size_t size() const
The current size of the vector.
Definition: SF_vector.h:104
void resize(size_t n)
Resize a vector.
Definition: SF_vector.h:209
iterator find(const K &key)
Search for key. Return iterator.
Definition: hashmap.hpp:641
void sort(Compare comp=Compare())
Sort data entries.
Definition: hashmap.hpp:707
void insert(InputIterator first, InputIterator last)
Insert Iterator range.
Definition: hashmap.hpp:587
T & at(const K &key)
Data access by key.
Definition: hashmap.hpp:661
size_t size() const
Definition: hashmap.hpp:735
iterator find(const K &key)
Definition: hashmap.hpp:1096
hm_int count(const K &key) const
Definition: hashmap.hpp:1082
@ Line
Definition: filament.h:16
@ Quad
Definition: filament.h:16
@ Tri
Definition: filament.h:16
Definition: dense_mat.hpp:34
void dsp_from_cnt(const vector< T > &cnt, vector< T > &dsp)
Compute displacements from counts.
Definition: SF_vector.h:310
void interval(vector< T > &vec, size_t start, size_t end)
Create an integer interval between start and end.
Definition: SF_vector.h:350
void binary_sort_copy(vector< T > &_V, vector< S > &_W)
Definition: SF_sort.h:296
T sum(const vector< T > &vec)
Compute sum of a vector's entries.
Definition: SF_vector.h:340
void unique_resize(vector< T > &_P)
Definition: SF_sort.h:348
void count(const vector< T > &data, vector< S > &cnt)
Count number of occurrences of indices.
Definition: SF_vector.h:332
void MPI_Exchange(commgraph< T > &grph, vector< S > &send, vector< S > &recv, MPI_Comm comm)
Exchange data in parallel over MPI.
Definition: SF_network.h:47
void binary_sort(vector< T > &_V)
Definition: SF_sort.h:284
@ Tri
Definition: SF_container.h:60
@ Prism
Definition: SF_container.h:58
@ Pyramid
Definition: SF_container.h:57
@ Tetra
Definition: SF_container.h:54
@ Quad
Definition: SF_container.h:59
@ Hexa
Definition: SF_container.h:55
SF_nbr
Enumeration encoding the different supported numberings.
Definition: SF_container.h:200
@ 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
void dsp_from_cnt(const std::vector< T > &cnt, std::vector< T > &dsp)
Compute displacements from counts.
Definition: kdpart.hpp:140
constexpr T min(T a, T b)
Definition: ion_type.h:33
constexpr T max(T a, T b)
Definition: ion_type.h:31
static hm_uint hash(const T &a)
Definition: hashmap.hpp:92