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

          Line data    Source code
       1             : //------------------------------------------------------------------------------
       2             : // LAGraph/experimental/test/test_MaximalMatching
       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             : // Contributed by Vidith Madhu, Texas A&M University
      15             : 
      16             : //------------------------------------------------------------------------------
      17             : 
      18             : /*
      19             : NOTE: Unlike the other tests, this does not use .mtx files, but rather generates the test
      20             : matrices using specified configurations and seeds with LAGraph_Random_Matrix
      21             : 
      22             : */
      23             : 
      24             : #include <stdio.h>
      25             : #include <acutest.h>
      26             : #include <LAGraphX.h>
      27             : #include <LAGraph_test.h>
      28             : #include <LG_Xtest.h>
      29             : 
      30             : GrB_Vector matching = NULL , weight = NULL, node_degree = NULL, hop_nodes = NULL, hop_edges = NULL ;
      31             : GrB_Matrix A = NULL, E = NULL, E_t = NULL ;
      32             : LAGraph_Graph G = NULL ;
      33             : 
      34             : typedef struct
      35             : {
      36             :     double matching_val ; // for unweighted matchings, the size of the matching. For weighted, sum of edge weights
      37             :     int matching_type ;   // 0: unweighted, 1: heavy, 2: light
      38             :     int is_exact ;        // whether or not matching_val is exactly computed for this test
      39             :     const char *name ;    // matrix file name
      40             : }
      41             : matrix_info ;
      42             : 
      43             : const matrix_info tests [ ] =
      44             : {
      45             :     // unweighted bipartite
      46             :     { 150, 0, 1, "random_unweighted_bipartite1.mtx" }, // 42
      47             :     { 150, 0, 1, "random_unweighted_bipartite2.mtx" }, // 69
      48             :     { 143, 0, 0, "random_unweighted_bipartite1.mtx" }, // repeat
      49             :     { 147, 0, 0, "random_unweighted_bipartite2.mtx" }, // repeat
      50             : 
      51             :     // unweighted general
      52             :     // { 25, 0, 1, 50, -1, 5.0 / 50, 31, "unweighted_general_1" } , // 31
      53             :     { 25, 0, 1, "random_unweighted_general1.mtx"},
      54             :     // { 100, 0, 1, 200, -1, 10.0 / 200, 101, "unweighted_general_2" }, // 101
      55             :     { 100, 0, 1, "random_unweighted_general2.mtx"},
      56             :     { 24, 0, 0, "random_unweighted_general1.mtx"},
      57             :     { 95, 0, 0, "random_unweighted_general2.mtx"},
      58             : 
      59             :     // weighted bipartite
      60             :     // answer, matching_type, is_exact, l_node, r_node, density, seed, name
      61             :     // { 3777422047635, 1, 0, 1000, 1000, 20.0 / 1000, 83, "weighted_bipartite_1" },
      62             :     { 775940425564, 1, 0, "random_weighted_bipartite1.mtx"}, // seed: 83, nodes: 500, spf: 8
      63             :     // { 9851292258178, 1, 0, 2500, 2500, 30.0 / 2500, 78, "weighted_bipartite_2" }
      64             :     { 417490248760, 1, 0, "random_weighted_bipartite2.mtx"}, // seed: 151, nodes: 300, spf: 5 
      65             :     { 181453589490, 2, 0, "random_weighted_bipartite1.mtx" }, // repeat
      66             :     { 133704435764, 2, 0, "random_weighted_bipartite2.mtx" }, // repeat
      67             :     // weighted general
      68             :     { 783685067769, 1, 0, "random_weighted_general1.mtx" }, // seed: 137, nodes: 500, spf: 8
      69             :     { 420609293186, 1, 0, "random_weighted_general2.mtx" }, // seed: 62, nodes: 300, spf: 5
      70             :     { 165090013148, 2, 0, "random_weighted_general1.mtx" }, // repeat
      71             :     { 128746478507, 2, 0, "random_weighted_general2.mtx" }, // repeat
      72             :     
      73             :     { 0, 0, 0, "" }
      74             : } ;
      75             : 
      76             : double thresholds [ ] = {
      77             :     0.85,   // unweighted matching, exact
      78             :     0.90,   // unweighted matching, naive
      79             :     0.80,   // weighted matching, naive, light
      80             :     0.90,   // weighted matching, naive, heavy
      81             : } ;
      82             : 
      83             : #define SEEDS_PER_TEST 10
      84             : #define LEN 512
      85             : 
      86             : char filename [LEN+1] ;
      87             : char msg [LAGRAPH_MSG_LEN] ;
      88             : 
      89           1 : void test_MaximalMatching (void) 
      90             : {
      91             : #if LAGRAPH_SUITESPARSE
      92           1 :     OK (LAGraph_Init (msg)) ;
      93             : //  OK (LG_SET_BURBLE (true)) ;
      94             : 
      95           1 :     for (int k = 0 ; ; k++)
      96          16 :     {
      97          17 :         const char *aname = tests [k].name ;
      98          17 :         if (strlen (aname) == 0) break ;
      99          16 :         printf ("\n======================= %s:\n", aname) ;
     100          16 :         TEST_CASE (aname) ;
     101             : 
     102             :         // old code using files
     103             :         //--------------
     104          16 :         snprintf (filename, LEN, LG_DATA_DIR "%s", aname) ;
     105          16 :         FILE *f = fopen (filename, "r") ;
     106          16 :         TEST_CHECK (f != NULL) ;
     107          16 :         TEST_MSG ("Filename %s is invalid", filename) ;
     108          16 :         OK (LAGraph_MMRead (&A, f, msg)) ;
     109          16 :         fclose (f) ;
     110             :         //--------------
     111             : 
     112          16 :         TEST_CHECK (A != NULL) ;
     113          16 :         TEST_MSG ("Building of adjacency matrix failed") ;
     114          16 :         GxB_print (A, 1) ;
     115             : 
     116          16 :         OK (LAGraph_New (&G, &A, LAGraph_ADJACENCY_DIRECTED, msg)) ;
     117             : 
     118          16 :         OK (LAGraph_Cached_NSelfEdges (G, msg)) ;
     119          16 :         OK (LAGraph_Cached_AT (G, msg)) ;
     120             : 
     121             : //      if (G->nself_edges != 0)
     122             :         {
     123             :             // remove self-edges
     124          16 :             printf ("graph has %g self edges\n", (double) G->nself_edges) ;
     125          16 :             OK (LAGraph_DeleteSelfEdges (G, msg)) ;
     126          16 :             printf ("now has %g self edges\n", (double) G->nself_edges) ;
     127          16 :             TEST_CHECK (G->nself_edges == 0) ;
     128             :         }
     129             : 
     130             :         // check if G is undirected
     131             :         // by definition, G->A must equal G->AT iff G is undirected
     132          16 :         bool ok = 0;
     133          16 :         OK (LAGraph_Matrix_IsEqual (&ok, G->A, G->AT, msg)) ;
     134          16 :         TEST_CHECK (ok) ;
     135          16 :         TEST_MSG ("Input graph is not undirected") ;
     136          16 :         G->kind = LAGraph_ADJACENCY_UNDIRECTED ;
     137             : 
     138          16 :         OK (LAGraph_Incidence_Matrix (&E, G, msg)) ;
     139             :         GrB_Index num_nodes ;
     140             :         GrB_Index num_edges ;
     141          16 :         OK (GrB_Matrix_nrows (&num_nodes, E)) ;
     142          16 :         OK (GrB_Matrix_ncols (&num_edges, E)) ;
     143          16 :         OK (GrB_Matrix_new (&E_t, GrB_FP64, num_edges, num_nodes)) ;
     144          16 :         OK (GrB_transpose (E_t, NULL, NULL, E, NULL)) ;
     145             :         // set to row major incase Incidence_Matrix gave col_major
     146          16 :         OK (GrB_set(E, GrB_ROWMAJOR, GrB_STORAGE_ORIENTATION_HINT)) ;
     147             : 
     148             :         // get weight vector
     149          16 :         OK (GrB_Vector_new (&weight, GrB_FP64, num_edges)) ;
     150          16 :         OK (GrB_reduce (weight, NULL, NULL, GrB_MAX_MONOID_FP64, E_t, NULL)) ;
     151             :         
     152             :         // used to check correctness of matching
     153          16 :         OK (GrB_Vector_new (&node_degree, GrB_UINT64, num_nodes)) ;
     154             : 
     155          16 :         OK (GrB_Vector_new (&hop_edges, GrB_BOOL, num_edges)) ;
     156          16 :         OK (GrB_Vector_new (&hop_nodes, GrB_BOOL, num_nodes)) ;
     157             : 
     158          16 :         double avg_acc = 0 ;
     159             :         size_t which_threshold ;
     160          16 :         uint64_t seed = 0 ;
     161             :         // run max matching
     162         176 :         for (int i = 0; i < SEEDS_PER_TEST; i++){
     163             :             // try random seeds
     164         160 :             OK (LAGraph_MaximalMatching (&matching, E, E_t, tests [k].matching_type, seed, msg)) ;
     165             :             // check correctness
     166         160 :             OK (GrB_mxv (node_degree, NULL, NULL, LAGraph_plus_one_uint64, E, matching, NULL)) ;
     167             :             GrB_Index max_degree ;
     168         160 :             OK (GrB_reduce (&max_degree, NULL, GrB_MAX_MONOID_UINT64, node_degree, NULL)) ;
     169         160 :             TEST_CHECK (max_degree <= 1) ;
     170         160 :             TEST_MSG ("Matching is invalid") ;
     171             :             // check maximality
     172             :             // not maximal <-> there is an edge where neither node is matched
     173             :             // we can do a 1-hop from the matched edges and see if every edge in the graph is covered
     174         160 :             OK (GrB_mxv (hop_nodes, NULL, NULL, LAGraph_any_one_bool, E, matching, NULL)) ;
     175         160 :             OK (GrB_mxv (hop_edges, NULL, NULL, LAGraph_any_one_bool, E_t, hop_nodes, NULL)) ;
     176             :             GrB_Index hop_edges_nvals ;
     177         160 :             OK (GrB_Vector_nvals (&hop_edges_nvals, hop_edges)) ;
     178         160 :             TEST_CHECK (hop_edges_nvals == num_edges) ;
     179         160 :             TEST_MSG ("Matching is not maximal") ;
     180             :             // check that the value of the matching is close enough
     181         160 :             double expected = tests [k].matching_val ;
     182         160 :             double matching_value = 0 ;
     183             : 
     184         160 :             if (tests [k].matching_type == 0) {
     185             :                 // unweighted
     186             :                 // we only care about the number of chosen edges
     187             :                 uint64_t matching_val_int ;
     188          80 :                 OK (GrB_Vector_nvals (&matching_val_int, matching)) ;
     189          80 :                 matching_value = matching_val_int ;
     190          80 :                 if (tests [k].is_exact) {
     191          40 :                     which_threshold = 0 ;
     192             :                 } else {
     193          40 :                     which_threshold = 1 ;
     194             :                 }
     195             :             } else {
     196             :                 // weighted
     197             :                 // sum the weights of the chosen edges.
     198             :                 // eliminate entries in the weight that are not in the matching, then sum the remaining entries
     199          80 :                 GrB_Vector use_weights = NULL ;
     200             : 
     201          80 :                 OK (GrB_Vector_new (&use_weights, GrB_FP64, num_edges)) ;
     202          80 :                 OK (GrB_eWiseMult (use_weights, NULL, NULL, GrB_TIMES_FP64, weight, matching, NULL)) ;
     203          80 :                 OK (GrB_reduce (&matching_value, NULL, GrB_PLUS_MONOID_FP64, use_weights, NULL)) ;
     204          80 :                 OK (GrB_free (&use_weights)) ;
     205             :                 
     206          80 :                 which_threshold = 3 ;
     207             :             }
     208         160 :             if (which_threshold == 0) {
     209             :                 // exact matching must have strict upper bound
     210          40 :                 printf ("matching_value %g expected %g\n", matching_value, expected) ;
     211          40 :                 TEST_CHECK (matching_value <= expected) ;
     212             :             }
     213         160 :             double acc = matching_value / expected ;
     214         160 :             if (tests [k].matching_type == 2) {
     215             :                 // flip it for light matchings
     216          40 :                 acc = expected / matching_value ;
     217          40 :                 which_threshold = 2 ;
     218             :             }
     219         160 :             avg_acc += acc ;
     220         160 :             OK (GrB_free (&matching)) ;
     221         160 :             seed += num_nodes ;
     222             :         }
     223          16 :         avg_acc /= SEEDS_PER_TEST ;
     224          16 :         ok = (avg_acc >= thresholds [which_threshold]) ;
     225             : 
     226          16 :         TEST_CHECK (ok) ;
     227          16 :         printf ("Value of produced matching has %.5f accuracy, passing threshold is %.5f\n for case (%d)\n", avg_acc, thresholds [which_threshold], k) ;
     228             : 
     229          16 :         OK (GrB_free (&A)) ;
     230          16 :         OK (GrB_free (&E)) ;
     231          16 :         OK (GrB_free (&E_t)) ;
     232          16 :         OK (GrB_free (&weight)) ;
     233          16 :         OK (GrB_free (&node_degree)) ;
     234          16 :         OK (GrB_free (&hop_edges)) ;
     235          16 :         OK (GrB_free (&hop_nodes)) ;
     236             : 
     237          16 :         OK (LAGraph_Delete (&G, msg)) ;
     238             :     }
     239           1 :     OK (LAGraph_Finalize (msg)) ;
     240             : #endif
     241           1 : }
     242             : 
     243           1 : void test_MaximalMatchingErrors (void)
     244             : {
     245             : #if LAGRAPH_SUITESPARSE
     246           1 :     OK (LAGraph_Init (msg)) ;
     247             : //  OK (LG_SET_BURBLE (true)) ;
     248             : 
     249           1 :     E = NULL ;
     250           1 :     matching = NULL ;
     251             : 
     252           1 :     OK (GrB_Matrix_new (&E, GrB_FP64, 1, 1)) ;
     253             : 
     254             :     // result pointer is null
     255           1 :     GrB_Info result = LAGraph_MaximalMatching (NULL, E, E, 0, 0, msg) ;
     256           1 :     printf ("\nresult: %d %s\n", result, msg) ;
     257           1 :     TEST_CHECK (result == GrB_NULL_POINTER) ;
     258             : 
     259             :     // E matrix is null
     260           1 :     result = LAGraph_MaximalMatching (&matching, NULL, E, 0, 0, msg) ;
     261           1 :     printf ("\nresult: %d %s\n", result, msg) ;
     262           1 :     TEST_CHECK (result == GrB_NULL_POINTER) ;
     263             : 
     264             :     // E_t matrix is null
     265           1 :     result = LAGraph_MaximalMatching (&matching, E, NULL, 0, 0, msg) ;
     266           1 :     printf ("\nresult: %d %s\n", result, msg) ;
     267           1 :     TEST_CHECK (result == GrB_NULL_POINTER) ;
     268             : 
     269           1 :     GrB_free (&E) ;
     270           1 :     OK (LAGraph_Finalize (msg)) ;
     271             : #endif
     272           1 : }
     273             : 
     274             : TEST_LIST = {
     275             :     { "MaximalMatching", test_MaximalMatching },
     276             :     { "MaximalMatchingErrors", test_MaximalMatchingErrors },
     277             :     { NULL, NULL }
     278             : } ;

Generated by: LCOV version 1.14