libMesh
Loading...
Searching...
No Matches
metis_partitioner.C
Go to the documentation of this file.
1// The libMesh Finite Element Library.
2// Copyright (C) 2002-2026 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3
4// This library is free software; you can redistribute it and/or
5// modify it under the terms of the GNU Lesser General Public
6// License as published by the Free Software Foundation; either
7// version 2.1 of the License, or (at your option) any later version.
8
9// This library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Lesser General Public License for more details.
13
14// You should have received a copy of the GNU Lesser General Public
15// License along with this library; if not, write to the Free Software
16// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18
19
20// Local Includes
21#include "libmesh/metis_partitioner.h"
22
23#include "libmesh/libmesh_config.h"
24#include "libmesh/elem.h"
25#include "libmesh/enum_partitioner_type.h"
26#include "libmesh/error_vector.h"
27#include "libmesh/libmesh_logging.h"
28#include "libmesh/mesh_base.h"
29#include "libmesh/mesh_communication.h"
30#include "libmesh/mesh_tools.h"
31#include "libmesh/metis_csr_graph.h"
32#include "libmesh/parallel.h"
33#include "libmesh/utility.h"
34#include "libmesh/vectormap.h"
35
36#ifdef LIBMESH_HAVE_METIS
37// MIPSPro 7.4.2 gets confused about these nested namespaces
38# ifdef __sgi
39# include <cstdarg>
40# endif
41namespace Metis {
42extern "C" {
43# include "libmesh/ignore_warnings.h"
44# include "metis.h"
45# include "libmesh/restore_warnings.h"
46}
47}
48#else
49# include "libmesh/sfc_partitioner.h"
50#endif
51
52
53// C++ includes
54#include <map>
55#include <unordered_map>
56#include <vector>
57
58
59namespace libMesh
60{
61
62
67
68
72 unsigned int n_pieces)
73{
74 // Check for easy returns
75 if (beg == end)
76 return;
77
78 if (n_pieces == 1)
79 {
80 this->single_partition_range (beg, end);
81 return;
82 }
83
84 libmesh_assert_greater (n_pieces, 0);
85
86 // We don't yet support distributed meshes with this Partitioner
87 if (!mesh.is_serial())
88 {
89 libMesh::out << "WARNING: Forced to gather a distributed mesh for METIS" << std::endl;
90 mesh.allgather();
91 }
92
93 // What to do if the Metis library IS NOT present
94#ifndef LIBMESH_HAVE_METIS
95
96 libmesh_do_once(
97 libMesh::out << "ERROR: The library has been built without" << std::endl
98 << "Metis support. Using a space-filling curve" << std::endl
99 << "partitioner instead!" << std::endl;);
100
101 SFCPartitioner sfcp;
102 sfcp.partition_range (mesh, beg, end, n_pieces);
103
104 // What to do if the Metis library IS present
105#else
106
107 LOG_SCOPE("partition_range()", "MetisPartitioner");
108
109 const dof_id_type n_range_elem = cast_int<dof_id_type>(std::distance(beg, end));
110
111 // Metis will only consider the elements in the range.
112 // We need to map the range element ids into a
113 // contiguous range. Further, we want the unique range indexing to be
114 // independent of the element ordering, otherwise a circular dependency
115 // can result in which the partitioning depends on the ordering which
116 // depends on the partitioning...
118 global_index_map.reserve (n_range_elem);
119
120 {
121 std::vector<dof_id_type> global_index;
122
125 beg, end, global_index);
126
127 libmesh_assert_equal_to (global_index.size(), n_range_elem);
128
129 // If we have unique_id() then global_index entries should be
130 // unique, so we just need a map for them.
131#ifdef LIBMESH_ENABLE_UNIQUE_ID
132 std::size_t cnt=0;
133 for (const auto & elem : as_range(beg, end))
134 global_index_map.emplace(elem->id(), global_index[cnt++]);
135#else
136 // If we don't have unique_id() then Hilbert SFC based
137 // global_index entries might overlap if elements overlap, so we
138 // need to fix any duplicates ...
139 //
140 // First, check for duplicates in O(N) time
141 std::unordered_map<dof_id_type, std::vector<dof_id_type>>
142 global_index_inverses;
143 bool found_duplicate_indices = false;
144 {
145 std::size_t cnt=0;
146 for (const auto & elem : as_range(beg, end))
147 {
148 auto & vec = global_index_inverses[global_index[cnt++]];
149 if (!vec.empty())
150 found_duplicate_indices = true;
151 vec.push_back(elem->id());
152 }
153 }
154
155 // But if we find them, we'll need O(N log N) to fix them while
156 // retaining the same space-filling-curve for efficient initial
157 // partitioning.
158 if (found_duplicate_indices)
159 {
160 const std::map<dof_id_type, std::vector<dof_id_type>>
161 sorted_inverses(global_index_inverses.begin(),
162 global_index_inverses.end());
163 std::size_t new_global_index=0;
164 for (const auto & [index, elem_ids] : sorted_inverses)
165 for (const dof_id_type elem_id : elem_ids)
166 global_index_map.emplace(elem_id, new_global_index++);
167 }
168 else
169 {
170 std::size_t cnt=0;
171 for (const auto & elem : as_range(beg, end))
172 global_index_map.emplace(elem->id(), global_index[cnt++]);
173 }
174#endif
175
176 libmesh_assert_equal_to (global_index_map.size(), n_range_elem);
177
178#ifdef DEBUG
179 std::unordered_set<dof_id_type> distinct_global_indices, distinct_ids;
180 for (const auto & [elem_id, index] : global_index_map)
181 {
182 distinct_ids.insert(elem_id);
183 distinct_global_indices.insert(index);
184 }
185 libmesh_assert_equal_to (distinct_global_indices.size(), n_range_elem);
186 libmesh_assert_equal_to (distinct_ids.size(), n_range_elem);
187#endif
188 }
189
190 // If we have boundary elements in this mesh, we want to account for
191 // the connectivity between them and interior elements. We can find
192 // interior elements from boundary elements, but we need to build up
193 // a lookup map to do the reverse.
194 typedef std::unordered_multimap<const Elem *, const Elem *> map_type;
195 map_type interior_to_boundary_map;
196
197 for (const auto & elem : as_range(beg, end))
198 {
199 // If we don't have an interior_parent then there's nothing
200 // to look us up.
201 if ((elem->dim() >= LIBMESH_DIM) ||
202 !elem->interior_parent())
203 continue;
204
205 // get all relevant interior elements
206 std::set<Elem *> neighbor_set;
207 elem->find_interior_neighbors(neighbor_set);
208
209 for (auto & neighbor : neighbor_set)
210 interior_to_boundary_map.emplace(neighbor, elem);
211 }
212
213 // Data structure that Metis will fill up on processor 0 and broadcast.
214 std::vector<Metis::idx_t> part(n_range_elem);
215
216 // Invoke METIS, but only on processor 0.
217 // Then broadcast the resulting decomposition
218 if (mesh.processor_id() == 0)
219 {
220 // Data structures and parameters needed only on processor 0 by Metis.
221 // std::vector<Metis::idx_t> options(5);
222 std::vector<Metis::idx_t> vwgt(n_range_elem);
223
224 Metis::idx_t
225 n = static_cast<Metis::idx_t>(n_range_elem), // number of "nodes" (elements) in the graph
226 // wgtflag = 2, // weights on vertices only, none on edges
227 // numflag = 0, // C-style 0-based numbering
228 nparts = static_cast<Metis::idx_t>(n_pieces), // number of subdomains to create
229 edgecut = 0; // the numbers of edges cut by the resulting partition
230
231 // Set the options
232 // options[0] = 0; // use default options
233
234 // build the graph
236
237 csr_graph.offsets.resize(n_range_elem + 1, 0);
238
239 // Local scope for these
240 {
241 // build the graph in CSR format. Note that
242 // the edges in the graph will correspond to
243 // face neighbors
244
245#ifdef LIBMESH_ENABLE_AMR
246 std::vector<const Elem *> neighbors_offspring;
247#endif
248
249#ifndef NDEBUG
250 std::size_t graph_size=0;
251#endif
252
253 // (1) first pass - get the row sizes for each element by counting the number
254 // of face neighbors. Also populate the vwght array if necessary
255 for (const auto & elem : as_range(beg, end))
256 {
257 const dof_id_type elem_global_index =
258 global_index_map[elem->id()];
259
260 libmesh_assert_less (elem_global_index, vwgt.size());
261
262 // The weight is used to define what a balanced graph is
263 if (!_weights)
264 {
265 // Spline nodes are a special case (storing all the
266 // unconstrained DoFs in an IGA simulation), but in
267 // general we'll try to distribute work by expecting
268 // it to be roughly proportional to DoFs, which are
269 // roughly proportional to nodes.
270 if (elem->type() == NODEELEM &&
271 elem->mapping_type() == RATIONAL_BERNSTEIN_MAP)
272 vwgt[elem_global_index] = 50;
273 else
274 vwgt[elem_global_index] = elem->n_nodes();
275 }
276 else
277 vwgt[elem_global_index] = static_cast<Metis::idx_t>((*_weights)[elem->id()]);
278
279 unsigned int num_neighbors = 0;
280
281 // Loop over the element's neighbors. An element
282 // adjacency corresponds to a face neighbor
283 for (auto neighbor : elem->neighbor_ptr_range())
284 {
285 if (neighbor != nullptr)
286 {
287 // If the neighbor is active, but is not in the
288 // range of elements being partitioned, treat it
289 // as a nullptr neighbor.
290 if (neighbor->active() && !global_index_map.count(neighbor->id()))
291 continue;
292
293 // If the neighbor is active treat it
294 // as a connection
295 if (neighbor->active())
296 num_neighbors++;
297
298#ifdef LIBMESH_ENABLE_AMR
299
300 // Otherwise we need to find all of the
301 // neighbor's children that are connected to
302 // us and add them
303 else
304 {
305 // The side of the neighbor to which
306 // we are connected
307 const unsigned int ns =
308 neighbor->which_neighbor_am_i (elem);
309 libmesh_assert_less (ns, neighbor->n_neighbors());
310
311 // Get all the active children (& grandchildren, etc...)
312 // of the neighbor.
313
314 // FIXME - this is the wrong thing, since we
315 // should be getting the active family tree on
316 // our side only. But adding too many graph
317 // links may cause hanging nodes to tend to be
318 // on partition interiors, which would reduce
319 // communication overhead for constraint
320 // equations, so we'll leave it.
321 neighbor->active_family_tree (neighbors_offspring);
322
323 // Get all the neighbor's children that
324 // live on that side and are thus connected
325 // to us
326 for (const auto & child : neighbors_offspring)
327 {
328 // Skip neighbor offspring which are not in the range of elements being partitioned.
329 if (!global_index_map.count(child->id()))
330 continue;
331
332 // This does not assume a level-1 mesh.
333 // Note that since children have sides numbered
334 // coincident with the parent then this is a sufficient test.
335 if (child->neighbor_ptr(ns) == elem)
336 {
337 libmesh_assert (child->active());
338 num_neighbors++;
339 }
340 }
341 }
342
343#endif /* ifdef LIBMESH_ENABLE_AMR */
344
345 }
346 }
347
348 // Check for any interior neighbors
349 if ((elem->dim() < LIBMESH_DIM) && elem->interior_parent())
350 {
351 // get all relevant interior elements
352 std::set<const Elem *> neighbor_set;
353 elem->find_interior_neighbors(neighbor_set);
354
355 num_neighbors += cast_int<unsigned int>(neighbor_set.size());
356 }
357
358 // Check for any boundary neighbors
359 typedef map_type::iterator map_it_type;
360 std::pair<map_it_type, map_it_type>
361 bounds = interior_to_boundary_map.equal_range(elem);
362 num_neighbors += cast_int<unsigned int>
363 (std::distance(bounds.first, bounds.second));
364
365 // Whether we enabled unique_id or had to fix any overlaps
366 // by hand, our global indices should be unique, so we
367 // should never see the same elem_global_index twice, so
368 // we should never be trying to edit an already-nonzero
369 // offset.
370 libmesh_assert(!csr_graph.offsets[elem_global_index+1]);
371
372 csr_graph.prep_n_nonzeros(elem_global_index, num_neighbors);
373#ifndef NDEBUG
374 graph_size += num_neighbors;
375#endif
376 }
377
378 csr_graph.prepare_for_use();
379
380 // (2) second pass - fill the compressed adjacency array
381 for (const auto & elem : as_range(beg, end))
382 {
383 const dof_id_type elem_global_index =
384 global_index_map[elem->id()];
385
386 unsigned int connection=0;
387
388 // Loop over the element's neighbors. An element
389 // adjacency corresponds to a face neighbor
390 for (auto neighbor : elem->neighbor_ptr_range())
391 {
392 if (neighbor != nullptr)
393 {
394 // If the neighbor is active, but is not in the
395 // range of elements being partitioned, treat it
396 // as a nullptr neighbor.
397 if (neighbor->active() && !global_index_map.count(neighbor->id()))
398 continue;
399
400 // If the neighbor is active treat it
401 // as a connection
402 if (neighbor->active())
403 csr_graph(elem_global_index, connection++) = global_index_map[neighbor->id()];
404
405#ifdef LIBMESH_ENABLE_AMR
406
407 // Otherwise we need to find all of the
408 // neighbor's children that are connected to
409 // us and add them
410 else
411 {
412 // The side of the neighbor to which
413 // we are connected
414 const unsigned int ns =
415 neighbor->which_neighbor_am_i (elem);
416 libmesh_assert_less (ns, neighbor->n_neighbors());
417
418 // Get all the active children (& grandchildren, etc...)
419 // of the neighbor.
420 neighbor->active_family_tree (neighbors_offspring);
421
422 // Get all the neighbor's children that
423 // live on that side and are thus connected
424 // to us
425 for (const auto & child : neighbors_offspring)
426 {
427 // Skip neighbor offspring which are not in the range of elements being partitioned.
428 if (!global_index_map.count(child->id()))
429 continue;
430
431 // This does not assume a level-1 mesh.
432 // Note that since children have sides numbered
433 // coincident with the parent then this is a sufficient test.
434 if (child->neighbor_ptr(ns) == elem)
435 {
436 libmesh_assert (child->active());
437
438 csr_graph(elem_global_index, connection++) = global_index_map[child->id()];
439 }
440 }
441 }
442
443#endif /* ifdef LIBMESH_ENABLE_AMR */
444
445 }
446 }
447
448 if ((elem->dim() < LIBMESH_DIM) &&
449 elem->interior_parent())
450 {
451 // get all relevant interior elements
452 std::set<const Elem *> neighbor_set;
453 elem->find_interior_neighbors(neighbor_set);
454
455 for (const Elem * neighbor : neighbor_set)
456 {
457 // Not all interior neighbors are necessarily in
458 // the same Mesh (hence not in the global_index_map).
459 // This will be the case when partitioning a
460 // BoundaryMesh, whose elements all have
461 // interior_parents() that belong to some other
462 // Mesh.
463 const Elem * queried_elem = mesh.query_elem_ptr(neighbor->id());
464
465 // Compare the neighbor and the queried_elem
466 // pointers, make sure they are the same.
467 if (queried_elem && queried_elem == neighbor)
468 csr_graph(elem_global_index, connection++) =
469 libmesh_map_find(global_index_map, neighbor->id());
470 }
471 }
472
473 // Check for any boundary neighbors
474 for (const auto & pr : as_range(interior_to_boundary_map.equal_range(elem)))
475 {
476 const Elem * neighbor = pr.second;
477 csr_graph(elem_global_index, connection++) =
478 global_index_map[neighbor->id()];
479 }
480 }
481
482 // We create a non-empty vals for a disconnected graph, to
483 // work around a segfault from METIS.
484 libmesh_assert_equal_to (csr_graph.vals.size(),
485 std::max(graph_size, std::size_t(1)));
486 } // done building the graph
487
488 Metis::idx_t ncon = 1;
489
490 // Select which type of partitioning to create
491
492 // Use recursive if the number of partitions is less than or equal to 8
493 if (n_pieces <= 8)
494 Metis::METIS_PartGraphRecursive(&n,
495 &ncon,
496 csr_graph.offsets.data(),
497 csr_graph.vals.data(),
498 vwgt.data(),
499 nullptr,
500 nullptr,
501 &nparts,
502 nullptr,
503 nullptr,
504 nullptr,
505 &edgecut,
506 part.data());
507
508 // Otherwise use kway
509 else
510 Metis::METIS_PartGraphKway(&n,
511 &ncon,
512 csr_graph.offsets.data(),
513 csr_graph.vals.data(),
514 vwgt.data(),
515 nullptr,
516 nullptr,
517 &nparts,
518 nullptr,
519 nullptr,
520 nullptr,
521 &edgecut,
522 part.data());
523
524 } // end processor 0 part
525
526 // Broadcast the resulting partition
527 mesh.comm().broadcast(part);
528
529 // Assign the returned processor ids. The part array contains
530 // the processor id for each active element, but in terms of
531 // the contiguous indexing we defined above
532 for (auto & elem : as_range(beg, end))
533 {
534 libmesh_assert (global_index_map.count(elem->id()));
535
536 const dof_id_type elem_global_index =
537 global_index_map[elem->id()];
538
539 libmesh_assert_less (elem_global_index, part.size());
540 const processor_id_type elem_procid =
541 static_cast<processor_id_type>(part[elem_global_index]);
542
543 elem->processor_id() = elem_procid;
544 }
545#endif
546}
547
548
549
551 const unsigned int n_pieces)
552{
553 this->partition_range(mesh,
554 mesh.active_elements_begin(),
555 mesh.active_elements_end(),
556 n_pieces);
557}
558
559} // namespace libMesh
dof_id_type id() const
Definition dof_object.h:819
This is the base class from which all geometric element types are derived.
Definition elem.h:96
This utility class provides a convenient implementation for building the compressed-row-storage graph...
std::vector< IndexType > vals
void prep_n_nonzeros(const libMesh::dof_id_type row, const libMesh::dof_id_type n_nonzeros_in)
Sets the number of non-zero values in the given row.
std::vector< IndexType > offsets
void prepare_for_use()
Replaces the entries in the offsets vector with their partial sums and resizes the val vector accordi...
This is the MeshBase class.
Definition mesh_base.h:81
This is the MeshCommunication class.
void find_global_indices(const Parallel::Communicator &communicator, const libMesh::BoundingBox &, const ForwardIterator &, const ForwardIterator &, std::vector< dof_id_type > &) const
This method determines a globally unique, partition-agnostic index for each object in the input range...
virtual PartitionerType type() const override
virtual void partition_range(MeshBase &mesh, MeshBase::element_iterator it, MeshBase::element_iterator end, const unsigned int n) override
Called by the SubdomainPartitioner to partition elements in the range (it, end).
virtual void _do_partition(MeshBase &mesh, const unsigned int n) override
Partition the MeshBase into n subdomains.
ErrorVector * _weights
The weights that might be used for partitioning.
bool single_partition_range(MeshBase::element_iterator it, MeshBase::element_iterator end)
Slightly generalized version of single_partition which acts on a range of elements defined by the pai...
The SFCPartitioner uses a Hilbert or Morton-ordered space filling curve to partition the elements.
virtual void partition_range(MeshBase &mesh, MeshBase::element_iterator it, MeshBase::element_iterator end, const unsigned int n) override
Called by the SubdomainPartitioner to partition elements in the range (it, end).
This vectormap templated class is intended to provide the performance characteristics of a sorted std...
Definition vectormap.h:62
difference_type count(const key_type &key) const
Definition vectormap.h:221
void emplace(Args &&... args)
Inserts x into the vector map, using "args" to construct it in-place.
Definition vectormap.h:127
MeshBase & mesh
libMesh::BoundingBox create_bounding_box(const MeshBase &mesh)
Definition mesh_tools.C:566
The libMesh namespace provides an interface to certain functionality in the library.
SimpleRange< IndexType > as_range(const std::pair< IndexType, IndexType > &p)
Helper function that allows us to treat a homogenous pair as a range.
PartitionerType
Defines an enum for mesh partitioner types.
@ RATIONAL_BERNSTEIN_MAP
libmesh_assert(ctx)
OStreamProxy out
uint8_t dof_id_type
Definition id_types.h:67
uint8_t processor_id_type
Definition id_types.h:104
The definition of the element_iterator struct.
Definition mesh_base.h:2538