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

          Line data    Source code
       1             : //------------------------------------------------------------------------------
       2             : // LAGraph_MaximalIndependentSet: maximal independent set, with constraints
       3             : //------------------------------------------------------------------------------
       4             : 
       5             : // LAGraph, (c) 2019-2022 by The LAGraph Contributors, All Rights Reserved.
       6             : // SPDX-License-Identifier: BSD-2-Clause
       7             : //
       8             : // For additional details (including references to third party source code and
       9             : // other files) see the LICENSE file or contact permission@sei.cmu.edu. See
      10             : // Contributors.txt for a full list of contributors. Created, in part, with
      11             : // funding and support from the U.S. Government (see Acknowledgments.txt file).
      12             : // DM22-0790
      13             : 
      14             : // Modified from the GraphBLAS C API Specification, by Aydin Buluc, Timothy
      15             : // Mattson, Scott McMillan, Jose' Moreira, Carl Yang.  Based on "GraphBLAS
      16             : // Mathematics" by Jeremy Kepner.  Revised by Timothy A. Davis, Texas A&M
      17             : // University.
      18             : 
      19             : //------------------------------------------------------------------------------
      20             : 
      21             : #define LG_FREE_WORK                \
      22             : {                                   \
      23             :     GrB_free (&neighbor_max) ;      \
      24             :     GrB_free (&new_members) ;       \
      25             :     GrB_free (&new_neighbors) ;     \
      26             :     GrB_free (&candidates) ;        \
      27             :     GrB_free (&empty) ;             \
      28             :     GrB_free (&Seed) ;              \
      29             :     GrB_free (&score) ;             \
      30             :     GrB_free (&degree) ;            \
      31             : }
      32             : 
      33             : #define LG_FREE_ALL                 \
      34             : {                                   \
      35             :     LG_FREE_WORK ;                  \
      36             :     GrB_free (&iset) ;              \
      37             : }
      38             : 
      39             : #include "LG_internal.h"
      40             : #include "LAGraphX.h"
      41             : 
      42             : // A variant of Luby's randomized algorithm [Luby 1985].
      43             : 
      44             : // Given a numeric n x n adjacency matrix A of an unweighted and undirected
      45             : // graph (where the value true represents an edge), compute a maximal set of
      46             : // independent nodes and return it in a boolean n-vector, mis where
      47             : // mis[i] == true implies node i is a member of the set.
      48             : 
      49             : // The graph cannot have any self edges, and it must be symmetric.  Self-edges
      50             : // (diagonal entries) will cause the method to stall, and thus G->nself_edges
      51             : // must be zero on input.  G->out_degree must be present on input.  It must not
      52             : // contain any explicit zeros (this is handled by LAGraph_Cached_OutDegree).
      53             : 
      54             : // Singletons require special treatment.  Since they have no neighbors, their
      55             : // score is never greater than the max of their neighbors, so they never get
      56             : // selected and cause the method to stall.  To avoid this case they are removed
      57             : // from the candidate set at the begining, and added to the independent set.
      58             : 
      59             : // TODO: rename LAGr_MaximalIndependentSet (this is expert)
      60             : // TODO: add a basic method
      61             : // vanilla OK: no GxB used
      62             : 
      63         371 : int LAGraph_MaximalIndependentSet       // maximal independent set
      64             : (
      65             :     // outputs:
      66             :     GrB_Vector *mis,            // mis(i) = true if i is in the set
      67             :     // inputs:
      68             :     LAGraph_Graph G,            // input graph
      69             :     uint64_t seed,              // random number seed
      70             :     GrB_Vector ignore_node,     // if NULL, no nodes are ignored.  Otherwise
      71             :                                 // ignore_node(i) = true if node i is to be
      72             :                                 // ignored, and not treated as a candidate
      73             :                                 // added to maximal independent set.
      74             :     char *msg
      75             : )
      76             : {
      77             : 
      78             :     //--------------------------------------------------------------------------
      79             :     // check inputs
      80             :     //--------------------------------------------------------------------------
      81             : 
      82         371 :     LG_CLEAR_MSG ;
      83         371 :     GrB_Vector iset = NULL ;            // independent set (output vector)
      84         371 :     GrB_Vector score = NULL ;           // random score for each node
      85         371 :     GrB_Vector neighbor_max = NULL ;    // value of max neighbor score
      86         371 :     GrB_Vector new_members = NULL ;     // set of new members to add to iset
      87         371 :     GrB_Vector new_neighbors = NULL ;   // new neighbors to new iset members
      88         371 :     GrB_Vector candidates = NULL ;      // candidate nodes
      89         371 :     GrB_Vector empty = NULL ;           // an empty vector
      90         371 :     GrB_Vector Seed = NULL ;            // random number seed vector
      91         371 :     GrB_Vector degree = NULL ;          // (float) G->out_degree
      92             :     GrB_Matrix A ;                      // G->A, the adjacency matrix
      93             :     GrB_Index n ;                       // # of nodes
      94             : 
      95         371 :     LG_TRY (LAGraph_CheckGraph (G, msg)) ;
      96         371 :     LG_ASSERT (mis != NULL, GrB_NULL_POINTER) ;
      97             : 
      98         371 :     if (G->kind == LAGraph_ADJACENCY_UNDIRECTED ||
      99          48 :        (G->kind == LAGraph_ADJACENCY_DIRECTED &&
     100          48 :         G->is_symmetric_structure == LAGraph_TRUE))
     101             :     {
     102             :         // the structure of A is known to be symmetric
     103         355 :         A = G->A ;
     104             :     }
     105             :     else
     106             :     {
     107             :         // A is not known to be symmetric
     108          16 :         LG_ASSERT_MSG (false, -105, "G->A must be symmetric") ;
     109             :     }
     110             : 
     111         355 :     LG_ASSERT_MSG (G->out_degree != NULL, -106,
     112             :         "G->out_degree must be defined") ;
     113         355 :     LG_ASSERT_MSG (G->nself_edges == 0, -107, "G->nself_edges must be zero") ;
     114             : 
     115             :     //--------------------------------------------------------------------------
     116             :     // initializations
     117             :     //--------------------------------------------------------------------------
     118             : 
     119         355 :     GRB_TRY (GrB_Matrix_nrows (&n, A)) ;
     120         355 :     GRB_TRY (GrB_Vector_new (&neighbor_max, GrB_FP32, n)) ;
     121         355 :     GRB_TRY (GrB_Vector_new (&degree, GrB_FP32, n)) ;
     122         355 :     GRB_TRY (GrB_Vector_new (&new_members, GrB_BOOL, n)) ;
     123         355 :     GRB_TRY (GrB_Vector_new (&new_neighbors, GrB_BOOL, n)) ;
     124         355 :     GRB_TRY (GrB_Vector_new (&candidates, GrB_BOOL, n)) ;
     125         355 :     GRB_TRY (GrB_Vector_new (&empty, GrB_BOOL, n)) ;
     126         355 :     GRB_TRY (GrB_Vector_new (&Seed, GrB_UINT64, n)) ;
     127         355 :     GRB_TRY (GrB_Vector_new (&score, GrB_FP32, n)) ;
     128         355 :     GRB_TRY (GrB_Vector_new (&iset, GrB_BOOL, n)) ;
     129             : 
     130             :     // degree = (float) G->out_degree
     131         355 :     GRB_TRY (GrB_assign (degree, NULL, NULL, G->out_degree, GrB_ALL, n, NULL)) ;
     132             : 
     133             :     //--------------------------------------------------------------------------
     134             :     // remove singletons (nodes of degree zero) and handle ignore_node
     135             :     //--------------------------------------------------------------------------
     136             : 
     137         355 :     GrB_Index nonsingletons = 0 ;
     138         355 :     GRB_TRY (GrB_Vector_nvals (&nonsingletons, degree)) ;
     139         355 :     if (nonsingletons == n)
     140             :     {
     141         163 :         if (ignore_node == NULL)
     142             :         {
     143             :             // all nodes have degree 1 or more; all nodes are candidates
     144             :             // candidates (0:n-1) = true
     145          80 :             GRB_TRY (GrB_assign (candidates, NULL, NULL, (bool) true, GrB_ALL,
     146             :                 n, NULL)) ;
     147             :             // Seed vector starts out dense
     148             :             // Seed (0:n-1) = 0
     149          80 :             GRB_TRY (GrB_assign (Seed, NULL, NULL, 0, GrB_ALL, n, NULL)) ;
     150             :         }
     151             :         else
     152             :         {
     153             :             // all nodes have degree 1 or more, but some nodes are to be
     154             :             // ignored.  Use ignore_node as a valued mask.
     155             :             // candidates<!ignore_node> = true
     156          83 :             GRB_TRY (GrB_assign (candidates, ignore_node, NULL, (bool) true,
     157             :                 GrB_ALL, n, GrB_DESC_C)) ;
     158             :             // Seed vector starts out sparse
     159             :             // Seed{candidates} = 0
     160          83 :             GRB_TRY (GrB_assign (Seed, candidates, NULL, 0, GrB_ALL, n,
     161             :                 GrB_DESC_S)) ;
     162             :         }
     163             :     }
     164             :     else
     165             :     {
     166             :         // one or more singleton is present.
     167             :         // candidates{degree} = 1
     168         192 :         GRB_TRY (GrB_assign (candidates, degree, NULL, (bool) true,
     169             :             GrB_ALL, n, GrB_DESC_S)) ;
     170             :         // add all singletons to iset
     171             :         // iset{!degree} = 1
     172         192 :         GRB_TRY (GrB_assign (iset, degree, NULL, (bool) true, GrB_ALL, n,
     173             :             GrB_DESC_SC)) ;
     174         192 :         if (ignore_node != NULL)
     175             :         {
     176             :             // one or more singletons are present, and some nodes are to be
     177             :             // ignored.  The candidates are all those nodes with degree > 0
     178             :             // for which ignore_node(i) is false (or not present).  Delete
     179             :             // any candidate i for which ignore_node(i) is true.  Use
     180             :             // ignore_node as a valued mask.
     181             :             // candidates<ignore_node> = empty
     182          80 :             GRB_TRY (GrB_assign (candidates, ignore_node, NULL, empty,
     183             :                 GrB_ALL, n, NULL)) ;
     184             :             // Delete any ignored nodes from iset
     185             :             // iset<ignore_node> = empty
     186          80 :             GRB_TRY (GrB_assign (iset, ignore_node, NULL, empty,
     187             :                 GrB_ALL, n, NULL)) ;
     188             :         }
     189             :         // Seed vector starts out sparse
     190             :         // Seed{candidates} = 0
     191         192 :         GRB_TRY (GrB_assign (Seed, candidates, NULL, 0, GrB_ALL, n,
     192             :             GrB_DESC_S)) ;
     193             :     }
     194             : 
     195             :     // create the random number seeds
     196         355 :     LG_TRY (LAGraph_Random_Seed (Seed, seed, msg)) ;
     197             : 
     198             :     //--------------------------------------------------------------------------
     199             :     // iterate while there are candidates to check
     200             :     //--------------------------------------------------------------------------
     201             : 
     202         355 :     int nstall = 0 ;
     203             :     GrB_Index ncandidates ;
     204         355 :     GRB_TRY (GrB_Vector_nvals (&ncandidates, candidates)) ;
     205         355 :     GrB_Index last_ncandidates = ncandidates ;
     206         355 :     GrB_Index n1 = (GrB_Index) (0.04 * (double) n) ;
     207         355 :     GrB_Index n2 = (GrB_Index) (0.10 * (double) n) ;
     208             : 
     209        1344 :     while (ncandidates > 0)
     210             :     {
     211             :         // compute the score for each node; scale the Seed by degree
     212             :         // score = (float) Seed
     213        1161 :         GRB_TRY (GrB_assign (score, NULL, NULL, Seed, GrB_ALL, n, NULL)) ;
     214             :         // score = score / degree
     215        1148 :         GRB_TRY (GrB_eWiseMult (score, NULL, NULL, GrB_DIV_FP32, score, degree,
     216             :             NULL)) ;
     217             : 
     218             :         // compute the max score of all candidate neighbors (only candidates
     219             :         // have a score, so non-candidate neighbors are excluded)
     220             :         // neighbor_max{candidates,replace} = score * A
     221        1148 :         GRB_TRY (GrB_Vector_nvals (&ncandidates, candidates)) ;
     222        1148 :         if (ncandidates < n1)
     223             :         {
     224             :             // push
     225             :             // neighbor_max'{candidates,replace} = score' * A
     226         113 :             GRB_TRY (GrB_vxm (neighbor_max, candidates, NULL,
     227             :                 GrB_MAX_FIRST_SEMIRING_FP32, score, A, GrB_DESC_RS)) ;
     228             :         }
     229             :         else
     230             :         {
     231             :             // pull
     232             :             // neighbor_max{candidates,replace} = A * score
     233        1035 :             GRB_TRY (GrB_mxv (neighbor_max, candidates, NULL,
     234             :                 GrB_MAX_SECOND_SEMIRING_FP32, A, score, GrB_DESC_RS)) ;
     235             :         }
     236             : 
     237             :         // select node if its score is > than all its active neighbors
     238             :         // new_members = (score > neighbor_max) using set union so that nodes
     239             :         // with no neighbors fall through to the output, as true (since no
     240             :         // score is equal to zero).
     241        1148 :         GRB_TRY (GrB_eWiseAdd (new_members, NULL, NULL, GrB_GT_FP32,
     242             :             score, neighbor_max, NULL)) ;
     243             : 
     244             :         // drop explicit zeros from new_members
     245             :         // in particular, this replaces all 0s with empty
     246        1148 :         GRB_TRY (GrB_select (new_members, NULL, NULL, GrB_VALUEEQ_BOOL,
     247             :             new_members, (bool) true, NULL)) ;
     248             : 
     249             :         // add new members to independent set
     250             :         // iset{new_members} = true
     251        1148 :         GRB_TRY (GrB_assign (iset, new_members, NULL, (bool) true,
     252             :             GrB_ALL, n, GrB_DESC_S)) ;
     253             : 
     254             :         // remove new members from set of candidates
     255             :         // candidates{new_members} = empty
     256        1148 :         GRB_TRY (GrB_assign (candidates, new_members, NULL, empty,
     257             :             GrB_ALL, n, GrB_DESC_S)) ;
     258             : 
     259             :         // early exit if candidates is empty
     260        1148 :         GRB_TRY (GrB_Vector_nvals (&ncandidates, candidates)) ;
     261        1148 :         if (ncandidates == 0) { break ; }
     262             : 
     263             :         // Neighbors of new members can also be removed from candidates
     264             :         // new_neighbors{candidates,replace} = new_members * A
     265             :         GrB_Index n_new_members ;
     266        1002 :         GRB_TRY (GrB_Vector_nvals (&n_new_members, new_members)) ;
     267        1002 :         if (n_new_members < n2)
     268             :         {
     269             :             // push
     270             :             // new_neighbors{candidates,replace} = new_members' * A
     271         398 :             GRB_TRY (GrB_vxm (new_neighbors, candidates, NULL,
     272             :                 LAGraph_any_one_bool, new_members, A, GrB_DESC_RS)) ;
     273             :         }
     274             :         else
     275             :         {
     276             :             // pull
     277             :             // new_neighbors{candidates,replace} = A * new_members
     278         604 :             GRB_TRY (GrB_mxv (new_neighbors, candidates, NULL,
     279             :                 LAGraph_any_one_bool, A, new_members, GrB_DESC_RS)) ;
     280             :         }
     281             : 
     282             :         // remove new neighbors of new members from set of candidates
     283             :         // candidates{new_neighbors} = empty
     284        1002 :         GRB_TRY (GrB_assign (candidates, new_neighbors, NULL, empty,
     285             :             GrB_ALL, n, GrB_DESC_S)) ;
     286             : 
     287             :         // sparsify the random number seeds (just keep it for each candidate)
     288             :         // Seed{candidates,replace} = Seed
     289        1002 :         GRB_TRY (GrB_assign (Seed, candidates, NULL, Seed, GrB_ALL, n,
     290             :             GrB_DESC_RS)) ;
     291             : 
     292             :         // Check for stall (can only occur if the matrix has self-edges, or in
     293             :         // the exceedingly rare case that 2 nodes have the exact same score).
     294             :         // If the method happens to stall, with no nodes selected because
     295             :         // the scores happen to tie, try again with another random score.
     296        1002 :         GRB_TRY (GrB_Vector_nvals (&ncandidates, candidates)) ;
     297        1002 :         if (last_ncandidates == ncandidates)
     298             :         {
     299             :             // This case is nearly untestable since it can almost never occur.
     300         429 :             nstall++ ;
     301             :             // terminate if the method has stalled too many times
     302         429 :             LG_ASSERT_MSG (nstall <= 32, LAGRAPH_CONVERGENCE_FAILURE,
     303             :                 "method has stalled") ;
     304             :             // recreate the random number seeds with a new starting seed
     305         416 :             LG_TRY (LAGraph_Random_Seed (Seed, seed + nstall, msg)) ;
     306             :         }
     307         989 :         last_ncandidates = ncandidates ;
     308             : 
     309             :         // get the next random Seed vector
     310         989 :         LG_TRY (LAGraph_Random_Next (Seed, msg)) ;
     311             :     }
     312             : 
     313             :     //--------------------------------------------------------------------------
     314             :     // free workspace and return result
     315             :     //--------------------------------------------------------------------------
     316             : 
     317         342 :     GRB_TRY (GrB_wait (iset, GrB_MATERIALIZE)) ;
     318         342 :     (*mis) = iset ;
     319         342 :     LG_FREE_WORK ;
     320         342 :     return (GrB_SUCCESS) ;
     321             : }

Generated by: LCOV version 1.14