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