libMesh
Loading...
Searching...
No Matches
partitioner.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#include "libmesh/partitioner.h"
21
22// libMesh includes
23#include "libmesh/compare_elems_by_level.h"
24#include "libmesh/elem.h"
25#include "libmesh/enum_to_string.h"
26#include "libmesh/int_range.h"
27#include "libmesh/libmesh_logging.h"
28#include "libmesh/mesh_base.h"
29#include "libmesh/mesh_tools.h"
30#include "libmesh/mesh_communication.h"
31#include "libmesh/parallel_ghost_sync.h"
32#include "libmesh/wrapped_petsc.h"
33#include "libmesh/boundary_info.h"
34
35// Subclasses to build()
36#include "libmesh/enum_partitioner_type.h"
37#include "libmesh/centroid_partitioner.h"
38#include "libmesh/hilbert_sfc_partitioner.h"
39#include "libmesh/linear_partitioner.h"
40#include "libmesh/mapped_subdomain_partitioner.h"
41#include "libmesh/metis_partitioner.h"
42#include "libmesh/morton_sfc_partitioner.h"
43#include "libmesh/parmetis_partitioner.h"
44#include "libmesh/sfc_partitioner.h"
45#include "libmesh/subdomain_partitioner.h"
46
47// TIMPI includes
49#include "timpi/parallel_sync.h"
50
51// C/C++ includes
52#ifdef LIBMESH_HAVE_PETSC
53#include "libmesh/ignore_warnings.h"
54#include "petscmat.h"
55#include "libmesh/restore_warnings.h"
56#include "libmesh/petsc_solver_exception.h"
57#endif
58
59
60namespace {
61
62using namespace libMesh;
63
64struct CorrectProcIds
65{
66 typedef processor_id_type datum;
67
68 CorrectProcIds(MeshBase & _mesh,
69 std::unordered_set<dof_id_type> & _bad_pids) :
70 mesh(_mesh), bad_pids(_bad_pids) {}
71
72 MeshBase & mesh;
73 std::unordered_set<dof_id_type> & bad_pids;
74
75 // ------------------------------------------------------------
76 void gather_data (const std::vector<dof_id_type> & ids,
77 std::vector<datum> & data)
78 {
79 // Find the processor id of each requested node
80 data.resize(ids.size());
81
82 for (auto i : index_range(ids))
83 {
84 // Look for this point in the mesh and make sure we have a
85 // good pid for it before sending that on
86 if (ids[i] != DofObject::invalid_id &&
87 !bad_pids.count(ids[i]))
88 {
89 Node & node = mesh.node_ref(ids[i]);
90
91 // Return the node's correct processor id,
92 data[i] = node.processor_id();
93 }
94 else
96 }
97 }
98
99 // ------------------------------------------------------------
100 bool act_on_data (const std::vector<dof_id_type> & ids,
101 const std::vector<datum> proc_ids)
102 {
103 bool data_changed = false;
104
105 // Set the ghost node processor ids we've now been informed of
106 for (auto i : index_range(ids))
107 {
108 Node & node = mesh.node_ref(ids[i]);
109
110 processor_id_type & proc_id = node.processor_id();
111 const processor_id_type new_proc_id = proc_ids[i];
112
113 if (const auto it = bad_pids.find(ids[i]);
114 (new_proc_id != proc_id || it != bad_pids.end()) &&
115 new_proc_id != DofObject::invalid_processor_id)
116 {
117 if (it != bad_pids.end())
118 {
119 proc_id = new_proc_id;
120 bad_pids.erase(it);
121 }
122 else
123 proc_id = node.choose_processor_id(proc_id, new_proc_id);
124
125 if (proc_id == new_proc_id)
126 data_changed = true;
127 }
128 }
129
130 return data_changed;
131 }
132};
133
134}
135
136namespace libMesh
137{
138
139
140
141// ------------------------------------------------------------
142// Partitioner static data
144 dof_id_type(1000000);
145
146
147
148// ------------------------------------------------------------
149// Partitioner implementation
150
151
156
157
158std::unique_ptr<Partitioner>
159Partitioner::build (const PartitionerType partitioner_type)
160{
161 switch (partitioner_type)
162 {
164 return std::make_unique<CentroidPartitioner>();
166 return std::make_unique<LinearPartitioner>();
168 return std::make_unique<MappedSubdomainPartitioner>();
170 return std::make_unique<MetisPartitioner>();
172 return std::make_unique<ParmetisPartitioner>();
174 return std::make_unique<HilbertSFCPartitioner>();
176 return std::make_unique<MortonSFCPartitioner>();
177 case SFC_PARTITIONER:
178 return std::make_unique<SFCPartitioner>();
180 return std::make_unique<SubdomainPartitioner>();
181 default:
182 libmesh_error_msg("Invalid partitioner type: " <<
183 Utility::enum_to_string(partitioner_type));
184 }
185}
186
187
188
190{
191 this->partition(mesh,mesh.n_processors());
192}
193
194
195
197 const unsigned int n)
198{
199 libmesh_parallel_only(mesh.comm());
200
201 // BSK - temporary fix while redistribution is integrated 6/26/2008
202 // Uncomment this to not repartition in parallel
203 // if (!mesh.is_serial())
204 // return;
205
206 // we cannot partition into more pieces than we have
207 // active elements!
208 const unsigned int n_parts =
209 static_cast<unsigned int>
210 (std::min(mesh.n_active_elem(), static_cast<dof_id_type>(n)));
211
212 // Set the number of partitions in the mesh
213 mesh.set_n_partitions()=n_parts;
214
215 if (n_parts == 1)
216 {
217 this->single_partition (mesh);
218 return;
219 }
220
221 // First assign a temporary partitioning to any unpartitioned elements. A spatially
222 // coherent assignment is only worth its parallel sort for a partitioner that refines what
223 // it is handed; the other partitioners just need one that is consistent across processors,
224 // which the cheap traversal-order assignment already is on a replicated mesh.
226 (mesh, n_parts,
227 /*spatial_indexing=*/!(mesh.is_replicated() && this->partitions_from_scratch()));
228
229 // Call the partitioning function
230 this->_do_partition(mesh,n_parts);
231
232 // Set the parent's processor ids
234
235 // Redistribute elements if necessary, before setting node processor
236 // ids, to make sure those will be set consistently
238
239#ifdef DEBUG
241
242 // Messed up elem processor_id()s can leave us without the child
243 // elements we need to restrict vectors on a distributed mesh
244 MeshTools::libmesh_assert_valid_procids<Elem>(mesh);
245#endif
246
247 // Set the node's processor ids
249
250#ifdef DEBUG
251 MeshTools::libmesh_assert_valid_procids<Elem>(mesh);
252#endif
253
254 // Give derived Mesh classes a chance to update any cached data to
255 // reflect the new partitioning
257}
258
259
260
265
266
267
269 const unsigned int n)
270{
271 // we cannot partition into more pieces than we have
272 // active elements!
273 const unsigned int n_parts =
274 static_cast<unsigned int>
275 (std::min(mesh.n_active_elem(), static_cast<dof_id_type>(n)));
276
277 // Set the number of partitions in the mesh
278 mesh.set_n_partitions()=n_parts;
279
280 if (n_parts == 1)
281 {
282 this->single_partition (mesh);
283 return;
284 }
285
286 // First assign a temporary partitioning to any unpartitioned elements; see partition()
287 // for why the indexing is chosen this way.
289 (mesh, n_parts,
290 /*spatial_indexing=*/!(mesh.is_replicated() && this->partitions_from_scratch()));
291
292 // Call the partitioning function
293 this->_do_repartition(mesh,n_parts);
294
295 // Set the parent's processor ids
297
298 // Set the node's processor ids
300}
301
302
303
304
305
307{
308 bool changed_pid =
309 this->single_partition_range(mesh.elements_begin(),
310 mesh.elements_end());
311
312 // If we have a distributed mesh with an empty rank (or where rank
313 // 0 has only its own component of a disconnected mesh, I guess),
314 // that rank might need to be informed of a change.
315 mesh.comm().max(changed_pid);
316
317 // We may need to redistribute, in case someone (like our unit
318 // tests) is doing something silly (like moving a whole
319 // already-distributed mesh back onto rank 0).
320 if (changed_pid)
322
323 return changed_pid;
324}
325
326
327
330{
331 LOG_SCOPE("single_partition_range()", "Partitioner");
332
333 bool changed_pid = false;
334
335 for (auto & elem : as_range(it, end))
336 {
337 if (elem->processor_id())
338 changed_pid = true;
339 elem->processor_id() = 0;
340
341 // Assign all this element's nodes to processor 0 as well.
342 for (Node & node : elem->node_ref_range())
343 {
344 if (node.processor_id())
345 changed_pid = true;
346 node.processor_id() = 0;
347 }
348 }
349
350 return changed_pid;
351}
352
357
358
359
361 const unsigned int n_subdomains)
362{
364 /*spatial_indexing=*/true);
365}
366
367
368
370 const unsigned int n_subdomains,
371 const bool spatial_indexing)
372{
373 MeshBase::element_iterator it = mesh.unpartitioned_elements_begin();
374 const MeshBase::element_iterator end = mesh.unpartitioned_elements_end();
375
376 const dof_id_type n_unpartitioned_elements = MeshTools::n_elem (it, end);
377
378 // the unpartitioned elements must exist on all processors. If the range is empty on one
379 // it is empty on all, and we can quit right here.
380 if (!n_unpartitioned_elements)
381 return;
382
383 // find the target subdomain sizes
384 std::vector<dof_id_type> subdomain_bounds(mesh.n_processors());
385
386 for (auto pid : make_range(mesh.n_processors()))
387 {
388 dof_id_type tgt_subdomain_size = 0;
389
390 // watch out for the case that n_subdomains < n_processors
391 if (pid < n_subdomains)
392 {
393 tgt_subdomain_size = n_unpartitioned_elements/n_subdomains;
394
395 if (pid < n_unpartitioned_elements%n_subdomains)
396 tgt_subdomain_size++;
397
398 }
399
400 //libMesh::out << "pid, #= " << pid << ", " << tgt_subdomain_size << std::endl;
401 if (pid == 0)
402 subdomain_bounds[0] = tgt_subdomain_size;
403 else
404 subdomain_bounds[pid] = subdomain_bounds[pid-1] + tgt_subdomain_size;
405 }
406
407 libmesh_assert_equal_to (subdomain_bounds.back(), n_unpartitioned_elements);
408
409 // create the unique mapping for all unpartitioned elements independent of partitioning
410 // determine the global indexing for all the unpartitioned elements
411 std::vector<dof_id_type> global_indices;
412
413 // Calling this on all processors a unique range in [0,n_unpartitioned_elements) is constructed.
414 // Only the indices for the elements we pass in are returned in the array.
415 // Without the spatial indexing an element's position in the traversal of the range is used
416 // instead, which a replicated mesh reaches in the same order on every processor and which
417 // therefore indexes [0, n_unpartitioned_elements) just as uniquely, at no cost.
418 if (spatial_indexing)
421 global_indices);
422 else
424
425 dof_id_type cnt=0;
426 for (auto & elem : as_range(it, end))
427 {
428 libmesh_assert (!spatial_indexing || cnt < global_indices.size());
429 const dof_id_type global_index =
430 spatial_indexing ? global_indices[cnt++] : cnt++;
431
432 libmesh_assert_less (global_index, subdomain_bounds.back());
433 libmesh_assert_less (global_index, n_unpartitioned_elements);
434
435 const processor_id_type subdomain_id =
436 cast_int<processor_id_type>
437 (std::distance(subdomain_bounds.begin(),
438 std::upper_bound(subdomain_bounds.begin(),
439 subdomain_bounds.end(),
440 global_index)));
441 libmesh_assert_less (subdomain_id, n_subdomains);
442
443 elem->processor_id() = subdomain_id;
444 //libMesh::out << "assigning " << global_index << " to " << subdomain_id << std::endl;
445 }
446}
447
448
449
451{
452 // Ignore the parameter when !LIBMESH_ENABLE_AMR
454
455 LOG_SCOPE("set_parent_processor_ids()", "Partitioner");
456
457#ifdef LIBMESH_ENABLE_AMR
458
459 // If the mesh is serial we have access to all the elements,
460 // in particular all the active ones. We can therefore set
461 // the parent processor ids indirectly through their children, and
462 // set the subactive processor ids while examining their active
463 // ancestors.
464 // By convention a parent is assigned to the minimum processor
465 // of all its children, and a subactive is assigned to the processor
466 // of its active ancestor.
467 if (mesh.is_serial())
468 {
469 for (auto & elem : mesh.active_element_ptr_range())
470 {
471 // First set descendents
472 std::vector<Elem *> subactive_family;
473 elem->total_family_tree(subactive_family);
474 for (const auto & f : subactive_family)
475 f->processor_id() = elem->processor_id();
476
477 // Then set ancestors
478 Elem * parent = elem->parent();
479
480 while (parent)
481 {
482 // invalidate the parent id, otherwise the min below
483 // will not work if the current parent id is less
484 // than all the children!
485 parent->invalidate_processor_id();
486
487 for (auto & child : parent->child_ref_range())
488 {
489 libmesh_assert(!child.is_remote());
490 libmesh_assert_not_equal_to (child.processor_id(), DofObject::invalid_processor_id);
491 parent->processor_id() = std::min(parent->processor_id(),
492 child.processor_id());
493 }
494 parent = parent->parent();
495 }
496 }
497 }
498
499 // When the mesh is parallel we cannot guarantee that parents have access to
500 // all their children.
501 else
502 {
503 // Setting subactive processor ids is easy: we can guarantee
504 // that children have access to all their parents.
505
506 // Loop over all the active elements in the mesh
507 for (auto & child : mesh.active_element_ptr_range())
508 {
509 std::vector<Elem *> subactive_family;
510 child->total_family_tree(subactive_family);
511 for (const auto & f : subactive_family)
512 f->processor_id() = child->processor_id();
513 }
514
515 // When the mesh is parallel we cannot guarantee that parents have access to
516 // all their children.
517
518 // We will use a brute-force approach here. Each processor finds its parent
519 // elements and sets the parent pid to the minimum of its
520 // semilocal descendants.
521 // A global reduction is then performed to make sure the true minimum is found.
522 // As noted, this is required because we cannot guarantee that a parent has
523 // access to all its children on any single processor.
524 libmesh_parallel_only(mesh.comm());
525 libmesh_assert(MeshTools::n_elem(mesh.unpartitioned_elements_begin(),
526 mesh.unpartitioned_elements_end()) == 0);
527
528 const dof_id_type max_elem_id = mesh.max_elem_id();
529
530 std::vector<processor_id_type>
531 parent_processor_ids (std::min(communication_blocksize,
532 max_elem_id));
533
534 for (dof_id_type blk=0, last_elem_id=0; last_elem_id<max_elem_id; blk++)
535 {
536 last_elem_id =
537 std::min(static_cast<dof_id_type>((blk+1)*communication_blocksize),
538 max_elem_id);
539 const dof_id_type first_elem_id = blk*communication_blocksize;
540
541 std::fill (parent_processor_ids.begin(),
542 parent_processor_ids.end(),
544
545 // first build up local contributions to parent_processor_ids
546 bool have_parent_in_block = false;
547
548 for (auto & parent : as_range(mesh.ancestor_elements_begin(),
549 mesh.ancestor_elements_end()))
550 {
551 const dof_id_type parent_idx = parent->id();
552 libmesh_assert_less (parent_idx, max_elem_id);
553
554 if ((parent_idx >= first_elem_id) &&
555 (parent_idx < last_elem_id))
556 {
557 have_parent_in_block = true;
559
560 std::vector<const Elem *> active_family;
561 parent->active_family_tree(active_family);
562 for (const auto & f : active_family)
563 parent_pid = std::min (parent_pid, f->processor_id());
564
565 const dof_id_type packed_idx = parent_idx - first_elem_id;
566 libmesh_assert_less (packed_idx, parent_processor_ids.size());
567
568 parent_processor_ids[packed_idx] = parent_pid;
569 }
570 }
571
572 // then find the global minimum
573 mesh.comm().min (parent_processor_ids);
574
575 // and assign the ids, if we have a parent in this block.
576 if (have_parent_in_block)
577 for (auto & parent : as_range(mesh.ancestor_elements_begin(),
578 mesh.ancestor_elements_end()))
579 {
580 const dof_id_type parent_idx = parent->id();
581
582 if ((parent_idx >= first_elem_id) &&
583 (parent_idx < last_elem_id))
584 {
585 const dof_id_type packed_idx = parent_idx - first_elem_id;
586 libmesh_assert_less (packed_idx, parent_processor_ids.size());
587
588 const processor_id_type parent_pid =
589 parent_processor_ids[packed_idx];
590
591 libmesh_assert_not_equal_to (parent_pid, DofObject::invalid_processor_id);
592
593 parent->processor_id() = parent_pid;
594 }
595 }
596 }
597 }
598
599#endif // LIBMESH_ENABLE_AMR
600}
601
602void
604 std::map<std::pair<processor_id_type, processor_id_type>, std::set<dof_id_type>> & processor_pair_to_nodes)
605{
606 // This function must be run on all processors at once
607 libmesh_parallel_only(mesh.comm());
608
609 processor_pair_to_nodes.clear();
610
611 std::set<dof_id_type> mynodes;
612 std::set<dof_id_type> neighbor_nodes;
613 std::vector<dof_id_type> common_nodes;
614
615 // Loop over all the active elements
616 for (auto & elem : mesh.active_element_ptr_range())
617 {
618 libmesh_assert(elem);
619
620 libmesh_assert_not_equal_to (elem->processor_id(), DofObject::invalid_processor_id);
621
622 auto n_nodes = elem->n_nodes();
623
624 // prepare data for this element
625 mynodes.clear();
626 neighbor_nodes.clear();
627 common_nodes.clear();
628
629 for (unsigned int inode = 0; inode < n_nodes; inode++)
630 mynodes.insert(elem->node_id(inode));
631
632 for (auto i : elem->side_index_range())
633 {
634 auto neigh = elem->neighbor_ptr(i);
635 if (neigh && !neigh->is_remote() && neigh->processor_id() != elem->processor_id())
636 {
637 neighbor_nodes.clear();
638 common_nodes.clear();
639 auto neigh_n_nodes = neigh->n_nodes();
640 for (unsigned int inode = 0; inode < neigh_n_nodes; inode++)
641 neighbor_nodes.insert(neigh->node_id(inode));
642
643 std::set_intersection(mynodes.begin(), mynodes.end(),
644 neighbor_nodes.begin(), neighbor_nodes.end(),
645 std::back_inserter(common_nodes));
646
647 auto & map_set = processor_pair_to_nodes[std::make_pair(std::min(elem->processor_id(), neigh->processor_id()),
648 std::max(elem->processor_id(), neigh->processor_id()))];
649 for (auto global_node_id : common_nodes)
650 map_set.insert(global_node_id);
651 }
652 }
653 }
654}
655
657{
658 // This function must be run on all processors at once
659 libmesh_parallel_only(mesh.comm());
660
661 std::map<std::pair<processor_id_type, processor_id_type>, std::set<dof_id_type>> processor_pair_to_nodes;
662
663 processor_pairs_to_interface_nodes(mesh, processor_pair_to_nodes);
664
665 for (auto & pmap : processor_pair_to_nodes)
666 {
667 std::size_t n_own_nodes = pmap.second.size()/2, i = 0;
668
669 for (dof_id_type id : pmap.second)
670 {
671 auto & node = mesh.node_ref(id);
672 if (i <= n_own_nodes)
673 node.processor_id() = pmap.first.first;
674 else
675 node.processor_id() = pmap.first.second;
676 i++;
677 }
678 }
679}
680
682{
683 // This function must be run on all processors at once
684 libmesh_parallel_only(mesh.comm());
685
686 // I see occasional consistency failures when using this on a
687 // distributed mesh
688 libmesh_experimental();
689
690 std::map<std::pair<processor_id_type, processor_id_type>, std::set<dof_id_type>> processor_pair_to_nodes;
691
692 processor_pairs_to_interface_nodes(mesh, processor_pair_to_nodes);
693
694 std::unordered_map<dof_id_type, std::vector<const Elem *>> nodes_to_elem_map;
695
696 MeshTools::build_nodes_to_elem_map(mesh, nodes_to_elem_map);
697
698 std::vector<const Node *> neighbors;
699 std::set<dof_id_type> neighbors_order;
700 std::vector<dof_id_type> common_nodes;
701 std::queue<dof_id_type> nodes_queue;
702 std::set<dof_id_type> visted_nodes;
703
704 for (auto & pmap : processor_pair_to_nodes)
705 {
706 std::size_t n_own_nodes = pmap.second.size()/2;
707
708 // Initialize node assignment
709 for (dof_id_type id : pmap.second)
710 mesh.node_ref(id).processor_id() = pmap.first.second;
711
712 visted_nodes.clear();
713 for (dof_id_type id : pmap.second)
714 {
715 mesh.node_ref(id).processor_id() = pmap.first.second;
716
717 if (visted_nodes.count(id))
718 continue;
719 else
720 {
721 nodes_queue.push(id);
722 visted_nodes.insert(id);
723 if (visted_nodes.size() >= n_own_nodes)
724 break;
725 }
726
727 while (!nodes_queue.empty())
728 {
729 auto & node = mesh.node_ref(nodes_queue.front());
730 nodes_queue.pop();
731
732 neighbors.clear();
733 MeshTools::find_nodal_neighbors(mesh, node, nodes_to_elem_map, neighbors);
734 neighbors_order.clear();
735 for (auto & neighbor : neighbors)
736 neighbors_order.insert(neighbor->id());
737
738 common_nodes.clear();
739 std::set_intersection(pmap.second.begin(), pmap.second.end(),
740 neighbors_order.begin(), neighbors_order.end(),
741 std::back_inserter(common_nodes));
742
743 for (auto c_node : common_nodes)
744 if (!visted_nodes.count(c_node))
745 {
746 nodes_queue.push(c_node);
747 visted_nodes.insert(c_node);
748 if (visted_nodes.size() >= n_own_nodes)
749 goto queue_done;
750 }
751
752 if (visted_nodes.size() >= n_own_nodes)
753 goto queue_done;
754 }
755 }
756 queue_done:
757 for (auto node : visted_nodes)
758 mesh.node_ref(node).processor_id() = pmap.first.first;
759 }
760}
761
763{
764 libmesh_ignore(mesh); // Only used if LIBMESH_HAVE_PETSC
765
766 // This function must be run on all processors at once
767 libmesh_parallel_only(mesh.comm());
768
769#ifdef LIBMESH_HAVE_PETSC
770 std::map<std::pair<processor_id_type, processor_id_type>, std::set<dof_id_type>> processor_pair_to_nodes;
771
772 processor_pairs_to_interface_nodes(mesh, processor_pair_to_nodes);
773
774 std::vector<std::vector<const Elem *>> nodes_to_elem_map;
775
776 MeshTools::build_nodes_to_elem_map(mesh, nodes_to_elem_map);
777
778 std::vector<const Node *> neighbors;
779 std::set<dof_id_type> neighbors_order;
780 std::vector<dof_id_type> common_nodes;
781
782 std::vector<dof_id_type> rows;
783 std::vector<dof_id_type> cols;
784
785 std::map<dof_id_type, dof_id_type> global_to_local;
786
787 for (auto & pmap : processor_pair_to_nodes)
788 {
789 unsigned int i = 0;
790
791 rows.clear();
792 rows.resize(pmap.second.size()+1);
793 cols.clear();
794 for (dof_id_type id : pmap.second)
795 global_to_local[id] = i++;
796
797 i = 0;
798 for (auto id : pmap.second)
799 {
800 auto & node = mesh.node_ref(id);
801 neighbors.clear();
802 MeshTools::find_nodal_neighbors(mesh, node, nodes_to_elem_map, neighbors);
803 neighbors_order.clear();
804 for (auto & neighbor : neighbors)
805 neighbors_order.insert(neighbor->id());
806
807 common_nodes.clear();
808 std::set_intersection(pmap.second.begin(), pmap.second.end(),
809 neighbors_order.begin(), neighbors_order.end(),
810 std::back_inserter(common_nodes));
811
812 rows[i+1] = rows[i] + cast_int<dof_id_type>(common_nodes.size());
813
814 for (auto c_node : common_nodes)
815 cols.push_back(global_to_local[c_node]);
816
817 i++;
818 }
819
820 // Next we construct an IS from a MatPartitioning
822 {
823 PetscInt *adj_i, *adj_j;
824 LibmeshPetscCall2(mesh.comm(), PetscCalloc1(rows.size(), &adj_i));
825 LibmeshPetscCall2(mesh.comm(), PetscCalloc1(cols.size(), &adj_j));
826 PetscInt rows_size = cast_int<PetscInt>(rows.size());
827 for (PetscInt ii=0; ii<rows_size; ii++)
828 adj_i[ii] = rows[ii];
829
830 PetscInt cols_size = cast_int<PetscInt>(cols.size());
831 for (PetscInt ii=0; ii<cols_size; ii++)
832 adj_j[ii] = cols[ii];
833
834 const PetscInt sz = cast_int<PetscInt>(pmap.second.size());
835
836 // Create sparse matrix representing an adjacency list
838 LibmeshPetscCall2(mesh.comm(), MatCreateMPIAdj(PETSC_COMM_SELF, sz, sz, adj_i, adj_j, nullptr, adj.get()));
839
840 // Create MatPartitioning object
842 LibmeshPetscCall2(mesh.comm(), MatPartitioningCreate(PETSC_COMM_SELF, part.get()));
843
844 // Apply MatPartitioning, storing results in "is"
845 LibmeshPetscCall2(mesh.comm(), MatPartitioningSetAdjacency(part, adj));
846 LibmeshPetscCall2(mesh.comm(), MatPartitioningSetNParts(part, 2));
847 LibmeshPetscCall2(mesh.comm(), PetscObjectSetOptionsPrefix((PetscObject)(*part), "balance_"));
848 LibmeshPetscCall2(mesh.comm(), MatPartitioningSetFromOptions(part));
849 LibmeshPetscCall2(mesh.comm(), MatPartitioningApply(part, is.get()));
850 }
851
852 PetscInt local_size;
853 const PetscInt *indices;
854 LibmeshPetscCall2(mesh.comm(), ISGetLocalSize(is, &local_size));
855 LibmeshPetscCall2(mesh.comm(), ISGetIndices(is, &indices));
856
857 i = 0;
858 for (auto id : pmap.second)
859 {
860 auto & node = mesh.node_ref(id);
861 if (indices[i])
862 node.processor_id() = pmap.first.second;
863 else
864 node.processor_id() = pmap.first.first;
865
866 i++;
867 }
868 LibmeshPetscCall2(mesh.comm(), ISRestoreIndices(is, &indices));
869 }
870#else
871 libmesh_error_msg("PETSc is required");
872#endif
873}
874
875
877{
878 LOG_SCOPE("set_node_processor_ids()","Partitioner");
879
880 // This function must be run on all processors at once
881 libmesh_parallel_only(mesh.comm());
882
883 // If we have any unpartitioned elements at this
884 // stage there is a problem
885 libmesh_assert (MeshTools::n_elem(mesh.unpartitioned_elements_begin(),
886 mesh.unpartitioned_elements_end()) == 0);
887
888 // Start from scratch here: nodes we used to own may not be
889 // eligible for us to own any more.
890 for (auto & node : mesh.node_ptr_range())
891 {
893 }
894
895 // Loop over all the active elements
896 for (auto & elem : mesh.active_element_ptr_range())
897 {
898 libmesh_assert(elem);
899
900 libmesh_assert_not_equal_to (elem->processor_id(), DofObject::invalid_processor_id);
901
902 // Consider updating the processor id on this element's nodes
903 for (Node & node : elem->node_ref_range())
904 {
905 processor_id_type & pid = node.processor_id();
906 pid = node.choose_processor_id(pid, elem->processor_id());
907 }
908 }
909
910 // How we finish off the node partitioning depends on our command
911 // line options.
912
913 const bool load_balanced_nodes_linear =
914 libMesh::on_command_line ("--load-balanced-nodes-linear");
915
916 const bool load_balanced_nodes_bfs =
917 libMesh::on_command_line ("--load-balanced-nodes-bfs");
918
919 const bool load_balanced_nodes_petscpartition =
920 libMesh::on_command_line ("--load-balanced-nodes-petscpartitioner");
921
922 unsigned int n_load_balance_options = load_balanced_nodes_linear;
923 n_load_balance_options += load_balanced_nodes_bfs;
924 n_load_balance_options += load_balanced_nodes_petscpartition;
925 libmesh_error_msg_if(n_load_balance_options > 1,
926 "Cannot perform more than one load balancing type at a time");
927
928 if (load_balanced_nodes_linear)
930 else if (load_balanced_nodes_bfs)
932 else if (load_balanced_nodes_petscpartition)
934
935 // Node balancing algorithm will response to assign owned nodes.
936 // We still need to sync PIDs
937 {
938 // For inactive elements, we will have already gotten most of
939 // these nodes, *except* for the case of a parent with a subset
940 // of active descendants which are remote elements. In that
941 // case some of the parent nodes will not have been properly
942 // handled yet on our processor.
943 //
944 // We don't want to inadvertently give one of them an incorrect
945 // processor id, but if we're not in serial then we have to
946 // assign them temporary pids to make querying work, so we'll
947 // save our *valid* pids before assigning temporaries.
948 //
949 // Even in serial we'll want to check and make sure we're not
950 // overwriting valid active node pids with pids from subactive
951 // elements.
952 std::unordered_set<dof_id_type> bad_pids;
953
954 for (auto & node : mesh.node_ptr_range())
956 bad_pids.insert(node->id());
957
958 // If we assign our temporary ids by looping from finer elements
959 // to coarser elements, we'll always get an id from the finest
960 // ghost element we can see, which will usually be "closer" to
961 // the true processor we want to query and so will reduce query
962 // cycles that don't reach that processor.
963
964 // But we can still end up with a query cycle that dead-ends, so
965 // we need to prepare a "push" communication step here.
966
967 const bool is_serial = mesh.is_serial();
968 std::unordered_map
970 std::unordered_map<dof_id_type, processor_id_type>>
971 potential_pids;
972
973 const unsigned int n_levels = MeshTools::n_levels(mesh);
974 for (unsigned int level = n_levels; level > 0; --level)
975 {
976 for (auto & elem : as_range(mesh.level_elements_begin(level-1),
977 mesh.level_elements_end(level-1)))
978 {
979 libmesh_assert_not_equal_to (elem->processor_id(),
981
982 const processor_id_type elem_pid = elem->processor_id();
983
984 // Consider updating the processor id on this element's nodes
985 for (Node & node : elem->node_ref_range())
986 {
987 processor_id_type & pid = node.processor_id();
988 if (bad_pids.count(node.id()))
989 pid = node.choose_processor_id(pid, elem_pid);
990 else if (!is_serial)
991 potential_pids[elem_pid][node.id()] = pid;
992 }
993 }
994 }
995
996 if (!is_serial)
997 {
998 std::unordered_map
1000 std::vector<std::pair<dof_id_type, processor_id_type>>>
1001 potential_pids_vecs;
1002
1003 for (auto & pair : potential_pids)
1004 potential_pids_vecs[pair.first].assign(pair.second.begin(), pair.second.end());
1005
1006 auto pids_action_functor =
1007 [& mesh, & bad_pids]
1008 (processor_id_type /* src_pid */,
1009 const std::vector<std::pair<dof_id_type, processor_id_type>> & data)
1010 {
1011 for (auto pair : data)
1012 {
1013 Node & node = mesh.node_ref(pair.first);
1014 processor_id_type & pid = node.processor_id();
1015 if (const auto it = bad_pids.find(pair.first);
1016 it != bad_pids.end())
1017 {
1018 pid = pair.second;
1019 bad_pids.erase(it);
1020 }
1021 else
1022 pid = node.choose_processor_id(pid, pair.second);
1023 }
1024 };
1025
1027 (mesh.comm(), potential_pids_vecs, pids_action_functor);
1028
1029 // Using default libMesh options, we'll just need to sync
1030 // between processors now. The catch here is that we can't
1031 // initially trust Node::choose_processor_id() because some
1032 // of those node processor ids are the temporary ones.
1033 CorrectProcIds correct_pids(mesh, bad_pids);
1035 (mesh, mesh.elements_begin(), mesh.elements_end(),
1037 correct_pids);
1038
1039 // But once we've got all the non-temporary pids synced, we
1040 // may need to sync again to get any pids on nodes only
1041 // connected to subactive elements, for which *only*
1042 // "temporary" pids are possible.
1043 bad_pids.clear();
1045 (mesh,
1046 mesh.elements_begin(), mesh.elements_end(),
1048 correct_pids);
1049 }
1050 }
1051
1052 // We can't assert that all nodes are connected to elements, because
1053 // a DistributedMesh with NodeConstraints might have pulled in some
1054 // remote nodes solely for evaluating those constraints.
1055 // MeshTools::libmesh_assert_connected_nodes(mesh);
1056
1057#ifdef DEBUG
1058 MeshTools::libmesh_assert_valid_procids<Node>(mesh);
1059 //MeshTools::libmesh_assert_canonical_node_procids(mesh);
1060#endif
1061}
1062
1063
1064
1066{
1068
1069 typedef std::unordered_map<dof_id_type, dof_id_type> map_type;
1070
1071 SyncLocalIDs(map_type & _id_map) : id_map(_id_map) {}
1072
1074
1075 void gather_data (const std::vector<dof_id_type> & ids,
1076 std::vector<datum> & local_ids) const
1077 {
1078 local_ids.resize(ids.size());
1079
1080 for (auto i : index_range(ids))
1081 local_ids[i] = id_map[ids[i]];
1082 }
1083
1084 void act_on_data (const std::vector<dof_id_type> & ids,
1085 const std::vector<datum> & local_ids)
1086 {
1087 for (auto i : index_range(local_ids))
1088 id_map[ids[i]] = local_ids[i];
1089 }
1090};
1091
1093{
1094 const dof_id_type n_active_local_elem = mesh.n_active_local_elem();
1095
1096 // Find the number of active elements on each processor. We cannot use
1097 // mesh.n_active_elem_on_proc(pid) since that only returns the number of
1098 // elements assigned to pid which are currently stored on the calling
1099 // processor. This will not in general be correct for parallel meshes
1100 // when (pid!=mesh.processor_id()).
1101 auto n_proc = mesh.n_processors();
1102 _n_active_elem_on_proc.resize(n_proc);
1103 mesh.comm().allgather(n_active_local_elem, _n_active_elem_on_proc);
1104
1105 std::vector<dof_id_type> n_active_elem_before_proc(mesh.n_processors());
1106
1107 for (auto i : make_range(n_proc-1))
1108 n_active_elem_before_proc[i+1] =
1109 n_active_elem_before_proc[i] + _n_active_elem_on_proc[i];
1110
1113
1115
1116 // create the mapping which is contiguous by processor
1118 mesh.active_local_elements_begin(),
1119 mesh.active_local_elements_end(),
1121
1123
1125 (mesh.comm(), mesh.active_elements_begin(), mesh.active_elements_end(), sync);
1126
1127 for (const auto & elem : mesh.active_element_ptr_range())
1128 {
1129 const processor_id_type pid = elem->processor_id();
1130 libmesh_assert_less (_global_index_by_pid_map[elem->id()], _n_active_elem_on_proc[pid]);
1131
1132 _global_index_by_pid_map[elem->id()] += n_active_elem_before_proc[pid];
1133 }
1134}
1135
1137{
1138 LOG_SCOPE("build_graph()", "Partitioner");
1139
1140 const dof_id_type n_active_local_elem = mesh.n_active_local_elem();
1141
1142 // If we have boundary elements in this mesh, we want to account for
1143 // the connectivity between them and interior elements. We can find
1144 // interior elements from boundary elements, but we need to build up
1145 // a lookup map to do the reverse.
1146 typedef std::unordered_multimap<const Elem *, const Elem *> map_type;
1147 map_type interior_to_boundary_map;
1148
1149 // If we have spline nodes in this mesh, we want to account for the
1150 // connectivity between them and integration elements. We can find
1151 // spline nodes from integration elements, but need a reverse map
1152 // {integration_elements} = elems_constrained_by[spline_nodeelem]
1153 map_type elems_constrained_by;
1154
1155 const auto & mesh_constrained_nodes = mesh.get_constraint_rows();
1156
1157 for (const Elem * elem : mesh.active_element_ptr_range())
1158 {
1159 if (!mesh_constrained_nodes.empty()) // quick test for non-IGA cases
1160 {
1161 const auto end_it = mesh_constrained_nodes.end();
1162
1163 // Use a set to avoid duplicates. Use a well-defined method
1164 // of ordering that set to make debugging easier.
1165 std::set<const Elem *, CompareElemIdsByLevel> constraining_elems;
1166 for (const Node & node : elem->node_ref_range())
1167 {
1168 if (const auto row_it = mesh_constrained_nodes.find(&node);
1169 row_it != end_it)
1170 for (const auto & [pr, coef] : row_it->second)
1171 {
1172 libmesh_ignore(coef); // avoid gcc 7 warning
1173 constraining_elems.insert(pr.first);
1174 }
1175 }
1176 for (const Elem * constraining_elem : constraining_elems)
1177 elems_constrained_by.emplace(constraining_elem, elem);
1178 }
1179
1180 // If we don't have an interior_parent and we don't have any
1181 // constrained nodes then there's nothing else to look up.
1182 if (elem->interior_parent())
1183 {
1184 // get all relevant interior elements
1185 std::set<const Elem *> neighbor_set;
1186 elem->find_interior_neighbors(neighbor_set);
1187
1188 for (const auto & neighbor : neighbor_set)
1189 interior_to_boundary_map.emplace(neighbor, elem);
1190 }
1191 }
1192
1193#ifdef LIBMESH_ENABLE_AMR
1194 std::vector<const Elem *> neighbors_offspring;
1195#endif
1196
1197 // This is costly, and we only need to do it if the mesh has
1198 // changed since we last partitioned... but the mesh probably has
1199 // changed since we last partitioned, and if it hasn't we don't
1200 // have a reliable way to be sure of that.
1202
1203 dof_id_type first_local_elem = 0;
1204 for (auto pid : make_range(mesh.processor_id()))
1205 first_local_elem += _n_active_elem_on_proc[pid];
1206
1207 _dual_graph.clear();
1208 _dual_graph.resize(n_active_local_elem);
1209 _local_id_to_elem.resize(n_active_local_elem);
1210
1211 // We may need to communicate constraint-row-based connections
1212 // between processors
1213 std::unordered_map<processor_id_type,
1214 std::vector<std::pair<dof_id_type, dof_id_type>>> connections_to_push;
1215
1216 for (const auto & elem : mesh.active_local_element_ptr_range())
1217 {
1218 libmesh_assert (_global_index_by_pid_map.count(elem->id()));
1219 const dof_id_type global_index_by_pid =
1220 _global_index_by_pid_map[elem->id()];
1221
1222 const dof_id_type local_index =
1223 global_index_by_pid - first_local_elem;
1224 libmesh_assert_less (local_index, n_active_local_elem);
1225
1226 std::vector<dof_id_type> & graph_row = _dual_graph[local_index];
1227
1228 // Save this off to make it easy to index later
1229 _local_id_to_elem[local_index] = const_cast<Elem*>(elem);
1230
1231 // Loop over the element's neighbors. An element
1232 // adjacency corresponds to a face neighbor
1233 for (auto neighbor : elem->neighbor_ptr_range())
1234 {
1235 if (neighbor != nullptr)
1236 {
1237 // If the neighbor is active treat it
1238 // as a connection
1239 if (neighbor->active())
1240 {
1241 libmesh_assert(_global_index_by_pid_map.count(neighbor->id()));
1242 const dof_id_type neighbor_global_index_by_pid =
1243 _global_index_by_pid_map[neighbor->id()];
1244
1245 graph_row.push_back(neighbor_global_index_by_pid);
1246 }
1247
1248#ifdef LIBMESH_ENABLE_AMR
1249
1250 // Otherwise we need to find all of the
1251 // neighbor's children that are connected to
1252 // us and add them
1253 else
1254 {
1255 // The side of the neighbor to which
1256 // we are connected
1257 const unsigned int ns =
1258 neighbor->which_neighbor_am_i (elem);
1259 libmesh_assert_less (ns, neighbor->n_neighbors());
1260
1261 // Get all the active children (& grandchildren, etc...)
1262 // of the neighbor
1263
1264 // FIXME - this is the wrong thing, since we
1265 // should be getting the active family tree on
1266 // our side only. But adding too many graph
1267 // links may cause hanging nodes to tend to be
1268 // on partition interiors, which would reduce
1269 // communication overhead for constraint
1270 // equations, so we'll leave it.
1271
1272 neighbor->active_family_tree (neighbors_offspring);
1273
1274 // Get all the neighbor's children that
1275 // live on that side and are thus connected
1276 // to us
1277 for (const auto & child : neighbors_offspring)
1278 {
1279 // This does not assume a level-1 mesh.
1280 // Note that since children have sides numbered
1281 // coincident with the parent then this is a sufficient test.
1282 if (child->neighbor_ptr(ns) == elem)
1283 {
1284 libmesh_assert (child->active());
1285 libmesh_assert (_global_index_by_pid_map.count(child->id()));
1286 const dof_id_type child_global_index_by_pid =
1287 _global_index_by_pid_map[child->id()];
1288
1289 graph_row.push_back(child_global_index_by_pid);
1290 }
1291 }
1292 }
1293
1294#endif /* ifdef LIBMESH_ENABLE_AMR */
1295
1296
1297 }
1298 }
1299
1300 if ((elem->dim() < LIBMESH_DIM) &&
1301 elem->interior_parent())
1302 {
1303 // get all relevant interior elements
1304 std::set<const Elem *> neighbor_set;
1305 elem->find_interior_neighbors(neighbor_set);
1306
1307 for (const auto & neighbor : neighbor_set)
1308 {
1309 const dof_id_type neighbor_global_index_by_pid =
1310 _global_index_by_pid_map[neighbor->id()];
1311
1312 graph_row.push_back(neighbor_global_index_by_pid);
1313 }
1314 }
1315
1316 // Check for any boundary neighbors
1317 for (const auto & pr : as_range(interior_to_boundary_map.equal_range(elem)))
1318 {
1319 const Elem * neighbor = pr.second;
1320
1321 const dof_id_type neighbor_global_index_by_pid =
1322 _global_index_by_pid_map[neighbor->id()];
1323
1324 graph_row.push_back(neighbor_global_index_by_pid);
1325 }
1326
1327 // Check for any constraining elements
1328 if (!mesh_constrained_nodes.empty()) // quick test for non-IGA cases
1329 {
1330 const auto end_it = mesh_constrained_nodes.end();
1331
1332 // Use a set to avoid duplicates. Use a well-defined method
1333 // of ordering that set to make debugging easier.
1334 std::set<const Elem *, CompareElemIdsByLevel> constraining_elems;
1335 for (const Node & node : elem->node_ref_range())
1336 {
1337 if (const auto row_it = mesh_constrained_nodes.find(&node);
1338 row_it != end_it)
1339 for (const auto & [pr, coef] : row_it->second)
1340 {
1341 libmesh_ignore(coef); // avoid gcc 7 warning
1342 constraining_elems.insert(pr.first);
1343 }
1344 }
1345 for (const Elem * constraining_elem : constraining_elems)
1346 {
1347 const dof_id_type constraining_global_index_by_pid =
1348 _global_index_by_pid_map[constraining_elem->id()];
1349
1350 graph_row.push_back(constraining_global_index_by_pid);
1351
1352 // We can't be sure if the constraining element's owner sees
1353 // the assembly element, so to get a symmetric connectivity
1354 // graph we'll need to tell them about us to be safe.
1355 if (constraining_elem->processor_id() != mesh.processor_id())
1356 connections_to_push[constraining_elem->processor_id()].emplace_back
1357 (global_index_by_pid, constraining_global_index_by_pid);
1358 }
1359 }
1360
1361 // Check for any constrained elements
1362 for (const auto & pr : as_range(elems_constrained_by.equal_range(elem)))
1363 {
1364 const Elem * constrained = pr.second;
1365 const dof_id_type constrained_global_index_by_pid =
1366 _global_index_by_pid_map[constrained->id()];
1367
1368 graph_row.push_back(constrained_global_index_by_pid);
1369
1370 // We can't be sure if the constrained element's owner sees
1371 // the assembly element, so to get a symmetric connectivity
1372 // graph we'll need to tell them about us to be safe.
1373 if (constrained->processor_id() != mesh.processor_id())
1374 connections_to_push[constrained->processor_id()].emplace_back
1375 (global_index_by_pid, constrained_global_index_by_pid);
1376 }
1377 }
1378
1379 // Partitioners like Parmetis require a symmetric adjacency matrix,
1380 // but if we have an assembly element constrained by a spline
1381 // NodeElem owned by another processor, it's possible that that
1382 // processor doesn't see our assembly element. Let's push those
1383 // entries, to ensure they're counted on both sides.
1384 auto symmetrize_entries =
1385 [this, first_local_elem]
1386 (processor_id_type /*src_pid*/,
1387 const std::vector<std::pair<dof_id_type, dof_id_type>> & incoming_entries)
1388 {
1389 for (auto [i, j] : incoming_entries)
1390 {
1391 libmesh_assert_greater_equal(j, first_local_elem);
1392 const std::size_t jl = j - first_local_elem;
1393 libmesh_assert_less(jl, _dual_graph.size());
1394 std::vector<dof_id_type> & graph_row = _dual_graph[jl];
1395 if (std::find(graph_row.begin(), graph_row.end(), i) == graph_row.end())
1396 {
1397// std::cerr << "Pushing back (" << j << ", " << i << ") from " << src_pid << std::endl;
1398 graph_row.push_back(i);
1399 }
1400 }
1401 };
1402
1404 connections_to_push,
1405 symmetrize_entries);
1406
1407 // *Now* we should have a symmetric adjacency matrix. Let's check
1408 // that, so any failures get caught before they e.g. confuse
1409 // Parmetis in hard-to-debug ways. That's a global communication so
1410 // we'll only do it in debug mode.
1411#ifdef DEBUG
1412 auto n_proc = mesh.n_processors();
1413 std::vector<dof_id_type> first_local_index_on_proc(n_proc, 0);
1414 for (auto pid : make_range(1u,n_proc))
1415 first_local_index_on_proc[pid] = first_local_index_on_proc[pid-1] + _n_active_elem_on_proc[pid-1];
1416
1417 libmesh_assert_equal_to(first_local_index_on_proc[mesh.processor_id()],
1418 first_local_elem);
1419
1420 std::unordered_map<processor_id_type, std::vector<std::pair<dof_id_type, dof_id_type>>> entries_to_send;
1421 for (auto il : index_range(_dual_graph))
1422 {
1423 const std::vector<dof_id_type> & graph_row = _dual_graph[il];
1424
1425 const auto i = il + first_local_elem;
1426
1427 for (auto j : graph_row)
1428 {
1429 // Stupid graph rows aren't sorted yet...
1430 processor_id_type target_pid = 0;
1431 while (target_pid+1 < n_proc &&
1432 j >= first_local_index_on_proc[target_pid+1])
1433 ++target_pid;
1434
1435 entries_to_send[target_pid].emplace_back(i,j);
1436 }
1437 }
1438
1439 std::vector<std::tuple<processor_id_type, dof_id_type, dof_id_type>> bad_entries;
1440
1441 auto check_incoming_entries =
1442 [this, first_local_elem, &bad_entries]
1443 (processor_id_type src_pid,
1444 const std::vector<std::pair<dof_id_type, dof_id_type>> & incoming_entries)
1445 {
1446 for (auto [i, j] : incoming_entries)
1447 {
1448 if (j < first_local_elem)
1449 {
1450 bad_entries.emplace_back(src_pid,i,j);
1451 continue;
1452 }
1453 const std::size_t jl = j - first_local_elem;
1454 if (jl >= _dual_graph.size())
1455 {
1456 bad_entries.emplace_back(src_pid,i,j);
1457 continue;
1458 }
1459 const std::vector<dof_id_type> & graph_row = _dual_graph[jl];
1460 if (std::find(graph_row.begin(), graph_row.end(), i) ==
1461 graph_row.end())
1462 {
1463 bad_entries.emplace_back(src_pid,i,j);
1464 continue;
1465 }
1466 }
1467 };
1468
1469 // Keep any failures in sync in parallel, for easier debugging in
1470 // unit tests where the failure exception will be caught.
1472 (mesh.comm(), entries_to_send, check_incoming_entries);
1473 bool bad_entries_exist = !bad_entries.empty();
1474 mesh.comm().max(bad_entries_exist);
1475 if (bad_entries_exist)
1476 {
1477#if 0 // Optional verbosity for if this breaks again...
1478 if (!bad_entries.empty())
1479 {
1480 std::cerr << "Bad entries on processor " << mesh.processor_id() << ": ";
1481 for (auto [p, i, j] : bad_entries)
1482 std::cerr << '(' << p << ", " << i << ", " << j << "), ";
1483 std::cerr << std::endl;
1484 }
1485#endif
1486 libmesh_error_msg("Asymmetric partitioner graph detected");
1487 }
1488#endif
1489}
1490
1491void Partitioner::assign_partitioning (MeshBase & mesh, const std::vector<dof_id_type> & parts)
1492{
1493 LOG_SCOPE("assign_partitioning()", "Partitioner");
1494
1495 // This function must be run on all processors at once
1496 libmesh_parallel_only(mesh.comm());
1497
1498 dof_id_type first_local_elem = 0;
1499 for (auto pid : make_range(mesh.processor_id()))
1500 first_local_elem += _n_active_elem_on_proc[pid];
1501
1502#ifndef NDEBUG
1503 const dof_id_type n_active_local_elem = mesh.n_active_local_elem();
1504#endif
1505
1506 std::map<processor_id_type, std::vector<dof_id_type>>
1507 requested_ids;
1508
1509 // Results to gather from each processor - kept in a map so we
1510 // do only one loop over elements after all receives are done.
1511 std::map<processor_id_type, std::vector<processor_id_type>>
1512 filled_request;
1513
1514 for (auto & elem : mesh.active_element_ptr_range())
1515 {
1516 // we need to get the index from the owning processor
1517 // (note we cannot assign it now -- we are iterating
1518 // over elements again and this will be bad!)
1519 requested_ids[elem->processor_id()].push_back(elem->id());
1520 }
1521
1522 auto gather_functor =
1523 [this,
1524 & parts,
1525#ifndef NDEBUG
1526 & mesh,
1527 n_active_local_elem,
1528#endif
1529 first_local_elem]
1530 (processor_id_type, const std::vector<dof_id_type> & ids,
1531 std::vector<processor_id_type> & data)
1532 {
1533 const std::size_t ids_size = ids.size();
1534 data.resize(ids.size());
1535
1536 for (std::size_t i=0; i != ids_size; i++)
1537 {
1538 const dof_id_type requested_elem_index = ids[i];
1539
1540 libmesh_assert(_global_index_by_pid_map.count(requested_elem_index));
1541
1542 const dof_id_type global_index_by_pid =
1543 _global_index_by_pid_map[requested_elem_index];
1544
1545 const dof_id_type local_index =
1546 global_index_by_pid - first_local_elem;
1547
1548 libmesh_assert_less (local_index, parts.size());
1549 libmesh_assert_less (local_index, n_active_local_elem);
1550
1551 const processor_id_type elem_procid =
1552 cast_int<processor_id_type>(parts[local_index]);
1553
1554 libmesh_assert_less (elem_procid, mesh.n_partitions());
1555
1556 data[i] = elem_procid;
1557 }
1558 };
1559
1560 auto action_functor =
1561 [&filled_request]
1562 (processor_id_type pid,
1563 const std::vector<dof_id_type> &,
1564 const std::vector<processor_id_type> & new_procids)
1565 {
1566 filled_request[pid] = new_procids;
1567 };
1568
1569 // Trade requests with other processors
1570 const processor_id_type * ex = nullptr;
1572 (mesh.comm(), requested_ids, gather_functor, action_functor, ex);
1573
1574 // and finally assign the partitioning.
1575 // note we are iterating in exactly the same order
1576 // used to build up the request, so we can expect the
1577 // required entries to be in the proper sequence.
1578 std::vector<unsigned int> counters(mesh.n_processors(), 0);
1579 for (auto & elem : mesh.active_element_ptr_range())
1580 {
1581 const processor_id_type current_pid = elem->processor_id();
1582
1583 libmesh_assert_less (counters[current_pid], requested_ids[current_pid].size());
1584
1585 const processor_id_type elem_procid =
1586 filled_request[current_pid][counters[current_pid]++];
1587
1588 libmesh_assert_less (elem_procid, mesh.n_partitions());
1589 elem->processor_id() = elem_procid;
1590 }
1591}
1592
1593
1594} // namespace libMesh
void max(const T &r, T &o, Request &req) const
void min(const T &r, T &o, Request &req) const
void allgather(const T &send_data, std::vector< T, A > &recv_data) const
Defines a Cartesian bounding box by the two corner extremum.
void invalidate_processor_id()
Sets the processor id to invalid_processor_id.
Definition dof_object.h:785
processor_id_type processor_id() const
Definition dof_object.h:895
static constexpr dof_id_type invalid_id
An invalid id to distinguish an uninitialized DofObject.
Definition dof_object.h:473
dof_id_type id() const
Definition dof_object.h:833
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
const Elem * parent() const
Definition elem.h:3168
void total_family_tree(std::vector< const Elem * > &family, bool reset=true) const
Same as the family_tree() member, but also adds any subactive descendants.
Definition elem.C:2228
SimpleRange< ChildRefIter > child_ref_range()
Returns a range with all children of a parent element, usable in range-based for loops.
Definition elem.h:2476
This is the MeshBase class.
Definition mesh_base.h:81
virtual const Node & node_ref(const dof_id_type i) const
Definition mesh_base.h:742
virtual bool is_serial() const
Definition mesh_base.h:357
virtual bool is_replicated() const
Definition mesh_base.h:379
virtual void redistribute()
Redistribute elements between processors.
Definition mesh_base.C:1169
virtual void update_post_partitioning()
Recalculate any cached data (or invalidate any caches that are computed on the fly) after elements an...
Definition mesh_base.C:1181
unsigned int n_partitions() const
Definition mesh_base.h:1530
virtual dof_id_type max_elem_id() const =0
constraint_rows_type & get_constraint_rows()
Constraint rows accessors.
Definition mesh_base.h:1940
dof_id_type n_active_local_elem() const
Definition mesh_base.h:711
virtual dof_id_type n_active_elem() const =0
unsigned int & set_n_partitions()
Definition mesh_base.h:2176
This is the MeshCommunication class.
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
processor_id_type choose_processor_id(processor_id_type pid1, processor_id_type pid2) const
Return which of pid1 and pid2 would be preferred by the current load-balancing heuristic applied to t...
Definition node.C:78
processor_id_type processor_id() const
const Parallel::Communicator & comm() const
processor_id_type n_processors() const
bool single_partition(MeshBase &mesh)
Trivially "partitions" the mesh for one processor.
static void set_interface_node_processor_ids_petscpartitioner(MeshBase &mesh)
Nodes on the partitioning interface is partitioned into two groups using a PETSc partitioner for each...
std::vector< dof_id_type > _n_active_elem_on_proc
The number of active elements on each processor.
virtual void partition(MeshBase &mesh, const unsigned int n)
Partitions the MeshBase into n parts by setting processor_id() on Nodes and Elems.
static void set_parent_processor_ids(MeshBase &mesh)
This function is called after partitioning to set the processor IDs for the inactive parent elements.
static void processor_pairs_to_interface_nodes(MeshBase &mesh, std::map< std::pair< processor_id_type, processor_id_type >, std::set< dof_id_type > > &processor_pair_to_nodes)
On the partitioning interface, a surface is shared by two and only two processors.
static void partition_unpartitioned_elements(MeshBase &mesh)
These functions assign processor IDs to newly-created elements (in parallel) which are currently assi...
std::unordered_map< dof_id_type, dof_id_type > _global_index_by_pid_map
Maps active element ids into a contiguous range, as needed by parallel partitioner.
static void set_interface_node_processor_ids_BFS(MeshBase &mesh)
Nodes on the partitioning interface is clustered into two groups BFS (Breadth First Search)scheme for...
std::vector< std::vector< dof_id_type > > _dual_graph
A dual graph corresponds to the mesh, and it is typically used in paritioner.
static std::unique_ptr< Partitioner > build(const PartitionerType solver_package)
Builds a Partitioner of the type specified by partitioner_type.
virtual void _do_repartition(MeshBase &mesh, const unsigned int n)
This is the actual re-partitioning method which can be overridden in derived classes.
virtual void _do_partition(MeshBase &mesh, const unsigned int n)=0
This is the actual partitioning method which must be overridden in derived classes.
virtual void build_graph(const MeshBase &mesh)
Build a dual graph for partitioner.
static void set_node_processor_ids(MeshBase &mesh)
This function is called after partitioning to set the processor IDs for the nodes.
virtual void _find_global_index_by_pid_map(const MeshBase &mesh)
Construct contiguous global indices for the current partitioning.
static void set_interface_node_processor_ids_linear(MeshBase &mesh)
Nodes on the partitioning interface is linearly assigned to each pair of processors.
void repartition(MeshBase &mesh, const unsigned int n)
Repartitions the MeshBase into n parts.
void assign_partitioning(MeshBase &mesh, const std::vector< dof_id_type > &parts)
Assign the computed partitioning to the mesh.
static const dof_id_type communication_blocksize
The blocksize to use when doing blocked parallel communication.
bool single_partition_range(MeshBase::element_iterator it, MeshBase::element_iterator end)
Slightly generalized version of single_partition which acts on a range of elements defined by the pai...
std::vector< Elem * > _local_id_to_elem
virtual PartitionerType type() 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 push_parallel_vector_data(const Communicator &comm, MapToVectors &&data, const ActionFunctor &act_on_data)
void build_nodes_to_elem_map(const MeshBase &mesh, std::vector< std::vector< dof_id_type > > &nodes_to_elem_map)
After calling this function the input vector nodes_to_elem_map will contain the node to element conne...
Definition mesh_tools.C:456
dof_id_type n_elem(const MeshBase::const_element_iterator &begin, const MeshBase::const_element_iterator &end)
Count up the number of elements of a specific type (as defined by an iterator range).
libMesh::BoundingBox create_bounding_box(const MeshBase &mesh)
Definition mesh_tools.C:566
void find_nodal_neighbors(const MeshBase &mesh, const Node &n, const std::vector< std::vector< const Elem * > > &nodes_to_elem_map, std::vector< const Node * > &neighbors)
Given a mesh and a node in the mesh, the vector will be filled with every node directly attached to t...
void libmesh_assert_valid_remote_elems(const MeshBase &mesh)
A function for verifying that active local elements' neighbors are never remote elements.
unsigned int n_levels(const MeshBase &mesh)
Definition mesh_tools.C:826
void sync_node_data_by_element_id(MeshBase &mesh, const MeshBase::const_element_iterator &range_begin, const MeshBase::const_element_iterator &range_end, const ElemCheckFunctor &elem_check, const NodeCheckFunctor &node_check, SyncFunctor &sync)
Synchronize data about a range of ghost nodes uniquely identified by an element id and local node id,...
void sync_dofobject_data_by_id(const Communicator &comm, const Iterator &range_begin, const Iterator &range_end, SyncFunctor &sync)
Request data about a range of ghost dofobjects uniquely identified by their id.
std::string enum_to_string(const T e)
The libMesh namespace provides an interface to certain functionality in the library.
SimpleRange< IndexType > as_range(const std::pair< IndexType, IndexType > &p)
Helper function that allows us to treat a homogenous pair as a range.
PartitionerType
Defines an enum for mesh partitioner types.
auto index_range(const T &sizable)
Helper function that returns an IntRange<std::size_t> representing all the indices of the passed-in v...
Definition int_range.h:153
void libmesh_ignore(const Args &...)
libmesh_assert(ctx)
PetscErrorCode PetscInt const PetscInt IS * is
uint8_t dof_id_type
Definition id_types.h:67
bool on_command_line(std::string arg)
Definition libmesh.C:934
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 element_iterator struct.
Definition mesh_base.h:2557
SyncLocalIDs(map_type &_id_map)
void act_on_data(const std::vector< dof_id_type > &ids, const std::vector< datum > &local_ids)
void gather_data(const std::vector< dof_id_type > &ids, std::vector< datum > &local_ids) const
std::unordered_map< dof_id_type, dof_id_type > map_type
const dof_id_type n_nodes
Definition tecplot_io.C:67