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/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
48 : #include "timpi/parallel_implementation.h"
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 :
60 : namespace {
61 :
62 : using namespace libMesh;
63 :
64 : struct CorrectProcIds
65 : {
66 : typedef processor_id_type datum;
67 :
68 144 : CorrectProcIds(MeshBase & _mesh,
69 87375 : std::unordered_set<dof_id_type> & _bad_pids) :
70 87375 : mesh(_mesh), bad_pids(_bad_pids) {}
71 :
72 : MeshBase & mesh;
73 : std::unordered_set<dof_id_type> & bad_pids;
74 :
75 : // ------------------------------------------------------------
76 1547080 : 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 1547080 : data.resize(ids.size());
81 :
82 136124319 : 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 134662944 : if (ids[i] != DofObject::invalid_id &&
87 134557542 : !bad_pids.count(ids[i]))
88 : {
89 134536738 : Node & node = mesh.node_ref(ids[i]);
90 :
91 : // Return the node's correct processor id,
92 134536738 : data[i] = node.processor_id();
93 : }
94 : else
95 40501 : data[i] = DofObject::invalid_processor_id;
96 : }
97 1547080 : }
98 :
99 : // ------------------------------------------------------------
100 1547080 : bool act_on_data (const std::vector<dof_id_type> & ids,
101 : const std::vector<datum> proc_ids)
102 : {
103 462 : bool data_changed = false;
104 :
105 : // Set the ghost node processor ids we've now been informed of
106 136124319 : for (auto i : index_range(ids))
107 : {
108 134577239 : Node & node = mesh.node_ref(ids[i]);
109 :
110 85705 : processor_id_type & proc_id = node.processor_id();
111 134577239 : const processor_id_type new_proc_id = proc_ids[i];
112 :
113 134577239 : if (const auto it = bad_pids.find(ids[i]);
114 134577239 : (new_proc_id != proc_id || it != bad_pids.end()) &&
115 : new_proc_id != DofObject::invalid_processor_id)
116 : {
117 626775 : if (it != bad_pids.end())
118 : {
119 272149 : proc_id = new_proc_id;
120 106 : bad_pids.erase(it);
121 : }
122 : else
123 354626 : proc_id = node.choose_processor_id(proc_id, new_proc_id);
124 :
125 626775 : if (proc_id == new_proc_id)
126 106 : data_changed = true;
127 : }
128 : }
129 :
130 1547080 : return data_changed;
131 : }
132 : };
133 :
134 : }
135 :
136 : namespace libMesh
137 : {
138 :
139 :
140 :
141 : // ------------------------------------------------------------
142 : // Partitioner static data
143 : const dof_id_type Partitioner::communication_blocksize =
144 : dof_id_type(1000000);
145 :
146 :
147 :
148 : // ------------------------------------------------------------
149 : // Partitioner implementation
150 :
151 :
152 0 : PartitionerType Partitioner::type() const
153 : {
154 0 : return INVALID_PARTITIONER;
155 : }
156 :
157 :
158 : std::unique_ptr<Partitioner>
159 418790 : Partitioner::build (const PartitionerType partitioner_type)
160 : {
161 418790 : switch (partitioner_type)
162 : {
163 71 : case CENTROID_PARTITIONER:
164 71 : return std::make_unique<CentroidPartitioner>();
165 213 : case LINEAR_PARTITIONER:
166 213 : return std::make_unique<LinearPartitioner>();
167 0 : case MAPPED_SUBDOMAIN_PARTITIONER:
168 0 : return std::make_unique<MappedSubdomainPartitioner>();
169 69993 : case METIS_PARTITIONER:
170 69993 : return std::make_unique<MetisPartitioner>();
171 348300 : case PARMETIS_PARTITIONER:
172 348300 : return std::make_unique<ParmetisPartitioner>();
173 71 : case HILBERT_SFC_PARTITIONER:
174 71 : return std::make_unique<HilbertSFCPartitioner>();
175 71 : case MORTON_SFC_PARTITIONER:
176 71 : return std::make_unique<MortonSFCPartitioner>();
177 71 : case SFC_PARTITIONER:
178 71 : return std::make_unique<SFCPartitioner>();
179 0 : case SUBDOMAIN_PARTITIONER:
180 0 : return std::make_unique<SubdomainPartitioner>();
181 0 : default:
182 0 : libmesh_error_msg("Invalid partitioner type: " <<
183 : Utility::enum_to_string(partitioner_type));
184 : }
185 : }
186 :
187 :
188 :
189 0 : void Partitioner::partition (MeshBase & mesh)
190 : {
191 0 : this->partition(mesh,mesh.n_processors());
192 0 : }
193 :
194 :
195 :
196 783730 : void Partitioner::partition (MeshBase & mesh,
197 : const unsigned int n)
198 : {
199 15774 : 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 783730 : (std::min(mesh.n_active_elem(), static_cast<dof_id_type>(n)));
211 :
212 : // Set the number of partitions in the mesh
213 783730 : mesh.set_n_partitions()=n_parts;
214 :
215 783730 : if (n_parts == 1)
216 : {
217 149914 : this->single_partition (mesh);
218 149914 : 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.
225 : Partitioner::partition_unpartitioned_elements
226 633816 : (mesh, n_parts,
227 633816 : /*spatial_indexing=*/!(mesh.is_replicated() && this->partitions_from_scratch()));
228 :
229 : // Call the partitioning function
230 633816 : this->_do_partition(mesh,n_parts);
231 :
232 : // Set the parent's processor ids
233 633816 : Partitioner::set_parent_processor_ids(mesh);
234 :
235 : // Redistribute elements if necessary, before setting node processor
236 : // ids, to make sure those will be set consistently
237 633816 : mesh.redistribute();
238 :
239 : #ifdef DEBUG
240 11289 : MeshTools::libmesh_assert_valid_remote_elems(mesh);
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 11289 : MeshTools::libmesh_assert_valid_procids<Elem>(mesh);
245 : #endif
246 :
247 : // Set the node's processor ids
248 633816 : Partitioner::set_node_processor_ids(mesh);
249 :
250 : #ifdef DEBUG
251 11289 : 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
256 633816 : mesh.update_post_partitioning();
257 : }
258 :
259 :
260 :
261 0 : void Partitioner::repartition (MeshBase & mesh)
262 : {
263 0 : this->repartition(mesh,mesh.n_processors());
264 0 : }
265 :
266 :
267 :
268 0 : void Partitioner::repartition (MeshBase & mesh,
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 0 : (std::min(mesh.n_active_elem(), static_cast<dof_id_type>(n)));
276 :
277 : // Set the number of partitions in the mesh
278 0 : mesh.set_n_partitions()=n_parts;
279 :
280 0 : if (n_parts == 1)
281 : {
282 0 : this->single_partition (mesh);
283 0 : return;
284 : }
285 :
286 : // First assign a temporary partitioning to any unpartitioned elements; see partition()
287 : // for why the indexing is chosen this way.
288 : Partitioner::partition_unpartitioned_elements
289 0 : (mesh, n_parts,
290 0 : /*spatial_indexing=*/!(mesh.is_replicated() && this->partitions_from_scratch()));
291 :
292 : // Call the partitioning function
293 0 : this->_do_repartition(mesh,n_parts);
294 :
295 : // Set the parent's processor ids
296 0 : Partitioner::set_parent_processor_ids(mesh);
297 :
298 : // Set the node's processor ids
299 0 : Partitioner::set_node_processor_ids(mesh);
300 : }
301 :
302 :
303 :
304 :
305 :
306 149914 : bool Partitioner::single_partition (MeshBase & mesh)
307 : {
308 : bool changed_pid =
309 295343 : this->single_partition_range(mesh.elements_begin(),
310 440772 : 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 149914 : 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 149914 : if (changed_pid)
321 142062 : mesh.redistribute();
322 :
323 149914 : return changed_pid;
324 : }
325 :
326 :
327 :
328 149914 : bool Partitioner::single_partition_range (MeshBase::element_iterator it,
329 : MeshBase::element_iterator end)
330 : {
331 8970 : LOG_SCOPE("single_partition_range()", "Partitioner");
332 :
333 4485 : bool changed_pid = false;
334 :
335 3219945 : for (auto & elem : as_range(it, end))
336 : {
337 1472380 : if (elem->processor_id())
338 8760 : changed_pid = true;
339 1472380 : elem->processor_id() = 0;
340 :
341 : // Assign all this element's nodes to processor 0 as well.
342 12561548 : for (Node & node : elem->node_ref_range())
343 : {
344 11089168 : if (node.processor_id())
345 40591 : changed_pid = true;
346 11089168 : node.processor_id() = 0;
347 : }
348 140944 : }
349 :
350 154399 : return changed_pid;
351 : }
352 :
353 0 : void Partitioner::partition_unpartitioned_elements (MeshBase & mesh)
354 : {
355 0 : Partitioner::partition_unpartitioned_elements(mesh, mesh.n_processors());
356 0 : }
357 :
358 :
359 :
360 0 : void Partitioner::partition_unpartitioned_elements (MeshBase & mesh,
361 : const unsigned int n_subdomains)
362 : {
363 0 : Partitioner::partition_unpartitioned_elements(mesh, n_subdomains,
364 : /*spatial_indexing=*/true);
365 0 : }
366 :
367 :
368 :
369 633816 : void Partitioner::partition_unpartitioned_elements (MeshBase & mesh,
370 : const unsigned int n_subdomains,
371 : const bool spatial_indexing)
372 : {
373 633816 : MeshBase::element_iterator it = mesh.unpartitioned_elements_begin();
374 633816 : const MeshBase::element_iterator end = mesh.unpartitioned_elements_end();
375 :
376 1256343 : 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 633816 : if (!n_unpartitioned_elements)
381 3679 : return;
382 :
383 : // find the target subdomain sizes
384 276043 : std::vector<dof_id_type> subdomain_bounds(mesh.n_processors());
385 :
386 2871756 : for (auto pid : make_range(mesh.n_processors()))
387 : {
388 15220 : dof_id_type tgt_subdomain_size = 0;
389 :
390 : // watch out for the case that n_subdomains < n_processors
391 2610933 : if (pid < n_subdomains)
392 : {
393 1454770 : tgt_subdomain_size = n_unpartitioned_elements/n_subdomains;
394 :
395 1454770 : if (pid < n_unpartitioned_elements%n_subdomains)
396 420410 : tgt_subdomain_size++;
397 :
398 : }
399 :
400 : //libMesh::out << "pid, #= " << pid << ", " << tgt_subdomain_size << std::endl;
401 2610933 : if (pid == 0)
402 260823 : subdomain_bounds[0] = tgt_subdomain_size;
403 : else
404 2365330 : subdomain_bounds[pid] = subdomain_bounds[pid-1] + tgt_subdomain_size;
405 : }
406 :
407 7610 : 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 15220 : 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 260823 : if (spatial_indexing)
419 223545 : MeshCommunication().find_global_indices (mesh.comm(),
420 447090 : MeshTools::create_bounding_box(mesh), it, end,
421 : global_indices);
422 : else
423 7398 : libmesh_assert (mesh.is_replicated());
424 :
425 7610 : dof_id_type cnt=0;
426 14001835 : for (auto & elem : as_range(it, end))
427 : {
428 374320 : libmesh_assert (!spatial_indexing || cnt < global_indices.size());
429 : const dof_id_type global_index =
430 13487799 : spatial_indexing ? global_indices[cnt++] : cnt++;
431 :
432 374320 : libmesh_assert_less (global_index, subdomain_bounds.back());
433 374320 : libmesh_assert_less (global_index, n_unpartitioned_elements);
434 :
435 : const processor_id_type subdomain_id =
436 : cast_int<processor_id_type>
437 13113479 : (std::distance(subdomain_bounds.begin(),
438 : std::upper_bound(subdomain_bounds.begin(),
439 : subdomain_bounds.end(),
440 374320 : global_index)));
441 374320 : libmesh_assert_less (subdomain_id, n_subdomains);
442 :
443 13487799 : elem->processor_id() = subdomain_id;
444 : //libMesh::out << "assigning " << global_index << " to " << subdomain_id << std::endl;
445 245603 : }
446 : }
447 :
448 :
449 :
450 633816 : void Partitioner::set_parent_processor_ids(MeshBase & mesh)
451 : {
452 : // Ignore the parameter when !LIBMESH_ENABLE_AMR
453 11289 : libmesh_ignore(mesh);
454 :
455 33867 : 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 633816 : if (mesh.is_serial())
468 : {
469 52786987 : for (auto & elem : mesh.active_element_ptr_range())
470 : {
471 : // First set descendents
472 2268744 : std::vector<Elem *> subactive_family;
473 26975115 : elem->total_family_tree(subactive_family);
474 55210266 : for (const auto & f : subactive_family)
475 28235151 : f->processor_id() = elem->processor_id();
476 :
477 : // Then set ancestors
478 26975115 : Elem * parent = elem->parent();
479 :
480 60375333 : 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 1988368 : parent->invalidate_processor_id();
486 :
487 173394452 : for (auto & child : parent->child_ref_range())
488 : {
489 9496964 : libmesh_assert(!child.is_remote());
490 9496964 : libmesh_assert_not_equal_to (child.processor_id(), DofObject::invalid_processor_id);
491 139994234 : parent->processor_id() = std::min(parent->processor_id(),
492 9496964 : child.processor_id());
493 : }
494 3976736 : parent = parent->parent();
495 : }
496 536012 : }
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 10877280 : for (auto & child : mesh.active_element_ptr_range())
508 : {
509 22684 : std::vector<Elem *> subactive_family;
510 5374561 : child->total_family_tree(subactive_family);
511 10752863 : for (const auto & f : subactive_family)
512 5378302 : f->processor_id() = child->processor_id();
513 75226 : }
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 130 : libmesh_parallel_only(mesh.comm());
525 130 : libmesh_assert(MeshTools::n_elem(mesh.unpartitioned_elements_begin(),
526 : mesh.unpartitioned_elements_end()) == 0);
527 :
528 75486 : const dof_id_type max_elem_id = mesh.max_elem_id();
529 :
530 : std::vector<processor_id_type>
531 260 : parent_processor_ids (std::min(communication_blocksize,
532 75616 : max_elem_id));
533 :
534 150972 : for (dof_id_type blk=0, last_elem_id=0; last_elem_id<max_elem_id; blk++)
535 : {
536 75486 : last_elem_id =
537 75616 : std::min(static_cast<dof_id_type>((blk+1)*communication_blocksize),
538 130 : max_elem_id);
539 75486 : const dof_id_type first_elem_id = blk*communication_blocksize;
540 :
541 130 : std::fill (parent_processor_ids.begin(),
542 : parent_processor_ids.end(),
543 : DofObject::invalid_processor_id);
544 :
545 : // first build up local contributions to parent_processor_ids
546 130 : bool have_parent_in_block = false;
547 :
548 151834 : for (auto & parent : as_range(mesh.ancestor_elements_begin(),
549 1674418 : mesh.ancestor_elements_end()))
550 : {
551 686863 : const dof_id_type parent_idx = parent->id();
552 992 : libmesh_assert_less (parent_idx, max_elem_id);
553 :
554 687855 : if ((parent_idx >= first_elem_id) &&
555 686863 : (parent_idx < last_elem_id))
556 : {
557 992 : have_parent_in_block = true;
558 686863 : processor_id_type parent_pid = DofObject::invalid_processor_id;
559 :
560 992 : std::vector<const Elem *> active_family;
561 686863 : parent->active_family_tree(active_family);
562 7909677 : for (const auto & f : active_family)
563 8010075 : parent_pid = std::min (parent_pid, f->processor_id());
564 :
565 686863 : const dof_id_type packed_idx = parent_idx - first_elem_id;
566 992 : libmesh_assert_less (packed_idx, parent_processor_ids.size());
567 :
568 687855 : parent_processor_ids[packed_idx] = parent_pid;
569 : }
570 75226 : }
571 :
572 : // then find the global minimum
573 75486 : mesh.comm().min (parent_processor_ids);
574 :
575 : // and assign the ids, if we have a parent in this block.
576 75486 : if (have_parent_in_block)
577 48956 : for (auto & parent : as_range(mesh.ancestor_elements_begin(),
578 1468662 : mesh.ancestor_elements_end()))
579 : {
580 686863 : const dof_id_type parent_idx = parent->id();
581 :
582 687855 : if ((parent_idx >= first_elem_id) &&
583 685871 : (parent_idx < last_elem_id))
584 : {
585 686863 : const dof_id_type packed_idx = parent_idx - first_elem_id;
586 992 : libmesh_assert_less (packed_idx, parent_processor_ids.size());
587 :
588 : const processor_id_type parent_pid =
589 686863 : parent_processor_ids[packed_idx];
590 :
591 992 : libmesh_assert_not_equal_to (parent_pid, DofObject::invalid_processor_id);
592 :
593 686863 : parent->processor_id() = parent_pid;
594 : }
595 23904 : }
596 : }
597 : }
598 :
599 : #endif // LIBMESH_ENABLE_AMR
600 633816 : }
601 :
602 : void
603 0 : Partitioner::processor_pairs_to_interface_nodes(MeshBase & mesh,
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 0 : libmesh_parallel_only(mesh.comm());
608 :
609 0 : processor_pair_to_nodes.clear();
610 :
611 0 : std::set<dof_id_type> mynodes;
612 0 : std::set<dof_id_type> neighbor_nodes;
613 0 : std::vector<dof_id_type> common_nodes;
614 :
615 : // Loop over all the active elements
616 0 : for (auto & elem : mesh.active_element_ptr_range())
617 : {
618 0 : libmesh_assert(elem);
619 :
620 0 : libmesh_assert_not_equal_to (elem->processor_id(), DofObject::invalid_processor_id);
621 :
622 0 : auto n_nodes = elem->n_nodes();
623 :
624 : // prepare data for this element
625 0 : mynodes.clear();
626 0 : neighbor_nodes.clear();
627 0 : common_nodes.clear();
628 :
629 0 : for (unsigned int inode = 0; inode < n_nodes; inode++)
630 0 : mynodes.insert(elem->node_id(inode));
631 :
632 0 : for (auto i : elem->side_index_range())
633 : {
634 0 : auto neigh = elem->neighbor_ptr(i);
635 0 : if (neigh && !neigh->is_remote() && neigh->processor_id() != elem->processor_id())
636 : {
637 0 : neighbor_nodes.clear();
638 0 : common_nodes.clear();
639 0 : auto neigh_n_nodes = neigh->n_nodes();
640 0 : for (unsigned int inode = 0; inode < neigh_n_nodes; inode++)
641 0 : neighbor_nodes.insert(neigh->node_id(inode));
642 :
643 0 : std::set_intersection(mynodes.begin(), mynodes.end(),
644 : neighbor_nodes.begin(), neighbor_nodes.end(),
645 0 : std::back_inserter(common_nodes));
646 :
647 0 : auto & map_set = processor_pair_to_nodes[std::make_pair(std::min(elem->processor_id(), neigh->processor_id()),
648 0 : std::max(elem->processor_id(), neigh->processor_id()))];
649 0 : for (auto global_node_id : common_nodes)
650 0 : map_set.insert(global_node_id);
651 : }
652 : }
653 0 : }
654 0 : }
655 :
656 0 : void Partitioner::set_interface_node_processor_ids_linear(MeshBase & mesh)
657 : {
658 : // This function must be run on all processors at once
659 0 : libmesh_parallel_only(mesh.comm());
660 :
661 0 : std::map<std::pair<processor_id_type, processor_id_type>, std::set<dof_id_type>> processor_pair_to_nodes;
662 :
663 0 : processor_pairs_to_interface_nodes(mesh, processor_pair_to_nodes);
664 :
665 0 : for (auto & pmap : processor_pair_to_nodes)
666 : {
667 0 : std::size_t n_own_nodes = pmap.second.size()/2, i = 0;
668 :
669 0 : for (dof_id_type id : pmap.second)
670 : {
671 0 : auto & node = mesh.node_ref(id);
672 0 : if (i <= n_own_nodes)
673 0 : node.processor_id() = pmap.first.first;
674 : else
675 0 : node.processor_id() = pmap.first.second;
676 0 : i++;
677 : }
678 : }
679 0 : }
680 :
681 0 : void Partitioner::set_interface_node_processor_ids_BFS(MeshBase & mesh)
682 : {
683 : // This function must be run on all processors at once
684 0 : 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 0 : std::map<std::pair<processor_id_type, processor_id_type>, std::set<dof_id_type>> processor_pair_to_nodes;
691 :
692 0 : processor_pairs_to_interface_nodes(mesh, processor_pair_to_nodes);
693 :
694 0 : std::unordered_map<dof_id_type, std::vector<const Elem *>> nodes_to_elem_map;
695 :
696 0 : MeshTools::build_nodes_to_elem_map(mesh, nodes_to_elem_map);
697 :
698 0 : std::vector<const Node *> neighbors;
699 0 : std::set<dof_id_type> neighbors_order;
700 0 : std::vector<dof_id_type> common_nodes;
701 0 : std::queue<dof_id_type> nodes_queue;
702 0 : std::set<dof_id_type> visted_nodes;
703 :
704 0 : for (auto & pmap : processor_pair_to_nodes)
705 : {
706 0 : std::size_t n_own_nodes = pmap.second.size()/2;
707 :
708 : // Initialize node assignment
709 0 : for (dof_id_type id : pmap.second)
710 0 : mesh.node_ref(id).processor_id() = pmap.first.second;
711 :
712 0 : visted_nodes.clear();
713 0 : for (dof_id_type id : pmap.second)
714 : {
715 0 : mesh.node_ref(id).processor_id() = pmap.first.second;
716 :
717 0 : if (visted_nodes.count(id))
718 0 : continue;
719 : else
720 : {
721 0 : nodes_queue.push(id);
722 0 : visted_nodes.insert(id);
723 0 : if (visted_nodes.size() >= n_own_nodes)
724 0 : break;
725 : }
726 :
727 0 : while (!nodes_queue.empty())
728 : {
729 0 : auto & node = mesh.node_ref(nodes_queue.front());
730 0 : nodes_queue.pop();
731 :
732 0 : neighbors.clear();
733 0 : MeshTools::find_nodal_neighbors(mesh, node, nodes_to_elem_map, neighbors);
734 0 : neighbors_order.clear();
735 0 : for (auto & neighbor : neighbors)
736 0 : neighbors_order.insert(neighbor->id());
737 :
738 0 : common_nodes.clear();
739 0 : std::set_intersection(pmap.second.begin(), pmap.second.end(),
740 : neighbors_order.begin(), neighbors_order.end(),
741 0 : std::back_inserter(common_nodes));
742 :
743 0 : for (auto c_node : common_nodes)
744 0 : if (!visted_nodes.count(c_node))
745 : {
746 0 : nodes_queue.push(c_node);
747 0 : visted_nodes.insert(c_node);
748 0 : if (visted_nodes.size() >= n_own_nodes)
749 0 : goto queue_done;
750 : }
751 :
752 0 : if (visted_nodes.size() >= n_own_nodes)
753 0 : goto queue_done;
754 : }
755 : }
756 0 : queue_done:
757 0 : for (auto node : visted_nodes)
758 0 : mesh.node_ref(node).processor_id() = pmap.first.first;
759 : }
760 0 : }
761 :
762 0 : void Partitioner::set_interface_node_processor_ids_petscpartitioner(MeshBase & mesh)
763 : {
764 0 : libmesh_ignore(mesh); // Only used if LIBMESH_HAVE_PETSC
765 :
766 : // This function must be run on all processors at once
767 0 : libmesh_parallel_only(mesh.comm());
768 :
769 : #ifdef LIBMESH_HAVE_PETSC
770 0 : std::map<std::pair<processor_id_type, processor_id_type>, std::set<dof_id_type>> processor_pair_to_nodes;
771 :
772 0 : processor_pairs_to_interface_nodes(mesh, processor_pair_to_nodes);
773 :
774 0 : std::vector<std::vector<const Elem *>> nodes_to_elem_map;
775 :
776 0 : MeshTools::build_nodes_to_elem_map(mesh, nodes_to_elem_map);
777 :
778 0 : std::vector<const Node *> neighbors;
779 0 : std::set<dof_id_type> neighbors_order;
780 0 : std::vector<dof_id_type> common_nodes;
781 :
782 0 : std::vector<dof_id_type> rows;
783 0 : std::vector<dof_id_type> cols;
784 :
785 0 : std::map<dof_id_type, dof_id_type> global_to_local;
786 :
787 0 : for (auto & pmap : processor_pair_to_nodes)
788 : {
789 0 : unsigned int i = 0;
790 :
791 0 : rows.clear();
792 0 : rows.resize(pmap.second.size()+1);
793 0 : cols.clear();
794 0 : for (dof_id_type id : pmap.second)
795 0 : global_to_local[id] = i++;
796 :
797 0 : i = 0;
798 0 : for (auto id : pmap.second)
799 : {
800 0 : auto & node = mesh.node_ref(id);
801 0 : neighbors.clear();
802 0 : MeshTools::find_nodal_neighbors(mesh, node, nodes_to_elem_map, neighbors);
803 0 : neighbors_order.clear();
804 0 : for (auto & neighbor : neighbors)
805 0 : neighbors_order.insert(neighbor->id());
806 :
807 0 : common_nodes.clear();
808 0 : std::set_intersection(pmap.second.begin(), pmap.second.end(),
809 : neighbors_order.begin(), neighbors_order.end(),
810 0 : std::back_inserter(common_nodes));
811 :
812 0 : rows[i+1] = rows[i] + cast_int<dof_id_type>(common_nodes.size());
813 :
814 0 : for (auto c_node : common_nodes)
815 0 : cols.push_back(global_to_local[c_node]);
816 :
817 0 : i++;
818 : }
819 :
820 : // Next we construct an IS from a MatPartitioning
821 0 : WrappedPetsc<IS> is;
822 : {
823 : PetscInt *adj_i, *adj_j;
824 0 : LibmeshPetscCall2(mesh.comm(), PetscCalloc1(rows.size(), &adj_i));
825 0 : LibmeshPetscCall2(mesh.comm(), PetscCalloc1(cols.size(), &adj_j));
826 0 : PetscInt rows_size = cast_int<PetscInt>(rows.size());
827 0 : for (PetscInt ii=0; ii<rows_size; ii++)
828 0 : adj_i[ii] = rows[ii];
829 :
830 0 : PetscInt cols_size = cast_int<PetscInt>(cols.size());
831 0 : for (PetscInt ii=0; ii<cols_size; ii++)
832 0 : adj_j[ii] = cols[ii];
833 :
834 0 : const PetscInt sz = cast_int<PetscInt>(pmap.second.size());
835 :
836 : // Create sparse matrix representing an adjacency list
837 0 : WrappedPetsc<Mat> adj;
838 0 : LibmeshPetscCall2(mesh.comm(), MatCreateMPIAdj(PETSC_COMM_SELF, sz, sz, adj_i, adj_j, nullptr, adj.get()));
839 :
840 : // Create MatPartitioning object
841 0 : WrappedPetsc<MatPartitioning> part;
842 0 : LibmeshPetscCall2(mesh.comm(), MatPartitioningCreate(PETSC_COMM_SELF, part.get()));
843 :
844 : // Apply MatPartitioning, storing results in "is"
845 0 : LibmeshPetscCall2(mesh.comm(), MatPartitioningSetAdjacency(part, adj));
846 0 : LibmeshPetscCall2(mesh.comm(), MatPartitioningSetNParts(part, 2));
847 0 : LibmeshPetscCall2(mesh.comm(), PetscObjectSetOptionsPrefix((PetscObject)(*part), "balance_"));
848 0 : LibmeshPetscCall2(mesh.comm(), MatPartitioningSetFromOptions(part));
849 0 : LibmeshPetscCall2(mesh.comm(), MatPartitioningApply(part, is.get()));
850 : }
851 :
852 : PetscInt local_size;
853 : const PetscInt *indices;
854 0 : LibmeshPetscCall2(mesh.comm(), ISGetLocalSize(is, &local_size));
855 0 : LibmeshPetscCall2(mesh.comm(), ISGetIndices(is, &indices));
856 :
857 0 : i = 0;
858 0 : for (auto id : pmap.second)
859 : {
860 0 : auto & node = mesh.node_ref(id);
861 0 : if (indices[i])
862 0 : node.processor_id() = pmap.first.second;
863 : else
864 0 : node.processor_id() = pmap.first.first;
865 :
866 0 : i++;
867 : }
868 0 : LibmeshPetscCall2(mesh.comm(), ISRestoreIndices(is, &indices));
869 : }
870 : #else
871 0 : libmesh_error_msg("PETSc is required");
872 : #endif
873 0 : }
874 :
875 :
876 672385 : void Partitioner::set_node_processor_ids(MeshBase & mesh)
877 : {
878 39771 : LOG_SCOPE("set_node_processor_ids()","Partitioner");
879 :
880 : // This function must be run on all processors at once
881 13257 : libmesh_parallel_only(mesh.comm());
882 :
883 : // If we have any unpartitioned elements at this
884 : // stage there is a problem
885 13257 : 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 98724729 : for (auto & node : mesh.node_ptr_range())
891 : {
892 97393216 : node->processor_id() = DofObject::invalid_processor_id;
893 645871 : }
894 :
895 : // Loop over all the active elements
896 79346111 : for (auto & elem : mesh.active_element_ptr_range())
897 : {
898 1779932 : libmesh_assert(elem);
899 :
900 1779932 : 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 356495846 : for (Node & node : elem->node_ref_range())
904 : {
905 12667995 : processor_id_type & pid = node.processor_id();
906 313928683 : pid = node.choose_processor_id(pid, elem->processor_id());
907 : }
908 645871 : }
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 672385 : libMesh::on_command_line ("--load-balanced-nodes-linear");
915 :
916 : const bool load_balanced_nodes_bfs =
917 672385 : libMesh::on_command_line ("--load-balanced-nodes-bfs");
918 :
919 : const bool load_balanced_nodes_petscpartition =
920 672385 : libMesh::on_command_line ("--load-balanced-nodes-petscpartitioner");
921 :
922 672385 : unsigned int n_load_balance_options = load_balanced_nodes_linear;
923 672385 : n_load_balance_options += load_balanced_nodes_bfs;
924 672385 : n_load_balance_options += load_balanced_nodes_petscpartition;
925 672385 : libmesh_error_msg_if(n_load_balance_options > 1,
926 : "Cannot perform more than one load balancing type at a time");
927 :
928 672385 : if (load_balanced_nodes_linear)
929 0 : set_interface_node_processor_ids_linear(mesh);
930 672385 : else if (load_balanced_nodes_bfs)
931 0 : set_interface_node_processor_ids_BFS(mesh);
932 672385 : else if (load_balanced_nodes_petscpartition)
933 0 : set_interface_node_processor_ids_petscpartitioner(mesh);
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 26514 : std::unordered_set<dof_id_type> bad_pids;
953 :
954 189037865 : for (auto & node : mesh.node_ptr_range())
955 97393216 : if (node->processor_id() == DofObject::invalid_processor_id)
956 2648545 : 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 672385 : const bool is_serial = mesh.is_serial();
968 : std::unordered_map
969 : <processor_id_type,
970 : std::unordered_map<dof_id_type, processor_id_type>>
971 26514 : potential_pids;
972 :
973 672385 : const unsigned int n_levels = MeshTools::n_levels(mesh);
974 1566314 : for (unsigned int level = n_levels; level > 0; --level)
975 : {
976 3935399 : for (auto & elem : as_range(mesh.level_elements_begin(level-1),
977 98201016 : mesh.level_elements_end(level-1)))
978 : {
979 2169376 : libmesh_assert_not_equal_to (elem->processor_id(),
980 : DofObject::invalid_processor_id);
981 :
982 48419173 : const processor_id_type elem_pid = elem->processor_id();
983 :
984 : // Consider updating the processor id on this element's nodes
985 398205429 : for (Node & node : elem->node_ref_range())
986 : {
987 14612985 : processor_id_type & pid = node.processor_id();
988 349786256 : if (bad_pids.count(node.id()))
989 5398401 : pid = node.choose_processor_id(pid, elem_pid);
990 344387855 : else if (!is_serial)
991 61187363 : potential_pids[elem_pid][node.id()] = pid;
992 : }
993 850259 : }
994 : }
995 :
996 672385 : if (!is_serial)
997 : {
998 : std::unordered_map
999 : <processor_id_type,
1000 : std::vector<std::pair<dof_id_type, processor_id_type>>>
1001 144 : potential_pids_vecs;
1002 :
1003 643797 : for (auto & pair : potential_pids)
1004 556422 : potential_pids_vecs[pair.first].assign(pair.second.begin(), pair.second.end());
1005 :
1006 : auto pids_action_functor =
1007 555886 : [& mesh, & bad_pids]
1008 : (processor_id_type /* src_pid */,
1009 23513389 : const std::vector<std::pair<dof_id_type, processor_id_type>> & data)
1010 : {
1011 23964250 : for (auto pair : data)
1012 : {
1013 23407828 : Node & node = mesh.node_ref(pair.first);
1014 35089 : processor_id_type & pid = node.processor_id();
1015 23407828 : if (const auto it = bad_pids.find(pair.first);
1016 35089 : it != bad_pids.end())
1017 : {
1018 15678 : pid = pair.second;
1019 26 : bad_pids.erase(it);
1020 : }
1021 : else
1022 23392150 : pid = node.choose_processor_id(pid, pair.second);
1023 : }
1024 87767 : };
1025 :
1026 : Parallel::push_parallel_vector_data
1027 87375 : (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 144 : CorrectProcIds correct_pids(mesh, bad_pids);
1034 : Parallel::sync_node_data_by_element_id
1035 261837 : (mesh, mesh.elements_begin(), mesh.elements_end(),
1036 87231 : Parallel::SyncEverything(), Parallel::SyncEverything(),
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 144 : bad_pids.clear();
1044 : Parallel::sync_node_data_by_element_id
1045 174750 : (mesh,
1046 262269 : mesh.elements_begin(), mesh.elements_end(),
1047 87375 : Parallel::SyncEverything(), Parallel::SyncEverything(),
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 13257 : MeshTools::libmesh_assert_valid_procids<Node>(mesh);
1059 : //MeshTools::libmesh_assert_canonical_node_procids(mesh);
1060 : #endif
1061 672385 : }
1062 :
1063 :
1064 :
1065 : struct SyncLocalIDs
1066 : {
1067 : typedef dof_id_type datum;
1068 :
1069 : typedef std::unordered_map<dof_id_type, dof_id_type> map_type;
1070 :
1071 624762 : SyncLocalIDs(map_type & _id_map) : id_map(_id_map) {}
1072 :
1073 : map_type & id_map;
1074 :
1075 3230402 : void gather_data (const std::vector<dof_id_type> & ids,
1076 : std::vector<datum> & local_ids) const
1077 : {
1078 3231152 : local_ids.resize(ids.size());
1079 :
1080 32181282 : for (auto i : index_range(ids))
1081 28950880 : local_ids[i] = id_map[ids[i]];
1082 3230402 : }
1083 :
1084 3230402 : void act_on_data (const std::vector<dof_id_type> & ids,
1085 : const std::vector<datum> & local_ids)
1086 : {
1087 32181282 : for (auto i : index_range(local_ids))
1088 28966784 : id_map[ids[i]] = local_ids[i];
1089 3230402 : }
1090 : };
1091 :
1092 624762 : void Partitioner::_find_global_index_by_pid_map(const MeshBase & mesh)
1093 : {
1094 624762 : 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 1528 : auto n_proc = mesh.n_processors();
1102 624762 : _n_active_elem_on_proc.resize(n_proc);
1103 624762 : mesh.comm().allgather(n_active_local_elem, _n_active_elem_on_proc);
1104 :
1105 626290 : std::vector<dof_id_type> n_active_elem_before_proc(mesh.n_processors());
1106 :
1107 6713270 : for (auto i : make_range(n_proc-1))
1108 6088508 : n_active_elem_before_proc[i+1] =
1109 6090036 : n_active_elem_before_proc[i] + _n_active_elem_on_proc[i];
1110 :
1111 : libMesh::BoundingBox bbox =
1112 624762 : MeshTools::create_bounding_box(mesh);
1113 :
1114 764 : _global_index_by_pid_map.clear();
1115 :
1116 : // create the mapping which is contiguous by processor
1117 624762 : MeshCommunication().find_local_indices (bbox,
1118 1249524 : mesh.active_local_elements_begin(),
1119 1248760 : mesh.active_local_elements_end(),
1120 624762 : _global_index_by_pid_map);
1121 :
1122 764 : SyncLocalIDs sync(_global_index_by_pid_map);
1123 :
1124 : Parallel::sync_dofobject_data_by_id
1125 1248760 : (mesh.comm(), mesh.active_elements_begin(), mesh.active_elements_end(), sync);
1126 :
1127 35804574 : for (const auto & elem : mesh.active_element_ptr_range())
1128 : {
1129 34555814 : const processor_id_type pid = elem->processor_id();
1130 39556 : libmesh_assert_less (_global_index_by_pid_map[elem->id()], _n_active_elem_on_proc[pid]);
1131 :
1132 34595370 : _global_index_by_pid_map[elem->id()] += n_active_elem_before_proc[pid];
1133 623234 : }
1134 624762 : }
1135 :
1136 312381 : void Partitioner::build_graph (const MeshBase & mesh)
1137 : {
1138 1146 : LOG_SCOPE("build_graph()", "Partitioner");
1139 :
1140 382 : 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 764 : 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 764 : map_type elems_constrained_by;
1154 :
1155 382 : const auto & mesh_constrained_nodes = mesh.get_constraint_rows();
1156 :
1157 52398767 : for (const Elem * elem : mesh.active_element_ptr_range())
1158 : {
1159 17277907 : if (!mesh_constrained_nodes.empty()) // quick test for non-IGA cases
1160 : {
1161 0 : 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 0 : std::set<const Elem *, CompareElemIdsByLevel> constraining_elems;
1166 1209187 : for (const Node & node : elem->node_ref_range())
1167 : {
1168 980466 : if (const auto row_it = mesh_constrained_nodes.find(&node);
1169 0 : row_it != end_it)
1170 3946461 : for (const auto & [pr, coef] : row_it->second)
1171 : {
1172 0 : libmesh_ignore(coef); // avoid gcc 7 warning
1173 3092771 : constraining_elems.insert(pr.first);
1174 : }
1175 : }
1176 1583959 : for (const Elem * constraining_elem : constraining_elems)
1177 0 : 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 17277907 : if (elem->interior_parent())
1183 : {
1184 : // get all relevant interior elements
1185 0 : std::set<const Elem *> neighbor_set;
1186 1094 : elem->find_interior_neighbors(neighbor_set);
1187 :
1188 2188 : for (const auto & neighbor : neighbor_set)
1189 0 : interior_to_boundary_map.emplace(neighbor, elem);
1190 : }
1191 311617 : }
1192 :
1193 : #ifdef LIBMESH_ENABLE_AMR
1194 764 : 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.
1201 312381 : _find_global_index_by_pid_map(mesh);
1202 :
1203 382 : dof_id_type first_local_elem = 0;
1204 1834508 : for (auto pid : make_range(mesh.processor_id()))
1205 1522318 : first_local_elem += _n_active_elem_on_proc[pid];
1206 :
1207 311999 : _dual_graph.clear();
1208 312381 : _dual_graph.resize(n_active_local_elem);
1209 312381 : _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 764 : std::vector<std::pair<dof_id_type, dof_id_type>>> connections_to_push;
1215 :
1216 6205662 : for (const auto & elem : mesh.active_local_element_ptr_range())
1217 : {
1218 11826 : libmesh_assert (_global_index_by_pid_map.count(elem->id()));
1219 : const dof_id_type global_index_by_pid =
1220 2802467 : _global_index_by_pid_map[elem->id()];
1221 :
1222 2802467 : const dof_id_type local_index =
1223 11826 : global_index_by_pid - first_local_elem;
1224 11826 : libmesh_assert_less (local_index, n_active_local_elem);
1225 :
1226 23652 : std::vector<dof_id_type> & graph_row = _dual_graph[local_index];
1227 :
1228 : // Save this off to make it easy to index later
1229 2802467 : _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 14777580 : for (auto neighbor : elem->neighbor_ptr_range())
1234 : {
1235 11963287 : if (neighbor != nullptr)
1236 : {
1237 : // If the neighbor is active treat it
1238 : // as a connection
1239 50897 : if (neighbor->active())
1240 : {
1241 50770 : libmesh_assert(_global_index_by_pid_map.count(neighbor->id()));
1242 : const dof_id_type neighbor_global_index_by_pid =
1243 10833013 : _global_index_by_pid_map[neighbor->id()];
1244 :
1245 10833013 : 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 44232 : neighbor->which_neighbor_am_i (elem);
1259 127 : 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 44232 : 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 293509 : 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 249866 : if (child->neighbor_ptr(ns) == elem)
1283 : {
1284 254 : libmesh_assert (child->active());
1285 254 : libmesh_assert (_global_index_by_pid_map.count(child->id()));
1286 : const dof_id_type child_global_index_by_pid =
1287 118113 : _global_index_by_pid_map[child->id()];
1288 :
1289 118113 : 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 3695315 : if ((elem->dim() < LIBMESH_DIM) &&
1301 892848 : elem->interior_parent())
1302 : {
1303 : // get all relevant interior elements
1304 0 : std::set<const Elem *> neighbor_set;
1305 896 : elem->find_interior_neighbors(neighbor_set);
1306 :
1307 1792 : for (const auto & neighbor : neighbor_set)
1308 : {
1309 : const dof_id_type neighbor_global_index_by_pid =
1310 896 : _global_index_by_pid_map[neighbor->id()];
1311 :
1312 896 : graph_row.push_back(neighbor_global_index_by_pid);
1313 : }
1314 : }
1315 :
1316 : // Check for any boundary neighbors
1317 2803363 : for (const auto & pr : as_range(interior_to_boundary_map.equal_range(elem)))
1318 : {
1319 896 : const Elem * neighbor = pr.second;
1320 :
1321 : const dof_id_type neighbor_global_index_by_pid =
1322 896 : _global_index_by_pid_map[neighbor->id()];
1323 :
1324 896 : graph_row.push_back(neighbor_global_index_by_pid);
1325 : }
1326 :
1327 : // Check for any constraining elements
1328 2802467 : if (!mesh_constrained_nodes.empty()) // quick test for non-IGA cases
1329 : {
1330 0 : 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 0 : std::set<const Elem *, CompareElemIdsByLevel> constraining_elems;
1335 146202 : for (const Node & node : elem->node_ref_range())
1336 : {
1337 118363 : if (const auto row_it = mesh_constrained_nodes.find(&node);
1338 0 : row_it != end_it)
1339 555100 : for (const auto & [pr, coef] : row_it->second)
1340 : {
1341 0 : libmesh_ignore(coef); // avoid gcc 7 warning
1342 451304 : constraining_elems.insert(pr.first);
1343 : }
1344 : }
1345 240695 : for (const Elem * constraining_elem : constraining_elems)
1346 : {
1347 : const dof_id_type constraining_global_index_by_pid =
1348 212856 : _global_index_by_pid_map[constraining_elem->id()];
1349 :
1350 212856 : 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 212856 : if (constraining_elem->processor_id() != mesh.processor_id())
1356 237014 : connections_to_push[constraining_elem->processor_id()].emplace_back
1357 118507 : (global_index_by_pid, constraining_global_index_by_pid);
1358 : }
1359 : }
1360 :
1361 : // Check for any constrained elements
1362 2978840 : for (const auto & pr : as_range(elems_constrained_by.equal_range(elem)))
1363 : {
1364 176373 : const Elem * constrained = pr.second;
1365 : const dof_id_type constrained_global_index_by_pid =
1366 176373 : _global_index_by_pid_map[constrained->id()];
1367 :
1368 176373 : 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 176373 : if (constrained->processor_id() != mesh.processor_id())
1374 164048 : connections_to_push[constrained->processor_id()].emplace_back
1375 82024 : (global_index_by_pid, constrained_global_index_by_pid);
1376 : }
1377 311617 : }
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 4059 : [this, first_local_elem]
1386 : (processor_id_type /*src_pid*/,
1387 200531 : const std::vector<std::pair<dof_id_type, dof_id_type>> & incoming_entries)
1388 : {
1389 204590 : for (auto [i, j] : incoming_entries)
1390 : {
1391 0 : libmesh_assert_greater_equal(j, first_local_elem);
1392 200531 : const std::size_t jl = j - first_local_elem;
1393 0 : libmesh_assert_less(jl, _dual_graph.size());
1394 0 : std::vector<dof_id_type> & graph_row = _dual_graph[jl];
1395 200531 : 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 36483 : graph_row.push_back(i);
1399 : }
1400 : }
1401 311999 : };
1402 :
1403 312381 : Parallel::push_parallel_vector_data(mesh.comm(),
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 382 : auto n_proc = mesh.n_processors();
1413 764 : std::vector<dof_id_type> first_local_index_on_proc(n_proc, 0);
1414 764 : for (auto pid : make_range(1u,n_proc))
1415 382 : first_local_index_on_proc[pid] = first_local_index_on_proc[pid-1] + _n_active_elem_on_proc[pid-1];
1416 :
1417 382 : libmesh_assert_equal_to(first_local_index_on_proc[mesh.processor_id()],
1418 : first_local_elem);
1419 :
1420 764 : std::unordered_map<processor_id_type, std::vector<std::pair<dof_id_type, dof_id_type>>> entries_to_send;
1421 12208 : for (auto il : index_range(_dual_graph))
1422 : {
1423 11826 : const std::vector<dof_id_type> & graph_row = _dual_graph[il];
1424 :
1425 11826 : const auto i = il + first_local_elem;
1426 :
1427 62850 : for (auto j : graph_row)
1428 : {
1429 : // Stupid graph rows aren't sorted yet...
1430 51024 : processor_id_type target_pid = 0;
1431 127340 : while (target_pid+1 < n_proc &&
1432 51024 : j >= first_local_index_on_proc[target_pid+1])
1433 25292 : ++target_pid;
1434 :
1435 51024 : entries_to_send[target_pid].emplace_back(i,j);
1436 : }
1437 : }
1438 :
1439 764 : 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 204824 : const std::vector<std::pair<dof_id_type, dof_id_type>> & incoming_entries)
1445 : {
1446 51752 : for (auto [i, j] : incoming_entries)
1447 : {
1448 51024 : if (j < first_local_elem)
1449 : {
1450 0 : bad_entries.emplace_back(src_pid,i,j);
1451 0 : continue;
1452 : }
1453 102048 : const std::size_t jl = j - first_local_elem;
1454 51024 : if (jl >= _dual_graph.size())
1455 : {
1456 0 : bad_entries.emplace_back(src_pid,i,j);
1457 0 : continue;
1458 : }
1459 51024 : const std::vector<dof_id_type> & graph_row = _dual_graph[jl];
1460 51024 : if (std::find(graph_row.begin(), graph_row.end(), i) ==
1461 102048 : graph_row.end())
1462 : {
1463 0 : bad_entries.emplace_back(src_pid,i,j);
1464 0 : continue;
1465 : }
1466 : }
1467 728 : };
1468 :
1469 : // Keep any failures in sync in parallel, for easier debugging in
1470 : // unit tests where the failure exception will be caught.
1471 : Parallel::push_parallel_vector_data
1472 382 : (mesh.comm(), entries_to_send, check_incoming_entries);
1473 382 : bool bad_entries_exist = !bad_entries.empty();
1474 382 : mesh.comm().max(bad_entries_exist);
1475 382 : 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 0 : libmesh_error_msg("Asymmetric partitioner graph detected");
1487 : }
1488 : #endif
1489 312381 : }
1490 :
1491 54981 : void Partitioner::assign_partitioning (MeshBase & mesh, const std::vector<dof_id_type> & parts)
1492 : {
1493 882 : LOG_SCOPE("assign_partitioning()", "Partitioner");
1494 :
1495 : // This function must be run on all processors at once
1496 294 : libmesh_parallel_only(mesh.comm());
1497 :
1498 294 : dof_id_type first_local_elem = 0;
1499 277223 : for (auto pid : make_range(mesh.processor_id()))
1500 222389 : first_local_elem += _n_active_elem_on_proc[pid];
1501 :
1502 : #ifndef NDEBUG
1503 294 : 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 588 : 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 588 : filled_request;
1513 :
1514 14363317 : 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 14272841 : requested_ids[elem->processor_id()].push_back(elem->id());
1520 54393 : }
1521 :
1522 : auto gather_functor =
1523 445731 : [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 28604434 : std::vector<processor_id_type> & data)
1532 : {
1533 1176 : const std::size_t ids_size = ids.size();
1534 446907 : data.resize(ids.size());
1535 :
1536 14700556 : for (std::size_t i=0; i != ids_size; i++)
1537 : {
1538 14272841 : const dof_id_type requested_elem_index = ids[i];
1539 :
1540 19192 : libmesh_assert(_global_index_by_pid_map.count(requested_elem_index));
1541 :
1542 : const dof_id_type global_index_by_pid =
1543 14253649 : _global_index_by_pid_map[requested_elem_index];
1544 :
1545 14253649 : const dof_id_type local_index =
1546 19192 : global_index_by_pid - first_local_elem;
1547 :
1548 19192 : libmesh_assert_less (local_index, parts.size());
1549 19192 : libmesh_assert_less (local_index, n_active_local_elem);
1550 :
1551 : const processor_id_type elem_procid =
1552 14253649 : cast_int<processor_id_type>(parts[local_index]);
1553 :
1554 19192 : libmesh_assert_less (elem_procid, mesh.n_partitions());
1555 :
1556 14272841 : data[i] = elem_procid;
1557 : }
1558 501594 : };
1559 :
1560 : auto action_functor =
1561 445731 : [&filled_request]
1562 : (processor_id_type pid,
1563 : const std::vector<dof_id_type> &,
1564 446319 : const std::vector<processor_id_type> & new_procids)
1565 : {
1566 446907 : filled_request[pid] = new_procids;
1567 55569 : };
1568 :
1569 : // Trade requests with other processors
1570 294 : const processor_id_type * ex = nullptr;
1571 : Parallel::pull_parallel_vector_data
1572 54981 : (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 55569 : std::vector<unsigned int> counters(mesh.n_processors(), 0);
1579 14363317 : for (auto & elem : mesh.active_element_ptr_range())
1580 : {
1581 14253649 : const processor_id_type current_pid = elem->processor_id();
1582 :
1583 19192 : libmesh_assert_less (counters[current_pid], requested_ids[current_pid].size());
1584 :
1585 : const processor_id_type elem_procid =
1586 14253649 : filled_request[current_pid][counters[current_pid]++];
1587 :
1588 19192 : libmesh_assert_less (elem_procid, mesh.n_partitions());
1589 14253649 : elem->processor_id() = elem_procid;
1590 54393 : }
1591 54981 : }
1592 :
1593 :
1594 : } // namespace libMesh
|