openCARP
Doxygen code documentation for the open cardiac electrophysiology simulator openCARP
SF_numbering.h
Go to the documentation of this file.
1 // SPDX-FileCopyrightText: Copyright (c) NumeriCor GmbH
2 // SPDX-License-Identifier: Apache-2.0
3 
13 #ifndef _SF_NUMBERING_H
14 #define _SF_NUMBERING_H
15 
16 #include <cassert>
17 #include <cstddef>
18 #include <iostream>
19 #include <mpi.h>
20 
21 #include "hashmap.hpp"
22 
23 #include "SF_container.h"
24 #include "SF_vector.h"
25 
26 namespace SF {
27 
34 template<class T, class S>
35 class numbering {
36 public:
37 
43  virtual void operator() (meshdata<T, S> & mesh) = 0;
44 };
45 
46 
53 template<class T, class S>
54 class submesh_numbering : public numbering<T, S>
55 {
56 public:
62  inline void renumber_sorted_ascending(meshdata<T, S> & submesh, SF_nbr in_nbr, SF_nbr out_nbr)
63  {
64  MPI_Comm comm = submesh.comm;
65 
66  int size, rank;
67  MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank);
68 
69  // determine global min and max indices
70  const vector<T> & rnod = submesh.get_numbering(in_nbr);
71  T gmax = global_max(rnod, comm);
72  T gmin = global_min(rnod, comm);
73 
74  // block size
75  T bsize = (gmax - gmin) / size + 1;
76 
77  // distribute indices uniquely and linearly ascending (w.r.t. rank index) --------------------
78  vector<T> dest(rnod.size()), sbuff(rnod);
79  for(size_t i=0; i<dest.size(); i++)
80  dest[i] = (rnod[i] - gmin) / bsize;
81  binary_sort_copy(dest, sbuff);
82 
83  commgraph<size_t> grph;
84  grph.configure(dest, comm);
85 
86  size_t rsize = sum(grph.rcnt);
87  vector<T> rbuff(rsize);
88  MPI_Exchange(grph, sbuff, rbuff, comm);
89 
90  // we need a data structure to store where we got the indices from
91  vector<T> proc(rsize), acc_cnt(rsize, T(1));
92  grph.source_ranks(proc);
93 
94  // compute local node set
95  binary_sort_copy(rbuff, proc);
96  unique_accumulate(rbuff, acc_cnt);
97  rsize = rbuff.size();
98 
99  // communicate local sizes
100  MPI_Allgather(&rsize, sizeof(size_t), MPI_BYTE, grph.rcnt.data(), sizeof(size_t), MPI_BYTE, comm);
101  dsp_from_cnt(grph.rcnt, grph.rdsp);
102 
103  // now the new numbering can be derived from the data layout
104  vector<T> new_nbr(rsize);
105  interval(new_nbr, grph.rdsp[rank], grph.rdsp[rank+1]);
106 
107  // we send the old and new numberings back -------------------------------------------------
108  sbuff.resize(proc.size());
109 
110  // expand the old numbering into the send buffer
111  for(size_t i=0, idx=0; i<acc_cnt.size(); i++)
112  for(T j=0; j<acc_cnt[i]; j++, idx++) sbuff[idx] = rbuff[i];
113  dest.assign(proc.begin(), proc.end());
114  binary_sort_copy(dest, sbuff); // permute for sending
115  grph.configure(dest, comm); // reconfigure comm graph
116  rbuff.resize(sum(grph.rcnt)); // resize receive buffer
117  MPI_Exchange(grph, sbuff, rbuff, comm); // communicate
118 
119  vector<T> old_idx(rbuff);
120 
121  // expand the new numbering into the send buffer
122  for(size_t i=0, idx=0; i<acc_cnt.size(); i++)
123  for(T j=0; j<acc_cnt[i]; j++, idx++) sbuff[idx] = new_nbr[i];
124  dest.assign(proc.begin(), proc.end());
125  binary_sort_copy(dest, sbuff); // permute for sending
126  MPI_Exchange(grph, sbuff, rbuff, comm); // communicate
127 
128  vector<T> new_idx(rbuff);
129 
130  // add a new numbering to submesh -----------------------------------------
131  // create mapping between original and new numbering
132  index_mapping<T> map(old_idx, new_idx);
133  vector<T> & snod = submesh.register_numbering(out_nbr);
134  snod.resize(rnod.size());
135  // use mapping for new numbering
136  for(size_t i=0; i < rnod.size(); i++) snod[i] = map.forward_map(rnod[i]);
137  }
138 
139  inline void operator() (meshdata<T, S> & submesh)
140  {
143  }
144 
145 };
146 
147 template<class T> inline
148 void reindex_cuthill_mckee(const vector<T> & n2n_cnt,
149  const vector<T> & n2n_dsp,
150  const vector<T> & n2n_con,
151  const bool reverse,
152  hashmap::unordered_map<T,T> & old2new)
153 {
154  /*
155  * We use a Cuthill-McKee style breadth-first traversal to generate a permutation
156  * of the original indices. This permutation is then renumbered
157  * either forward or reverse.
158  *
159  * The permutation is generated as following:
160  * Initially, an arbitrary node is selected. It is inserted in the permutation
161  * and in the imaginary set R (represented by a boolean vector).
162  * Then we loop over each node in perm, adding all connected nodes, that are not yet
163  * in R, in ascending order to perm and R.
164  *
165  * Note that this is a simplified variant of canonical Cuthill-McKee: the traversal
166  * seeds at node 0 and orders neighbours by index, instead of using the degree and
167  * pseudo-peripheral node heuristics. It therefore reduces the bandwidth less than a
168  * canonical implementation would.
169  */
170  size_t nnod = n2n_cnt.size();
171  if(nnod == 0) return; // empty partition: nothing to renumber
172 
173  vector<bool> inR(nnod, false);
174  vector<T> perm(nnod);
175  perm.resize(1); perm[0] = 0; inR[0] = true;
176  T pidx=0;
177  // nodes below this were all visited, so seeding never has to look at them again
178  size_t seed = 0;
179  size_t ncomp = 1;
180 
181  while(perm.size() < nnod)
182  {
183  T nidx;
184  if (pidx < T(perm.size())) nidx = perm[pidx++];
185  else {
186  // the traversal has exhausted the current connected component. Seed the next
187  // one with the first unvisited node and consume it as if it had been reached.
188  while(inR[seed] == true) seed++;
189  nidx = T(seed);
190  inR[seed] = true;
191  perm.push_back(nidx);
192  pidx++;
193  ncomp++;
194  }
195  T start = n2n_dsp[nidx], stop = start + n2n_cnt[nidx];
196  vector<T> adj(stop - start);
197 
198  T widx=0;
199  for(T j = start; j<stop; j++) {
200  T cidx = n2n_con[j];
201  if( ! inR[cidx] ) {
202  adj[widx++] = cidx;
203  inR[cidx] = true;
204  }
205  }
206 
207  adj.resize(widx);
208  binary_sort(adj);
209  perm.append(adj.begin(), adj.end());
210  }
211 
212  // Reported once per rank rather than per component. Several components are legitimate
213  // for meshes that are decoupled by design, such as the cells of an EMI mesh, but they
214  // also weaken the ordering: the traversal restarts and cannot relate one component to
215  // the next, so the bandwidth reduction only holds within each of them.
216  if(ncomp > 1)
217  std::cerr << "Note: the mesh graph splits into " << ncomp << " connected components; "
218  << "renumbering treats each of them separately." << std::endl;
219 
220  if(!reverse) {
221  // Cuthill-McKee
222  for(size_t i=0; i<perm.size(); i++) old2new[perm[i]] = i;
223  } else {
224  // Reverse Cuthill-McKee
225  for(size_t i=0, j=perm.size()-1; i<perm.size(); i++, j--) old2new[perm[j]] = i;
226  }
227 }
228 
229 
236 template<class T, class S>
237 class petsc_numbering : public numbering<T, S>
238 {
239 private:
240  overlapping_layout<T> & _pl;
241  bool _use_rcm;
242 public:
247  petsc_numbering(overlapping_layout<T> & pl, bool use_rcm = false) : _pl(pl), _use_rcm(use_rcm)
248  {}
249 
270  inline void operator() (meshdata<T, S> & mesh)
271  {
272  vector<T> & petsc_idx = mesh.register_numbering(NBR_PETSC);
273  petsc_idx.assign(_pl.num_local_idx(), -1);
274 
275  int size, rank;
276  MPI_Comm comm = mesh.comm;
277  MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank);
278 
279 
280  const vector<T> & alg_dsp = _pl.algebraic_layout();
281  const vector<T> & alg_nod = _pl.algebraic_nodes();
282  const T alg_start = alg_dsp[rank], alg_end = alg_dsp[rank+1];
283 
284  if(!_use_rcm) {
285  // trivial ascending numbering
286  for(size_t i=0; i<alg_nod.size(); i++)
287  petsc_idx[alg_nod[i]] = alg_start + i;
288  }
289  // a rank may own no algebraic nodes at all. The restricted graph is then empty and
290  // the connectivity kernels would index into zero-sized buffers, so skip straight to
291  // the communication step below.
292  else if(alg_nod.size() > 0) {
293  // reversed breadth-first renumbering on the restricted submesh of local algebraic nodes.
294  meshdata<T,S> restr_mesh;
295  vector<T> cnt(mesh.dsp.size() - 1, 0);
296  hashmap::unordered_map<T,T> nodal2restr;
297  restr_mesh.con.reserve(mesh.con.size());
298 
299  for(size_t i=0; i<alg_nod.size(); i++) {
300  nodal2restr[alg_nod[i]] = i;
301  }
302 
303  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++)
304  {
305  T start = mesh.dsp[eidx], stop = mesh.dsp[eidx+1];
306  for(T i = start; i<stop; i++) {
307  if(nodal2restr.count(mesh.con[i])) {
308  cnt[eidx]++;
309  restr_mesh.con.push_back(nodal2restr[mesh.con[i]]);
310  }
311  }
312  }
313 
314  dsp_from_cnt(cnt, restr_mesh.dsp);
315 
316  vector<T> n2n_cnt, n2n_dsp, n2n_con;
318 
319  nodal_connectivity_graph(restr_mesh, n2n_cnt, n2n_con);
320  dsp_from_cnt(n2n_cnt, n2n_dsp);
321 
322  reindex_cuthill_mckee(n2n_cnt, n2n_dsp, n2n_con, true, old2new);
323 
324  assert(old2new.size() == alg_nod.size());
325 
326  // Apply the reordering to the algebraic node list, not to the indices handed out.
327  // Both give a node the same PETSc index, but only this way does the position of a
328  // node in algebraic_nodes() keep matching its slot in the PETSc vector, which is
329  // how the ionic model masks and the asynchronous output address the local arrays.
330  // at() rather than operator[]: a node missing from the graph would otherwise be
331  // silently mapped to 0, leaving perm_nod partly unwritten and corrupting the node
332  // list the layout is about to adopt.
333  vector<T> perm_nod(alg_nod.size());
334  for(size_t i=0; i<alg_nod.size(); i++)
335  perm_nod[old2new.at(i)] = alg_nod[i];
336 
337  _pl.permute_algebraic_nodes(perm_nod);
338 
339  // alg_nod refers to the layout's list, so it reflects the new order here
340  for(size_t i=0; i<alg_nod.size(); i++)
341  petsc_idx[alg_nod[i]] = alg_start + i;
342  }
343  // communicate the new numbering so that all nodes in the local DD domain
344  // -- also the ones not in the algebraic range -- have a new index
345 
346  _pl.reduce(petsc_idx, "max");
347 
348  bool print_indexing = false;
349  if(print_indexing) {
350  for(int pid = 0; pid < size; pid++) {
351  if(pid == rank) {
352  for(size_t eidx = 0; eidx < 100; eidx++) {
353  printf("rank %d elem %d: ", rank, int(eidx));
354  for(T j = mesh.dsp[eidx]; j < mesh.dsp[eidx+1] - 1; j++) {
355  const T c = mesh.con[j];
356  printf("%lld ", static_cast<long long>(petsc_idx[c]));
357  }
358  printf("%lld\n",
359  static_cast<long long>(petsc_idx[mesh.con[mesh.dsp[eidx+1] - 1]]));
360  }
361  }
362  MPI_Barrier(PETSC_COMM_WORLD);
363  }
364  }
365  }
366 };
367 
368 }
369 
370 #endif
Basic containers.
The vector class and related algorithms.
The class holds the communication graph for a MPI_Exchange() call.
Definition: SF_container.h:623
vector< T > rcnt
Number of elements received from each rank.
Definition: SF_container.h:627
void configure(const vector< V > &dest, MPI_Comm comm)
Set up the communication graph.
Definition: SF_container.h:664
vector< T > rdsp
Displacements w.r.t. rcnt.
Definition: SF_container.h:628
void source_ranks(vector< V > &source)
For every received data element, get the rank indices it was receive from.
Definition: SF_container.h:711
Index mapping class. This is a bijective mapping.
Definition: SF_container.h:205
T forward_map(T idx) const
Map one index from a to b.
Definition: SF_container.h:249
The mesh storage class. It contains both element and vertex data.
Definition: SF_container.h:381
vector< T > dsp
connectivity starting index of each element
Definition: SF_container.h:401
size_t l_numelem
local number of elements
Definition: SF_container.h:384
vector< T > & register_numbering(SF_nbr nbr_type)
Register a new numbering to the mesh and return the associated index vector.
Definition: SF_container.h:429
vector< T > con
Definition: SF_container.h:397
MPI_Comm comm
the parallel mesh is defined on a MPI world
Definition: SF_container.h:389
vector< T > & get_numbering(SF_nbr nbr_type)
Get the vector defining a certain numbering.
Definition: SF_container.h:449
The abstract numbering class.
Definition: SF_numbering.h:35
virtual void operator()(meshdata< T, S > &mesh)=0
Add a numbering to a mesh.
The overlapping_layout class contains the algorithms related to managing overlapping parallel index s...
void permute_algebraic_nodes(const vector< T > &perm_nod)
Reorder the local algebraic node set.
const vector< T > & algebraic_layout() const
Getter function for the global algebraic node layout.
size_t num_local_idx() const
Retrieve the local number of indices.
const vector< T > & algebraic_nodes() const
Getter function for the local indices forming the local algebraic node set.
void reduce(vector< V > &ndat, const char *op) const
Compute a reduction on overlapping data.
Functor class generating a numbering optimized for PETSc.
Definition: SF_numbering.h:238
void operator()(meshdata< T, S > &mesh)
Generate a numbering as necessary for good PETSc parallel performance.
Definition: SF_numbering.h:270
petsc_numbering(overlapping_layout< T > &pl, bool use_rcm=false)
Definition: SF_numbering.h:247
Functor class applying a submesh renumbering.
Definition: SF_numbering.h:55
void operator()(meshdata< T, S > &submesh)
Add a numbering to a mesh.
Definition: SF_numbering.h:139
void renumber_sorted_ascending(meshdata< T, S > &submesh, SF_nbr in_nbr, SF_nbr out_nbr)
Renumber the global indexing of in_nbr globally ascending into the out_nbr.
Definition: SF_numbering.h:62
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
const T * end() const
Pointer to the vector's end.
Definition: SF_vector.h:113
void append(InputIterator s, InputIterator e)
Append data to the current data chunk.
Definition: SF_vector.h:253
void assign(InputIterator s, InputIterator e)
Assign a memory range.
Definition: SF_vector.h:146
const T * begin() const
Pointer to the vector's start.
Definition: SF_vector.h:101
T & push_back(T val)
Definition: SF_vector.h:268
hm_int count(const K &key) const
Check if key exists.
Definition: hashmap.hpp:612
T & at(const K &key)
Data access by key.
Definition: hashmap.hpp:646
size_t size() const
Definition: hashmap.hpp:720
Classes similar to unordered_set and unordered_map, but with better performance.
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 unique_accumulate(vector< T > &_P, vector< S > &_A)
Definition: SF_sort.h:399
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
T global_min(const vector< T > &vec, MPI_Comm comm)
Compute the global minimum of a distributed vector.
Definition: SF_network.h:111
void reindex_cuthill_mckee(const vector< T > &n2n_cnt, const vector< T > &n2n_dsp, const vector< T > &n2n_con, const bool reverse, hashmap::unordered_map< T, T > &old2new)
Definition: SF_numbering.h:148
void MPI_Exchange(commgraph< T > &grph, vector< S > &send, vector< S > &recv, MPI_Comm comm)
Exchange data in parallel over MPI.
Definition: SF_network.h:32
void binary_sort(vector< T > &_V)
Definition: SF_sort.h:274
T global_max(const vector< T > &vec, MPI_Comm comm)
Compute the global maximum of a distributed vector.
Definition: SF_network.h:141
void nodal_connectivity_graph(const meshdata< T, S > &mesh, vector< T > &n2n_cnt, vector< T > &n2n_con)
Compute the node-to-node connectivity.
Definition: SF_container.h:568
SF_nbr
Enumeration encoding the different supported numberings.
Definition: SF_container.h:185
@ NBR_PETSC
PETSc numbering of nodes.
Definition: SF_container.h:188
@ NBR_ELEM_REF
The element numbering of the reference mesh (the one stored on HD).
Definition: SF_container.h:189
@ NBR_REF
The nodal numbering of the reference mesh (the one stored on HD).
Definition: SF_container.h:186
@ NBR_SUBMESH
Submesh nodal numbering: The globally ascending sorted reference indices are reindexed.
Definition: SF_container.h:187
@ NBR_ELEM_SUBMESH
Submesh element numbering: The globally ascending sorted reference indices are reindexed.
Definition: SF_container.h:190