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 total number of vertices in the original
2271  // mesh and adding the shift counts from all processes with a smaller rank. This is a parallel prefix sum, and it guarantees that the ranges
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  T start;
2277  if(rank==0){
2278  start = mesh.g_numpts;
2279  }
2280  else
2281  {
2282  start = mesh.g_numpts;
2283  for (int r = 0; r < rank; ++r)
2284  {
2285  start+= dsp_dof[r];
2286  }
2287  }
2288 
2289  T count = 0;
2290  auto lexicographic_comp_pair = [](const std::pair<mesh_int_t,mesh_int_t> & a, const std::pair<mesh_int_t,mesh_int_t> & b)
2291  {
2292  if (a.second == b.second) return a.first < b.first;
2293  return a.second < b.second;
2294  };
2295 
2296  map_mark_new_dofs.sort(lexicographic_comp_pair);
2297 
2298  for(const auto & entry : map_mark_new_dofs)
2299  {
2300  T newIndex = start+count;
2301  map_vertex_tag_to_dof.insert({entry.first,newIndex});
2302  count++;
2303  }
2304 }
2305 
2317 template<class K, class IntersectionIndices> inline
2318 void assign_counter_dofs(hashmap::unordered_map<K, IntersectionIndices>& map, const MPI_Comm comm)
2319 {
2320  size_t dsize = map.size();
2321  vector<K> key_vec(dsize);
2322  vector<IntersectionIndices> value_vec(dsize);
2323  IntersectionIndices indices;
2324  size_t idx = 0;
2325  for (const auto& v : map) {
2326  key_vec[idx] = v.first;
2327  value_vec[idx] = v.second;
2328  idx++;
2329  }
2330 
2331  vector<int> perm, dest;
2332  emi_select_merge_destinations(key_vec, dsize, sizeof(K) + sizeof(intersection_indices), dest, comm,
2333  "assign_counter_dofs",
2334  [](const K& key) { return hashmap::hash_ops<K>::hash(key); });
2335 
2336  commgraph<size_t> grph;
2337  grph.configure(dest, comm);
2338  size_t nrecv = sum(grph.rcnt);
2339 
2340  interval(perm, 0, dsize);
2341  binary_sort_copy(dest, perm);
2342 
2343  // fill send buffer and communicate
2344  vector<K> sbuff_key(dsize), rbuff_key(nrecv);
2345  vector<IntersectionIndices> sbuff_value(dsize), ibuff_value(dsize), rbuff_value(nrecv);
2346  for (size_t i = 0; i < dsize; i++) {
2347  sbuff_key[i] = key_vec[perm[i]];
2348  sbuff_value[i] = value_vec[perm[i]];
2349  }
2350  MPI_Exchange(grph, sbuff_key, rbuff_key, comm);
2351  MPI_Exchange(grph, sbuff_value, rbuff_value, comm);
2352 
2354  for (size_t i = 0; i < nrecv; i++)
2355  {
2356  auto it = rmap.find(rbuff_key[i]);
2357  if (it != rmap.end())
2358  {
2359  IntersectionIndices& map_indices = it->second;
2360  IntersectionIndices& received_indices = rbuff_value[i];
2361 
2362  for (mesh_int_t received_index : received_indices.indices)
2363  {
2364  if (received_index == -1) continue;
2365  bool found = false;
2366  for (mesh_int_t map_index : map_indices.indices)
2367  {
2368  if (map_index == received_index) {
2369  found = true;
2370  break;
2371  }
2372  }
2373  if (!found)
2374  {
2375  for (size_t k = 0; k < MAX_INTERSECTIONS; ++k) {
2376  if (map_indices.indices[k] == -1) {
2377  map_indices.indices[k] = received_index;
2378  break;
2379  }
2380  }
2381  }
2382  }
2383  }
2384  else
2385  {
2386  rmap.insert({ rbuff_key[i], rbuff_value[i] });
2387  }
2388  }
2389 
2390  for (size_t i = 0; i < nrecv; i++) {
2391  auto it = rmap.find(rbuff_key[i]);
2392  if (it != rmap.end()) rbuff_value[i] = it->second;
2393  }
2394 
2395  // sending the assigned data back to original rank
2396  grph.transpose();
2397  MPI_Exchange(grph, rbuff_value, ibuff_value, comm);
2398 
2399  for (size_t i = 0; i < dsize; i++) {
2400  auto it = map.find(sbuff_key[i]);
2401  if (it != map.end()) it->second = ibuff_value[i];
2402  }
2403 }
2414 template<class T, class S> inline
2415 void update_emi_mesh_with_dofs(meshdata<T,S> & mesh,
2416  const SF_nbr numbering,
2417  hashmap::unordered_map<std::pair<mesh_int_t,mesh_int_t>,
2418  mesh_int_t> & map_vertex_tag_to_dof,
2420 {
2421  // map between old index to set of new indices
2423  MPI_Comm comm = mesh.comm;
2424  mesh.globalize(numbering);
2425  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++) {
2426  T tag = mesh.tag[eidx];
2427  T size_elem = mesh.dsp[eidx+1]-mesh.dsp[eidx];
2428 
2429  for (int n = mesh.dsp[eidx]; n < mesh.dsp[eidx+1];n++)
2430  {
2431  T gIndex = mesh.con[n];
2432 
2433  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
2434  Index_tag_old = std::make_pair(gIndex,tag);
2435 
2436  auto it_new = map_vertex_tag_to_dof.find(Index_tag_old);
2437  if (it_new != map_vertex_tag_to_dof.end() )
2438  {
2439  mesh_int_t dof_new = (*it_new).second;
2440  dof2vertex.insert({dof_new, gIndex});
2441  mesh.con[n] = dof_new;
2442 
2443  auto it = vertex2dof.find(gIndex);
2444  if (it != vertex2dof.end())
2445  {
2446  intersection_indices& indices = it->second;
2447  bool found = false;
2448  for(T t : indices.indices) {
2449  if(t == dof_new) {
2450  found = true;
2451  break;
2452  }
2453  }
2454  if(!found) {
2455  // 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.
2456  for(size_t i=0; i<MAX_INTERSECTIONS; ++i) {
2457  if(indices.indices[i] == -1) {
2458  indices.indices[i] = dof_new;
2459  break;
2460  }
2461  }
2462  }
2463  }else{
2464  intersection_indices indices;
2465  indices.indices[0] = dof_new;
2466  vertex2dof.insert({gIndex,indices});
2467  }
2468  }
2469  }
2470  }
2471 
2472  assign_counter_dofs(vertex2dof, comm);
2473 
2474  // Now update dof2vertex using vertex2dof
2475  for (const auto& [old_idx, indices] : vertex2dof) {
2476  for (mesh_int_t new_idx : indices.indices) {
2477  if (new_idx != -1) {
2478  // Avoid overwriting unless necessary
2479  if (dof2vertex.find(new_idx) == dof2vertex.end()) {
2480  dof2vertex.insert({new_idx, old_idx});
2481  }
2482  }
2483  }
2484  }
2485 
2486  mesh.localize(numbering);
2487 }
2488 
2502 template<class T, class S> inline
2503 void update_map_indices_to_petsc(meshdata<T,S> & mesh,
2504  const SF_nbr numbering_ref,const SF_nbr numbering_petsc,
2505  const hashmap::unordered_set<int> & extra_tags,
2506  hashmap::unordered_map<std::pair<mesh_int_t,mesh_int_t>,
2507  std::pair<mesh_int_t,mesh_int_t>> & map_vertex_tag_to_dof_petsc,
2509  SF::vector<mesh_int_t> & elemTag_emi_mesh)
2510 {
2511  const SF::vector<T> & ref_nbr = mesh.get_numbering(numbering_ref);
2512  const SF::vector<T> & petsc_nbr = mesh.get_numbering(numbering_petsc);
2513 
2514  elemTag_emi_mesh.resize(mesh.l_numelem);
2515  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++) {
2516  T tag = mesh.tag[eidx];
2517  elemTag_emi_mesh[eidx] = 1; // assigned 1 for for extracellular located on extracellular side
2518  if(extra_tags.find(tag) == extra_tags.end())
2519  elemTag_emi_mesh[eidx] = 2; // assigned 2 for for intracellular located on myocyte
2520  for (int n = mesh.dsp[eidx]; n < mesh.dsp[eidx+1];n++)
2521  {
2522  T l_Indx = mesh.con[n];
2523  T petsc_Idx = petsc_nbr[l_Indx];
2524  T n_Indx = ref_nbr[l_Indx];
2525  T o_Indx = dof2vertex[n_Indx];
2526 
2527  std::pair <mesh_int_t,mesh_int_t> oIndx_tag;
2528  oIndx_tag = std::make_pair(o_Indx,tag);
2529 
2530  auto it_new = map_vertex_tag_to_dof_petsc.find(oIndx_tag);
2531  if (it_new != map_vertex_tag_to_dof_petsc.end() )
2532  {
2533  std::pair <mesh_int_t,mesh_int_t> nIndx_petsc;
2534  nIndx_petsc = std::make_pair(n_Indx,petsc_Idx);
2535 
2536  (*it_new).second = nIndx_petsc;
2537  }
2538  }
2539  }
2540 }
2541 
2553 template<class T, class S>
2554 inline void update_faces_on_surface_mesh_after_decoupling_with_dofs(meshdata<T, S> & mesh,
2555  hashmap::unordered_map<std::pair<mesh_int_t,mesh_int_t>,
2556  mesh_int_t> & map_vertex_tag_to_dof,
2557  hashmap::unordered_map<tuple<T>,
2558  std::pair<emi_face<T,tuple<T>>, emi_face<T,tuple<T>>>> & line_face,
2559  hashmap::unordered_map<triple<T>,
2560  std::pair<emi_face<T,triple<T>>, emi_face<T,triple<T>>>> & tri_face,
2561  hashmap::unordered_map<quadruple<T>,
2562  std::pair<emi_face<T,quadruple<T>>, emi_face<T,quadruple<T>>>> & quad_face)
2563 {
2564  MPI_Comm comm = SF_COMM;
2565  int size, rank;
2566  MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank);
2567 
2568  SF::vector<mesh_int_t> idxbuff(mesh.con);
2569  binary_sort(idxbuff); unique_resize(idxbuff);
2570 
2573  for(size_t i=0; i<idxbuff.size(); i++){
2574  g2l[idxbuff[i]] = i;
2575  l2g[i] = idxbuff[i];
2576  }
2577 
2578  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++) {
2579  T tag = mesh.tag[eidx];
2580  T size_elem = mesh.dsp[eidx+1]-mesh.dsp[eidx];
2581  std::vector<mesh_int_t> elem_nodes;
2582  for (int n = mesh.dsp[eidx]; n < mesh.dsp[eidx+1];n++)
2583  {
2584  T gIndex = mesh.con[n];
2585  elem_nodes.push_back(gIndex);
2586  }
2587  std::sort(elem_nodes.begin(),elem_nodes.end());
2588  if(elem_nodes.size()==2){
2589  tuple<T> key;
2590  key.v1 = elem_nodes[0];
2591  key.v2 = elem_nodes[1];
2592  std::pair<emi_face<T,tuple<T>>, emi_face<T,tuple<T>>> value = line_face[key];
2593 
2594  line_face.erase(key);
2595  std::vector<mesh_int_t> new_nodes(2);
2596 
2597  auto tag_key = (value.first.rank == rank) ? value.first.tag : value.second.tag;
2598  {
2599  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
2600  Index_tag_old = std::make_pair(elem_nodes[0],tag_key);
2601  mesh_int_t Index_new = map_vertex_tag_to_dof[Index_tag_old];
2602  new_nodes[0] = Index_new;
2603  }
2604  {
2605  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
2606  Index_tag_old = std::make_pair(elem_nodes[1],tag_key);
2607  mesh_int_t Index_new = map_vertex_tag_to_dof[Index_tag_old];
2608  new_nodes[1] = Index_new;
2609  }
2610  std::sort(new_nodes.begin(),new_nodes.end());
2611  // update key
2612  tuple<T> new_key;
2613  new_key.v1 = new_nodes[0];
2614  new_key.v2 = new_nodes[1];
2615 
2616  // first
2617  {
2618  mesh_int_t Index_new1 = map_vertex_tag_to_dof[std::make_pair(value.first.points.v1, value.first.tag)];
2619  mesh_int_t Index_new2 = map_vertex_tag_to_dof[std::make_pair(value.first.points.v2, value.first.tag)];
2620  value.first.points.v1 = Index_new1;
2621  value.first.points.v2 = Index_new2;
2622  }
2623  // second
2624  {
2625  mesh_int_t Index_new1 = map_vertex_tag_to_dof[std::make_pair(value.second.points.v1, value.second.tag)];
2626  mesh_int_t Index_new2 = map_vertex_tag_to_dof[std::make_pair(value.second.points.v2, value.second.tag)];
2627  value.second.points.v1 = Index_new1;
2628  value.second.points.v2 = Index_new2;
2629  }
2630  line_face.insert({new_key,value});
2631  }
2632  if(elem_nodes.size()==3){
2633  triple<T> key;
2634  key.v1 = elem_nodes[0];
2635  key.v2 = elem_nodes[1];
2636  key.v3 = elem_nodes[2];
2637  std::pair<emi_face<T,triple<T>>, emi_face<T,triple<T>>> value = tri_face[key];
2638  tri_face.erase(key);
2639  std::vector<mesh_int_t> new_nodes(3);
2640 
2641  auto tag_key = (value.first.rank == rank) ? value.first.tag : value.second.tag;
2642  {
2643  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
2644  Index_tag_old = std::make_pair(elem_nodes[0],tag_key);
2645  mesh_int_t Index_new = map_vertex_tag_to_dof[Index_tag_old];
2646  new_nodes[0] = Index_new;
2647  }
2648  {
2649  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
2650  Index_tag_old = std::make_pair(elem_nodes[1],tag_key);
2651  mesh_int_t Index_new = map_vertex_tag_to_dof[Index_tag_old];
2652  new_nodes[1] = Index_new;
2653  }
2654  {
2655  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
2656  Index_tag_old = std::make_pair(elem_nodes[2],tag_key);
2657  mesh_int_t Index_new = map_vertex_tag_to_dof[Index_tag_old];
2658  new_nodes[2] = Index_new;
2659  }
2660  std::sort(new_nodes.begin(),new_nodes.end());
2661  // update key
2662  triple<T> new_key;
2663  new_key.v1 = new_nodes[0];
2664  new_key.v2 = new_nodes[1];
2665  new_key.v3 = new_nodes[2];
2666 
2667  // first
2668  {
2669  mesh_int_t Index_new1 = map_vertex_tag_to_dof[std::make_pair(value.first.points.v1, value.first.tag)];
2670  mesh_int_t Index_new2 = map_vertex_tag_to_dof[std::make_pair(value.first.points.v2, value.first.tag)];
2671  mesh_int_t Index_new3 = map_vertex_tag_to_dof[std::make_pair(value.first.points.v3, value.first.tag)];
2672  value.first.points.v1 = Index_new1;
2673  value.first.points.v2 = Index_new2;
2674  value.first.points.v3 = Index_new3;
2675  }
2676  // second
2677  {
2678  mesh_int_t Index_new1 = map_vertex_tag_to_dof[std::make_pair(value.second.points.v1, value.second.tag)];
2679  mesh_int_t Index_new2 = map_vertex_tag_to_dof[std::make_pair(value.second.points.v2, value.second.tag)];
2680  mesh_int_t Index_new3 = map_vertex_tag_to_dof[std::make_pair(value.second.points.v3, value.second.tag)];
2681  value.second.points.v1 = Index_new1;
2682  value.second.points.v2 = Index_new2;
2683  value.second.points.v3 = Index_new3;
2684  }
2685  tri_face.insert({new_key,value});
2686  }
2687  if(elem_nodes.size()==4){
2688  quadruple<T> key;
2689  key.v1 = elem_nodes[0];
2690  key.v2 = elem_nodes[1];
2691  key.v3 = elem_nodes[2];
2692  key.v4 = elem_nodes[3];
2693  std::pair<emi_face<T,quadruple<T>>, emi_face<T,quadruple<T>>> value = quad_face[key];
2694  quad_face.erase(key);
2695  std::vector<mesh_int_t> new_nodes(4);
2696 
2697  auto tag_key = (value.first.rank == rank) ? value.first.tag : value.second.tag;
2698  {
2699  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
2700  Index_tag_old = std::make_pair(elem_nodes[0],tag_key);
2701  mesh_int_t Index_new = map_vertex_tag_to_dof[Index_tag_old];
2702  new_nodes[0] = Index_new;
2703  }
2704  {
2705  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
2706  Index_tag_old = std::make_pair(elem_nodes[1],tag_key);
2707  mesh_int_t Index_new = map_vertex_tag_to_dof[Index_tag_old];
2708  new_nodes[1] = Index_new;
2709  }
2710  {
2711  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
2712  Index_tag_old = std::make_pair(elem_nodes[2],tag_key);
2713  mesh_int_t Index_new = map_vertex_tag_to_dof[Index_tag_old];
2714  new_nodes[2] = Index_new;
2715  }
2716  {
2717  std::pair <mesh_int_t,mesh_int_t> Index_tag_old;
2718  Index_tag_old = std::make_pair(elem_nodes[3],tag_key);
2719  mesh_int_t Index_new = map_vertex_tag_to_dof[Index_tag_old];
2720  new_nodes[3] = Index_new;
2721  }
2722  std::sort(new_nodes.begin(),new_nodes.end());
2723  // update key
2724  quadruple<T> new_key;
2725  new_key.v1 = new_nodes[0];
2726  new_key.v2 = new_nodes[1];
2727  new_key.v3 = new_nodes[2];
2728  new_key.v4 = new_nodes[3];
2729 
2730  // first
2731  {
2732  mesh_int_t Index_new1 = map_vertex_tag_to_dof[std::make_pair(value.first.points.v1, value.first.tag)];
2733  mesh_int_t Index_new2 = map_vertex_tag_to_dof[std::make_pair(value.first.points.v2, value.first.tag)];
2734  mesh_int_t Index_new3 = map_vertex_tag_to_dof[std::make_pair(value.first.points.v3, value.first.tag)];
2735  mesh_int_t Index_new4 = map_vertex_tag_to_dof[std::make_pair(value.first.points.v4, value.first.tag)];
2736  value.first.points.v1 = Index_new1;
2737  value.first.points.v2 = Index_new2;
2738  value.first.points.v3 = Index_new3;
2739  value.first.points.v4 = Index_new4;
2740  }
2741  // second
2742  {
2743  mesh_int_t Index_new1 = map_vertex_tag_to_dof[std::make_pair(value.second.points.v1, value.second.tag)];
2744  mesh_int_t Index_new2 = map_vertex_tag_to_dof[std::make_pair(value.second.points.v2, value.second.tag)];
2745  mesh_int_t Index_new3 = map_vertex_tag_to_dof[std::make_pair(value.second.points.v3, value.second.tag)];
2746  mesh_int_t Index_new4 = map_vertex_tag_to_dof[std::make_pair(value.second.points.v4, value.second.tag)];
2747  value.second.points.v1 = Index_new1;
2748  value.second.points.v2 = Index_new2;
2749  value.second.points.v3 = Index_new3;
2750  value.second.points.v4 = Index_new4;
2751  }
2752  quad_face.insert({new_key,value});
2753  }
2754  }
2755 }
2756 
2768 template<class T, class S> inline
2769 void compute_surface_mesh_with_counter_face(meshdata<T, S> & mesh, const SF_nbr numbering,
2770  hashmap::unordered_map<tuple<T>,
2771  std::pair<emi_face<T,tuple<T>>, emi_face<T,tuple<T>>>> & line_face,
2772  hashmap::unordered_map<triple<T>,
2773  std::pair<emi_face<T,triple<T>>, emi_face<T,triple<T>>>> & tri_face,
2774  hashmap::unordered_map<quadruple<T>,
2775  std::pair<emi_face<T,quadruple<T>>, emi_face<T,quadruple<T>>>> & quad_face)
2776 {
2777  mesh.register_numbering(numbering);
2778 
2779  MPI_Comm comm = SF_COMM;
2780  int size, rank;
2781  MPI_Comm_size(comm, &size);
2782  MPI_Comm_rank(comm, &rank);
2783 
2784  // To ensure deterministic order, we must sort the keys of the hashmaps before iterating.
2785  vector<tuple<T>> line_keys;
2786  for(auto const& [key, val] : line_face) line_keys.push_back(key);
2787  std::sort(line_keys.begin(), line_keys.end());
2788 
2789  vector<triple<T>> tri_keys;
2790  for(auto const& [key, val] : tri_face) tri_keys.push_back(key);
2791  std::sort(tri_keys.begin(), tri_keys.end());
2792 
2793  vector<quadruple<T>> quad_keys;
2794  for(auto const& [key, val] : quad_face) quad_keys.push_back(key);
2795  std::sort(quad_keys.begin(), quad_keys.end());
2796 
2797  vector<T> cnt(mesh.l_numelem);
2798  size_t idx = 0, cidx = 0;
2799 
2800  // Add faces to the surface mesh used in ionic computation.
2801  // For each {line,tri,quad}_face, add faces only if one of the pair matches the current rank,
2802  // or if both faces have the same rank, add both.
2803  for(const auto & key : line_keys) {
2804  const auto & v = line_face.at(key);
2805  bool both_faces = (v.first.rank == v.second.rank) ? true: false;
2806 
2807  cnt[idx] = 2;
2808 
2809  emi_face<T,tuple<T>> surf_first;
2810  emi_face<T,tuple<T>> surf_second;
2811  if(v.first.rank == rank)
2812  {
2813  surf_first = v.first;
2814  surf_second = v.second;
2815  }
2816  else
2817  {
2818  surf_first = v.second;
2819  surf_second = v.first;
2820  }
2821 
2822  mesh.type[idx] = Line;
2823  mesh.tag[idx] = surf_first.tag;
2824  mesh.con[cidx + 0] = surf_first.points.v1;
2825  mesh.con[cidx + 1] = surf_first.points.v2;
2826  idx += 1;
2827  cidx += 2;
2828 
2829  if(both_faces){
2830 
2831  cnt[idx] = 2;
2832  mesh.type[idx] = Line;
2833  mesh.tag[idx] = surf_second.tag;
2834  mesh.con[cidx + 0] = surf_second.points.v1;
2835  mesh.con[cidx + 1] = surf_second.points.v2;
2836 
2837  idx += 1;
2838  cidx += 2;
2839  }
2840  }
2841 
2842  for(const auto & key : tri_keys) {
2843  const auto & v = tri_face.at(key);
2844  bool both_faces = (v.first.rank == v.second.rank) ? true: false;
2845 
2846  cnt[idx] = 3;
2847 
2848  emi_face<T,triple<T>> surf_first;
2849  emi_face<T,triple<T>> surf_second;
2850  if(v.first.rank == rank)
2851  {
2852  surf_first = v.first;
2853  surf_second = v.second;
2854  }
2855  else
2856  {
2857  surf_first = v.second;
2858  surf_second = v.first;
2859  }
2860 
2861  mesh.type[idx] = Tri;
2862  mesh.tag[idx] = surf_first.tag;
2863  mesh.con[cidx + 0] = surf_first.points.v1;
2864  mesh.con[cidx + 1] = surf_first.points.v2;
2865  mesh.con[cidx + 2] = surf_first.points.v3;
2866 
2867  idx += 1;
2868  cidx += 3;
2869 
2870  if(both_faces){
2871 
2872  cnt[idx] = 3;
2873 
2874  mesh.type[idx] = Tri;
2875  mesh.tag[idx] = surf_second.tag;
2876  mesh.con[cidx + 0] = surf_second.points.v1;
2877  mesh.con[cidx + 1] = surf_second.points.v2;
2878  mesh.con[cidx + 2] = surf_second.points.v3;
2879 
2880  idx += 1;
2881  cidx += 3;
2882  }
2883  }
2884 
2885  for(const auto & key : quad_keys) {
2886  const auto & v = quad_face.at(key);
2887  bool both_faces = (v.first.rank == v.second.rank) ? true: false;
2888  cnt[idx] = 4;
2889 
2890  emi_face<T,quadruple<T>> surf_first;
2891  emi_face<T,quadruple<T>> surf_second;
2892  if(v.first.rank == rank)
2893  {
2894  surf_first = v.first;
2895  surf_second = v.second;
2896  }
2897  else
2898  {
2899  surf_first = v.second;
2900  surf_second = v.first;
2901  }
2902 
2903  mesh.type[idx] = Quad;
2904  mesh.tag[idx] = surf_first.tag;
2905  mesh.con[cidx + 0] = surf_first.points.v1;
2906  mesh.con[cidx + 1] = surf_first.points.v2;
2907  mesh.con[cidx + 2] = surf_first.points.v3;
2908  mesh.con[cidx + 3] = surf_first.points.v4;
2909 
2910  idx += 1;
2911  cidx += 4;
2912 
2913  if(both_faces){
2914 
2915  cnt[idx] = 4;
2916 
2917  mesh.type[idx] = Quad;
2918  mesh.tag[idx] = surf_second.tag;
2919  mesh.con[cidx + 0] = surf_second.points.v1;
2920  mesh.con[cidx + 1] = surf_second.points.v2;
2921  mesh.con[cidx + 2] = surf_second.points.v3;
2922  mesh.con[cidx + 3] = surf_second.points.v4;
2923 
2924  idx += 1;
2925  cidx += 4;
2926  }
2927  }
2928  dsp_from_cnt(cnt, mesh.dsp);
2929 }
2930 
2962 template<class T, class S> inline
2963 void compute_surface_mesh_with_unique_face(meshdata<T, S> & mesh, const SF_nbr numbering,
2964  hashmap::unordered_map<tuple<T>,
2965  std::pair<emi_face<T,tuple<T>>, emi_face<T,tuple<T>>>> & line_face,
2966  hashmap::unordered_map<triple<T>,
2967  std::pair<emi_face<T,triple<T>>, emi_face<T,triple<T>>>> & tri_face,
2968  hashmap::unordered_map<quadruple<T>,
2969  std::pair<emi_face<T,quadruple<T>>, emi_face<T,quadruple<T>>>> & quad_face,
2970  hashmap::unordered_map<mesh_int_t, std::pair<mesh_int_t, mesh_int_t>> & map_elem_uniqueFace_to_tags)
2971 {
2972  mesh.register_numbering(numbering);
2973 
2974  MPI_Comm comm = SF_COMM;
2975  int size, rank;
2976  MPI_Comm_size(comm, &size);
2977  MPI_Comm_rank(comm, &rank);
2978 
2979  // To ensure deterministic order, we must sort the keys of the hashmaps before iterating.
2980  vector<tuple<T>> line_keys;
2981  for(auto const& [key, val] : line_face) line_keys.push_back(key);
2982  std::sort(line_keys.begin(), line_keys.end());
2983 
2984  vector<triple<T>> tri_keys;
2985  for(auto const& [key, val] : tri_face) tri_keys.push_back(key);
2986  std::sort(tri_keys.begin(), tri_keys.end());
2987 
2988  vector<quadruple<T>> quad_keys;
2989  for(auto const& [key, val] : quad_face) quad_keys.push_back(key);
2990  std::sort(quad_keys.begin(), quad_keys.end());
2991 
2992  vector<T> cnt(mesh.l_numelem);
2993  size_t idx = 0, cidx = 0;
2994 
2995  // Add faces to the surface mesh used in ionic computation.
2996  // For each {line,tri,quad}_face, add faces only if one of the pair matches the current rank,
2997  // or if both faces have the same rank, add both.
2998  for(const auto & key : line_keys) {
2999  const auto & v = line_face.at(key);
3000 
3001  bool to_take_face = false;
3002 
3003  emi_face<T,tuple<T>> surf_take;
3004  std::pair <mesh_int_t,mesh_int_t> tag_pairs_take;
3005  {
3006  if(v.first.rank == rank && v.first.mark_to_take == true)
3007  {
3008  surf_take = v.first;
3009  to_take_face = true;
3010  tag_pairs_take = std::make_pair(v.first.tag,v.second.tag);
3011  }
3012  else if(v.second.rank == rank && v.second.mark_to_take == true)
3013  {
3014  surf_take = v.second;
3015  to_take_face = true;
3016  tag_pairs_take = std::make_pair(v.second.tag,v.first.tag);
3017  }
3018 
3019  if(to_take_face)
3020  {
3021  map_elem_uniqueFace_to_tags.insert({idx,tag_pairs_take});
3022  cnt[idx] = 2;
3023  mesh.type[idx] = Line;
3024  mesh.tag[idx] = surf_take.tag;
3025  mesh.con[cidx + 0] = surf_take.points.v1;
3026  mesh.con[cidx + 1] = surf_take.points.v2;
3027  idx += 1;
3028  cidx += 2;
3029  }
3030  }
3031  }
3032 
3033  for(const auto & key : tri_keys) {
3034  const auto & v = tri_face.at(key);
3035 
3036  bool to_take_face = false;
3037 
3038  emi_face<T,triple<T>> surf_take;
3039  std::pair <mesh_int_t,mesh_int_t> tag_pairs_take;
3040  {
3041  if(v.first.rank == rank && v.first.mark_to_take == true)
3042  {
3043  surf_take = v.first;
3044  to_take_face = true;
3045  tag_pairs_take = std::make_pair(v.first.tag,v.second.tag);
3046  }
3047  else if(v.second.rank == rank && v.second.mark_to_take == true)
3048  {
3049  surf_take = v.second;
3050  to_take_face = true;
3051  tag_pairs_take = std::make_pair(v.second.tag,v.first.tag);
3052  }
3053 
3054  if(to_take_face)
3055  {
3056  map_elem_uniqueFace_to_tags.insert({idx,tag_pairs_take});
3057  cnt[idx] = 3;
3058  mesh.type[idx] = Tri;
3059  mesh.tag[idx] = surf_take.tag;
3060  mesh.con[cidx + 0] = surf_take.points.v1;
3061  mesh.con[cidx + 1] = surf_take.points.v2;
3062  mesh.con[cidx + 2] = surf_take.points.v3;
3063 
3064  idx += 1;
3065  cidx += 3;
3066  }
3067  }
3068  }
3069 
3070  for(const auto & key : quad_keys) {
3071  const auto & v = quad_face.at(key);
3072 
3073  bool to_take_face = false;
3074 
3075  emi_face<T,quadruple<T>> surf_take;
3076  std::pair <mesh_int_t,mesh_int_t> tag_pairs_take;
3077  {
3078  if(v.first.rank == rank && v.first.mark_to_take == true)
3079  {
3080  surf_take = v.first;
3081  to_take_face = true;
3082  tag_pairs_take = std::make_pair(v.first.tag,v.second.tag);
3083  }
3084  else if(v.second.rank == rank && v.second.mark_to_take == true)
3085  {
3086  surf_take = v.second;
3087  to_take_face = true;
3088  tag_pairs_take = std::make_pair(v.second.tag,v.first.tag);
3089  }
3090 
3091  if(to_take_face)
3092  {
3093  map_elem_uniqueFace_to_tags.insert({idx,tag_pairs_take});
3094  cnt[idx] = 4;
3095  mesh.type[idx] = Quad;
3096  mesh.tag[idx] = surf_take.tag;
3097  mesh.con[cidx + 0] = surf_take.points.v1;
3098  mesh.con[cidx + 1] = surf_take.points.v2;
3099  mesh.con[cidx + 2] = surf_take.points.v3;
3100  mesh.con[cidx + 3] = surf_take.points.v4;
3101 
3102  idx += 1;
3103  cidx += 4;
3104  }
3105  }
3106  }
3107  dsp_from_cnt(cnt, mesh.dsp);
3108 }
3109 
3129 template<class T> inline
3130 void create_reverse_elem_mapping_between_surface_meshes(
3131  hashmap::unordered_map<tuple<T>, std::pair<emi_face<T,tuple<T>>, emi_face<T,tuple<T>>>>& line_face,
3132  hashmap::unordered_map<triple<T>, std::pair<emi_face<T,triple<T>>, emi_face<T,triple<T>>>>& tri_face,
3133  hashmap::unordered_map<quadruple<T>, std::pair<emi_face<T,quadruple<T>>, emi_face<T,quadruple<T>>>>& quad_face,
3134  SF::vector<T>& vec_one_to_both_face,
3135  MPI_Comm comm)
3136 {
3137  int rank;
3138  MPI_Comm_rank(comm, &rank);
3139 
3140  // To ensure deterministic order, we must sort the keys of the hashmaps before iterating.
3141  vector<tuple<T>> line_keys;
3142  for(auto const& [key, val] : line_face) line_keys.push_back(key);
3143  std::sort(line_keys.begin(), line_keys.end());
3144 
3145  vector<triple<T>> tri_keys;
3146  for(auto const& [key, val] : tri_face) tri_keys.push_back(key);
3147  std::sort(tri_keys.begin(), tri_keys.end());
3148 
3149  vector<quadruple<T>> quad_keys;
3150  for(auto const& [key, val] : quad_face) quad_keys.push_back(key);
3151  std::sort(quad_keys.begin(), quad_keys.end());
3152 
3153  size_t surf_elem_idx = 0;
3154  size_t w_counter_elem_idx = 0;
3155 
3156  size_t lsize_line = 0;
3157  size_t lsize_tri = 0;
3158  size_t lsize_quad = 0;
3159 
3160  // Count how many both-face elements are local on this rank.
3161  // If both faces are on the same rank, the both-face mesh has two entries.
3162  for(const auto& key : line_keys) {
3163  const auto& v = line_face.at(key);
3164  const bool local_involved = (v.first.rank == rank) || (v.second.rank == rank);
3165  if (!local_involved) {
3166  continue;
3167  }
3168  if(v.first.rank == v.second.rank) lsize_line+=2;
3169  else lsize_line+=1;
3170  }
3171  for(const auto& key : tri_keys) {
3172  const auto& v = tri_face.at(key);
3173  const bool local_involved = (v.first.rank == rank) || (v.second.rank == rank);
3174  if (!local_involved) {
3175  continue;
3176  }
3177  if(v.first.rank == v.second.rank) lsize_tri+=2;
3178  else lsize_tri+=1;
3179  }
3180  for(const auto& key : quad_keys) {
3181  const auto& v = quad_face.at(key);
3182  const bool local_involved = (v.first.rank == rank) || (v.second.rank == rank);
3183  if (!local_involved) {
3184  continue;
3185  }
3186  if(v.first.rank == v.second.rank) lsize_quad+=2;
3187  else lsize_quad+=1;
3188  }
3189 
3190  vec_one_to_both_face.resize(lsize_line + lsize_tri + lsize_quad);
3191 
3192  // Fill mapping in deterministic order:
3193  // each both-face element gets the index of its corresponding one-face element.
3194  for (const auto& key : line_keys) {
3195  const auto& v = line_face.at(key);
3196  const bool local_involved = (v.first.rank == rank) || (v.second.rank == rank);
3197  if (!local_involved) {
3198  continue;
3199  }
3200  vec_one_to_both_face[w_counter_elem_idx++] = surf_elem_idx;
3201  if (v.first.rank == v.second.rank) {
3202  vec_one_to_both_face[w_counter_elem_idx++] = surf_elem_idx;
3203  }
3204  surf_elem_idx++;
3205  }
3206 
3207  for (const auto& key : tri_keys) {
3208  const auto& v = tri_face.at(key);
3209  const bool local_involved = (v.first.rank == rank) || (v.second.rank == rank);
3210  if (!local_involved) {
3211  continue;
3212  }
3213  vec_one_to_both_face[w_counter_elem_idx++] = surf_elem_idx;
3214  if (v.first.rank == v.second.rank) {
3215  vec_one_to_both_face[w_counter_elem_idx++] = surf_elem_idx;
3216  }
3217  surf_elem_idx++;
3218  }
3219 
3220  for (const auto& key : quad_keys) {
3221  const auto& v = quad_face.at(key);
3222  const bool local_involved = (v.first.rank == rank) || (v.second.rank == rank);
3223  if (!local_involved) {
3224  continue;
3225  }
3226  vec_one_to_both_face[w_counter_elem_idx++] = surf_elem_idx;
3227  if (v.first.rank == v.second.rank) {
3228  vec_one_to_both_face[w_counter_elem_idx++] = surf_elem_idx;
3229  }
3230  surf_elem_idx++;
3231  }
3232 }
3233 
3242 template<class T> inline
3243 void added_counter_faces_to_map(hashmap::unordered_map<tuple<T>,
3244  std::pair<emi_face<T,tuple<T>>, emi_face<T,tuple<T>>>> & line_face,
3245  hashmap::unordered_map<triple<T>,
3246  std::pair<emi_face<T,triple<T>>, emi_face<T,triple<T>>>> & tri_face,
3247  hashmap::unordered_map<quadruple<T>,
3248  std::pair<emi_face<T,quadruple<T>>, emi_face<T,quadruple<T>>>> & quad_face)
3249 {
3250  // Buffer inserts not to mutate an unordered_map while iterating over it and later add to unordered_map
3251  std::vector<std::pair<tuple<T>, std::pair<emi_face<T,tuple<T>>, emi_face<T,tuple<T>>>>> line_inserts;
3252  for(const auto & v : line_face) {
3253  SF::emi_face<mesh_int_t,SF::tuple<mesh_int_t>> surf_first = v.second.first;
3254  SF::emi_face<mesh_int_t,SF::tuple<mesh_int_t>> surf_second = v.second.second;
3255 
3256  std::vector<mesh_int_t> elem_nodes_first; elem_nodes_first.resize(2);
3257  std::vector<mesh_int_t> elem_nodes_second; elem_nodes_second.resize(2);
3258 
3259  SF::tuple<mesh_int_t> key_first;
3260  elem_nodes_first[0] = surf_first.points.v1;
3261  elem_nodes_first[1] = surf_first.points.v2;
3262  std::sort(elem_nodes_first.begin(),elem_nodes_first.end());
3263  key_first.v1 = elem_nodes_first[0];
3264  key_first.v2 = elem_nodes_first[1];
3265 
3266  SF::tuple<mesh_int_t> key_second;
3267  elem_nodes_second[0] = surf_second.points.v1;
3268  elem_nodes_second[1] = surf_second.points.v2;
3269  std::sort(elem_nodes_second.begin(),elem_nodes_second.end());
3270  key_second.v1 = elem_nodes_second[0];
3271  key_second.v2 = elem_nodes_second[1];
3272 
3273  const bool same_key_first = (v.first.v1 == key_first.v1 && v.first.v2 == key_first.v2);
3274  if (!same_key_first && line_face.find(key_first) == line_face.end()) {
3275  line_inserts.push_back({key_first, v.second});
3276  }
3277  const bool same_key_second = (v.first.v1 == key_second.v1 && v.first.v2 == key_second.v2);
3278  if (!same_key_second && line_face.find(key_second) == line_face.end()) {
3279  line_inserts.push_back({key_second, v.second});
3280  }
3281  }
3282  for (const auto & entry : line_inserts) line_face.insert(entry);
3283 
3284  std::vector<std::pair<triple<T>, std::pair<emi_face<T,triple<T>>, emi_face<T,triple<T>>>>> tri_inserts;
3285  for(const auto & v : tri_face) {
3286  SF::emi_face<mesh_int_t,SF::triple<mesh_int_t>> surf_first = v.second.first;
3287  SF::emi_face<mesh_int_t,SF::triple<mesh_int_t>> surf_second = v.second.second;
3288 
3289  std::vector<mesh_int_t> elem_nodes_first; elem_nodes_first.resize(3);
3290  std::vector<mesh_int_t> elem_nodes_second; elem_nodes_second.resize(3);
3291 
3292  SF::triple<mesh_int_t> key_first;
3293  elem_nodes_first[0] = surf_first.points.v1;
3294  elem_nodes_first[1] = surf_first.points.v2;
3295  elem_nodes_first[2] = surf_first.points.v3;
3296  std::sort(elem_nodes_first.begin(),elem_nodes_first.end());
3297  key_first.v1 = elem_nodes_first[0];
3298  key_first.v2 = elem_nodes_first[1];
3299  key_first.v3 = elem_nodes_first[2];
3300 
3301  SF::triple<mesh_int_t> key_second;
3302  elem_nodes_second[0] = surf_second.points.v1;
3303  elem_nodes_second[1] = surf_second.points.v2;
3304  elem_nodes_second[2] = surf_second.points.v3;
3305  std::sort(elem_nodes_second.begin(),elem_nodes_second.end());
3306  key_second.v1 = elem_nodes_second[0];
3307  key_second.v2 = elem_nodes_second[1];
3308  key_second.v3 = elem_nodes_second[2];
3309 
3310  const bool same_key_first = (v.first.v1 == key_first.v1 &&
3311  v.first.v2 == key_first.v2 &&
3312  v.first.v3 == key_first.v3);
3313  if (!same_key_first && tri_face.find(key_first) == tri_face.end()) {
3314  tri_inserts.push_back({key_first, v.second});
3315  }
3316  const bool same_key_second = (v.first.v1 == key_second.v1 &&
3317  v.first.v2 == key_second.v2 &&
3318  v.first.v3 == key_second.v3);
3319  if (!same_key_second && tri_face.find(key_second) == tri_face.end()) {
3320  tri_inserts.push_back({key_second, v.second});
3321  }
3322  }
3323  for (const auto & entry : tri_inserts) tri_face.insert(entry);
3324 
3325  std::vector<std::pair<quadruple<T>, std::pair<emi_face<T,quadruple<T>>, emi_face<T,quadruple<T>>>>> quad_inserts;
3326  for(const auto & v : quad_face) {
3327  SF::emi_face<mesh_int_t,SF::quadruple<mesh_int_t>> surf_first = v.second.first;
3328  SF::emi_face<mesh_int_t,SF::quadruple<mesh_int_t>> surf_second = v.second.second;
3329 
3330  std::vector<mesh_int_t> elem_nodes_first; elem_nodes_first.resize(4);
3331  std::vector<mesh_int_t> elem_nodes_second; elem_nodes_second.resize(4);
3332 
3333  SF::quadruple<mesh_int_t> key_first;
3334  elem_nodes_first[0] = surf_first.points.v1;
3335  elem_nodes_first[1] = surf_first.points.v2;
3336  elem_nodes_first[2] = surf_first.points.v3;
3337  elem_nodes_first[3] = surf_first.points.v4;
3338  std::sort(elem_nodes_first.begin(),elem_nodes_first.end());
3339  key_first.v1 = elem_nodes_first[0];
3340  key_first.v2 = elem_nodes_first[1];
3341  key_first.v3 = elem_nodes_first[2];
3342  key_first.v4 = elem_nodes_first[3];
3343 
3344  SF::quadruple<mesh_int_t> key_second;
3345  elem_nodes_second[0] = surf_second.points.v1;
3346  elem_nodes_second[1] = surf_second.points.v2;
3347  elem_nodes_second[2] = surf_second.points.v3;
3348  elem_nodes_second[3] = surf_second.points.v4;
3349  std::sort(elem_nodes_second.begin(),elem_nodes_second.end());
3350  key_second.v1 = elem_nodes_second[0];
3351  key_second.v2 = elem_nodes_second[1];
3352  key_second.v3 = elem_nodes_second[2];
3353  key_second.v4 = elem_nodes_second[3];
3354 
3355  const bool same_key_first = (v.first.v1 == key_first.v1 &&
3356  v.first.v2 == key_first.v2 &&
3357  v.first.v3 == key_first.v3 &&
3358  v.first.v4 == key_first.v4);
3359  if (!same_key_first && quad_face.find(key_first) == quad_face.end()) {
3360  quad_inserts.push_back({key_first, v.second});
3361  }
3362  const bool same_key_second = (v.first.v1 == key_second.v1 &&
3363  v.first.v2 == key_second.v2 &&
3364  v.first.v3 == key_second.v3 &&
3365  v.first.v4 == key_second.v4);
3366  if (!same_key_second && quad_face.find(key_second) == quad_face.end()) {
3367  quad_inserts.push_back({key_second, v.second});
3368  }
3369  }
3370  for (const auto & entry : quad_inserts) quad_face.insert(entry);
3371 }
3372 
3380 template<class K> inline
3381 void assign_counter_tags(hashmap::unordered_map<K, intersection_tags>& map, const MPI_Comm comm)
3382 {
3383  size_t dsize = map.size();
3384  vector<K> key_vec(dsize);
3385  vector<intersection_tags> value_vec(dsize);
3386 
3387  size_t idx = 0;
3388  for (const auto& v : map) {
3389  key_vec[idx] = v.first;
3390  value_vec[idx] = v.second;
3391  idx++;
3392  }
3393 
3394  vector<int> perm, dest;
3395  emi_select_merge_destinations(key_vec, dsize, sizeof(K) + sizeof(intersection_tags), dest, comm,
3396  "assign_counter_tags",
3397  [](const K& key) { return hashmap::hash_ops<K>::hash(key); });
3398 
3399  commgraph<size_t> grph;
3400  grph.configure(dest, comm);
3401  size_t nrecv = sum(grph.rcnt);
3402 
3403  interval(perm, 0, dsize);
3404  binary_sort_copy(dest, perm);
3405 
3406  // fill send buffer and communicate
3407  vector<K> sbuff_key(dsize), rbuff_key(nrecv);
3408  vector<intersection_tags> sbuff_value(dsize), ibuff_value(dsize), rbuff_value(nrecv);
3409  for (size_t i = 0; i < dsize; i++) {
3410  sbuff_key[i] = key_vec[perm[i]];
3411  sbuff_value[i] = value_vec[perm[i]];
3412  }
3413  MPI_Exchange(grph, sbuff_key, rbuff_key, comm);
3414  MPI_Exchange(grph, sbuff_value, rbuff_value, comm);
3415 
3417  for (size_t i = 0; i < nrecv; i++)
3418  {
3419  auto it = rmap.find(rbuff_key[i]);
3420  if (it != rmap.end())
3421  {
3422  intersection_tags& map_tags = it->second;
3423  intersection_tags& r_tags = rbuff_value[i];
3424 
3425  for (int r_tag : r_tags.tags)
3426  {
3427  if (r_tag == -1) continue;
3428  bool found = false;
3429  for (int m_tag : map_tags.tags)
3430  {
3431  if (m_tag == r_tag) {
3432  found = true;
3433  break;
3434  }
3435  }
3436  if (!found)
3437  {
3438  for (size_t k = 0; k < MAX_INTERSECTIONS; ++k) {
3439  if (map_tags.tags[k] == -1) {
3440  map_tags.tags[k] = r_tag;
3441  break;
3442  }
3443  }
3444  }
3445  }
3446  }
3447  else
3448  {
3449  rmap.insert({ rbuff_key[i], rbuff_value[i] });
3450  }
3451  }
3452 
3453  for (size_t i = 0; i < nrecv; i++) {
3454  auto it = rmap.find(rbuff_key[i]);
3455  if (it != rmap.end()) rbuff_value[i] = it->second;
3456  }
3457 
3458  // sending the assigned data back to original rank
3459  grph.transpose();
3460  MPI_Exchange(grph, rbuff_value, ibuff_value, comm);
3461 
3462  for (size_t i = 0; i < dsize; i++) {
3463  auto it = map.find(sbuff_key[i]);
3464  if (it != map.end()) it->second = ibuff_value[i];
3465  }
3466 }
3467 
3489 template<class T, class S> inline
3490 void compute_ptsdata_from_original_mesh(meshdata<T,S> & mesh,
3491  const SF_nbr numbering,
3492  hashmap::unordered_map<T,T> & vertex2ptsdata,
3493  hashmap::unordered_set<int> extra_tags,
3494  hashmap::unordered_set<int> intra_tags)
3495 {
3496  MPI_Comm comm = mesh.comm;
3497  int size, rank;
3498  MPI_Comm_size(comm, &size);
3499  MPI_Comm_rank(comm, &rank);
3500 
3501  const T* con = mesh.con.data();
3502  const SF::vector<T> & rnod = mesh.get_numbering(numbering);
3503 
3504  // Step 1: Gather all unique region tags associated with each vertex.
3505  // This map is local to each process and will store a list of tags for each vertex
3506  // based on the elements owned by the current process.
3508  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++)
3509  {
3510  T tag = mesh.tag[eidx];
3511  for (int n = mesh.dsp[eidx]; n < mesh.dsp[eidx+1];n++)
3512  {
3513  T gIndex = rnod[con[n]];
3514 
3515  auto it_new = map_index_to_tags.find(gIndex);
3516  if (it_new != map_index_to_tags.end())
3517  {
3518  // If vertex is already in the map, add the new tag if it's not already present.
3519  intersection_tags& tags = it_new->second;
3520  bool found = false;
3521  for(T t : tags.tags) {
3522  if(t == tag) {
3523  found = true;
3524  break;
3525  }
3526  }
3527  if(!found) {
3528  // This is an intracellular tag. Find an empty slot and add it.
3529  for(int i=0; i <MAX_INTERSECTIONS; ++i) {
3530  if (tags.tags[i] == -1) {
3531  tags.tags[i] = tag;
3532  break;
3533  }
3534  }
3535  }
3536  }
3537  else{
3538  // If vertex is not in the map, add it with the current tag.
3539  intersection_tags tags;
3540  tags.tags[0] = tag;
3541  map_index_to_tags.insert({gIndex,tags});
3542  }
3543  }
3544  }
3545 
3546  // Step 2: Globalize the tag information.
3547  // After this call, `map_index_to_tags` on every process will contain the
3548  // complete list of unique tags for every vertex in the entire mesh.
3549  assign_counter_tags(map_index_to_tags, comm);
3550 
3551  // Step 3: Classify each vertex based on its global list of tags.
3552  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++)
3553  {
3554  for (int n = mesh.dsp[eidx]; n < mesh.dsp[eidx+1];n++)
3555  {
3556  T gIndex = rnod[con[n]];
3557  intersection_tags& vec_tags = map_index_to_tags[gIndex];
3558  int count_intra_tags = 0;
3559  int count_extra_tags = 0;
3560 
3561  // Count the number of unique intracellular and extracellular tags for the vertex.
3562  int count_tags = 0;
3563  for(T t : vec_tags.tags) {
3564  if(intra_tags.count(t)) count_intra_tags++;
3565  if(extra_tags.count(t)) count_extra_tags++;
3566  }
3567 
3568  // Apply classification rules based on the tag counts.
3569  if(count_extra_tags>=1 && count_intra_tags==0){
3570  vertex2ptsdata[gIndex] = 0; // Interior to an extracellular region
3571  }else if(count_extra_tags==0 && count_intra_tags==1){
3572  vertex2ptsdata[gIndex] = 0; // Interior to a myocyte
3573  }else if(count_extra_tags>=1 && count_intra_tags==1){
3574  vertex2ptsdata[gIndex] = 1; // On a membrane (1 myocyte, 1 extracellular)
3575  }else if(count_extra_tags==0 && count_intra_tags==2){
3576  vertex2ptsdata[gIndex] = 2; // On a gap junction (2 myocytes)
3577  }else if(count_extra_tags>=1 && count_intra_tags==2){
3578  vertex2ptsdata[gIndex] = 3; // On a complex junction (2 myocytes, 1 extracellular)
3579  }
3580  else if(count_intra_tags>2){
3581  // Error for unhandled cases.
3582  fprintf(stderr, "More than two intracellular are connected %lld. Tags:",
3583  static_cast<long long>(gIndex));
3584  for(T tag : vec_tags.tags) if(tag != -1) fprintf(stderr, " %lld", static_cast<long long>(tag));
3585  fprintf(stderr, "\n");
3586  EXIT(1);
3587  }
3588  else{
3589  // Error for unhandled cases.
3590  fprintf(stderr, "WARN: unhandled case in ptsData computation for gIndex %lld. Tags:",
3591  static_cast<long long>(gIndex));
3592  for(T tag : vec_tags.tags) if(tag != -1) fprintf(stderr, " %lld", static_cast<long long>(tag));
3593  fprintf(stderr, "\n");
3594  EXIT(1);
3595  }
3596  }
3597  }
3598 }
3599 
3600 
3616 template <class T, class S, class V, class emi_index_rank>
3617 inline void assemble_map_both_to_unique(SF::abstract_matrix<T, S>& op,
3619  std::pair<emi_index_rank, emi_index_rank>>& map,
3620  const SF::meshdata<mesh_int_t, V>& unique_mesh,
3621  const SF::meshdata<mesh_int_t, V>& both_mesh)
3622 {
3623  // Recover global row/column ownership so locally assembled entries can be
3624  // inserted directly with global matrix indices.
3625  int rank;
3626  MPI_Comm_rank(both_mesh.comm, &rank);
3627 
3628  SF::vector<long int> layout_both;
3629  SF::layout_from_count<long int>(both_mesh.l_numelem, layout_both, both_mesh.comm);
3630 
3631  SF::vector<long int> layout_unique;
3632  SF::layout_from_count<long int>(unique_mesh.l_numelem, layout_unique, unique_mesh.comm);
3633 
3634  SF::vector<SF_int> row_idx(1), col_idx(1);
3635  SF::dmat<SF_real> ebuff(1, 1);
3636 
3637  // Assemble one restriction row per locally owned unique face.
3638  for (const auto& [local_unique_idx, both_pair] : map) {
3639  const auto& first_both = both_pair.first;
3640  const auto& second_both = both_pair.second;
3641 
3642  // The unique-face row is owned by this rank, so its global row index comes
3643  // from the unique-face layout plus the local unique-face element id.
3644  T global_unique_row = layout_unique[rank] + local_unique_idx;
3645  row_idx[0] = global_unique_row;
3646 
3647  // Resolve the candidate both-face columns and discard invalid sides.
3648  bool first_valid = (first_both.index >= 0 && first_both.rank >= 0);
3649  bool second_valid = (second_both.index >= 0 && second_both.rank >= 0);
3650 
3651  T global_both_col_first = -1;
3652  T global_both_col_second = -1;
3653  if (first_valid) {
3654  global_both_col_first = layout_both[first_both.rank] + first_both.index;
3655  }
3656  if (second_valid) {
3657  global_both_col_second = layout_both[second_both.rank] + second_both.index;
3658  }
3659 
3660  // If both sides collapse to the same global both-face column, do not insert
3661  // the same contribution twice; treat it as a single-sided restriction row.
3662  if (first_valid && second_valid && global_both_col_first == global_both_col_second) {
3663  second_valid = false;
3664  }
3665 
3666  // Count how many distinct both-face columns contribute to this unique face.
3667  int count = 0;
3668  if (first_valid) count++;
3669  if (second_valid) count++;
3670 
3671  if (count == 0) continue; // No data to average
3672 
3673  // Use arithmetic averaging when both sides exist; otherwise preserve the
3674  // single available value without halving it.
3675  double weight = 0.5;
3676  if (count == 1) weight = 1.0;
3677 
3678  ebuff.assign(1, 1, weight);
3679 
3680  // Insert the first contributing both-face column.
3681  if (first_valid) {
3682  col_idx[0] = global_both_col_first;
3683  op.set_values(row_idx, col_idx, ebuff.data(), false);
3684  }
3685 
3686  // Insert the second contributing both-face column when present.
3687  if (second_valid) {
3688  col_idx[0] = global_both_col_second;
3689  op.set_values(row_idx, col_idx, ebuff.data(), false);
3690  }
3691  }
3692 
3693  op.finish_assembly();
3694 }
3695 
3712 template<class T>
3713 inline void restrict_to_membrane(vector<T> & v,
3714  hashmap::unordered_map<T,T> & dof2ptsData,
3715  const meshdata<mesh_int_t, mesh_real_t> & mesh)
3716 {
3717  const SF::vector<SF_int> & rnod = mesh.get_numbering(SF::NBR_REF);
3720  for(size_t i=0; i<rnod.size(); i++){
3721  g2l[rnod[i]] = i;
3722  l2g[i] = rnod[i];
3723  }
3724 
3725  size_t widx = 0;
3726 
3727  for(size_t i=0; i<v.size(); i++){
3728  T g = l2g[v[i]];
3729 
3730  if (dof2ptsData[g] > 0) { // dof2ptsData requires global index
3731  v[widx++] = v[i];
3732  }
3733  }
3734 
3735  v.resize(widx);
3736 }
3737 
3738 }
3739 
3740 #endif
3741 #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:13
Functions related to EMI mesh IO.
Functions handling a distributed mesh.
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
@ 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