LCOV - code coverage report
Current view: top level - experimental/algorithm - LAGraph_MaximalMatching.c (source / functions) Hit Total Coverage
Test: LAGraph code coverage report. Commit id: 7b9d2ee. Current time (UTC): 2025-06-03T21:57:17Z Lines: 100 100 100.0 %
Date: 2025-06-03 22:02:40 Functions: 1 1 100.0 %

          Line data    Source code
       1             : //------------------------------------------------------------------------------
       2             : // LAGraph_MaximalMatching: maximal matching using an adaptation of Luby's MIS algorithm
       3             : // on a line graph. Derived from LAGraph_MaximalIndependentSet
       4             : //------------------------------------------------------------------------------
       5             : 
       6             : // LAGraph, (c) 2022 by The LAGraph Contributors, All Rights Reserved.
       7             : // SPDX-License-Identifier: BSD-2-Clause
       8             : 
       9             : // Contributed by Vidith Madhu, Texas A&M University
      10             : 
      11             : //------------------------------------------------------------------------------
      12             : 
      13             : // TODO: ready for src? need a vanilla non-GxB, and incidence graphs.
      14             : 
      15             : /*
      16             : Uses a modified version of Luby's MIS algorithm
      17             : Major algorithm steps:
      18             : - Compute score for each edge
      19             : - Find max score neighbor of each edge (*)
      20             : - Retain edges with score == max score neighbor (*)
      21             : - Add retained edges to result
      22             : - Remove retained edges and their neighbors from the graph (*)
      23             : 
      24             : (*): these steps involve what can be thought as a "2-hop" process that involves two
      25             : GrB_mxv's: the first to go from edges to vertices, and the second from vertices back to edges.
      26             : Tying both steps together yields a single BFS-like step in the line graph. A important side effect
      27             : of this is that the source edge gets included in the result of this 2-hop step, which cannot be avoided
      28             : since we do not compute E'E explicitly.
      29             : 
      30             : The input to this method is an incidence matrix E, of size n-by-e where the
      31             : undirected graph G has n nodes and e edges.  If the kth edge of G is the edge
      32             : (i,j), then the column E(:,k) contains two entries:  E(i,k) and E(j,k), which
      33             : have the same value.  If the graph G is weighted, then both E(i,k) and E(j,k)
      34             : are equal to the weight of the (i,j) edge.  If G is unweighted, then both are
      35             : equal to 1 (and the matrix E is thus iso-valued).
      36             : 
      37             : The output is vector 'matching' of size e, where matching(k) is present (and
      38             : equal to true) if the kth edge appears in the maximal matching.  If (i,j) is
      39             : a matched edge, then no other edges of G that are incident on nodes i and j
      40             : appear in the matching.
      41             : 
      42             : This method requires O(e) space for an undirected graph with e edges
      43             : */
      44             : 
      45             : #include "LG_internal.h"
      46             : #include "LAGraphX.h"
      47             : 
      48             : // #define dbg
      49             : 
      50             : #undef LG_FREE_ALL
      51             : #undef LG_FREE_WORK
      52             : 
      53             : #define OPTIMIZE_PUSH_PULL
      54             : 
      55             : #define LG_FREE_WORK                        \
      56             : {                                           \
      57             :     GrB_free(&score) ;                      \
      58             :     GrB_free(&candidates) ;                 \
      59             :     GrB_free(&Seed) ;                       \
      60             :     GrB_free(&node_degree) ;                \
      61             :     GrB_free(&degree) ;                     \
      62             :     GrB_free(&max_node_neighbor) ;          \
      63             :     GrB_free(&max_neighbor) ;               \
      64             :     GrB_free(&new_members) ;                \
      65             :     GrB_free(&new_neighbors) ;              \
      66             :     GrB_free(&new_members_nodes) ;          \
      67             :     GrB_free(&new_members_node_degree) ;    \
      68             :     GrB_free(&empty) ;                      \
      69             :     GrB_free(&weight) ;                     \
      70             : }                                           \
      71             : 
      72             : #define LG_FREE_ALL                         \
      73             : {                                           \
      74             :     LG_FREE_WORK ;                          \
      75             :     GrB_free(&result) ;                     \
      76             : }                                           \
      77             : 
      78             : #define MAX_FAILURES 50
      79             : 
      80         213 : int LAGraph_MaximalMatching
      81             : (
      82             :     // outputs:
      83             :     GrB_Vector *matching,                 // pointer to output vector
      84             :     // inputs:
      85             :     GrB_Matrix E,                         // incidence
      86             :     GrB_Matrix E_t,                       // incidence transposed (if incorrect, results are undefined)
      87             :     LAGraph_Matching_kind matching_type,  // 0 (random), 1 (heavy weight matching), 2 (light weight matching)
      88             :     uint64_t seed,                        // random number seed
      89             :     char *msg
      90             : )
      91             : {
      92             : #if LAGRAPH_SUITESPARSE
      93         213 :     LG_CLEAR_MSG ;
      94             : 
      95         213 :     if ((matching == NULL) || (E == NULL) || (E_t == NULL)) {
      96           3 :         return GrB_NULL_POINTER ;
      97             :     }
      98             : 
      99         210 :     GrB_Vector score = NULL ;                   // score for each edge. Computed according to matching_type
     100         210 :     GrB_Vector weight = NULL ;                  // weight of each edge
     101         210 :     GrB_Vector candidates = NULL ;              // set of candidate edges
     102         210 :     GrB_Vector Seed = NULL ;                    // random number seed vector
     103         210 :     GrB_Vector node_degree = NULL ;             // intermediate result for computing edge degree; degree of the node. Only computed once
     104         210 :     GrB_Vector degree = NULL ;                  // edge degree; number of incident edges. Only computed once
     105         210 :     GrB_Vector max_node_neighbor = NULL ;       // intermediate result for computing max edge neighbor; max edge touching a node
     106         210 :     GrB_Vector max_neighbor = NULL ;            // max neighbor of an edge (including itself)
     107         210 :     GrB_Vector new_members = NULL ;             // new edges to include in the matching
     108         210 :     GrB_Vector new_neighbors = NULL ;           // union of new members and their neighbor edges
     109         210 :     GrB_Vector new_members_nodes = NULL ;       // nodes touching an edge in new_members
     110         210 :     GrB_Vector new_members_node_degree = NULL ; // node degree considering only new members
     111         210 :     GrB_Vector result = NULL ;                  // resulting matching    
     112         210 :     GrB_Vector empty = NULL ;                   // empty vector
     113             : 
     114             :     GrB_Index num_edges ;
     115             :     GrB_Index num_nodes ;
     116             : 
     117             :     char typename[LAGRAPH_MAX_NAME_LEN] ;
     118             :     GrB_Type type ;
     119         210 :     LG_TRY (LAGraph_Matrix_TypeName (typename, E, msg)) ;
     120         210 :     LG_TRY (LAGraph_TypeFromName (&type, typename, msg)) ;
     121             : 
     122             : 
     123         210 :     GRB_TRY (GrB_Matrix_nrows (&num_nodes, E)) ;
     124         210 :     GRB_TRY (GrB_Matrix_ncols (&num_edges, E)) ;
     125             : 
     126         210 :     GRB_TRY (GrB_Vector_new (&candidates, GrB_BOOL, num_edges)) ;
     127         210 :     GRB_TRY (GrB_Vector_new (&Seed, GrB_UINT64, num_edges)) ;
     128         210 :     GRB_TRY (GrB_Vector_new (&score, GrB_FP64, num_edges)) ;
     129         210 :     GRB_TRY (GrB_Vector_new (&weight, type, num_edges)) ;
     130         210 :     GRB_TRY (GrB_Vector_new (&node_degree, GrB_UINT64, num_nodes)) ;
     131         210 :     GRB_TRY (GrB_Vector_new (&degree, GrB_UINT64, num_edges)) ;
     132         210 :     GRB_TRY (GrB_Vector_new (&max_node_neighbor, GrB_FP64, num_nodes)) ;
     133         210 :     GRB_TRY (GrB_Vector_new (&max_neighbor, GrB_FP64, num_edges)) ;
     134         210 :     GRB_TRY (GrB_Vector_new (&new_members, GrB_BOOL, num_edges)) ;
     135         210 :     GRB_TRY (GrB_Vector_new (&new_neighbors, GrB_BOOL, num_edges)) ;
     136         210 :     GRB_TRY (GrB_Vector_new (&new_members_nodes, GrB_BOOL, num_nodes)) ;
     137         210 :     GRB_TRY (GrB_Vector_new (&new_members_node_degree, GrB_UINT64, num_nodes)) ;
     138         210 :     GRB_TRY (GrB_Vector_new (&result, GrB_BOOL, num_edges)) ;
     139         210 :     GRB_TRY (GrB_Vector_new (&empty, GrB_BOOL, num_edges)) ;
     140             :     
     141         210 :     GRB_TRY (GrB_assign (Seed, NULL, NULL, 0, GrB_ALL, num_edges, NULL)) ;
     142             : 
     143         210 :     LG_TRY (LAGraph_Random_Seed (Seed, seed, msg)) ;
     144             : 
     145             :     // initially all edges are considered
     146         210 :     GRB_TRY (GrB_assign (candidates, NULL, NULL, true, GrB_ALL, 
     147             :         num_edges, NULL)) ;
     148             :      
     149             :     GrB_Index ncandidates ;
     150         210 :     GrB_Index nfailures = 0 ; // counts how many iterations have failed due to invalid matchings
     151             : 
     152         210 :     GRB_TRY (GrB_Vector_nvals(&ncandidates, candidates)) ;
     153             : 
     154             :     // for each node, counts incident edges
     155         210 :     GRB_TRY (GrB_mxv (node_degree, NULL, NULL, LAGraph_plus_one_uint64, E, candidates, NULL)) ;
     156             : 
     157             :     // for each edge, sums incident edges for each node. Each edge has an excess of 2 degree, but it doesn't matter since
     158             :     // we care about relative degree
     159         210 :     GRB_TRY (GrB_mxv (degree, NULL, NULL, LAGraph_plus_second_uint64, E_t, node_degree, NULL)) ;
     160             : 
     161         210 :     GRB_TRY (GrB_reduce (weight, NULL, NULL, GrB_MAX_MONOID_FP64, E_t, NULL)) ; // use ANY ?
     162             : 
     163         210 :     double sparsity_thresh = 
     164             :     #ifdef OPTIMIZE_PUSH_PULL
     165             :         0.04 ;
     166             :     #else
     167             :         1.0;
     168             :     #endif
     169             : 
     170             :     #if defined ( COVERAGE )
     171         210 :         int kount = 0 ;
     172             :     #endif
     173             : 
     174        1161 :     while (ncandidates > 0) {
     175             :         // first just generate the scores again
     176         951 :         GRB_TRY (GrB_eWiseMult (score, candidates, NULL, GrB_DIV_FP64, Seed, degree, GrB_DESC_RS)) ;
     177             : 
     178             :         // for light matching, can multiply scores by 1 / (edge weight)
     179         951 :         if (matching_type == LAGraph_Matching_heavy) {
     180             :             // heavy
     181         225 :             GRB_TRY (GrB_eWiseMult (score, NULL, NULL, GrB_TIMES_FP64, score, weight, NULL)) ;
     182         726 :         } else if (matching_type == LAGraph_Matching_light) {
     183             :             // light
     184         217 :             GRB_TRY (GrB_eWiseMult (score, NULL, NULL, GrB_DIV_FP64, score, weight, NULL)) ;
     185             :         }
     186             : 
     187             :         // the actual edge selection is common regardless of matching type
     188             : 
     189             :         // intermediate result. Max score edge touching each node
     190             :         // don't need to clear this out first because we populate the result for all nodes
     191         951 :         if (ncandidates > sparsity_thresh * num_edges) {
     192         586 :             GRB_TRY (LG_SET_FORMAT_HINT (score, LG_BITMAP)) ;
     193         586 :             GRB_TRY (GrB_mxv (max_node_neighbor, NULL, NULL, GrB_MAX_SECOND_SEMIRING_FP64, E, score, NULL)) ;
     194             :         } else {
     195         365 :             GRB_TRY (LG_SET_FORMAT_HINT (score, LG_SPARSE)) ;
     196         365 :             GRB_TRY (GrB_vxm (max_node_neighbor, NULL, NULL, GrB_MAX_FIRST_SEMIRING_FP64, score, E_t, NULL)) ;
     197             :         }
     198             : 
     199             :         GrB_Index node_nvals ;
     200         951 :         GRB_TRY (GrB_Vector_nvals (&node_nvals, max_node_neighbor)) ;
     201             : 
     202             :         // Max edge touching each candidate edge, including itself
     203         951 :         if (node_nvals > sparsity_thresh * num_nodes) {
     204         760 :             GRB_TRY (LG_SET_FORMAT_HINT (max_node_neighbor, LG_BITMAP)) ;
     205         760 :             GRB_TRY (GrB_mxv (max_neighbor, candidates, NULL, GrB_MAX_SECOND_SEMIRING_FP64, E_t, max_node_neighbor, GrB_DESC_RS)) ;
     206             :         } else {
     207         191 :             GRB_TRY (LG_SET_FORMAT_HINT (max_node_neighbor, LG_SPARSE)) ;
     208         191 :             GRB_TRY (GrB_vxm (max_neighbor, candidates, NULL, GrB_MAX_FIRST_SEMIRING_FP64, max_node_neighbor, E, GrB_DESC_RS)) ;
     209             :         }
     210             :         // Note that we are using the GE operator and not G, since max_neighbor includes the self score
     211             :         // correctness: both score and max_neighbor only have entries for candidates, so no non-candidate members are produced
     212             :         // GRB_TRY (GrB_assign (new_members, NULL, NULL, empty, GrB_ALL, num_edges, NULL)) ; // just experimenting
     213         951 :         GRB_TRY (GrB_eWiseAdd (new_members, NULL, NULL, GrB_GE_FP64, score, max_neighbor, NULL)) ;
     214             : 
     215             :         // makes new_members structural
     216         951 :         GRB_TRY (GrB_select (new_members, NULL, NULL, GrB_VALUEEQ_BOOL, new_members, true, NULL)) ; 
     217             :     #ifdef dbg
     218             :         printf("new members for ncandidates = %lld:\n", ncandidates);
     219             :         LAGRAPH_TRY (LAGraph_Vector_Print (new_members, LAGraph_SHORT, stdout, msg)) ;
     220             :     #endif
     221             : 
     222             :         GrB_Index new_members_nvals ;
     223         951 :         GRB_TRY (GrB_Vector_nvals (&new_members_nvals, new_members)) ;
     224             : 
     225             :         // check if any node has > 1 edge touching it. 
     226         951 :         if (new_members_nvals > sparsity_thresh * num_edges) {
     227         271 :             GRB_TRY (LG_SET_FORMAT_HINT (new_members, LG_BITMAP)) ;
     228         271 :             GRB_TRY (GrB_mxv (new_members_node_degree, NULL, NULL, LAGraph_plus_one_uint64, E, new_members, NULL)) ;
     229             :         } else {
     230         680 :             GRB_TRY (LG_SET_FORMAT_HINT (new_members, LG_SPARSE)) ;
     231         680 :             GRB_TRY (GrB_vxm (new_members_node_degree, NULL, NULL, LAGraph_plus_one_uint64, new_members, E_t, NULL)) ;
     232             :         }
     233             : 
     234             :         GrB_Index max_degree ; 
     235         951 :         GRB_TRY (GrB_reduce (&max_degree, NULL, GrB_MAX_MONOID_UINT64, new_members_node_degree, NULL)) ;
     236             : 
     237             :     #if defined ( COVERAGE )
     238         951 :         if (num_nodes == 20 && kount++ == 1) max_degree = 2 ;
     239         951 :         if (num_nodes == 30 && kount++ == 0) max_degree = 2 ;
     240             :     #endif
     241             : 
     242         951 :         if (max_degree > 1) {
     243          10 :             nfailures++ ;
     244          10 :             LG_ASSERT_MSG (nfailures <= MAX_FAILURES, LAGRAPH_CONVERGENCE_FAILURE,
     245             :                 "method has stalled") ;
     246             :             // regen seed and seed vector
     247          10 :             LG_TRY (LAGraph_Random_Seed (Seed, seed + nfailures, msg)) ;
     248          10 :             continue ;
     249             :         }
     250             :         // add new members to result and remove from candidates
     251             :         // also want to remove all adjacent edges in new_members from candidates
     252         941 :         GRB_TRY (GrB_assign (result, new_members, NULL, true, GrB_ALL, num_edges, GrB_DESC_S)) ; 
     253             :         // to include neighbor edges, need to compute new_neighbors
     254             :         // to do this, we need to compute the intermediate result new_members_nodes
     255         941 :         if (new_members_nvals > sparsity_thresh * num_edges) {
     256         261 :             GRB_TRY (LG_SET_FORMAT_HINT (new_members, LG_BITMAP)) ;
     257         261 :             GRB_TRY (GrB_mxv (new_members_nodes, NULL, NULL, LAGraph_any_one_bool, E, new_members, NULL)) ;
     258             :         } else {
     259         680 :             GRB_TRY (LG_SET_FORMAT_HINT (new_members, LG_SPARSE)) ;
     260         680 :             GRB_TRY (GrB_vxm (new_members_nodes, NULL, NULL, LAGraph_any_one_bool, new_members, E_t, NULL)) ;
     261             :         }
     262             : 
     263         941 :         GRB_TRY (GrB_Vector_nvals (&node_nvals, new_members_nodes)) ;
     264             : 
     265         941 :         if (node_nvals > sparsity_thresh * num_nodes) {
     266         682 :             GRB_TRY (LG_SET_FORMAT_HINT (new_members_nodes, LG_BITMAP)) ;
     267         682 :             GRB_TRY (GrB_mxv (new_neighbors, NULL, NULL, LAGraph_any_one_bool, E_t, new_members_nodes, NULL)) ;
     268             :         } else {
     269         259 :             GRB_TRY (LG_SET_FORMAT_HINT (new_members_nodes, LG_SPARSE)) ;
     270         259 :             GRB_TRY (GrB_vxm (new_neighbors, NULL, NULL, LAGraph_any_one_bool, new_members_nodes, E, NULL)) ;
     271             :         }
     272             : 
     273             :     #ifdef dbg
     274             :         LAGRAPH_TRY (LAGraph_Vector_Print (new_neighbors, LAGraph_SHORT, stdout, msg)) ;
     275             :     #endif
     276             :         // removes the union of new_members and their neighbors
     277         941 :         GRB_TRY (GrB_assign (candidates, new_neighbors, NULL, empty, GrB_ALL, num_edges, GrB_DESC_S)) ;
     278             : 
     279             :     #ifdef dbg
     280             :         printf("candidates:\n");
     281             :         LAGRAPH_TRY (LAGraph_Vector_Print (candidates, LAGraph_SHORT, stdout, msg)) ;
     282             :     #endif
     283         941 :         GrB_Index last_ncandidates = ncandidates ;
     284             : 
     285         941 :         GrB_Vector_nvals(&ncandidates, candidates) ;
     286             :         
     287             :         // advance seed vector
     288         941 :         LG_TRY (LAGraph_Random_Next (Seed, msg)) ;
     289             : 
     290             :     #if defined ( COVERAGE )
     291         941 :         if (num_nodes == 50 && kount++ == 0)
     292             :         {
     293             :             // hack the Seed vector
     294          20 :             GRB_TRY (GrB_assign (Seed, NULL, NULL, 42, GrB_ALL, num_edges, NULL)) ;
     295             :         }
     296             :     #endif
     297             :     }
     298             : 
     299         210 :     (*matching) = result ;
     300             :     
     301         210 :     LG_FREE_WORK ;
     302         210 :     return (GrB_SUCCESS) ;
     303             : #else
     304             :     return (GrB_NOT_IMPLEMENTED) ;
     305             : #endif
     306             : }

Generated by: LCOV version 1.14