libMesh
metis_partitioner.C
Go to the documentation of this file.
1 // The libMesh Finite Element Library.
2 // Copyright (C) 2002-2019 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/libmesh_config.h"
22 #include "libmesh/mesh_base.h"
23 #include "libmesh/metis_partitioner.h"
24 #include "libmesh/libmesh_logging.h"
25 #include "libmesh/elem.h"
26 #include "libmesh/mesh_communication.h"
27 #include "libmesh/error_vector.h"
28 #include "libmesh/vectormap.h"
29 #include "libmesh/metis_csr_graph.h"
30 #include "libmesh/parallel.h"
31 #include "libmesh/utility.h"
32 
33 #ifdef LIBMESH_HAVE_METIS
34 // MIPSPro 7.4.2 gets confused about these nested namespaces
35 # ifdef __sgi
36 # include <cstdarg>
37 # endif
38 namespace Metis {
39 extern "C" {
40 # include "libmesh/ignore_warnings.h"
41 # include "metis.h"
42 # include "libmesh/restore_warnings.h"
43 }
44 }
45 #else
46 # include "libmesh/sfc_partitioner.h"
47 #endif
48 
49 
50 // C++ includes
51 #include <unordered_map>
52 
53 
54 namespace libMesh
55 {
56 
57 
61  unsigned int n_pieces)
62 {
63  // Check for easy returns
64  if (beg == end)
65  return;
66 
67  if (n_pieces == 1)
68  {
69  this->single_partition_range (beg, end);
70  return;
71  }
72 
73  libmesh_assert_greater (n_pieces, 0);
74 
75  // We don't yet support distributed meshes with this Partitioner
76  if (!mesh.is_serial())
77  {
78  libMesh::out << "WARNING: Forced to gather a distributed mesh for METIS" << std::endl;
79  mesh.allgather();
80  }
81 
82  // What to do if the Metis library IS NOT present
83 #ifndef LIBMESH_HAVE_METIS
84 
85  libmesh_do_once(
86  libMesh::out << "ERROR: The library has been built without" << std::endl
87  << "Metis support. Using a space-filling curve" << std::endl
88  << "partitioner instead!" << std::endl;);
89 
90  SFCPartitioner sfcp;
91  sfcp.partition_range (mesh, beg, end, n_pieces);
92 
93  // What to do if the Metis library IS present
94 #else
95 
96  LOG_SCOPE("partition_range()", "MetisPartitioner");
97 
98  const dof_id_type n_range_elem = cast_int<dof_id_type>(std::distance(beg, end));
99 
100  // Metis will only consider the elements in the range.
101  // We need to map the range element ids into a
102  // contiguous range. Further, we want the unique range indexing to be
103  // independent of the element ordering, otherwise a circular dependency
104  // can result in which the partitioning depends on the ordering which
105  // depends on the partitioning...
106  vectormap<dof_id_type, dof_id_type> global_index_map;
107  global_index_map.reserve (n_range_elem);
108 
109  {
110  std::vector<dof_id_type> global_index;
111 
114  beg, end, global_index);
115 
116  libmesh_assert_equal_to (global_index.size(), n_range_elem);
117 
118  std::size_t cnt=0;
119  for (const auto & elem : as_range(beg, end))
120  global_index_map.insert (std::make_pair(elem->id(), global_index[cnt++]));
121 
122  libmesh_assert_equal_to (global_index_map.size(), n_range_elem);
123  }
124 
125  // If we have boundary elements in this mesh, we want to account for
126  // the connectivity between them and interior elements. We can find
127  // interior elements from boundary elements, but we need to build up
128  // a lookup map to do the reverse.
129  typedef std::unordered_multimap<const Elem *, const Elem *> map_type;
130  map_type interior_to_boundary_map;
131 
132  for (const auto & elem : as_range(beg, end))
133  {
134  // If we don't have an interior_parent then there's nothing
135  // to look us up.
136  if ((elem->dim() >= LIBMESH_DIM) ||
137  !elem->interior_parent())
138  continue;
139 
140  // get all relevant interior elements
141  std::set<Elem *> neighbor_set;
142  elem->find_interior_neighbors(neighbor_set);
143 
144  for (auto & neighbor : neighbor_set)
145  interior_to_boundary_map.insert(std::make_pair(neighbor, elem));
146  }
147 
148  // Data structure that Metis will fill up on processor 0 and broadcast.
149  std::vector<Metis::idx_t> part(n_range_elem);
150 
151  // Invoke METIS, but only on processor 0.
152  // Then broadcast the resulting decomposition
153  if (mesh.processor_id() == 0)
154  {
155  // Data structures and parameters needed only on processor 0 by Metis.
156  // std::vector<Metis::idx_t> options(5);
157  std::vector<Metis::idx_t> vwgt(n_range_elem);
158 
159  Metis::idx_t
160  n = static_cast<Metis::idx_t>(n_range_elem), // number of "nodes" (elements) in the graph
161  // wgtflag = 2, // weights on vertices only, none on edges
162  // numflag = 0, // C-style 0-based numbering
163  nparts = static_cast<Metis::idx_t>(n_pieces), // number of subdomains to create
164  edgecut = 0; // the numbers of edges cut by the resulting partition
165 
166  // Set the options
167  // options[0] = 0; // use default options
168 
169  // build the graph
171 
172  csr_graph.offsets.resize(n_range_elem + 1, 0);
173 
174  // Local scope for these
175  {
176  // build the graph in CSR format. Note that
177  // the edges in the graph will correspond to
178  // face neighbors
179 
180 #ifdef LIBMESH_ENABLE_AMR
181  std::vector<const Elem *> neighbors_offspring;
182 #endif
183 
184 #ifndef NDEBUG
185  std::size_t graph_size=0;
186 #endif
187 
188  // (1) first pass - get the row sizes for each element by counting the number
189  // of face neighbors. Also populate the vwght array if necessary
190  for (const auto & elem : as_range(beg, end))
191  {
192  const dof_id_type elem_global_index =
193  global_index_map[elem->id()];
194 
195  libmesh_assert_less (elem_global_index, vwgt.size());
196 
197  // maybe there is a better weight?
198  // The weight is used to define what a balanced graph is
199  if (!_weights)
200  vwgt[elem_global_index] = elem->n_nodes();
201  else
202  vwgt[elem_global_index] = static_cast<Metis::idx_t>((*_weights)[elem->id()]);
203 
204  unsigned int num_neighbors = 0;
205 
206  // Loop over the element's neighbors. An element
207  // adjacency corresponds to a face neighbor
208  for (auto neighbor : elem->neighbor_ptr_range())
209  {
210  if (neighbor != nullptr)
211  {
212  // If the neighbor is active, but is not in the
213  // range of elements being partitioned, treat it
214  // as a nullptr neighbor.
215  if (neighbor->active() && !global_index_map.count(neighbor->id()))
216  continue;
217 
218  // If the neighbor is active treat it
219  // as a connection
220  if (neighbor->active())
221  num_neighbors++;
222 
223 #ifdef LIBMESH_ENABLE_AMR
224 
225  // Otherwise we need to find all of the
226  // neighbor's children that are connected to
227  // us and add them
228  else
229  {
230  // The side of the neighbor to which
231  // we are connected
232  const unsigned int ns =
233  neighbor->which_neighbor_am_i (elem);
234  libmesh_assert_less (ns, neighbor->n_neighbors());
235 
236  // Get all the active children (& grandchildren, etc...)
237  // of the neighbor.
238 
239  // FIXME - this is the wrong thing, since we
240  // should be getting the active family tree on
241  // our side only. But adding too many graph
242  // links may cause hanging nodes to tend to be
243  // on partition interiors, which would reduce
244  // communication overhead for constraint
245  // equations, so we'll leave it.
246  neighbor->active_family_tree (neighbors_offspring);
247 
248  // Get all the neighbor's children that
249  // live on that side and are thus connected
250  // to us
251  for (const auto & child : neighbors_offspring)
252  {
253  // Skip neighbor offspring which are not in the range of elements being partitioned.
254  if (!global_index_map.count(child->id()))
255  continue;
256 
257  // This does not assume a level-1 mesh.
258  // Note that since children have sides numbered
259  // coincident with the parent then this is a sufficient test.
260  if (child->neighbor_ptr(ns) == elem)
261  {
262  libmesh_assert (child->active());
263  num_neighbors++;
264  }
265  }
266  }
267 
268 #endif /* ifdef LIBMESH_ENABLE_AMR */
269 
270  }
271  }
272 
273  // Check for any interior neighbors
274  if ((elem->dim() < LIBMESH_DIM) && elem->interior_parent())
275  {
276  // get all relevant interior elements
277  std::set<const Elem *> neighbor_set;
278  elem->find_interior_neighbors(neighbor_set);
279 
280  num_neighbors += cast_int<unsigned int>(neighbor_set.size());
281  }
282 
283  // Check for any boundary neighbors
284  typedef map_type::iterator map_it_type;
285  std::pair<map_it_type, map_it_type>
286  bounds = interior_to_boundary_map.equal_range(elem);
287  num_neighbors += cast_int<unsigned int>
288  (std::distance(bounds.first, bounds.second));
289 
290  csr_graph.prep_n_nonzeros(elem_global_index, num_neighbors);
291 #ifndef NDEBUG
292  graph_size += num_neighbors;
293 #endif
294  }
295 
296  csr_graph.prepare_for_use();
297 
298  // (2) second pass - fill the compressed adjacency array
299  for (const auto & elem : as_range(beg, end))
300  {
301  const dof_id_type elem_global_index =
302  global_index_map[elem->id()];
303 
304  unsigned int connection=0;
305 
306  // Loop over the element's neighbors. An element
307  // adjacency corresponds to a face neighbor
308  for (auto neighbor : elem->neighbor_ptr_range())
309  {
310  if (neighbor != nullptr)
311  {
312  // If the neighbor is active, but is not in the
313  // range of elements being partitioned, treat it
314  // as a nullptr neighbor.
315  if (neighbor->active() && !global_index_map.count(neighbor->id()))
316  continue;
317 
318  // If the neighbor is active treat it
319  // as a connection
320  if (neighbor->active())
321  csr_graph(elem_global_index, connection++) = global_index_map[neighbor->id()];
322 
323 #ifdef LIBMESH_ENABLE_AMR
324 
325  // Otherwise we need to find all of the
326  // neighbor's children that are connected to
327  // us and add them
328  else
329  {
330  // The side of the neighbor to which
331  // we are connected
332  const unsigned int ns =
333  neighbor->which_neighbor_am_i (elem);
334  libmesh_assert_less (ns, neighbor->n_neighbors());
335 
336  // Get all the active children (& grandchildren, etc...)
337  // of the neighbor.
338  neighbor->active_family_tree (neighbors_offspring);
339 
340  // Get all the neighbor's children that
341  // live on that side and are thus connected
342  // to us
343  for (const auto & child : neighbors_offspring)
344  {
345  // Skip neighbor offspring which are not in the range of elements being partitioned.
346  if (!global_index_map.count(child->id()))
347  continue;
348 
349  // This does not assume a level-1 mesh.
350  // Note that since children have sides numbered
351  // coincident with the parent then this is a sufficient test.
352  if (child->neighbor_ptr(ns) == elem)
353  {
354  libmesh_assert (child->active());
355 
356  csr_graph(elem_global_index, connection++) = global_index_map[child->id()];
357  }
358  }
359  }
360 
361 #endif /* ifdef LIBMESH_ENABLE_AMR */
362 
363  }
364  }
365 
366  if ((elem->dim() < LIBMESH_DIM) &&
367  elem->interior_parent())
368  {
369  // get all relevant interior elements
370  std::set<const Elem *> neighbor_set;
371  elem->find_interior_neighbors(neighbor_set);
372 
373  for (const Elem * neighbor : neighbor_set)
374  {
375  // Not all interior neighbors are necessarily in
376  // the same Mesh (hence not in the global_index_map).
377  // This will be the case when partitioning a
378  // BoundaryMesh, whose elements all have
379  // interior_parents() that belong to some other
380  // Mesh.
381  const Elem * queried_elem = mesh.query_elem_ptr(neighbor->id());
382 
383  // Compare the neighbor and the queried_elem
384  // pointers, make sure they are the same.
385  if (queried_elem && queried_elem == neighbor)
386  csr_graph(elem_global_index, connection++) =
387  libmesh_map_find(global_index_map, neighbor->id());
388  }
389  }
390 
391  // Check for any boundary neighbors
392  for (const auto & pr : as_range(interior_to_boundary_map.equal_range(elem)))
393  {
394  const Elem * neighbor = pr.second;
395  csr_graph(elem_global_index, connection++) =
396  global_index_map[neighbor->id()];
397  }
398  }
399 
400  // We create a non-empty vals for a disconnected graph, to
401  // work around a segfault from METIS.
402  libmesh_assert_equal_to (csr_graph.vals.size(),
403  std::max(graph_size, std::size_t(1)));
404  } // done building the graph
405 
406  Metis::idx_t ncon = 1;
407 
408  // Select which type of partitioning to create
409 
410  // Use recursive if the number of partitions is less than or equal to 8
411  if (n_pieces <= 8)
412  Metis::METIS_PartGraphRecursive(&n,
413  &ncon,
414  csr_graph.offsets.data(),
415  csr_graph.vals.data(),
416  vwgt.data(),
417  nullptr,
418  nullptr,
419  &nparts,
420  nullptr,
421  nullptr,
422  nullptr,
423  &edgecut,
424  part.data());
425 
426  // Otherwise use kway
427  else
428  Metis::METIS_PartGraphKway(&n,
429  &ncon,
430  csr_graph.offsets.data(),
431  csr_graph.vals.data(),
432  vwgt.data(),
433  nullptr,
434  nullptr,
435  &nparts,
436  nullptr,
437  nullptr,
438  nullptr,
439  &edgecut,
440  part.data());
441 
442  } // end processor 0 part
443 
444  // Broadcast the resulting partition
445  mesh.comm().broadcast(part);
446 
447  // Assign the returned processor ids. The part array contains
448  // the processor id for each active element, but in terms of
449  // the contiguous indexing we defined above
450  for (auto & elem : as_range(beg, end))
451  {
452  libmesh_assert (global_index_map.count(elem->id()));
453 
454  const dof_id_type elem_global_index =
455  global_index_map[elem->id()];
456 
457  libmesh_assert_less (elem_global_index, part.size());
458  const processor_id_type elem_procid =
459  static_cast<processor_id_type>(part[elem_global_index]);
460 
461  elem->processor_id() = elem_procid;
462  }
463 #endif
464 }
465 
466 
467 
469  const unsigned int n_pieces)
470 {
471  this->partition_range(mesh,
472  mesh.active_elements_begin(),
473  mesh.active_elements_end(),
474  n_pieces);
475 }
476 
477 } // namespace libMesh
libMesh::Partitioner::single_partition_range
void 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...
Definition: partitioner.C:179
libMesh::METIS_CSR_Graph::prep_n_nonzeros
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.
Definition: metis_csr_graph.h:52
libMesh::dof_id_type
uint8_t dof_id_type
Definition: id_types.h:67
libMesh::MeshBase::element_iterator
The definition of the element_iterator struct.
Definition: mesh_base.h:1873
libMesh::METIS_CSR_Graph::prepare_for_use
void prepare_for_use()
Replaces the entries in the offsets vector with their partial sums and resizes the val vector accordi...
Definition: metis_csr_graph.h:73
libMesh::METIS_CSR_Graph
This utility class provides a convenient implementation for building the compressed-row-storage graph...
Definition: metis_csr_graph.h:43
libMesh::vectormap
This vectormap templated class is intended to provide the performance characteristics of a sorted std...
Definition: vectormap.h:61
libMesh::MetisPartitioner::_do_partition
virtual void _do_partition(MeshBase &mesh, const unsigned int n) override
Partition the MeshBase into n subdomains.
Definition: metis_partitioner.C:468
libMesh::SFCPartitioner::partition_range
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).
Definition: sfc_partitioner.C:41
libMesh
The libMesh namespace provides an interface to certain functionality in the library.
Definition: factoryfunction.C:55
end
IterBase * end
Also have a polymorphic pointer to the end object, this prevents iterating past the end.
Definition: variant_filter_iterator.h:343
mesh
MeshBase & mesh
Definition: mesh_communication.C:1257
libMesh::vectormap::count
difference_type count(const key_type &key) const
Definition: vectormap.h:210
libMesh::MeshCommunication::find_global_indices
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...
Definition: mesh_communication_global_indices.C:710
libMesh::libmesh_assert
libmesh_assert(ctx)
libMesh::MeshTools::create_bounding_box
libMesh::BoundingBox create_bounding_box(const MeshBase &mesh)
The same functionality as the deprecated MeshTools::bounding_box().
Definition: mesh_tools.C:389
libMesh::MeshBase
This is the MeshBase class.
Definition: mesh_base.h:78
libMesh::MetisPartitioner::partition_range
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).
Definition: metis_partitioner.C:58
Metis
Definition: metis_partitioner.C:38
libMesh::processor_id_type
uint8_t processor_id_type
Definition: id_types.h:104
libMesh::as_range
SimpleRange< IndexType > as_range(const std::pair< IndexType, IndexType > &p)
Helper function that allows us to treat a homogenous pair as a range.
Definition: simple_range.h:57
libMesh::vectormap::insert
void insert(const value_type &x)
Inserts x into the vectormap.
Definition: vectormap.h:116
distance
Real distance(const Point &p)
Definition: subdomains_ex3.C:50
libMesh::METIS_CSR_Graph::offsets
std::vector< IndexType > offsets
Definition: metis_csr_graph.h:46
libMesh::SFCPartitioner
The SFCPartitioner uses a Hilbert or Morton-ordered space filling curve to partition the elements.
Definition: sfc_partitioner.h:41
libMesh::DofObject::id
dof_id_type id() const
Definition: dof_object.h:767
libMesh::Elem
This is the base class from which all geometric element types are derived.
Definition: elem.h:100
libMesh::MeshCommunication
This is the MeshCommunication class.
Definition: mesh_communication.h:50
libMesh::out
OStreamProxy out
libMesh::METIS_CSR_Graph::vals
std::vector< IndexType > vals
Definition: metis_csr_graph.h:46
libMesh::Partitioner::_weights
ErrorVector * _weights
The weights that might be used for partitioning.
Definition: partitioner.h:267