LCOV - code coverage report
Current view: top level - src/partitioning - metis_partitioner.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4229 (6a9aeb) with base 727f46 Lines: 129 138 93.5 %
Date: 2025-08-19 19:27:09 Functions: 3 3 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // The libMesh Finite Element Library.
       2             : // Copyright (C) 2002-2025 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
      41             : namespace Metis {
      42             : extern "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             : 
      59             : namespace libMesh
      60             : {
      61             : 
      62             : 
      63         142 : PartitionerType MetisPartitioner::type() const
      64             : {
      65         142 :   return METIS_PARTITIONER;
      66             : }
      67             : 
      68             : 
      69      233102 : void MetisPartitioner::partition_range(MeshBase & mesh,
      70             :                                        MeshBase::element_iterator beg,
      71             :                                        MeshBase::element_iterator end,
      72             :                                        unsigned int n_pieces)
      73             : {
      74             :   // Check for easy returns
      75      233102 :   if (beg == end)
      76          71 :     return;
      77             : 
      78      233031 :   if (n_pieces == 1)
      79             :     {
      80           0 :       this->single_partition_range (beg, end);
      81           0 :       return;
      82             :     }
      83             : 
      84        8000 :   libmesh_assert_greater (n_pieces, 0);
      85             : 
      86             :   // We don't yet support distributed meshes with this Partitioner
      87      233031 :   if (!mesh.is_serial())
      88             :     {
      89           0 :       libMesh::out << "WARNING: Forced to gather a distributed mesh for METIS" << std::endl;
      90           0 :       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       16000 :   LOG_SCOPE("partition_range()", "MetisPartitioner");
     108             : 
     109      458062 :   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...
     117       16000 :   vectormap<dof_id_type, dof_id_type> global_index_map;
     118      233031 :   global_index_map.reserve (n_range_elem);
     119             : 
     120             :   {
     121       16000 :     std::vector<dof_id_type> global_index;
     122             : 
     123      233031 :     MeshCommunication().find_global_indices (mesh.comm(),
     124      233031 :                                              MeshTools::create_bounding_box(mesh),
     125             :                                              beg, end, global_index);
     126             : 
     127        8000 :     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        8000 :     std::size_t cnt=0;
     133    13322829 :     for (const auto & elem : as_range(beg, end))
     134    24900037 :       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        8000 :     libmesh_assert_equal_to (global_index_map.size(), n_range_elem);
     177             : 
     178             : #ifdef DEBUG
     179       16000 :     std::unordered_set<dof_id_type> distinct_global_indices, distinct_ids;
     180     1054528 :     for (const auto & [elem_id, index] : global_index_map)
     181             :       {
     182     1046528 :         distinct_ids.insert(elem_id);
     183     1046528 :         distinct_global_indices.insert(index);
     184             :       }
     185        8000 :     libmesh_assert_equal_to (distinct_global_indices.size(), n_range_elem);
     186        8000 :     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       16000 :   map_type interior_to_boundary_map;
     196             : 
     197    24094538 :   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    20645077 :       if ((elem->dim() >= LIBMESH_DIM) ||
     202     7780310 :           !elem->interior_parent())
     203    12861494 :         continue;
     204             : 
     205             :       // get all relevant interior elements
     206         880 :       std::set<Elem *> neighbor_set;
     207        3273 :       elem->find_interior_neighbors(neighbor_set);
     208             : 
     209        8685 :       for (auto & neighbor : neighbor_set)
     210         502 :         interior_to_boundary_map.emplace(neighbor, elem);
     211      217031 :     }
     212             : 
     213             :   // Data structure that Metis will fill up on processor 0 and broadcast.
     214      241031 :   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      241031 :   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       37961 :       std::vector<Metis::idx_t> vwgt(n_range_elem);
     223             : 
     224             :       Metis::idx_t
     225       33961 :         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       33961 :         nparts  = static_cast<Metis::idx_t>(n_pieces), // number of subdomains to create
     229       33961 :         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
     235       12000 :       METIS_CSR_Graph<Metis::idx_t> csr_graph;
     236             : 
     237       33961 :       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        8000 :         std::vector<const Elem *> neighbors_offspring;
     247             : #endif
     248             : 
     249             : #ifndef NDEBUG
     250        4000 :         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     4101673 :         for (const auto & elem : as_range(beg, end))
     256             :           {
     257             :             const dof_id_type elem_global_index =
     258     2542140 :               global_index_map[elem->id()];
     259             : 
     260      523264 :             libmesh_assert_less (elem_global_index, vwgt.size());
     261             : 
     262             :             // The weight is used to define what a balanced graph is
     263     2542140 :             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     2544127 :                 if (elem->type() == NODEELEM &&
     271        6667 :                     elem->mapping_type() == RATIONAL_BERNSTEIN_MAP)
     272        7813 :                   vwgt[elem_global_index] = 50;
     273             :                 else
     274     2536154 :                   vwgt[elem_global_index] = elem->n_nodes();
     275             :               }
     276             :             else
     277           0 :               vwgt[elem_global_index] = static_cast<Metis::idx_t>((*_weights)[elem->id()]);
     278             : 
     279      523264 :             unsigned int num_neighbors = 0;
     280             : 
     281             :             // Loop over the element's neighbors.  An element
     282             :             // adjacency corresponds to a face neighbor
     283    13167127 :             for (auto neighbor : elem->neighbor_ptr_range())
     284             :               {
     285    10101722 :                 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     9282750 :                     if (neighbor->active() && !global_index_map.count(neighbor->id()))
     291           0 :                       continue;
     292             : 
     293             :                     // If the neighbor is active treat it
     294             :                     // as a connection
     295     2005101 :                     if (neighbor->active())
     296     9183949 :                       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       98801 :                           neighbor->which_neighbor_am_i (elem);
     309       18141 :                         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       98801 :                         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      584297 :                         for (const auto & child : neighbors_offspring)
     327             :                           {
     328             :                             // Skip neighbor offspring which are not in the range of elements being partitioned.
     329      485496 :                             if (!global_index_map.count(child->id()))
     330           0 :                               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      579031 :                             if (child->neighbor_ptr(ns) == elem)
     336             :                               {
     337       40870 :                                 libmesh_assert (child->active());
     338      211079 :                                 num_neighbors++;
     339             :                               }
     340             :                           }
     341             :                       }
     342             : 
     343             : #endif /* ifdef LIBMESH_ENABLE_AMR */
     344             : 
     345             :                   }
     346             :               }
     347             : 
     348             :             // Check for any interior neighbors
     349     2542140 :             if ((elem->dim() < LIBMESH_DIM) && elem->interior_parent())
     350             :               {
     351             :                 // get all relevant interior elements
     352         220 :                 std::set<const Elem *> neighbor_set;
     353         877 :                 elem->find_interior_neighbors(neighbor_set);
     354             : 
     355         877 :                 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     1046529 :               bounds = interior_to_boundary_map.equal_range(elem);
     362     2542140 :             num_neighbors += cast_int<unsigned int>
     363      523264 :               (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      523264 :             libmesh_assert(!csr_graph.offsets[elem_global_index+1]);
     371             : 
     372      523264 :             csr_graph.prep_n_nonzeros(elem_global_index, num_neighbors);
     373             : #ifndef NDEBUG
     374      523264 :             graph_size += num_neighbors;
     375             : #endif
     376       25961 :           }
     377             : 
     378       33961 :         csr_graph.prepare_for_use();
     379             : 
     380             :         // (2) second pass - fill the compressed adjacency array
     381     4101673 :         for (const auto & elem : as_range(beg, end))
     382             :           {
     383             :             const dof_id_type elem_global_index =
     384     2542140 :               global_index_map[elem->id()];
     385             : 
     386      523264 :             unsigned int connection=0;
     387             : 
     388             :             // Loop over the element's neighbors.  An element
     389             :             // adjacency corresponds to a face neighbor
     390    13167127 :             for (auto neighbor : elem->neighbor_ptr_range())
     391             :               {
     392    10101722 :                 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     9282750 :                     if (neighbor->active() && !global_index_map.count(neighbor->id()))
     398           0 :                       continue;
     399             : 
     400             :                     // If the neighbor is active treat it
     401             :                     // as a connection
     402     2005101 :                     if (neighbor->active())
     403     9183949 :                       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       98801 :                           neighbor->which_neighbor_am_i (elem);
     416       18141 :                         libmesh_assert_less (ns, neighbor->n_neighbors());
     417             : 
     418             :                         // Get all the active children (& grandchildren, etc...)
     419             :                         // of the neighbor.
     420       98801 :                         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      584297 :                         for (const auto & child : neighbors_offspring)
     426             :                           {
     427             :                             // Skip neighbor offspring which are not in the range of elements being partitioned.
     428      485496 :                             if (!global_index_map.count(child->id()))
     429           0 :                               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      579031 :                             if (child->neighbor_ptr(ns) == elem)
     435             :                               {
     436       40870 :                                 libmesh_assert (child->active());
     437             : 
     438      211079 :                                 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     3913240 :             if ((elem->dim() < LIBMESH_DIM) &&
     449     1371100 :                 elem->interior_parent())
     450             :               {
     451             :                 // get all relevant interior elements
     452         440 :                 std::set<const Elem *> neighbor_set;
     453         877 :                 elem->find_interior_neighbors(neighbor_set);
     454             : 
     455        2064 :                 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        1187 :                     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        1187 :                     if (queried_elem && queried_elem == neighbor)
     468        1689 :                       csr_graph(elem_global_index, connection++) =
     469        1187 :                         libmesh_map_find(global_index_map, neighbor->id());
     470             :                   }
     471             :               }
     472             : 
     473             :             // Check for any boundary neighbors
     474     2543327 :             for (const auto & pr : as_range(interior_to_boundary_map.equal_range(elem)))
     475             :               {
     476        1187 :                 const Elem * neighbor = pr.second;
     477        1438 :                 csr_graph(elem_global_index, connection++) =
     478        1187 :                   global_index_map[neighbor->id()];
     479             :               }
     480       25961 :           }
     481             : 
     482             :         // We create a non-empty vals for a disconnected graph, to
     483             :         // work around a segfault from METIS.
     484        4000 :         libmesh_assert_equal_to (csr_graph.vals.size(),
     485             :                                  std::max(graph_size, std::size_t(1)));
     486             :       } // done building the graph
     487             : 
     488       33961 :       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       33961 :       if (n_pieces <= 8)
     494       28369 :         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        5592 :         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      233031 :   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    13322829 :   for (auto & elem : as_range(beg, end))
     533             :     {
     534     1046528 :       libmesh_assert (global_index_map.count(elem->id()));
     535             : 
     536             :       const dof_id_type elem_global_index =
     537    12864767 :         global_index_map[elem->id()];
     538             : 
     539     1046528 :       libmesh_assert_less (elem_global_index, part.size());
     540             :       const processor_id_type elem_procid =
     541    12864767 :         static_cast<processor_id_type>(part[elem_global_index]);
     542             : 
     543    12864767 :       elem->processor_id() = elem_procid;
     544      217031 :     }
     545             : #endif
     546             : }
     547             : 
     548             : 
     549             : 
     550      233098 : void MetisPartitioner::_do_partition (MeshBase & mesh,
     551             :                                       const unsigned int n_pieces)
     552             : {
     553      233098 :   this->partition_range(mesh,
     554      466196 :                         mesh.active_elements_begin(),
     555      241100 :                         mesh.active_elements_end(),
     556       16004 :                         n_pieces);
     557      233098 : }
     558             : 
     559             : } // namespace libMesh

Generated by: LCOV version 1.14