diff --git a/kaminpar-dist/coarsening/coarsener.h b/kaminpar-dist/coarsening/coarsener.h index 6c274155..7ffae5ac 100644 --- a/kaminpar-dist/coarsening/coarsener.h +++ b/kaminpar-dist/coarsening/coarsener.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Builds and manages a hierarchy of coarse graphs. + * Interface for graph coarseners. * * @file: coarsener.h * @author: Daniel Seemaier @@ -7,55 +7,63 @@ ******************************************************************************/ #pragma once -#include - -#include "kaminpar-dist/coarsening/clustering/clusterer.h" -#include "kaminpar-dist/coarsening/contraction/cluster_contraction.h" #include "kaminpar-dist/context.h" #include "kaminpar-dist/datastructures/distributed_graph.h" #include "kaminpar-dist/datastructures/distributed_partitioned_graph.h" #include "kaminpar-dist/dkaminpar.h" -#include "kaminpar-common/datastructures/scalable_vector.h" - namespace kaminpar::dist { class Coarsener { public: - Coarsener(const DistributedGraph &input_graph, const Context &input_ctx); - - const DistributedGraph *coarsen_once(); - - const DistributedGraph *coarsen_once(GlobalNodeWeight max_cluster_weight); - - DistributedPartitionedGraph uncoarsen_once(DistributedPartitionedGraph &&p_graph); - - GlobalNodeWeight max_cluster_weight() const; - const DistributedGraph *coarsest() const; - std::size_t level() const; - -private: - const DistributedGraph *coarsen_once_local(GlobalNodeWeight max_cluster_weight); - const DistributedGraph *coarsen_once_global(GlobalNodeWeight max_cluster_weight); - - DistributedPartitionedGraph uncoarsen_once_local(DistributedPartitionedGraph &&p_graph); - DistributedPartitionedGraph uncoarsen_once_global(DistributedPartitionedGraph &&p_graph); - - const DistributedGraph *nth_coarsest(std::size_t n) const; - - bool has_converged(const DistributedGraph &before, const DistributedGraph &after) const; - - const DistributedGraph &_input_graph; - const Context &_input_ctx; - - std::unique_ptr _global_clusterer; - std::unique_ptr _local_clusterer; - - std::vector _graph_hierarchy; - std::vector _global_mapping_hierarchy; //< produced by global clustering algorithm - std::vector _node_migration_history; - std::vector> - _local_mapping_hierarchy; //< produced by local clustering_algorithm - - bool _local_clustering_converged = false; + Coarsener() = default; + + Coarsener(const Coarsener &) = delete; + Coarsener &operator=(const Coarsener &) = delete; + + Coarsener(Coarsener &&) noexcept = default; + Coarsener &operator=(Coarsener &&) noexcept = default; + + virtual ~Coarsener() = default; + + /** + * Initializes the coarsener with a new toplevel graph. + */ + virtual void initialize(const DistributedGraph *graph) = 0; + + /** + * Computes the next level of the graph hierarchy. + * + * @return whether coarsening has *not* yet converged. + */ + virtual bool coarsen() = 0; + + /** + * @return the coarsest graph in the hierarchy. + */ + [[nodiscard]] virtual const DistributedGraph ¤t() const = 0; + + /** + * @return number of coarse graphs in the hierarchy. + */ + [[nodiscard]] virtual std::size_t level() const = 0; + + /** + * @return whether we have *not* yet computed any coarse graphs. + */ + [[nodiscard]] bool empty() const { + return level() == 0; + } + + /** + * Projects a partition of the currently coarsest graph onto the next finer + * graph and frees the currently coarsest graph, i.e., unrolls one level of + * the coarse graph hierarchy. + * + * @param p_graph Partition of the currently coarsest graph. + * Precondition: `p_graph.graph() == current()`. + * + * @return partition of the *new* coarsest graph. + */ + virtual DistributedPartitionedGraph uncoarsen(DistributedPartitionedGraph &&p_graph) = 0; }; } // namespace kaminpar::dist diff --git a/kaminpar-dist/coarsening/contraction/cluster_contraction.cc b/kaminpar-dist/coarsening/contraction/cluster_contraction.cc index 41f9014b..24e5f189 100644 --- a/kaminpar-dist/coarsening/contraction/cluster_contraction.cc +++ b/kaminpar-dist/coarsening/contraction/cluster_contraction.cc @@ -33,11 +33,15 @@ #include "kaminpar-common/timer.h" namespace kaminpar::dist { +namespace { SET_STATISTICS_FROM_GLOBAL(); SET_DEBUG(false); +} // namespace ContractionResult contract_clustering( - const DistributedGraph &graph, GlobalClustering &clustering, const CoarseningContext &c_ctx + const DistributedGraph &graph, + StaticArray &clustering, + const CoarseningContext &c_ctx ) { return contract_clustering( graph, @@ -50,8 +54,8 @@ ContractionResult contract_clustering( namespace { struct AssignmentShifts { - NoinitVector overload; - NoinitVector underload; + StaticArray overload; + StaticArray underload; }; struct GlobalEdge { @@ -66,7 +70,7 @@ struct GlobalNode { }; template struct MigrationResult { - NoinitVector elements; + StaticArray elements; // Can be re-used for mapping exchange ... std::vector sendcounts; @@ -81,15 +85,16 @@ struct NodeMapping { }; struct MigratedNodesMapping { - NoinitVector my_nonlocal_to_gcnode; - NoinitVector their_req_to_lcnode; + StaticArray my_nonlocal_to_gcnode; + StaticArray their_req_to_lcnode; }; -NoinitVector -find_nonlocal_nodes(const DistributedGraph &graph, const GlobalClustering &lnode_to_gcluster) { +StaticArray find_nonlocal_nodes( + const DistributedGraph &graph, const StaticArray &lnode_to_gcluster +) { SCOPED_TIMER("Collect nonlocal nodes"); - NoinitVector node_position_buffer(graph.n() + 1); + StaticArray node_position_buffer(graph.n() + 1); node_position_buffer.front() = 0; graph.pfor_nodes([&](const NodeID lnode) { const GlobalNodeID gcluster = lnode_to_gcluster[lnode]; @@ -99,7 +104,7 @@ find_nonlocal_nodes(const DistributedGraph &graph, const GlobalClustering &lnode node_position_buffer.begin(), node_position_buffer.end(), node_position_buffer.begin() ); - NoinitVector nonlocal_nodes(node_position_buffer.back()); + StaticArray nonlocal_nodes(node_position_buffer.back()); graph.pfor_nodes([&](const NodeID lnode) { const GlobalNodeID gcluster = lnode_to_gcluster[lnode]; if (!graph.is_owned_global_node(gcluster)) { @@ -112,11 +117,12 @@ find_nonlocal_nodes(const DistributedGraph &graph, const GlobalClustering &lnode return nonlocal_nodes; } -NoinitVector -find_nonlocal_edges(const DistributedGraph &graph, const GlobalClustering &lnode_to_gcluster) { +StaticArray find_nonlocal_edges( + const DistributedGraph &graph, const StaticArray &lnode_to_gcluster +) { SCOPED_TIMER("Collect nonlocal edges"); - NoinitVector edge_position_buffer(graph.n() + 1); + StaticArray edge_position_buffer(graph.n() + 1); edge_position_buffer.front() = 0; graph.pfor_nodes([&](const NodeID lnode_u) { @@ -139,7 +145,7 @@ find_nonlocal_edges(const DistributedGraph &graph, const GlobalClustering &lnode edge_position_buffer.begin(), edge_position_buffer.end(), edge_position_buffer.begin() ); - NoinitVector nonlocal_edges(edge_position_buffer.back()); + StaticArray nonlocal_edges(edge_position_buffer.back()); graph.pfor_nodes([&](const NodeID lnode_u) { const GlobalNodeID gcluster_u = lnode_to_gcluster[lnode_u]; @@ -162,7 +168,7 @@ find_nonlocal_edges(const DistributedGraph &graph, const GlobalClustering &lnode return nonlocal_edges; } -void deduplicate_edge_list(NoinitVector &edges) { +void deduplicate_edge_list(StaticArray &edges) { SCOPED_TIMER("Deduplicate edge list"); if (edges.empty()) { @@ -179,7 +185,7 @@ void deduplicate_edge_list(NoinitVector &edges) { // Mark the first edge in every block of duplicate edges START_TIMER("Mark start of parallel edge blocks"); - NoinitVector edge_position_buffer(edges.size()); + StaticArray edge_position_buffer(edges.size()); edge_position_buffer.front() = 0; tbb::parallel_for(1, edges.size(), [&](const std::size_t i) { edge_position_buffer[i] = (edges[i].u != edges[i - 1].u || edges[i].v != edges[i - 1].v); @@ -193,7 +199,7 @@ void deduplicate_edge_list(NoinitVector &edges) { // Deduplicate edges in a separate buffer START_TIMER("Deduplicate"); - NoinitVector tmp_nonlocal_edges(edge_position_buffer.back() + 1); + StaticArray tmp_nonlocal_edges(edge_position_buffer.back() + 1); tbb::parallel_for(0, edge_position_buffer.back() + 1, [&](const std::size_t i) { tmp_nonlocal_edges[i].weight = 0; }); @@ -207,7 +213,7 @@ void deduplicate_edge_list(NoinitVector &edges) { STOP_TIMER(); } -void sort_node_list(NoinitVector &nodes) { +void sort_node_list(StaticArray &nodes) { SCOPED_TIMER("Sort nodes"); tbb::parallel_sort(nodes.begin(), nodes.end(), [&](const GlobalNode &lhs, const GlobalNode &rhs) { @@ -265,14 +271,14 @@ template double compute_distribution_imbalance(const StaticArray return 1.0 * max / (1.0 * distribution.back() / (distribution.size() - 1)); } -NoinitVector build_lcluster_to_lcnode_mapping( +StaticArray build_lcluster_to_lcnode_mapping( const DistributedGraph &graph, - const GlobalClustering &lnode_to_gcluster, - const NoinitVector &local_nodes + const StaticArray &lnode_to_gcluster, + const StaticArray &local_nodes ) { SCOPED_TIMER("Build lcluster_to_lcnode"); - NoinitVector lcluster_to_lcnode(graph.n()); + StaticArray lcluster_to_lcnode(graph.n()); graph.pfor_nodes([&](const NodeID u) { lcluster_to_lcnode[u] = 0; }); tbb::parallel_invoke( [&] { @@ -304,9 +310,9 @@ NoinitVector build_lcluster_to_lcnode_mapping( } void localize_global_edge_list( - NoinitVector &edges, + StaticArray &edges, const GlobalNodeID offset, - const NoinitVector &lnode_to_lcnode + const StaticArray &lnode_to_lcnode ) { tbb::parallel_for(0, edges.size(), [&](const std::size_t i) { const NodeID lcluster = static_cast(edges[i].u - offset); @@ -314,16 +320,16 @@ void localize_global_edge_list( }); } -std::pair, NoinitVector> build_node_buckets( +std::pair, StaticArray> build_node_buckets( const DistributedGraph &graph, - const NoinitVector &lcluster_to_lcnode, + const StaticArray &lcluster_to_lcnode, const GlobalNodeID c_n, - const NoinitVector &local_edges, - const GlobalClustering &lnode_to_gcluster + const StaticArray &local_edges, + const StaticArray &lnode_to_gcluster ) { SCOPED_TIMER("Bucket sort nodes by clusters"); - NoinitVector buckets_position_buffer(c_n + 1); + StaticArray buckets_position_buffer(c_n + 1); tbb::parallel_for(0, c_n + 1, [&](const NodeID lcnode) { buckets_position_buffer[lcnode] = 0; }); @@ -355,9 +361,7 @@ std::pair, NoinitVector> build_node_buckets( buckets_position_buffer.begin() ); - NoinitVector buckets( - buckets_position_buffer.empty() ? 0 : buckets_position_buffer.back() - ); + StaticArray buckets(buckets_position_buffer.empty() ? 0 : buckets_position_buffer.back()); tbb::parallel_invoke( [&] { graph.pfor_nodes([&](const NodeID lnode) { @@ -389,12 +393,11 @@ std::pair, NoinitVector> build_node_buckets( template MigrationResult migrate_elements( const NumElementsForPEContainer &num_elements_for_pe, - const NoinitVector &elements, + const StaticArray &elements, MPI_Comm comm ) { const PEID size = mpi::get_comm_size(comm); - NoinitVector recvbuf; std::vector sendcounts(size); std::vector sdispls(size); std::vector recvcounts(size); @@ -405,7 +408,7 @@ MigrationResult migrate_elements( MPI_Alltoall(sendcounts.data(), 1, MPI_INT, recvcounts.data(), 1, MPI_INT, comm); std::exclusive_scan(recvcounts.begin(), recvcounts.end(), rdispls.begin(), 0); - recvbuf.resize(rdispls.back() + recvcounts.back()); + StaticArray recvbuf(rdispls.back() + recvcounts.back()); MPI_Alltoallv( elements.data(), sendcounts.data(), @@ -428,7 +431,7 @@ MigrationResult migrate_elements( } MigrationResult -migrate_nodes(const DistributedGraph &graph, const NoinitVector &nonlocal_nodes) { +migrate_nodes(const DistributedGraph &graph, const StaticArray &nonlocal_nodes) { SCOPED_TIMER("Exchange nonlocal nodes"); const PEID size = mpi::get_comm_size(graph.communicator()); @@ -448,7 +451,7 @@ migrate_nodes(const DistributedGraph &graph, const NoinitVector &non } MigrationResult -migrate_edges(const DistributedGraph &graph, const NoinitVector &nonlocal_edges) { +migrate_edges(const DistributedGraph &graph, const StaticArray &nonlocal_edges) { SCOPED_TIMER("Exchange nonlocal edges"); const PEID size = mpi::get_comm_size(graph.communicator()); @@ -484,17 +487,17 @@ migrate_edges(const DistributedGraph &graph, const NoinitVector &non MigratedNodesMapping exchange_migrated_nodes_mapping( const DistributedGraph &graph, - const NoinitVector &nonlocal_nodes, + const StaticArray &nonlocal_nodes, const MigrationResult &local_nodes, - const NoinitVector &lcluster_to_lcnode, + const StaticArray &lcluster_to_lcnode, const StaticArray &c_node_distribution ) { SCOPED_TIMER("Exchange node mapping for migrated nodes"); const PEID rank = mpi::get_comm_rank(graph.communicator()); - NoinitVector their_nonlocal_to_gcnode(local_nodes.elements.size()); - NoinitVector their_req_to_lcnode(their_nonlocal_to_gcnode.size()); + StaticArray their_nonlocal_to_gcnode(local_nodes.elements.size()); + StaticArray their_req_to_lcnode(their_nonlocal_to_gcnode.size()); tbb::parallel_for(0, local_nodes.elements.size(), [&](const std::size_t i) { const GlobalNodeID gcluster = local_nodes.elements[i].u; @@ -508,7 +511,7 @@ MigratedNodesMapping exchange_migrated_nodes_mapping( their_req_to_lcnode[i] = lcnode; }); - NoinitVector my_nonlocal_to_gcnode(nonlocal_nodes.size()); + StaticArray my_nonlocal_to_gcnode(nonlocal_nodes.size()); MPI_Alltoallv( their_nonlocal_to_gcnode.data(), local_nodes.recvcounts.data(), @@ -541,8 +544,8 @@ std::pair remap_gcnode( const GlobalNodeID gcnode, const PEID current_owner, const StaticArray ¤t_cnode_distribution, - const NoinitVector &pe_overload, - const NoinitVector &pe_underload + const StaticArray &pe_overload, + const StaticArray &pe_underload ) { const auto lcnode = static_cast(gcnode - current_cnode_distribution[current_owner]); @@ -601,9 +604,9 @@ AssignmentShifts compute_assignment_shifts( GlobalNodeID count; }; - NoinitVector pe_load(size); - NoinitVector pe_overload(size + 1); - NoinitVector pe_underload(size + 1); + ScalableVector pe_load(size); + StaticArray pe_overload(size + 1); + StaticArray pe_underload(size + 1); pe_overload.front() = 0; pe_underload.front() = 0; @@ -732,9 +735,9 @@ AssignmentShifts compute_assignment_shifts( void rebalance_cluster_placement( const DistributedGraph &graph, const StaticArray ¤t_cnode_distribution, - const NoinitVector &lcluster_to_lcnode, - const NoinitVector &nonlocal_gcluster_to_gcnode, - GlobalClustering &lnode_to_gcluster, + const StaticArray &lcluster_to_lcnode, + const StaticArray &nonlocal_gcluster_to_gcnode, + StaticArray &lnode_to_gcluster, const double max_cnode_imbalance, const double migrate_cnode_prefix ) { @@ -830,7 +833,9 @@ void rebalance_cluster_placement( } // namespace namespace debug { -bool validate_clustering(const DistributedGraph &graph, const GlobalClustering &lnode_to_gcluster) { +bool validate_clustering( + const DistributedGraph &graph, const StaticArray &lnode_to_gcluster +) { for (const NodeID lnode : graph.all_nodes()) { const GlobalNodeID gcluster = lnode_to_gcluster[lnode]; if (gcluster > graph.global_n()) { @@ -876,7 +881,7 @@ bool validate_clustering(const DistributedGraph &graph, const GlobalClustering & ContractionResult contract_clustering( const DistributedGraph &graph, - GlobalClustering &lnode_to_gcluster, + StaticArray &lnode_to_gcluster, const double max_cnode_imbalance, const bool migrate_cnode_prefix, const bool force_perfect_cnode_balance @@ -1108,7 +1113,7 @@ ContractionResult contract_clustering( ); // Build a mapping array from fine nodes to coarse nodes - NoinitVector lnode_to_gcnode(graph.n()); + StaticArray lnode_to_gcnode(graph.n()); graph.pfor_nodes([&](const NodeID u) { const GlobalNodeID cluster = lnode_to_gcluster[u]; @@ -1375,7 +1380,7 @@ ContractionResult contract_clustering( DistributedPartitionedGraph project_partition( const DistributedGraph &graph, DistributedPartitionedGraph p_c_graph, - const NoinitVector &c_mapping, + const StaticArray &c_mapping, const MigratedNodes &migration ) { SCOPED_TIMER("Project partition"); @@ -1384,10 +1389,10 @@ DistributedPartitionedGraph project_partition( GlobalNodeID gcnode; BlockID block; }; - NoinitVector migrated_nodes_sendbuf( + StaticArray migrated_nodes_sendbuf( migration.sdispls.back() + migration.sendcounts.back() ); - NoinitVector migrated_nodes_recvbuf( + StaticArray migrated_nodes_recvbuf( migration.rdispls.back() + migration.recvcounts.back() ); diff --git a/kaminpar-dist/coarsening/contraction/cluster_contraction.h b/kaminpar-dist/coarsening/contraction/cluster_contraction.h index 5f468ad8..81f3c76d 100644 --- a/kaminpar-dist/coarsening/contraction/cluster_contraction.h +++ b/kaminpar-dist/coarsening/contraction/cluster_contraction.h @@ -12,16 +12,11 @@ #include "kaminpar-dist/datastructures/distributed_graph.h" #include "kaminpar-dist/datastructures/distributed_partitioned_graph.h" +#include "kaminpar-dist/dkaminpar.h" -#include "kaminpar-common/datastructures/noinit_vector.h" +#include "kaminpar-common/datastructures/static_array.h" namespace kaminpar::dist { -/// Data type to map node IDs of the fine graph to node IDs in the coarse graph. -using GlobalMapping = NoinitVector; - -/// Data type for clusterings, i.e., a mapping from node IDs in the graph to cluster IDs. -using GlobalClustering = NoinitVector; - namespace debug { /** * Validates the given clustering, i.e., whether it is a valid input to the `contract_clustering()` @@ -33,7 +28,9 @@ namespace debug { * @return `true` if the clustering is valid, `false` otherwise. If `false` is returned, calling * `contract_clustering()` with the same clustering is undefined behavior. */ -bool validate_clustering(const DistributedGraph &graph, const GlobalClustering &lnode_to_gcluster); +bool validate_clustering( + const DistributedGraph &graph, const StaticArray &lnode_to_gcluster +); } // namespace debug /** @@ -41,7 +38,7 @@ bool validate_clustering(const DistributedGraph &graph, const GlobalClustering & * Part of the contraction result and should not be used outside the `project_partition()` function. */ struct MigratedNodes { - NoinitVector nodes; + StaticArray nodes; std::vector sendcounts; std::vector sdispls; @@ -55,7 +52,7 @@ struct MigratedNodes { */ struct ContractionResult { DistributedGraph graph; - NoinitVector mapping; + StaticArray mapping; MigratedNodes migration; }; @@ -74,7 +71,9 @@ struct ContractionResult { * the fine graph. */ ContractionResult contract_clustering( - const DistributedGraph &graph, GlobalClustering &clustering, const CoarseningContext &c_ctx + const DistributedGraph &graph, + StaticArray &clustering, + const CoarseningContext &c_ctx ); /** @@ -99,7 +98,7 @@ ContractionResult contract_clustering( */ ContractionResult contract_clustering( const DistributedGraph &graph, - GlobalClustering &clustering, + StaticArray &clustering, double max_cnode_imbalance = std::numeric_limits::max(), bool migrate_cnode_prefix = false, bool force_perfect_cnode_balance = true @@ -118,7 +117,7 @@ ContractionResult contract_clustering( DistributedPartitionedGraph project_partition( const DistributedGraph &graph, DistributedPartitionedGraph p_c_graph, - const NoinitVector &c_mapping, + const StaticArray &c_mapping, const MigratedNodes &migration ); } // namespace kaminpar::dist diff --git a/kaminpar-dist/coarsening/global_cluster_coarsener.cc b/kaminpar-dist/coarsening/global_cluster_coarsener.cc index 96fbbc64..6ec432c0 100644 --- a/kaminpar-dist/coarsening/global_cluster_coarsener.cc +++ b/kaminpar-dist/coarsening/global_cluster_coarsener.cc @@ -1,152 +1,68 @@ /******************************************************************************* - * Builds and manages a hierarchy of coarse graphs. + * Graph coarsener based on global clusterings. * - * @file: coarsener.cc + * @file: global_cluster_coarsener.cc * @author: Daniel Seemaier * @date: 28.04.2022 ******************************************************************************/ -#include "kaminpar-dist/coarsening/coarsener.h" +#include "kaminpar-dist/coarsening/global_cluster_coarsener.h" #include "kaminpar-dist/coarsening/contraction/cluster_contraction.h" -#include "kaminpar-dist/coarsening/contraction/local_cluster_contraction.h" #include "kaminpar-dist/datastructures/distributed_graph.h" #include "kaminpar-dist/datastructures/distributed_partitioned_graph.h" #include "kaminpar-dist/factories.h" +#include "kaminpar-dist/logger.h" #include "kaminpar-shm/coarsening/max_cluster_weights.h" -#include "kaminpar-common/logger.h" - namespace kaminpar::dist { +namespace { SET_DEBUG(false); - -Coarsener::Coarsener(const DistributedGraph &input_graph, const Context &input_ctx) - : _input_graph(input_graph), - _input_ctx(input_ctx), - _global_clusterer(factory::create_global_clusterer(_input_ctx)), - _local_clusterer(factory::create_local_clusterer(_input_ctx)) {} - -const DistributedGraph *Coarsener::coarsen_once() { - return coarsen_once(max_cluster_weight()); } -const DistributedGraph *Coarsener::coarsen_once_local(const GlobalNodeWeight max_cluster_weight) { - DBG << "Coarsen graph using local clustering algorithm ..."; - const DistributedGraph *graph = coarsest(); - - _local_clusterer->initialize(*graph); - auto &clustering = _local_clusterer->cluster(*graph, max_cluster_weight); - if (clustering.empty()) { - DBG << "... converged with empty clustering"; - return graph; - } - - ScalableVector> legacy_clustering(clustering.begin(), clustering.end()); - auto [c_graph, mapping, m_ctx] = contract_local_clustering(*graph, legacy_clustering); - KASSERT(debug::validate_graph(c_graph), "", assert::heavy); - DBG << "Reduced number of nodes from " << graph->global_n() << " to " << c_graph.global_n(); - - if (!has_converged(*graph, c_graph)) { - DBG << "... success"; - - _graph_hierarchy.push_back(std::move(c_graph)); - _local_mapping_hierarchy.push_back(std::move(mapping)); - return coarsest(); - } +GlobalClusterCoarsener::GlobalClusterCoarsener(const Context &input_ctx) + : _input_ctx(input_ctx), + _clusterer(factory::create_global_clusterer(_input_ctx)) {} - DBG << "... converged due to insufficient shrinkage"; - return graph; +void GlobalClusterCoarsener::initialize(const DistributedGraph *graph) { + _input_graph = graph; + _graph_hierarchy.clear(); } -const DistributedGraph *Coarsener::coarsen_once_global(const GlobalNodeWeight max_cluster_weight) { +bool GlobalClusterCoarsener::coarsen() { DBG << "Coarsen graph using global clustering algorithm ..."; - const DistributedGraph *graph = coarsest(); + const DistributedGraph &graph = current(); - // Call global clustering algorithm - _global_clusterer->initialize(*graph); - auto &clustering = - _global_clusterer->cluster(*graph, static_cast(max_cluster_weight)); + StaticArray clustering(graph.total_n(), static_array::noinit); - bool empty = clustering.empty(); - MPI_Allreduce(MPI_IN_PLACE, &empty, 1, MPI_CXX_BOOL, MPI_LAND, graph->communicator()); - if (empty) { // Empty --> converged - DBG << "... converged with empty clustering"; - return graph; - } + _clusterer->set_max_cluster_weight(max_cluster_weight()); + _clusterer->cluster(clustering, graph); - // Construct the coarse graph - auto result = contract_clustering(*graph, clustering, _input_ctx.coarsening); + auto result = contract_clustering(graph, clustering, _input_ctx.coarsening); - KASSERT(debug::validate_graph(result.graph), "", assert::heavy); - DBG << "Reduced number of nodes from " << graph->global_n() << " to " << result.graph.global_n(); + KASSERT( + debug::validate_graph(result.graph), + "invalid graph after global cluster contraction", + assert::heavy + ); + DBG << "Reduced number of nodes from " << graph.global_n() << " to " << result.graph.global_n(); - // Only keep graph if coarsening has not converged yet - if (!has_converged(*graph, result.graph)) { + if (!has_converged(graph, result.graph)) { DBG << "... success"; _graph_hierarchy.push_back(std::move(result.graph)); _global_mapping_hierarchy.push_back(std::move(result.mapping)); _node_migration_history.push_back(std::move(result.migration)); - return coarsest(); + return true; } DBG << "... converged due to insufficient shrinkage"; - return graph; -} - -const DistributedGraph *Coarsener::coarsen_once(const GlobalNodeWeight max_cluster_weight) { - const DistributedGraph *graph = coarsest(); - - if (level() >= _input_ctx.coarsening.max_global_clustering_levels) { - return graph; - } else if (level() == _input_ctx.coarsening.max_local_clustering_levels) { - _local_clustering_converged = true; - } - - if (!_local_clustering_converged) { - const DistributedGraph *c_graph = coarsen_once_local(max_cluster_weight); - if (c_graph == graph) { - _local_clustering_converged = true; - // no return -> try global clustering right away - } else { - return c_graph; - } - } - - return coarsen_once_global(max_cluster_weight); -} - -DistributedPartitionedGraph Coarsener::uncoarsen_once(DistributedPartitionedGraph &&p_graph) { - KASSERT(coarsest() == &p_graph.graph(), "expected graph partition of current coarsest graph"); - KASSERT(!_global_mapping_hierarchy.empty() || !_local_mapping_hierarchy.empty()); - - if (!_global_mapping_hierarchy.empty()) { - return uncoarsen_once_global(std::move(p_graph)); - } - - return uncoarsen_once_local(std::move(p_graph)); + return false; } -DistributedPartitionedGraph Coarsener::uncoarsen_once_local(DistributedPartitionedGraph &&p_graph) { - KASSERT(!_local_mapping_hierarchy.empty(), "", assert::light); - - auto block_weights = p_graph.take_block_weights(); - const DistributedGraph *new_coarsest = nth_coarsest(1); - const auto &mapping = _local_mapping_hierarchy.back(); - - StaticArray partition(new_coarsest->total_n()); - new_coarsest->pfor_all_nodes([&](const NodeID u) { partition[u] = p_graph.block(mapping[u]); }); - const BlockID k = p_graph.k(); - - _local_mapping_hierarchy.pop_back(); - _graph_hierarchy.pop_back(); - - return {coarsest(), k, std::move(partition), std::move(block_weights)}; -} - -DistributedPartitionedGraph Coarsener::uncoarsen_once_global(DistributedPartitionedGraph &&p_graph +DistributedPartitionedGraph GlobalClusterCoarsener::uncoarsen(DistributedPartitionedGraph &&p_graph ) { const DistributedGraph *new_coarsest = nth_coarsest(1); @@ -174,31 +90,26 @@ DistributedPartitionedGraph Coarsener::uncoarsen_once_global(DistributedPartitio return std::move(p_graph); } -bool Coarsener::has_converged(const DistributedGraph &before, const DistributedGraph &after) const { +bool GlobalClusterCoarsener::has_converged( + const DistributedGraph &before, const DistributedGraph &after +) const { return 1.0 * after.global_n() / before.global_n() >= 0.95; } -const DistributedGraph *Coarsener::coarsest() const { - return nth_coarsest(0); +const DistributedGraph &GlobalClusterCoarsener::current() const { + return _graph_hierarchy.empty() ? *_input_graph : _graph_hierarchy.back(); } -std::size_t Coarsener::level() const { +std::size_t GlobalClusterCoarsener::level() const { return _graph_hierarchy.size(); } -const DistributedGraph *Coarsener::nth_coarsest(const std::size_t n) const { - return _graph_hierarchy.size() > n ? &_graph_hierarchy[_graph_hierarchy.size() - n - 1] - : &_input_graph; -} - -GlobalNodeWeight Coarsener::max_cluster_weight() const { - const auto *graph = coarsest(); - +GlobalNodeWeight GlobalClusterCoarsener::max_cluster_weight() const { return shm::compute_max_cluster_weight( _input_ctx.coarsening, _input_ctx.partition, - graph->global_n(), - graph->global_total_node_weight() + current().global_n(), + current().global_total_node_weight() ); } } // namespace kaminpar::dist diff --git a/kaminpar-dist/coarsening/global_cluster_coarsener.h b/kaminpar-dist/coarsening/global_cluster_coarsener.h index 5ed6a3d2..5a870a5d 100644 --- a/kaminpar-dist/coarsening/global_cluster_coarsener.h +++ b/kaminpar-dist/coarsening/global_cluster_coarsener.h @@ -1,7 +1,7 @@ /******************************************************************************* - * Builds and manages a hierarchy of coarse graphs. + * Graph coarsener based on global clusterings. * - * @file: coarsener.h + * @file: global_cluster_coarsener.h * @author: Daniel Seemaier * @date: 28.04.2022 ******************************************************************************/ @@ -9,54 +9,42 @@ #include -#include "kaminpar-dist/coarsening/clustering/clusterer.h" +#include "kaminpar-dist/coarsening/clusterer.h" +#include "kaminpar-dist/coarsening/coarsener.h" #include "kaminpar-dist/coarsening/contraction/cluster_contraction.h" #include "kaminpar-dist/context.h" #include "kaminpar-dist/datastructures/distributed_graph.h" #include "kaminpar-dist/datastructures/distributed_partitioned_graph.h" #include "kaminpar-dist/dkaminpar.h" -#include "kaminpar-common/datastructures/scalable_vector.h" - namespace kaminpar::dist { -class Coarsener { +class GlobalClusterCoarsener : public Coarsener { public: - Coarsener(const DistributedGraph &input_graph, const Context &input_ctx); - - const DistributedGraph *coarsen_once(); + GlobalClusterCoarsener(const Context &input_ctx); - const DistributedGraph *coarsen_once(GlobalNodeWeight max_cluster_weight); + void initialize(const DistributedGraph *graph) final; - DistributedPartitionedGraph uncoarsen_once(DistributedPartitionedGraph &&p_graph); + bool coarsen() final; - GlobalNodeWeight max_cluster_weight() const; - const DistributedGraph *coarsest() const; - std::size_t level() const; - -private: - const DistributedGraph *coarsen_once_local(GlobalNodeWeight max_cluster_weight); - const DistributedGraph *coarsen_once_global(GlobalNodeWeight max_cluster_weight); + [[nodiscard]] virtual std::size_t level() const final; - DistributedPartitionedGraph uncoarsen_once_local(DistributedPartitionedGraph &&p_graph); - DistributedPartitionedGraph uncoarsen_once_global(DistributedPartitionedGraph &&p_graph); + [[nodiscard]] virtual const DistributedGraph ¤t() const final; - const DistributedGraph *nth_coarsest(std::size_t n) const; + DistributedPartitionedGraph uncoarsen(DistributedPartitionedGraph &&p_graph) final; +private: bool has_converged(const DistributedGraph &before, const DistributedGraph &after) const; + GlobalNodeWeight max_cluster_weight() const; - const DistributedGraph &_input_graph; const Context &_input_ctx; - std::unique_ptr _global_clusterer; - std::unique_ptr _local_clusterer; + const DistributedGraph *_input_graph = nullptr; + + std::unique_ptr _clusterer; std::vector _graph_hierarchy; - std::vector _global_mapping_hierarchy; //< produced by global clustering algorithm + std::vector _global_mapping_hierarchy; std::vector _node_migration_history; - std::vector> - _local_mapping_hierarchy; //< produced by local clustering_algorithm - - bool _local_clustering_converged = false; }; } // namespace kaminpar::dist diff --git a/kaminpar-dist/coarsening/local_then_global_cluster_coarsener.cc b/kaminpar-dist/coarsening/local_then_global_cluster_coarsener.cc.2 similarity index 100% rename from kaminpar-dist/coarsening/local_then_global_cluster_coarsener.cc rename to kaminpar-dist/coarsening/local_then_global_cluster_coarsener.cc.2 diff --git a/kaminpar-dist/coarsening/local_then_global_cluster_coarsener.h b/kaminpar-dist/coarsening/local_then_global_cluster_coarsener.h.2 similarity index 100% rename from kaminpar-dist/coarsening/local_then_global_cluster_coarsener.h rename to kaminpar-dist/coarsening/local_then_global_cluster_coarsener.h.2