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