openCARP
Doxygen code documentation for the open cardiac electrophysiology simulator openCARP
SF_numbering.h
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // openCARP is an open cardiac electrophysiology simulator.
3 //
4 // Copyright (C) 2020 openCARP project
5 //
6 // This program is licensed under the openCARP Academic Public License (APL)
7 // v1.0: You can use and redistribute it and/or modify it in non-commercial
8 // academic environments under the terms of APL as published by the openCARP
9 // project v1.0, or (at your option) any later version. Commercial use requires
10 // a commercial license (info@opencarp.org).
11 //
12 // This program is distributed without any warranty; see the openCARP APL for
13 // more details.
14 //
15 // You should have received a copy of the openCARP APL along with this program
16 // and can find it online: http://www.opencarp.org/license
17 // ----------------------------------------------------------------------------
18 
28 #ifndef _SF_NUMBERING_H
29 #define _SF_NUMBERING_H
30 
31 #include <cstddef>
32 #include <mpi.h>
33 
34 #include "hashmap.hpp"
35 
36 #include "SF_container.h"
37 #include "SF_vector.h"
38 
39 namespace SF {
40 
47 template<class T, class S>
48 class numbering {
49 public:
50 
56  virtual void operator() (meshdata<T, S> & mesh) = 0;
57 };
58 
59 
66 template<class T, class S>
67 class submesh_numbering : public numbering<T, S>
68 {
69 public:
75  inline void renumber_sorted_ascending(meshdata<T, S> & submesh, SF_nbr in_nbr, SF_nbr out_nbr)
76  {
77  MPI_Comm comm = submesh.comm;
78 
79  int size, rank;
80  MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank);
81 
82  // determine global min and max indices
83  const vector<T> & rnod = submesh.get_numbering(in_nbr);
84  T gmax = global_max(rnod, comm);
85  T gmin = global_min(rnod, comm);
86 
87  // block size
88  T bsize = (gmax - gmin) / size + 1;
89 
90  // distribute indices uniquely and linearly ascending (w.r.t. rank index) --------------------
91  vector<T> dest(rnod.size()), sbuff(rnod);
92  for(size_t i=0; i<dest.size(); i++)
93  dest[i] = (rnod[i] - gmin) / bsize;
94  binary_sort_copy(dest, sbuff);
95 
96  commgraph<size_t> grph;
97  grph.configure(dest, comm);
98 
99  size_t rsize = sum(grph.rcnt);
100  vector<T> rbuff(rsize);
101  MPI_Exchange(grph, sbuff, rbuff, comm);
102 
103  // we need a data structure to store where we got the indices from
104  vector<T> proc(rsize), acc_cnt(rsize, T(1));
105  grph.source_ranks(proc);
106 
107  // compute local node set
108  binary_sort_copy(rbuff, proc);
109  unique_accumulate(rbuff, acc_cnt);
110  rsize = rbuff.size();
111 
112  // communicate local sizes
113  MPI_Allgather(&rsize, sizeof(size_t), MPI_BYTE, grph.rcnt.data(), sizeof(size_t), MPI_BYTE, comm);
114  dsp_from_cnt(grph.rcnt, grph.rdsp);
115 
116  // now the new numbering can be derived from the data layout
117  vector<T> new_nbr(rsize);
118  interval(new_nbr, grph.rdsp[rank], grph.rdsp[rank+1]);
119 
120  // we send the old and new numberings back -------------------------------------------------
121  sbuff.resize(proc.size());
122 
123  // expand the old numbering into the send buffer
124  for(size_t i=0, idx=0; i<acc_cnt.size(); i++)
125  for(T j=0; j<acc_cnt[i]; j++, idx++) sbuff[idx] = rbuff[i];
126  dest.assign(proc.begin(), proc.end());
127  binary_sort_copy(dest, sbuff); // permute for sending
128  grph.configure(dest, comm); // reconfigure comm graph
129  rbuff.resize(sum(grph.rcnt)); // resize receive buffer
130  MPI_Exchange(grph, sbuff, rbuff, comm); // communicate
131 
132  vector<T> old_idx(rbuff);
133 
134  // expand the new numbering into the send buffer
135  for(size_t i=0, idx=0; i<acc_cnt.size(); i++)
136  for(T j=0; j<acc_cnt[i]; j++, idx++) sbuff[idx] = new_nbr[i];
137  dest.assign(proc.begin(), proc.end());
138  binary_sort_copy(dest, sbuff); // permute for sending
139  MPI_Exchange(grph, sbuff, rbuff, comm); // communicate
140 
141  vector<T> new_idx(rbuff);
142 
143  // add a new numbering to submesh -----------------------------------------
144  // create mapping between original and new numbering
145  index_mapping<T> map(old_idx, new_idx);
146  vector<T> & snod = submesh.register_numbering(out_nbr);
147  snod.resize(rnod.size());
148  // use mapping for new numbering
149  for(size_t i=0; i < rnod.size(); i++) snod[i] = map.forward_map(rnod[i]);
150  }
151 
152  inline void operator() (meshdata<T, S> & submesh)
153  {
154  renumber_sorted_ascending(submesh, NBR_REF, NBR_SUBMESH);
155  renumber_sorted_ascending(submesh, NBR_ELEM_REF, NBR_ELEM_SUBMESH);
156  }
157 
158 };
159 
160 template<class T> inline
161 void reindex_cuthill_mckee(const vector<T> & n2n_cnt,
162  const vector<T> & n2n_dsp,
163  const vector<T> & n2n_con,
164  const bool reverse,
165  hashmap::unordered_map<T,T> & old2new)
166 {
167  /*
168  * We use the Cuthill-McKee algorithm to generate a permutation
169  * of the original indices. This permutation is then renumbered
170  * either forward or reverse.
171  *
172  * The permutation is generated as following:
173  * Initially, an arbitrary node is selected. It is inserted in the permutation
174  * and in the imaginary set R (represented by a boolean vector).
175  * Then we loop over each node in perm, adding all connected nodes, that are not yet
176  * in R, in ascending order to perm and R.
177  *
178  */
179  size_t nnod = n2n_cnt.size();
180 
181  vector<bool> inR(nnod, false);
182  vector<T> perm(nnod);
183  perm.resize(1); perm[0] = 0; inR[0] = true;
184  T pidx=0;
185 
186  while(perm.size() < nnod)
187  {
188  T nidx;
189  if (pidx < T(perm.size())) nidx = perm[pidx++];
190  else {
191  int i=0;
192  while(inR[i] == true) i++;
193  nidx = i;
194  // std::cerr << "Warning: node " << nidx << " seems decoupled from mesh as it could not be reached by traversing the mesh edges!" << std::endl;
195  }
196  T start = n2n_dsp[nidx], stop = start + n2n_cnt[nidx];
197  vector<T> adj(stop - start);
198 
199  T widx=0;
200  for(T j = start; j<stop; j++) {
201  T cidx = n2n_con[j];
202  if( ! inR[cidx] ) {
203  adj[widx++] = cidx;
204  inR[cidx] = true;
205  }
206  }
207 
208  adj.resize(widx);
209  binary_sort(adj);
210  perm.append(adj.begin(), adj.end());
211  }
212 
213  if(!reverse) {
214  // Cuthill-McKee
215  for(size_t i=0; i<perm.size(); i++) old2new[perm[i]] = i;
216  } else {
217  // Reverse Cuthill-McKee
218  for(size_t i=0, j=perm.size()-1; i<perm.size(); i++, j--) old2new[perm[j]] = i;
219  }
220 }
221 
222 
229 template<class T, class S>
230 class petsc_numbering : public numbering<T, S>
231 {
232 private:
233  const overlapping_layout<T> & _pl;
234 public:
236  {}
237 
258  inline void operator() (meshdata<T, S> & mesh)
259  {
260  vector<T> & petsc_idx = mesh.register_numbering(NBR_PETSC);
261  petsc_idx.assign(_pl.num_local_idx(), -1);
262 
263  int size, rank;
264  MPI_Comm comm = mesh.comm;
265  MPI_Comm_size(comm, &size); MPI_Comm_rank(comm, &rank);
266 
267 
268  const vector<T> & alg_dsp = _pl.algebraic_layout();
269  const vector<T> & alg_nod = _pl.algebraic_nodes();
270  const T alg_start = alg_dsp[rank], alg_end = alg_dsp[rank+1];
271 
272 #if 1
273  for(size_t i=0; i<alg_nod.size(); i++)
274  petsc_idx[alg_nod[i]] = alg_start + i;
275 #else
276  #if 0
277  vector<bool> is_alg(mesh.l_numpts, false);
278  for(const T & n : alg_nod) is_alg[n] = true;
279 
280  size_t loc_petsc_idx = 0;
281  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++) {
282  for(T j = mesh.dsp[eidx]; j < mesh.dsp[eidx+1]; j++) {
283  const T c = mesh.con[j];
284  if(is_alg[c] && petsc_idx[c] == -1) {
285  petsc_idx[c] = alg_start + loc_petsc_idx;
286  loc_petsc_idx++;
287  }
288  }
289  }
290 
291  assert(loc_petsc_idx == alg_nod.size());
292  #else
293  meshdata<T,S> restr_mesh;
294  vector<T> cnt(mesh.dsp.size() - 1, 0);
295  hashmap::unordered_map<T,T> nodal2restr;
296  restr_mesh.con.reserve(mesh.con.size());
297 
298  for(size_t i=0; i<alg_nod.size(); i++) {
299  nodal2restr[alg_nod[i]] = i;
300  }
301 
302  for(size_t eidx = 0; eidx < mesh.l_numelem; eidx++)
303  {
304  T start = mesh.dsp[eidx], stop = mesh.dsp[eidx+1];
305  for(T i = start; i<stop; i++) {
306  if(nodal2restr.count(mesh.con[i])) {
307  cnt[eidx]++;
308  restr_mesh.con.push_back(nodal2restr[mesh.con[i]]);
309  }
310  }
311  }
312 
313  dsp_from_cnt(cnt, restr_mesh.dsp);
314 
315  vector<T> n2n_cnt, n2n_dsp, n2n_con;
317 
318  nodal_connectivity_graph(restr_mesh, n2n_cnt, n2n_con);
319  dsp_from_cnt(n2n_cnt, n2n_dsp);
320 
321  reindex_cuthill_mckee(n2n_cnt, n2n_dsp, n2n_con, true, old2new);
322 
323  for(size_t i=0; i<alg_nod.size(); i++)
324  petsc_idx[alg_nod[i]] = old2new[i] + alg_start;
325  #endif
326 #endif
327  // communicate the new numbering so that all nodes in the local DD domain
328  // -- also the ones not in the algebraic range -- have a new index
329 
330  _pl.reduce(petsc_idx, "max");
331 
332  bool print_indexing = false;
333  if(print_indexing) {
334  for(int pid = 0; pid < size; pid++) {
335  if(pid == rank) {
336  for(size_t eidx = 0; eidx < 100; eidx++) {
337  printf("rank %d elem %d: ", rank, int(eidx));
338  for(T j = mesh.dsp[eidx]; j < mesh.dsp[eidx+1] - 1; j++) {
339  const T c = mesh.con[j];
340  printf("%d ", petsc_idx[c]);
341  }
342  printf("%d\n", petsc_idx[mesh.con[mesh.dsp[eidx+1] - 1]]);
343  }
344  }
345  MPI_Barrier(PETSC_COMM_WORLD);
346  }
347  }
348  }
349 };
350 
351 }
352 
353 #endif
const vector< T > & algebraic_layout() const
Getter function for the global algebraic node layout.
Definition: dense_mat.hpp:34
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:161
The mesh storage class. It contains both element and vertex data.
Definition: SF_container.h:383
T global_min(const vector< T > &vec, MPI_Comm comm)
Compute the global minimum of a distributed vector.
Definition: SF_network.h:126
The vector class and related algorithms.
vector< T > & get_numbering(SF_nbr nbr_type)
Get the vector defining a certain numbering.
Definition: SF_container.h:452
virtual void operator()(meshdata< T, S > &mesh)=0
Add a numbering to a mesh.
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:75
size_t num_local_idx() const
Retrieve the local number of indices.
MPI_Comm comm
the parallel mesh is defined on a MPI world
Definition: SF_container.h:392
void reserve(size_t n)
Definition: hashmap.hpp:686
void MPI_Exchange(commgraph< T > &grph, vector< S > &send, vector< S > &recv, MPI_Comm comm)
Exchange data in parallel over MPI.
Definition: SF_network.h:47
PETSc numbering of nodes.
Definition: SF_container.h:191
vector< T > dsp
connectivity starting index of each element
Definition: SF_container.h:404
void interval(vector< T > &vec, size_t start, size_t end)
Create an integer interval between start and end.
Definition: SF_vector.h:350
The nodal numbering of the reference mesh (the one stored on HD).
Definition: SF_container.h:189
Basic containers.
vector< T > rdsp
Displacements w.r.t. rcnt.
Definition: SF_container.h:631
T * data()
Pointer to the vector&#39;s start.
Definition: SF_vector.h:91
vector< T > con
Definition: SF_container.h:400
SF_nbr
Enumeration encoding the different supported numberings.
Definition: SF_container.h:188
const T * end() const
Pointer to the vector&#39;s end.
Definition: SF_vector.h:128
The element numbering of the reference mesh (the one stored on HD).
Definition: SF_container.h:192
Submesh nodal numbering: The globally ascending sorted reference indices are reindexed.
Definition: SF_container.h:190
The abstract numbering class.
Definition: SF_numbering.h:48
Submesh element numbering: The globally ascending sorted reference indices are reindexed.
Definition: SF_container.h:193
void configure(const vector< V > &dest, MPI_Comm comm)
Set up the communication graph.
Definition: SF_container.h:667
T sum(const vector< T > &vec)
Compute sum of a vector&#39;s entries.
Definition: SF_vector.h:340
Functor class generating a numbering optimized for PETSc.
Definition: SF_numbering.h:230
void binary_sort_copy(vector< T > &_V, vector< S > &_W)
Definition: SF_sort.h:296
Index mapping class. This is a bijective mapping.
Definition: SF_container.h:207
T global_max(const vector< T > &vec, MPI_Comm comm)
Compute the global maximum of a distributed vector.
Definition: SF_network.h:156
size_t size() const
The current size of the vector.
Definition: SF_vector.h:104
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:571
void reduce(vector< V > &ndat, const char *op) const
Compute a reduction on overlapping data.
void unique_accumulate(vector< T > &_P, vector< S > &_A)
Definition: SF_sort.h:409
size_t l_numpts
local number of points
Definition: SF_container.h:389
A vector storing arbitrary data.
Definition: SF_vector.h:42
size_t l_numelem
local number of elements
Definition: SF_container.h:387
const T * begin() const
Pointer to the vector&#39;s start.
Definition: SF_vector.h:116
petsc_numbering(const overlapping_layout< T > &pl)
Definition: SF_numbering.h:235
void assign(InputIterator s, InputIterator e)
Assign a memory range.
Definition: SF_vector.h:161
void source_ranks(vector< V > &source)
For every received data element, get the rank indices it was receive from.
Definition: SF_container.h:714
T forward_map(T idx) const
Map one index from a to b.
Definition: SF_container.h:252
const vector< T > & algebraic_nodes() const
Getter function for the local indices forming the local algebraic node set.
Functor class applying a submesh renumbering.
Definition: SF_numbering.h:67
vector< T > rcnt
Number of elements received from each rank.
Definition: SF_container.h:630
void binary_sort(vector< T > &_V)
Definition: SF_sort.h:284
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:432
void resize(size_t n)
Resize a vector.
Definition: SF_vector.h:209
void append(InputIterator s, InputIterator e)
Append data to the current data chunk.
Definition: SF_vector.h:268
Classes similar to unordered_set and unordered_map, but with better performance.
The overlapping_layout class contains the algorithms related to managing overlapping parallel index s...
Definition: SF_container.h:197
void dsp_from_cnt(const vector< T > &cnt, vector< T > &dsp)
Compute displacements from counts.
Definition: SF_vector.h:310