libMesh
Loading...
Searching...
No Matches
mesh_communication_global_indices.C
Go to the documentation of this file.
1// The libMesh Finite Element Library.
2// Copyright (C) 2002-2026 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
47namespace { // anonymous namespace for helper functions
48
49using namespace libMesh;
50
51
52// Trying to divide per mesh, not per point
53Point 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
77void 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
108get_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
132get_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
153class ComputeHilbertKeys
154{
155public:
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
188private:
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
198namespace 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
218 const Parallel::Communicator & communicator (mesh.comm());
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
311 Parallel::Sort<Parallel::DofObjectKey> node_sorter (communicator,
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
318 Parallel::Sort<Parallel::DofObjectKey> elem_sorter (communicator,
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]
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;
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]
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;
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)
709template <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
747template <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");
804 Parallel::Sort<Parallel::DofObjectKey> sorter (communicator,
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]
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;
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
1009template <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
1021template <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//------------------------------------------------------------------
1039template 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
1045template 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;
1050template LIBMESH_EXPORT void MeshCommunication::find_global_indices<MeshBase::node_iterator> (const Parallel::Communicator &,
1051 const libMesh::BoundingBox &,
1054 std::vector<dof_id_type> &) const;
1055
1056template LIBMESH_EXPORT void MeshCommunication::find_global_indices<MeshBase::element_iterator> (const Parallel::Communicator &,
1057 const libMesh::BoundingBox &,
1060 std::vector<dof_id_type> &) const;
1061template 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
Defines a Cartesian bounding box by the two corner extremum.
unique_id_type unique_id() const
Definition dof_object.h:835
static constexpr 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:484
This is the base class from which all geometric element types are derived.
Definition elem.h:96
Point vertex_average() const
Definition elem.C:669
The IntRange templated class is intended to make it easy to loop over integers which are indices of a...
Definition int_range.h:54
This is the MeshBase class.
Definition mesh_base.h:81
virtual dof_id_type n_elem() const =0
virtual dof_id_type n_nodes() const =0
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.
void assign_global_indices(MeshBase &) const
This method assigns globally unique, partition-agnostic indices to the nodes and elements in the mesh...
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.
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:55
const Parallel::Communicator & comm() const
The parallel sorting method is templated on the type of data which is to be sorted.
const std::vector< KeyType > & bin()
Return a constant reference to _my_bin.
void sort()
This is the only method which needs to be called by the user.
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition point.h:40
The StoredRange class defines a contiguous, divisible set of objects.
std::size_t first_idx() const
Index in the stored vector of the first object.
std::size_t size() const
MeshBase & mesh
void pull_parallel_vector_data(const Communicator &comm, const MapToVectors &queries, GatherFunctor &gather_data, const ActionFunctor &act_on_data, const datum *example)
void libmesh_assert_equal_connectivity(const MeshBase &mesh)
A function for testing that element nodal connectivities match across processors.
libMesh::BoundingBox create_nodal_bounding_box(const MeshBase &mesh)
Definition mesh_tools.C:591
void libmesh_assert_equal_points(const MeshBase &mesh)
A function for testing that node locations match across processors.
std::pair< Hilbert::HilbertIndices, unique_id_type > DofObjectKey
void parallel_for(const Range &range, const Body &body, unsigned int n_threads=libMesh::n_threads())
Execute the provided function object in parallel on the specified range.
The libMesh namespace provides an interface to certain functionality in the library.
OStreamProxy err
void libmesh_ignore(const Args &...)
libmesh_assert(ctx)
uint8_t dof_id_type
Definition id_types.h:67
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
uint8_t processor_id_type
Definition id_types.h:104
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:176
The definition of the const_element_iterator struct.
Definition mesh_base.h:2556
The definition of the const_node_iterator struct.
Definition mesh_base.h:2607
The definition of the element_iterator struct.
Definition mesh_base.h:2538
The definition of the node_iterator struct.
Definition mesh_base.h:2587