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