libMesh
mesh_communication_global_indices.C
Go to the documentation of this file.
1 // The libMesh Finite Element Library.
2 // Copyright (C) 2002-2019 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3 
4 // This library is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU Lesser General Public
6 // License as published by the Free Software Foundation; either
7 // version 2.1 of the License, or (at your option) any later version.
8 
9 // This library is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // Lesser General Public License for more details.
13 
14 // You should have received a copy of the GNU Lesser General Public
15 // License along with this library; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 
18 
19 
20 // Local includes
21 #include "libmesh/mesh_communication.h"
22 
23 // libMesh includes
24 #include "libmesh/libmesh_config.h"
25 #include "libmesh/libmesh_common.h"
26 #include "libmesh/libmesh_logging.h"
27 #include "libmesh/mesh_base.h"
28 #include "libmesh/mesh_tools.h"
29 #include "libmesh/parallel_hilbert.h"
30 #include "libmesh/parallel_sort.h"
31 #include "libmesh/elem.h"
32 #include "libmesh/elem_range.h"
33 #include "libmesh/node_range.h"
34 
35 // TIMPI includes
37 #include "timpi/parallel_sync.h"
38 
39 // C/C++ includes
40 #ifdef LIBMESH_HAVE_LIBHILBERT
41 # include "hilbert.h"
42 #endif
43 
44 
45 #ifdef LIBMESH_HAVE_LIBHILBERT
46 namespace { // anonymous namespace for helper functions
47 
48 using namespace libMesh;
49 
50 // Utility function to map (x,y,z) in [bbox.min, bbox.max]^3 into
51 // [0,max_inttype]^3 for computing Hilbert keys
52 void get_hilbert_coords (const Point & p,
53  const libMesh::BoundingBox & bbox,
54  CFixBitVec icoords[3])
55 {
56  static const Hilbert::inttype max_inttype = static_cast<Hilbert::inttype>(-1);
57 
58  const long double // put (x,y,z) in [0,1]^3 (don't divide by 0)
59  x = static_cast<long double>
60  ((bbox.first(0) == bbox.second(0)) ? 0. :
61  (p(0)-bbox.first(0))/(bbox.second(0)-bbox.first(0))),
62 
63 #if LIBMESH_DIM > 1
64  y = static_cast<long double>
65  ((bbox.first(1) == bbox.second(1)) ? 0. :
66  (p(1)-bbox.first(1))/(bbox.second(1)-bbox.first(1))),
67 #else
68  y = 0.,
69 #endif
70 
71 #if LIBMESH_DIM > 2
72  z = static_cast<long double>
73  ((bbox.first(2) == bbox.second(2)) ? 0. :
74  (p(2)-bbox.first(2))/(bbox.second(2)-bbox.first(2)));
75 #else
76  z = 0.;
77 #endif
78 
79  // (icoords) in [0,max_inttype]^3
80  icoords[0] = static_cast<Hilbert::inttype>(x*max_inttype);
81  icoords[1] = static_cast<Hilbert::inttype>(y*max_inttype);
82  icoords[2] = static_cast<Hilbert::inttype>(z*max_inttype);
83 }
84 
85 
86 
88 get_dofobject_key (const Elem * e,
89  const libMesh::BoundingBox & bbox)
90 {
91  static const unsigned int sizeof_inttype = sizeof(Hilbert::inttype);
92 
93  Hilbert::HilbertIndices index;
94  CFixBitVec icoords[3];
95  Hilbert::BitVecType bv;
96  get_hilbert_coords (e->centroid(), bbox, icoords);
97  Hilbert::coordsToIndex (icoords, 8*sizeof_inttype, 3, bv);
98  index = bv;
99 
100 #ifdef LIBMESH_ENABLE_UNIQUE_ID
101  return std::make_pair(index, e->unique_id());
102 #else
103  return index;
104 #endif
105 }
106 
107 
108 
109 // Compute the hilbert index
111 get_dofobject_key (const Node * n,
112  const libMesh::BoundingBox & bbox)
113 {
114  static const unsigned int sizeof_inttype = sizeof(Hilbert::inttype);
115 
116  Hilbert::HilbertIndices index;
117  CFixBitVec icoords[3];
118  Hilbert::BitVecType bv;
119  get_hilbert_coords (*n, bbox, icoords);
120  Hilbert::coordsToIndex (icoords, 8*sizeof_inttype, 3, bv);
121  index = bv;
122 
123 #ifdef LIBMESH_ENABLE_UNIQUE_ID
124  return std::make_pair(index, n->unique_id());
125 #else
126  return index;
127 #endif
128 }
129 
130 // Helper class for threaded Hilbert key computation
131 class ComputeHilbertKeys
132 {
133 public:
134  ComputeHilbertKeys (const libMesh::BoundingBox & bbox,
135  std::vector<Parallel::DofObjectKey> & keys) :
136  _bbox(bbox),
137  _keys(keys)
138  {}
139 
140  // computes the hilbert index for a node
141  void operator() (const ConstNodeRange & range) const
142  {
143  std::size_t pos = range.first_idx();
144  for (const auto & node : range)
145  {
146  libmesh_assert(node);
147  libmesh_assert_less (pos, _keys.size());
148  _keys[pos++] = get_dofobject_key (node, _bbox);
149  }
150  }
151 
152  // computes the hilbert index for an element
153  void operator() (const ConstElemRange & range) const
154  {
155  std::size_t pos = range.first_idx();
156  for (const auto & elem : range)
157  {
158  libmesh_assert(elem);
159  libmesh_assert_less (pos, _keys.size());
160  _keys[pos++] = get_dofobject_key (elem, _bbox);
161  }
162  }
163 
164 private:
165  const libMesh::BoundingBox & _bbox;
166  std::vector<Parallel::DofObjectKey> & _keys;
167 };
168 }
169 #endif
170 
171 
172 namespace libMesh
173 {
174 
175 // ------------------------------------------------------------
176 // MeshCommunication class members
177 #if defined(LIBMESH_HAVE_LIBHILBERT) && defined(LIBMESH_HAVE_MPI)
179 {
180  LOG_SCOPE ("assign_global_indices()", "MeshCommunication");
181 
182  // This method determines partition-agnostic global indices
183  // for nodes and elements.
184 
185  // Algorithm:
186  // (1) compute the Hilbert key for each local node/element
187  // (2) perform a parallel sort of the Hilbert key
188  // (3) get the min/max value on each processor
189  // (4) determine the position in the global ranking for
190  // each local object
191 
192  const Parallel::Communicator & communicator (mesh.comm());
193 
194  // Global bounding box. We choose the nodal bounding box for
195  // backwards compatibility; the element bounding box may be looser
196  // on curved elements.
197  BoundingBox bbox =
199 
200  //-------------------------------------------------------------
201  // (1) compute Hilbert keys
202  std::vector<Parallel::DofObjectKey>
203  node_keys, elem_keys;
204 
205  {
206  // Nodes first
207  {
210  node_keys.resize (nr.size());
211  Threads::parallel_for (nr, ComputeHilbertKeys (bbox, node_keys));
212 
213  // // It's O(N^2) to check that these keys don't duplicate before the
214  // // sort...
215  // MeshBase::const_node_iterator nodei = mesh.local_nodes_begin();
216  // for (std::size_t i = 0; i != node_keys.size(); ++i, ++nodei)
217  // {
218  // MeshBase::const_node_iterator nodej = mesh.local_nodes_begin();
219  // for (std::size_t j = 0; j != i; ++j, ++nodej)
220  // {
221  // if (node_keys[i] == node_keys[j])
222  // {
223  // CFixBitVec icoords[3], jcoords[3];
224  // get_hilbert_coords(**nodej, bbox, jcoords);
225  // libMesh::err << "node " << (*nodej)->id() << ", " << static_cast<Point &>(**nodej) << " has HilbertIndices " << node_keys[j] << std::endl;
226  // get_hilbert_coords(**nodei, bbox, icoords);
227  // libMesh::err << "node " << (*nodei)->id() << ", " << static_cast<Point &>(**nodei) << " has HilbertIndices " << node_keys[i] << std::endl;
228  // libmesh_error_msg("Error: nodes with duplicate Hilbert keys!");
229  // }
230  // }
231  // }
232  }
233 
234  // Elements next
235  {
238  elem_keys.resize (er.size());
239  Threads::parallel_for (er, ComputeHilbertKeys (bbox, elem_keys));
240 
241  // // For elements, the keys can be (and in the case of TRI, are
242  // // expected to be) duplicates, but only if the elements are at
243  // // different levels
244  // MeshBase::const_element_iterator elemi = mesh.local_elements_begin();
245  // for (std::size_t i = 0; i != elem_keys.size(); ++i, ++elemi)
246  // {
247  // MeshBase::const_element_iterator elemj = mesh.local_elements_begin();
248  // for (std::size_t j = 0; j != i; ++j, ++elemj)
249  // {
250  // if ((elem_keys[i] == elem_keys[j]) &&
251  // ((*elemi)->level() == (*elemj)->level()))
252  // {
253  // libMesh::err << "level " << (*elemj)->level()
254  // << " elem\n" << (**elemj)
255  // << " centroid " << (*elemj)->centroid()
256  // << " has HilbertIndices " << elem_keys[j]
257  // << " or " << get_dofobject_key((*elemj), bbox)
258  // << std::endl;
259  // libMesh::err << "level " << (*elemi)->level()
260  // << " elem\n" << (**elemi)
261  // << " centroid " << (*elemi)->centroid()
262  // << " has HilbertIndices " << elem_keys[i]
263  // << " or " << get_dofobject_key((*elemi), bbox)
264  // << std::endl;
265  // libmesh_error_msg("Error: level " << (*elemi)->level() << " elements with duplicate Hilbert keys!");
266  // }
267  // }
268  // }
269  }
270  } // done computing Hilbert keys
271 
272 
273 
274  //-------------------------------------------------------------
275  // (2) parallel sort the Hilbert keys
276  Parallel::Sort<Parallel::DofObjectKey> node_sorter (communicator,
277  node_keys);
278  node_sorter.sort(); /* done with node_keys */ //node_keys.clear();
279 
280  const std::vector<Parallel::DofObjectKey> & my_node_bin =
281  node_sorter.bin();
282 
283  Parallel::Sort<Parallel::DofObjectKey> elem_sorter (communicator,
284  elem_keys);
285  elem_sorter.sort(); /* done with elem_keys */ //elem_keys.clear();
286 
287  const std::vector<Parallel::DofObjectKey> & my_elem_bin =
288  elem_sorter.bin();
289 
290 
291 
292  //-------------------------------------------------------------
293  // (3) get the max value on each processor
294  std::vector<Parallel::DofObjectKey>
295  node_upper_bounds(communicator.size()),
296  elem_upper_bounds(communicator.size());
297 
298  { // limit scope of temporaries
299  std::vector<Parallel::DofObjectKey> recvbuf(2*communicator.size());
300  std::vector<unsigned short int> /* do not use a vector of bools here since it is not always so! */
301  empty_nodes (communicator.size()),
302  empty_elem (communicator.size());
303  std::vector<Parallel::DofObjectKey> my_max(2);
304 
305  communicator.allgather (static_cast<unsigned short int>(my_node_bin.empty()), empty_nodes);
306  communicator.allgather (static_cast<unsigned short int>(my_elem_bin.empty()), empty_elem);
307 
308  if (!my_node_bin.empty()) my_max[0] = my_node_bin.back();
309  if (!my_elem_bin.empty()) my_max[1] = my_elem_bin.back();
310 
311  communicator.allgather (my_max, /* identical_buffer_sizes = */ true);
312 
313  // Be careful here. The *_upper_bounds will be used to find the processor
314  // a given object belongs to. So, if a processor contains no objects (possible!)
315  // then copy the bound from the lower processor id.
316  for (auto p : IntRange<processor_id_type>(0, communicator.size()))
317  {
318  node_upper_bounds[p] = my_max[2*p+0];
319  elem_upper_bounds[p] = my_max[2*p+1];
320 
321  if (p > 0) // default hilbert index value is the OK upper bound for processor 0.
322  {
323  if (empty_nodes[p]) node_upper_bounds[p] = node_upper_bounds[p-1];
324  if (empty_elem[p]) elem_upper_bounds[p] = elem_upper_bounds[p-1];
325  }
326  }
327  }
328 
329 
330 
331  //-------------------------------------------------------------
332  // (4) determine the position in the global ranking for
333  // each local object
334  {
335  //----------------------------------------------
336  // Nodes first -- all nodes, not just local ones
337  {
338  // Request sets to send to each processor
339  std::map<processor_id_type, std::vector<Parallel::DofObjectKey>>
340  requested_ids;
341  // Results to gather from each processor - kept in a map so we
342  // do only one loop over nodes after all receives are done.
343  std::map<dof_id_type, std::vector<dof_id_type>>
344  filled_request;
345 
346  // build up list of requests
347  for (const auto & node : mesh.node_ptr_range())
348  {
349  libmesh_assert(node);
350  const Parallel::DofObjectKey hi =
351  get_dofobject_key (node, bbox);
352  const processor_id_type pid =
353  cast_int<processor_id_type>
354  (std::distance (node_upper_bounds.begin(),
355  std::lower_bound(node_upper_bounds.begin(),
356  node_upper_bounds.end(),
357  hi)));
358 
359  libmesh_assert_less (pid, communicator.size());
360 
361  requested_ids[pid].push_back(hi);
362  }
363 
364  // The number of objects in my_node_bin on each processor
365  std::vector<dof_id_type> node_bin_sizes(communicator.size());
366  communicator.allgather (static_cast<dof_id_type>(my_node_bin.size()), node_bin_sizes);
367 
368  // The offset of my first global index
369  dof_id_type my_offset = 0;
370  for (auto pid : IntRange<processor_id_type>(0, communicator.rank()))
371  my_offset += node_bin_sizes[pid];
372 
373  auto gather_functor =
374  [
375 #ifndef NDEBUG
376  & node_upper_bounds,
377  & communicator,
378 #endif
379  & my_node_bin,
380  my_offset
381  ]
383  const std::vector<Parallel::DofObjectKey> & keys,
384  std::vector<dof_id_type> & global_ids)
385  {
386  // Fill the requests
387  const std::size_t keys_size = keys.size();
388  global_ids.reserve(keys_size);
389  for (std::size_t idx=0; idx != keys_size; idx++)
390  {
391  const Parallel::DofObjectKey & hi = keys[idx];
392  libmesh_assert_less_equal (hi, node_upper_bounds[communicator.rank()]);
393 
394  // find the requested index in my node bin
395  std::vector<Parallel::DofObjectKey>::const_iterator pos =
396  std::lower_bound (my_node_bin.begin(), my_node_bin.end(), hi);
397  libmesh_assert (pos != my_node_bin.end());
398  libmesh_assert_equal_to (*pos, hi);
399 
400  // Finally, assign the global index based off the position of the index
401  // in my array, properly offset.
402  global_ids.push_back(cast_int<dof_id_type>(std::distance(my_node_bin.begin(), pos) + my_offset));
403  }
404  };
405 
406  auto action_functor =
407  [&filled_request]
408  (processor_id_type pid,
409  const std::vector<Parallel::DofObjectKey> &,
410  const std::vector<dof_id_type> & global_ids)
411  {
412  filled_request[pid] = global_ids;
413  };
414 
415  // Trade requests with other processors
416  const dof_id_type * ex = nullptr;
417  Parallel::pull_parallel_vector_data
418  (communicator, requested_ids, gather_functor, action_functor, ex);
419 
420  // We now have all the filled requests, so we can loop through our
421  // nodes once and assign the global index to each one.
422  {
423  std::map<dof_id_type, std::vector<dof_id_type>::const_iterator>
424  next_obj_on_proc;
425  for (auto & p : filled_request)
426  next_obj_on_proc[p.first] = p.second.begin();
427 
428  for (auto & node : mesh.node_ptr_range())
429  {
430  libmesh_assert(node);
431  const Parallel::DofObjectKey hi =
432  get_dofobject_key (node, bbox);
433  const processor_id_type pid =
434  cast_int<processor_id_type>
435  (std::distance (node_upper_bounds.begin(),
436  std::lower_bound(node_upper_bounds.begin(),
437  node_upper_bounds.end(),
438  hi)));
439 
440  libmesh_assert_less (pid, communicator.size());
441  libmesh_assert (next_obj_on_proc[pid] != filled_request[pid].end());
442 
443  const dof_id_type global_index = *next_obj_on_proc[pid];
444  libmesh_assert_less (global_index, mesh.n_nodes());
445  node->set_id() = global_index;
446 
447  ++next_obj_on_proc[pid];
448  }
449  }
450  }
451 
452  //---------------------------------------------------
453  // elements next -- all elements, not just local ones
454  {
455  // Request sets to send to each processor
456  std::map<processor_id_type, std::vector<Parallel::DofObjectKey>>
457  requested_ids;
458  // Results to gather from each processor - kept in a map so we
459  // do only one loop over elements after all receives are done.
460  std::map<dof_id_type, std::vector<dof_id_type>>
461  filled_request;
462 
463  for (const auto & elem : mesh.element_ptr_range())
464  {
465  libmesh_assert(elem);
466  const Parallel::DofObjectKey hi =
467  get_dofobject_key (elem, bbox);
468  const processor_id_type pid =
469  cast_int<processor_id_type>
470  (std::distance (elem_upper_bounds.begin(),
471  std::lower_bound(elem_upper_bounds.begin(),
472  elem_upper_bounds.end(),
473  hi)));
474 
475  libmesh_assert_less (pid, communicator.size());
476 
477  requested_ids[pid].push_back(hi);
478  }
479 
480  // The number of objects in my_elem_bin on each processor
481  std::vector<dof_id_type> elem_bin_sizes(communicator.size());
482  communicator.allgather (static_cast<dof_id_type>(my_elem_bin.size()), elem_bin_sizes);
483 
484  // The offset of my first global index
485  dof_id_type my_offset = 0;
486  for (auto pid : IntRange<processor_id_type>(0, communicator.rank()))
487  my_offset += elem_bin_sizes[pid];
488 
489  auto gather_functor =
490  [
491 #ifndef NDEBUG
492  & elem_upper_bounds,
493  & communicator,
494 #endif
495  & my_elem_bin,
496  my_offset
497  ]
499  const std::vector<Parallel::DofObjectKey> & keys,
500  std::vector<dof_id_type> & global_ids)
501  {
502  // Fill the requests
503  const std::size_t keys_size = keys.size();
504  global_ids.reserve(keys_size);
505  for (std::size_t idx=0; idx != keys_size; idx++)
506  {
507  const Parallel::DofObjectKey & hi = keys[idx];
508  libmesh_assert_less_equal (hi, elem_upper_bounds[communicator.rank()]);
509 
510  // find the requested index in my elem bin
511  std::vector<Parallel::DofObjectKey>::const_iterator pos =
512  std::lower_bound (my_elem_bin.begin(), my_elem_bin.end(), hi);
513  libmesh_assert (pos != my_elem_bin.end());
514  libmesh_assert_equal_to (*pos, hi);
515 
516  // Finally, assign the global index based off the position of the index
517  // in my array, properly offset.
518  global_ids.push_back (cast_int<dof_id_type>(std::distance(my_elem_bin.begin(), pos) + my_offset));
519  }
520  };
521 
522  auto action_functor =
523  [&filled_request]
524  (processor_id_type pid,
525  const std::vector<Parallel::DofObjectKey> &,
526  const std::vector<dof_id_type> & global_ids)
527  {
528  filled_request[pid] = global_ids;
529  };
530 
531  // Trade requests with other processors
532  const dof_id_type * ex = nullptr;
533  Parallel::pull_parallel_vector_data
534  (communicator, requested_ids, gather_functor, action_functor, ex);
535 
536  // We now have all the filled requests, so we can loop through our
537  // elements once and assign the global index to each one.
538  {
539  std::vector<std::vector<dof_id_type>::const_iterator>
540  next_obj_on_proc; next_obj_on_proc.reserve(communicator.size());
541  for (auto pid : IntRange<processor_id_type>(0, communicator.size()))
542  next_obj_on_proc.push_back(filled_request[pid].begin());
543 
544  for (auto & elem : mesh.element_ptr_range())
545  {
546  libmesh_assert(elem);
547  const Parallel::DofObjectKey hi =
548  get_dofobject_key (elem, bbox);
549  const processor_id_type pid =
550  cast_int<processor_id_type>
551  (std::distance (elem_upper_bounds.begin(),
552  std::lower_bound(elem_upper_bounds.begin(),
553  elem_upper_bounds.end(),
554  hi)));
555 
556  libmesh_assert_less (pid, communicator.size());
557  libmesh_assert (next_obj_on_proc[pid] != filled_request[pid].end());
558 
559  const dof_id_type global_index = *next_obj_on_proc[pid];
560  libmesh_assert_less (global_index, mesh.n_elem());
561  elem->set_id() = global_index;
562 
563  ++next_obj_on_proc[pid];
564  }
565  }
566  }
567  }
568 }
569 #else // LIBMESH_HAVE_LIBHILBERT, LIBMESH_HAVE_MPI
571 {
572 }
573 #endif // LIBMESH_HAVE_LIBHILBERT, LIBMESH_HAVE_MPI
574 
575 
576 #if defined(LIBMESH_HAVE_LIBHILBERT) && defined(LIBMESH_HAVE_MPI)
578 {
579  LOG_SCOPE ("check_for_duplicate_global_indices()", "MeshCommunication");
580 
581  // Global bounding box. We choose the nodal bounding box for
582  // backwards compatibility; the element bounding box may be looser
583  // on curved elements.
584  BoundingBox bbox =
586 
587 
588  std::vector<Parallel::DofObjectKey>
589  node_keys, elem_keys;
590 
591  {
592  // Nodes first
593  {
596  node_keys.resize (nr.size());
597  Threads::parallel_for (nr, ComputeHilbertKeys (bbox, node_keys));
598 
599  // It's O(N^2) to check that these keys don't duplicate before the
600  // sort...
602  for (std::size_t i = 0; i != node_keys.size(); ++i, ++nodei)
603  {
605  for (std::size_t j = 0; j != i; ++j, ++nodej)
606  {
607  if (node_keys[i] == node_keys[j])
608  {
609  CFixBitVec icoords[3], jcoords[3];
610  get_hilbert_coords(**nodej, bbox, jcoords);
611  libMesh::err <<
612  "node " << (*nodej)->id() << ", " <<
613  *(Point *)(*nodej) << " has HilbertIndices " <<
614  node_keys[j] << std::endl;
615  get_hilbert_coords(**nodei, bbox, icoords);
616  libMesh::err <<
617  "node " << (*nodei)->id() << ", " <<
618  *(Point *)(*nodei) << " has HilbertIndices " <<
619  node_keys[i] << std::endl;
620  libmesh_error_msg("Error: nodes with duplicate Hilbert keys!");
621  }
622  }
623  }
624  }
625 
626  // Elements next
627  {
630  elem_keys.resize (er.size());
631  Threads::parallel_for (er, ComputeHilbertKeys (bbox, elem_keys));
632 
633  // For elements, the keys can be (and in the case of TRI, are
634  // expected to be) duplicates, but only if the elements are at
635  // different levels
637  for (std::size_t i = 0; i != elem_keys.size(); ++i, ++elemi)
638  {
640  for (std::size_t j = 0; j != i; ++j, ++elemj)
641  {
642  if ((elem_keys[i] == elem_keys[j]) &&
643  ((*elemi)->level() == (*elemj)->level()))
644  {
645  libMesh::err <<
646  "level " << (*elemj)->level() << " elem\n" <<
647  (**elemj) << " centroid " <<
648  (*elemj)->centroid() << " has HilbertIndices " <<
649  elem_keys[j] << " or " <<
650  get_dofobject_key((*elemj), bbox) <<
651  std::endl;
652  libMesh::err <<
653  "level " << (*elemi)->level() << " elem\n" <<
654  (**elemi) << " centroid " <<
655  (*elemi)->centroid() << " has HilbertIndices " <<
656  elem_keys[i] << " or " <<
657  get_dofobject_key((*elemi), bbox) <<
658  std::endl;
659  libmesh_error_msg("Error: level " << (*elemi)->level() << " elements with duplicate Hilbert keys!");
660  }
661  }
662  }
663  }
664  } // done checking Hilbert keys
665 }
666 #else // LIBMESH_HAVE_LIBHILBERT, LIBMESH_HAVE_MPI
668 {
669 }
670 #endif // LIBMESH_HAVE_LIBHILBERT, LIBMESH_HAVE_MPI
671 
672 #if defined(LIBMESH_HAVE_LIBHILBERT) && defined(LIBMESH_HAVE_MPI)
673 template <typename ForwardIterator>
675  const ForwardIterator & begin,
676  const ForwardIterator & end,
677  std::unordered_map<dof_id_type, dof_id_type> & index_map) const
678 {
679  LOG_SCOPE ("find_local_indices()", "MeshCommunication");
680 
681  // This method determines id-agnostic local indices
682  // for nodes and elements by sorting Hilbert keys.
683 
684  index_map.clear();
685 
686  //-------------------------------------------------------------
687  // (1) compute Hilbert keys
688  // These aren't trivial to compute, and we will need them again.
689  // But the binsort will sort the input vector, trashing the order
690  // that we'd like to rely on. So, two vectors...
691  std::map<Parallel::DofObjectKey, dof_id_type> hilbert_keys;
692  {
693  LOG_SCOPE("local_hilbert_indices", "MeshCommunication");
694  for (ForwardIterator it=begin; it!=end; ++it)
695  {
696  const Parallel::DofObjectKey hi(get_dofobject_key ((*it), bbox));
697  hilbert_keys.emplace(hi, (*it)->id());
698  }
699  }
700 
701  {
702  dof_id_type cnt = 0;
703  for (auto key_val : hilbert_keys)
704  index_map[key_val.second] = cnt++;
705  }
706 }
707 
708 
709 template <typename ForwardIterator>
710 void MeshCommunication::find_global_indices (const Parallel::Communicator & communicator,
711  const libMesh::BoundingBox & bbox,
712  const ForwardIterator & begin,
713  const ForwardIterator & end,
714  std::vector<dof_id_type> & index_map) const
715 {
716  LOG_SCOPE ("find_global_indices()", "MeshCommunication");
717 
718  // This method determines partition-agnostic global indices
719  // for nodes and elements.
720 
721  // Algorithm:
722  // (1) compute the Hilbert key for each local node/element
723  // (2) perform a parallel sort of the Hilbert key
724  // (3) get the min/max value on each processor
725  // (4) determine the position in the global ranking for
726  // each local object
727  index_map.clear();
728  std::size_t n_objects = std::distance (begin, end);
729  index_map.reserve(n_objects);
730 
731  //-------------------------------------------------------------
732  // (1) compute Hilbert keys
733  // These aren't trivial to compute, and we will need them again.
734  // But the binsort will sort the input vector, trashing the order
735  // that we'd like to rely on. So, two vectors...
736  std::vector<Parallel::DofObjectKey>
737  sorted_hilbert_keys,
738  hilbert_keys;
739  sorted_hilbert_keys.reserve(n_objects);
740  hilbert_keys.reserve(n_objects);
741  {
742  LOG_SCOPE("compute_hilbert_indices()", "MeshCommunication");
743  for (ForwardIterator it=begin; it!=end; ++it)
744  {
745  const Parallel::DofObjectKey hi(get_dofobject_key (*it, bbox));
746  hilbert_keys.push_back(hi);
747 
748  if ((*it)->processor_id() == communicator.rank())
749  sorted_hilbert_keys.push_back(hi);
750 
751  // someone needs to take care of unpartitioned objects!
752  if ((communicator.rank() == 0) &&
753  ((*it)->processor_id() == DofObject::invalid_processor_id))
754  sorted_hilbert_keys.push_back(hi);
755  }
756  }
757 
758  //-------------------------------------------------------------
759  // (2) parallel sort the Hilbert keys
760  START_LOG ("parallel_sort()", "MeshCommunication");
761  Parallel::Sort<Parallel::DofObjectKey> sorter (communicator,
762  sorted_hilbert_keys);
763  sorter.sort();
764  STOP_LOG ("parallel_sort()", "MeshCommunication");
765  const std::vector<Parallel::DofObjectKey> & my_bin = sorter.bin();
766 
767  // The number of objects in my_bin on each processor
768  std::vector<unsigned int> bin_sizes(communicator.size());
769  communicator.allgather (static_cast<unsigned int>(my_bin.size()), bin_sizes);
770 
771  // The offset of my first global index
772  unsigned int my_offset = 0;
773  for (auto pid : IntRange<processor_id_type>(0, communicator.rank()))
774  my_offset += bin_sizes[pid];
775 
776  //-------------------------------------------------------------
777  // (3) get the max value on each processor
778  std::vector<Parallel::DofObjectKey>
779  upper_bounds(1);
780 
781  if (!my_bin.empty())
782  upper_bounds[0] = my_bin.back();
783 
784  communicator.allgather (upper_bounds, /* identical_buffer_sizes = */ true);
785 
786  // Be careful here. The *_upper_bounds will be used to find the processor
787  // a given object belongs to. So, if a processor contains no objects (possible!)
788  // then copy the bound from the lower processor id.
789  for (auto p : IntRange<processor_id_type>(1, communicator.size()))
790  if (!bin_sizes[p]) upper_bounds[p] = upper_bounds[p-1];
791 
792 
793  //-------------------------------------------------------------
794  // (4) determine the position in the global ranking for
795  // each local object
796  {
797  //----------------------------------------------
798  // all objects, not just local ones
799 
800  // Request sets to send to each processor
801  std::map<processor_id_type, std::vector<Parallel::DofObjectKey>>
802  requested_ids;
803  // Results to gather from each processor
804  std::map<processor_id_type, std::vector<dof_id_type>>
805  filled_request;
806 
807  // build up list of requests
808  std::vector<Parallel::DofObjectKey>::const_iterator hi =
809  hilbert_keys.begin();
810 
811  for (ForwardIterator it = begin; it != end; ++it)
812  {
813  libmesh_assert (hi != hilbert_keys.end());
814 
815  std::vector<Parallel::DofObjectKey>::iterator lb =
816  std::lower_bound(upper_bounds.begin(), upper_bounds.end(),
817  *hi);
818 
819  const processor_id_type pid =
820  cast_int<processor_id_type>
821  (std::distance (upper_bounds.begin(), lb));
822 
823  libmesh_assert_less (pid, communicator.size());
824 
825  requested_ids[pid].push_back(*hi);
826 
827  ++hi;
828  // go ahead and put pid in index_map, that way we
829  // don't have to repeat the std::lower_bound()
830  index_map.push_back(pid);
831  }
832 
833  auto gather_functor =
834  [
835 #ifndef NDEBUG
836  & upper_bounds,
837  & communicator,
838 #endif
839  & bbox,
840  & my_bin,
841  my_offset
842  ]
843  (processor_id_type, const std::vector<Parallel::DofObjectKey> & keys,
844  std::vector<dof_id_type> & global_ids)
845  {
846  // Ignore unused lambda capture warnings in devel mode
847  libmesh_ignore(bbox);
848 
849  // Fill the requests
850  const std::size_t keys_size = keys.size();
851  global_ids.clear();
852  global_ids.reserve(keys_size);
853  for (std::size_t idx=0; idx != keys_size; idx++)
854  {
855  const Parallel::DofObjectKey & hilbert_indices = keys[idx];
856  libmesh_assert_less_equal (hilbert_indices, upper_bounds[communicator.rank()]);
857 
858  // find the requested index in my node bin
859  std::vector<Parallel::DofObjectKey>::const_iterator pos =
860  std::lower_bound (my_bin.begin(), my_bin.end(), hilbert_indices);
861  libmesh_assert (pos != my_bin.end());
862 #ifdef DEBUG
863  // If we could not find the requested Hilbert index in
864  // my_bin, something went terribly wrong, possibly the
865  // Mesh was displaced differently on different processors,
866  // and therefore the Hilbert indices don't agree.
867  if (*pos != hilbert_indices)
868  {
869  // The input will be hilbert_indices. We convert it
870  // to BitVecType using the operator= provided by the
871  // BitVecType class. BitVecType is a CBigBitVec!
872  Hilbert::BitVecType input;
873 #ifdef LIBMESH_ENABLE_UNIQUE_ID
874  input = hilbert_indices.first;
875 #else
876  input = hilbert_indices;
877 #endif
878 
879  // Get output in a vector of CBigBitVec
880  std::vector<CBigBitVec> output(3);
881 
882  // Call the indexToCoords function
883  Hilbert::indexToCoords(output.data(), 8*sizeof(Hilbert::inttype), 3, input);
884 
885  // The entries in the output racks are integers in the
886  // range [0, Hilbert::inttype::max] which can be
887  // converted to floating point values in [0,1] and
888  // finally to actual values using the bounding box.
889  const Real max_int_as_real =
890  static_cast<Real>(std::numeric_limits<Hilbert::inttype>::max());
891 
892  // Get the points in [0,1]^3. The zeroth rack of each entry in
893  // 'output' maps to the normalized x, y, and z locations,
894  // respectively.
895  Point p_hat(static_cast<Real>(output[0].racks()[0]) / max_int_as_real,
896  static_cast<Real>(output[1].racks()[0]) / max_int_as_real,
897  static_cast<Real>(output[2].racks()[0]) / max_int_as_real);
898 
899  // Convert the points from [0,1]^3 to their actual (x,y,z) locations
900  Real
901  xmin = bbox.first(0),
902  xmax = bbox.second(0),
903  ymin = bbox.first(1),
904  ymax = bbox.second(1),
905  zmin = bbox.first(2),
906  zmax = bbox.second(2);
907 
908  // Convert the points from [0,1]^3 to their actual (x,y,z) locations
909  Point p(xmin + (xmax-xmin)*p_hat(0),
910  ymin + (ymax-ymin)*p_hat(1),
911  zmin + (zmax-zmin)*p_hat(2));
912 
913  libmesh_error_msg("Could not find hilbert indices: "
914  << hilbert_indices
915  << " corresponding to point " << p);
916  }
917 #endif
918 
919  // Finally, assign the global index based off the position of the index
920  // in my array, properly offset.
921  global_ids.push_back (cast_int<dof_id_type>(std::distance(my_bin.begin(), pos) + my_offset));
922  }
923  };
924 
925  auto action_functor =
926  [&filled_request]
927  (processor_id_type pid,
928  const std::vector<Parallel::DofObjectKey> &,
929  const std::vector<dof_id_type> & global_ids)
930  {
931  filled_request[pid] = global_ids;
932  };
933 
934  const dof_id_type * ex = nullptr;
935  Parallel::pull_parallel_vector_data
936  (communicator, requested_ids, gather_functor, action_functor, ex);
937 
938  // We now have all the filled requests, so we can loop through our
939  // nodes once and assign the global index to each one.
940  {
941  std::vector<std::vector<dof_id_type>::const_iterator>
942  next_obj_on_proc; next_obj_on_proc.reserve(communicator.size());
943  for (auto pid : IntRange<processor_id_type>(0, communicator.size()))
944  next_obj_on_proc.push_back(filled_request[pid].begin());
945 
946  unsigned int cnt=0;
947  for (ForwardIterator it = begin; it != end; ++it, cnt++)
948  {
949  const processor_id_type pid = cast_int<processor_id_type>
950  (index_map[cnt]);
951 
952  libmesh_assert_less (pid, communicator.size());
953  libmesh_assert (next_obj_on_proc[pid] != filled_request[pid].end());
954 
955  const dof_id_type global_index = *next_obj_on_proc[pid];
956  index_map[cnt] = global_index;
957 
958  ++next_obj_on_proc[pid];
959  }
960  }
961  }
962 
963  libmesh_assert_equal_to(index_map.size(), n_objects);
964 }
965 #else // LIBMESH_HAVE_LIBHILBERT, LIBMESH_HAVE_MPI
966 template <typename ForwardIterator>
968  const ForwardIterator & begin,
969  const ForwardIterator & end,
970  std::unordered_map<dof_id_type, dof_id_type> & index_map) const
971 {
972  index_map.clear();
973  dof_id_type index = 0;
974  for (ForwardIterator it=begin; it!=end; ++it)
975  index_map[(*it)->id()] = (index++);
976 }
977 
978 template <typename ForwardIterator>
979 void MeshCommunication::find_global_indices (const Parallel::Communicator &,
980  const libMesh::BoundingBox &,
981  const ForwardIterator & begin,
982  const ForwardIterator & end,
983  std::vector<dof_id_type> & index_map) const
984 {
985  index_map.clear();
986  index_map.reserve(std::distance (begin, end));
987  unsigned int index = 0;
988  for (ForwardIterator it=begin; it!=end; ++it)
989  index_map.push_back(index++);
990 }
991 #endif // LIBMESH_HAVE_LIBHILBERT, LIBMESH_HAVE_MPI
992 
993 
994 
995 //------------------------------------------------------------------
996 template void MeshCommunication::find_global_indices<MeshBase::const_node_iterator> (const Parallel::Communicator &,
997  const libMesh::BoundingBox &,
1000  std::vector<dof_id_type> &) const;
1001 
1002 template void MeshCommunication::find_global_indices<MeshBase::const_element_iterator> (const Parallel::Communicator &,
1003  const libMesh::BoundingBox &,
1006  std::vector<dof_id_type> &) const;
1007 template void MeshCommunication::find_global_indices<MeshBase::node_iterator> (const Parallel::Communicator &,
1008  const libMesh::BoundingBox &,
1009  const MeshBase::node_iterator &,
1010  const MeshBase::node_iterator &,
1011  std::vector<dof_id_type> &) const;
1012 
1013 template void MeshCommunication::find_global_indices<MeshBase::element_iterator> (const Parallel::Communicator &,
1014  const libMesh::BoundingBox &,
1017  std::vector<dof_id_type> &) const;
1018 template void MeshCommunication::find_local_indices<MeshBase::const_element_iterator> (const libMesh::BoundingBox &,
1021  std::unordered_map<dof_id_type, dof_id_type> &) const;
1022 
1023 } // namespace libMesh
libMesh::MeshCommunication::check_for_duplicate_global_indices
void check_for_duplicate_global_indices(MeshBase &) const
Throw an error if we have any index clashes in the numbering used by assign_global_indices.
Definition: mesh_communication_global_indices.C:577
libMesh::dof_id_type
uint8_t dof_id_type
Definition: id_types.h:67
libMesh::MeshBase::element_iterator
The definition of the element_iterator struct.
Definition: mesh_base.h:1873
libMesh::Parallel::DofObjectKey
std::pair< Hilbert::HilbertIndices, unique_id_type > DofObjectKey
Definition: parallel_hilbert.h:77
libMesh::Parallel::Sort
The parallel sorting method is templated on the type of data which is to be sorted.
Definition: parallel_sort.h:55
libMesh::BoundingBox
Defines a Cartesian bounding box by the two corner extremum.
Definition: bounding_box.h:40
libMesh::MeshBase::local_nodes_begin
virtual node_iterator local_nodes_begin()=0
Iterate over local nodes (nodes whose processor_id() matches the current processor).
libMesh::MeshBase::n_elem
virtual dof_id_type n_elem() const =0
libMesh::MeshTools::create_nodal_bounding_box
libMesh::BoundingBox create_nodal_bounding_box(const MeshBase &mesh)
Definition: mesh_tools.C:414
libMesh
The libMesh namespace provides an interface to certain functionality in the library.
Definition: factoryfunction.C:55
libMesh::MeshCommunication::find_local_indices
void find_local_indices(const libMesh::BoundingBox &, const ForwardIterator &, const ForwardIterator &, std::unordered_map< dof_id_type, dof_id_type > &) const
This method determines a locally unique, contiguous index for each object in the input range.
Definition: mesh_communication_global_indices.C:674
end
IterBase * end
Also have a polymorphic pointer to the end object, this prevents iterating past the end.
Definition: variant_filter_iterator.h:343
libMesh::ParallelObject::comm
const Parallel::Communicator & comm() const
Definition: parallel_object.h:94
mesh
MeshBase & mesh
Definition: mesh_communication.C:1257
libMesh::Parallel::Sort::sort
void sort()
This is the only method which needs to be called by the user.
Definition: parallel_sort.C:64
parallel_sync.h
libMesh::MeshBase::local_elements_begin
virtual element_iterator local_elements_begin()=0
libMesh::Elem::centroid
virtual Point centroid() const
Definition: elem.C:345
libMesh::MeshCommunication::find_global_indices
void find_global_indices(const Parallel::Communicator &communicator, const libMesh::BoundingBox &, const ForwardIterator &, const ForwardIterator &, std::vector< dof_id_type > &) const
This method determines a globally unique, partition-agnostic index for each object in the input range...
Definition: mesh_communication_global_indices.C:710
libMesh::MeshBase::element_ptr_range
virtual SimpleRange< element_iterator > element_ptr_range()=0
libMesh::libmesh_assert
libmesh_assert(ctx)
libMesh::IntRange
The IntRange templated class is intended to make it easy to loop over integers which are indices of a...
Definition: int_range.h:53
libMesh::MeshBase
This is the MeshBase class.
Definition: mesh_base.h:78
libMesh::Threads::parallel_for
void parallel_for(const Range &range, const Body &body)
Execute the provided function object in parallel on the specified range.
Definition: threads_none.h:73
libMesh::MeshBase::node_ptr_range
virtual SimpleRange< node_iterator > node_ptr_range()=0
libMesh::MeshBase::const_node_iterator
The definition of the const_node_iterator struct.
Definition: mesh_base.h:1945
libMesh::libmesh_ignore
void libmesh_ignore(const Args &...)
Definition: libmesh_common.h:526
libMesh::Parallel::Sort::bin
const std::vector< KeyType > & bin()
Return a constant reference to _my_bin.
Definition: parallel_sort.C:264
parallel_implementation.h
libMesh::Point
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition: point.h:38
libMesh::processor_id_type
uint8_t processor_id_type
Definition: id_types.h:104
libMesh::Node
A Node is like a Point, but with more information.
Definition: node.h:52
libMesh::MeshTools::Generation::Private::idx
unsigned int idx(const ElemType type, const unsigned int nx, const unsigned int i, const unsigned int j)
A useful inline function which replaces the macros used previously.
Definition: mesh_generation.C:72
libMesh::DofObject::unique_id
unique_id_type unique_id() const
Definition: dof_object.h:784
libMesh::MeshBase::const_element_iterator
The definition of the const_element_iterator struct.
Definition: mesh_base.h:1891
libMesh::MeshBase::n_nodes
virtual dof_id_type n_nodes() const =0
distance
Real distance(const Point &p)
Definition: subdomains_ex3.C:50
libMesh::MeshBase::node_iterator
The definition of the node_iterator struct.
Definition: mesh_base.h:1925
libMesh::MeshBase::local_elements_end
virtual element_iterator local_elements_end()=0
libMesh::StoredRange::first_idx
std::size_t first_idx() const
Index in the stored vector of the first object.
Definition: stored_range.h:271
libMesh::StoredRange
The StoredRange class defines a contiguous, divisible set of objects.
Definition: stored_range.h:52
libMesh::MeshCommunication::assign_global_indices
void assign_global_indices(MeshBase &) const
This method assigns globally unique, partition-agnostic indices to the nodes and elements in the mesh...
Definition: mesh_communication_global_indices.C:178
libMesh::DofObject::invalid_processor_id
static const processor_id_type invalid_processor_id
An invalid processor_id to distinguish DoFs that have not been assigned to a processor.
Definition: dof_object.h:432
libMesh::Elem
This is the base class from which all geometric element types are derived.
Definition: elem.h:100
libMesh::err
OStreamProxy err
libMesh::Real
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
Definition: libmesh_common.h:121
libMesh::MeshBase::local_nodes_end
virtual node_iterator local_nodes_end()=0