/* ========================================================================== */ /* === Source/Mongoose_Matching.cpp ========================================= */ /* ========================================================================== */ /* ----------------------------------------------------------------------------- * Mongoose Graph Partitioning Library, Copyright (C) 2017-2018, * Scott P. Kolodziej, Nuri S. Yeralan, Timothy A. Davis, William W. Hager * Mongoose is licensed under Version 3 of the GNU General Public License. * Mongoose is also available under other licenses; contact authors for details. * SPDX-License-Identifier: GPL-3.0-only * -------------------------------------------------------------------------- */ /** * For computing vertex matchings * * During coarsening, a matching of vertices is computed to determine * which vertices are combined together into supervertices. This can be done * using a number of different strategies, including Heavy Edge Matching and * Community/Brotherly (similar to 2-hop) Matching. */ #include "Mongoose_Matching.hpp" #include "Mongoose_Debug.hpp" #include "Mongoose_Internal.hpp" #include "Mongoose_Logger.hpp" namespace Mongoose { //----------------------------------------------------------------------------- // top-level matching code that serves as a multiple-dispatch system. //----------------------------------------------------------------------------- void match(EdgeCutProblem *graph, const EdgeCut_Options *options) { Logger::tic(MatchingTiming); switch (options->matching_strategy) { case Random: matching_Random(graph, options); break; case HEM: matching_HEM(graph, options); break; case HEMSR: matching_HEM(graph, options); matching_SR(graph, options); break; case HEMSRdeg: matching_HEM(graph, options); matching_SRdeg(graph, options); break; } matching_Cleanup(graph, options); Logger::toc(MatchingTiming); } //----------------------------------------------------------------------------- // Cleans up a matching by matching remaining unmatched vertices to themselves //----------------------------------------------------------------------------- void matching_Cleanup(EdgeCutProblem *graph, const EdgeCut_Options *options) { (void)options; // Unused variable Int n = graph->n; Int *Gp = graph->p; /* Match unmatched vertices to themselves. */ for (Int k = 0; k < n; k++) { if (!graph->isMatched(k)) { Int degree = Gp[k + 1] - Gp[k]; if (degree == 0) { // Singleton! if (graph->singleton == -1) { graph->singleton = k; } else { graph->createMatch(k, graph->singleton, MatchType_Standard); graph->singleton = -1; } } else { // Not a singleton if (options->do_community_matching) { int i; for (i = 0; i < graph->n; i++) { if (graph->matchtype[i] != MatchType_Community) break; } graph->createCommunityMatch(i, k, MatchType_Community); } else { graph->createMatch(k, k, MatchType_Orphan); } } } } if (graph->singleton != -1) { // Leftover singleton Int k = graph->singleton; if (options->do_community_matching) { int i; for (i = 0; i < graph->n; i++) { if (graph->matchtype[i] != MatchType_Community) break; } graph->createCommunityMatch(i, k, MatchType_Community); } else { graph->createMatch(k, k, MatchType_Orphan); } } #ifndef NDEBUG /* Every vertex must be matched in no more than a 3-way matching. */ for (Int k = 0; k < n; k++) { if (options->do_community_matching) { if (!graph->isMatched(k)) PR(("%ld is unmatched\n", k)); ASSERT(graph->isMatched(k)); } /* Load matching. */ Int v[3] = { -1, -1, -1 }; v[0] = k; v[1] = graph->getMatch(v[0]); if (v[1] == v[0]) { v[1] = -1; } if (v[1] != -1) { v[2] = graph->getMatch(v[1]); if (v[2] == v[0]) v[2] = -1; } if (options->do_community_matching) { if (v[2] != -1) { ASSERT(graph->getMatch(v[2]) == v[0]); } else { ASSERT(graph->getMatch(v[1]) == v[0]); } } else { if (v[1] != -1) { ASSERT(graph->getMatch(v[1]) == v[0]); } else { ASSERT(graph->getMatch(v[0]) == v[0]); } } } #endif } //----------------------------------------------------------------------------- // This is a random matching strategy //----------------------------------------------------------------------------- void matching_Random(EdgeCutProblem *graph, const EdgeCut_Options *options) { (void)options; // Unused variable Int n = graph->n; Int *Gp = graph->p; Int *Gi = graph->i; for (Int k = 0; k < n; k++) { /* Consider only unmatched vertices */ if (graph->isMatched(k)) continue; bool unmatched = true; for (Int p = Gp[k]; p < Gp[k + 1] && unmatched; p++) { Int neighbor = Gi[p]; /* Consider only unmatched neighbors */ if (graph->isMatched(neighbor)) continue; unmatched = false; graph->createMatch(k, neighbor, MatchType_Standard); } } #ifndef NDEBUG /* If we want to do expensive checks, make sure that every vertex is either: * 1) matched * 2) has no unmatched neighbors */ for (Int k = 0; k < n; k++) { /* Check condition 1 */ if (graph->matching[k]) continue; /* Check condition 2 */ for (Int p = Gp[k]; p < Gp[k + 1]; p++) { ASSERT(graph->matching[Gi[p]]); } } #endif } //----------------------------------------------------------------------------- // This is the implementation of stall-reducing matching //----------------------------------------------------------------------------- void matching_SR(EdgeCutProblem *graph, const EdgeCut_Options *options) { Int n = graph->n; Int *Gp = graph->p; Int *Gi = graph->i; double *Gx = graph->x; #ifndef NDEBUG /* In order for us to use Passive-Aggressive matching, * all unmatched vertices must have matched neighbors. */ for (Int k = 0; k < n; k++) { if (graph->isMatched(k)) continue; for (Int p = Gp[k]; p < Gp[k + 1]; p++) { ASSERT(graph->isMatched(Gi[p])); } } #endif for (Int k = 0; k < n; k++) { /* Consider only unmatched vertices */ if (graph->isMatched(k)) continue; Int heaviestNeighbor = -1; double heaviestWeight = -1.0; for (Int p = Gp[k]; p < Gp[k + 1]; p++) { Int neighbor = Gi[p]; /* Keep track of the heaviest. */ double x = (Gx) ? Gx[p] : 1; if (x > heaviestWeight) { heaviestWeight = x; heaviestNeighbor = neighbor; } } /* If we found a heaviest neighbor then begin resolving matches. */ if (heaviestNeighbor != -1) { Int v = -1; for (Int p = Gp[heaviestNeighbor]; p < Gp[heaviestNeighbor + 1]; p++) { Int neighbor = Gi[p]; if (graph->isMatched(neighbor)) continue; if (v == -1) { v = neighbor; } else { graph->createMatch(v, neighbor, MatchType_Brotherly); v = -1; } } /* If we had a vertex left over: */ if (v != -1) { if (options->do_community_matching) { graph->createCommunityMatch(heaviestNeighbor, v, MatchType_Community); } else { graph->createMatch(v, v, MatchType_Orphan); } } } } } //----------------------------------------------------------------------------- // This uses the stall-reducing matching where we only try SR matching // with vertices with degree above a user-defined threshold. //----------------------------------------------------------------------------- void matching_SRdeg(EdgeCutProblem *graph, const EdgeCut_Options *options) { Int n = graph->n; Int *Gp = graph->p; Int *Gi = graph->i; /* The brotherly threshold is the minimum degree a "high degree" vertex. * It is the options->degreeThreshold times the average degree. */ double bt = options->high_degree_threshold * ((double)graph->nz / (double)graph->n); #ifndef NDEBUG /* In order for us to use Passive-Aggressive matching, * all unmatched vertices must have matched neighbors. */ for (Int k = 0; k < n; k++) { if (graph->isMatched(k)) continue; for (Int p = Gp[k]; p < Gp[k + 1]; p++) { ASSERT(graph->isMatched(Gi[p])); } } #endif for (Int k = 0; k < n; k++) { /* Consider only matched vertices */ if (!graph->isMatched(k)) continue; Int degree = Gp[k + 1] - Gp[k]; if (degree >= (Int)bt) { Int v = -1; for (Int p = Gp[k]; p < Gp[k + 1]; p++) { Int neighbor = Gi[p]; if (graph->isMatched(neighbor)) continue; if (v == -1) { v = neighbor; } else { graph->createMatch(v, neighbor, MatchType_Brotherly); v = -1; } } /* If we had a vertex left over: */ if (v != -1) { if (options->do_community_matching) { graph->createCommunityMatch(k, v, MatchType_Community); } else { graph->createMatch(v, v, MatchType_Orphan); } } } } ASSERT(graph->cn < n); } //----------------------------------------------------------------------------- // This is a vanilla implementation of heavy edge matching //----------------------------------------------------------------------------- void matching_HEM(EdgeCutProblem *graph, const EdgeCut_Options *options) { (void)options; // Unused variable Int n = graph->n; Int *Gp = graph->p; Int *Gi = graph->i; double *Gx = graph->x; for (Int k = 0; k < n; k++) { /* Consider only unmatched vertices */ if (graph->isMatched(k)) continue; Int heaviestNeighbor = -1; double heaviestWeight = -1.0; for (Int p = Gp[k]; p < Gp[k + 1]; p++) { Int neighbor = Gi[p]; /* Consider only unmatched neighbors */ if (graph->isMatched(neighbor)) continue; /* Keep track of the heaviest. */ double x = (Gx) ? Gx[p] : 1; if (x > heaviestWeight) { heaviestWeight = x; heaviestNeighbor = neighbor; } } /* Match to the heaviest. */ if (heaviestNeighbor != -1) { graph->createMatch(k, heaviestNeighbor, MatchType_Standard); } } #ifndef NDEBUG /* If we want to do expensive checks, make sure that every vertex is either: * 1) matched * 2) has no unmatched neighbors */ for (Int k = 0; k < n; k++) { /* Check condition 1 */ if (graph->matching[k]) continue; /* Check condition 2 */ for (Int p = Gp[k]; p < Gp[k + 1]; p++) { ASSERT(graph->matching[Gi[p]]); } } #endif } } // end namespace Mongoose