diff --git a/experimental/algorithm/LAGraph_NumberOfWalks.c b/experimental/algorithm/LAGraph_NumberOfWalks.c new file mode 100644 index 0000000000..ae8683e873 --- /dev/null +++ b/experimental/algorithm/LAGraph_NumberOfWalks.c @@ -0,0 +1,101 @@ +#include "GraphBLAS.h" +#include "LAGraphX.h" + +// Inner recursive helper: computes C = A^k using binary exponentiation. +// Uses LAGraph_plus_one_int64 (structural semiring) only for A*A (k==2), +// where both operands are still the original 0/1 adjacency matrix +// All other squarings use PLUS_TIMES because T = A^(k/2) has walk counts +static GrB_Info NumberOfWalks_inner(GrB_Matrix *C, GrB_Matrix A, int64_t k) +{ + if (C == NULL || A == NULL || k < 0) return GrB_INVALID_VALUE ; + + GrB_Info info ; + GrB_Index n ; + GrB_Matrix_nrows (&n, A) ; + + // --- BASE CASES --- + if (k == 0) + { + GrB_Matrix_new (C, GrB_INT64, n, n) ; + for (GrB_Index i = 0 ; i < n ; i++) + GrB_Matrix_setElement_INT64 (*C, 1, i, i) ; + return GrB_SUCCESS ; + } + + if (k == 1) return GrB_Matrix_dup (C, A) ; + + // k==2: adjacency matrix, + if (k == 2) + { + GrB_Matrix_new (C, GrB_INT64, n, n) ; + return GrB_mxm (*C, NULL, NULL, LAGraph_plus_one_int64, A, A, NULL) ; + } + + // --- RECURSION (binary exponentiation) --- + GrB_Matrix T = NULL ; + info = NumberOfWalks_inner (&T, A, k / 2) ; + if (info != GrB_SUCCESS) return info ; + + // T = A^(k/2) + GrB_Matrix_new (C, GrB_INT64, n, n) ; + GrB_mxm (*C, NULL, NULL, GrB_PLUS_TIMES_SEMIRING_INT64, T, T, NULL) ; + GrB_free (&T) ; + + // If k is odd, multiply by one more A: *C = *C * A + if (k % 2 != 0) + { + GrB_mxm (*C, NULL, NULL, LAGraph_plus_first_int64, *C, A, NULL) ; + } + + return GrB_SUCCESS ; +} + +/** + * LAGraph_NumberOfWalks: compute number of walks of length k. + * + * If src is NULL, computes the full n×n matrix A^k where C(i,j) is the + * number of distinct walks of length k from node i to node j. + * + * If src is a non-NULL indicator vector (1 at the source node index), + * computes only the walks from that source to all destinations. + * Result is stored in *C as a 1×n matrix where C(0,j) = walks from src to j. + */ +GrB_Info LAGraph_NumberOfWalks +( + GrB_Matrix *C, // output: A^k (n×n) or single-source walks (1×n) + GrB_Matrix A, // input: adjacency matrix + GrB_Vector src, // input: source indicator vector (NULL = all pairs) + int64_t k // input: walk length +) +{ + if (C == NULL || A == NULL || k < 0) return GrB_INVALID_VALUE ; + + // All-pairs case: compute full A^k + if (src == NULL) + { + return NumberOfWalks_inner (C, A, k) ; + } + + // Single-source case: compute A^k, then w = src * A^k via vxm + GrB_Info info ; + GrB_Index n ; + GrB_Matrix_nrows (&n, A) ; + + // w represents the counts of walks of length 't' starting from 'src' + GrB_Vector w = NULL ; + GrB_Vector_dup (&w, src) ; + + for (int64_t t = 0 ; t < k ; t++) + { + // w^T = w^T * A: walks of length t+1 from source to each vertex + info = GrB_vxm (w, NULL, NULL, LAGraph_plus_first_int64, w, A, NULL) ; + if (info != GrB_SUCCESS) { GrB_free (&w) ; return info ; } + } + + GrB_Matrix_new (C, GrB_INT64, 1, n) ; + info = GrB_assign (*C, NULL, NULL, w, 0, GrB_ALL, n, NULL) ; + + GrB_free (&w) ; + + return info; +} diff --git a/experimental/benchmark/walks_demo.c b/experimental/benchmark/walks_demo.c new file mode 100644 index 0000000000..56513a0655 --- /dev/null +++ b/experimental/benchmark/walks_demo.c @@ -0,0 +1,361 @@ +//------------------------------------------------------------------------------ +// walks_demo.c: benchmark LAGraph_NumberOfWalks vs BFS algorithms +//------------------------------------------------------------------------------ + +// Benchmarks two parts of the NumberOfWalks algorithm: +// +// All-pairs: +// LAGraph_NumberOfWalks vs LAGraph_MultiSourceBFS +// Both produce an n x (n or nsrc) matrix. +// NumberOfWalks uses binary exponentiation (O(log k) mxm calls) +// MultiSourceBFS uses k mxm calls. +// +// Single-source: +// LAGraph_NumberOfWalks (src=indicator) vs LAGr_BreadthFirstSearch +// Both start from one node and produce a length-n result vector/row. +// +// Usage: +// ./walks_demo graph.mtx (generates default sources) +// ./walks_demo graph.mtx sources.mtx (sources in GAP format) +// ./walks_demo graph.mtx sources.mtx 6 (walk length k=6) +// +// The sources file is a Matrix Market file with one source node index +// per row in column 0, matching the GAP benchmark format used by bfs_demo. +// If omitted, the first N_DEFAULT_SOURCES node indices are used. + +#include "../../src/benchmark/LAGraph_demo.h" +#include "LAGraphX.h" + +#define NTHREAD_LIST 1 +#define THREAD_LIST 0 +#define DEFAULT_K 4 +#define N_DEFAULT_SOURCES 64 +#define MAX_TRIALS 8 + +#undef LG_FREE_ALL +#define LG_FREE_ALL \ +{ \ + LAGraph_Delete (&G, msg) ; \ + GrB_free (&C) ; \ + GrB_free (&level) ; \ + GrB_free (&parent) ; \ + GrB_free (&src_indicator) ; \ + GrB_free (&multisrc_vector) ; \ + GrB_free (&SourceNodes) ; \ + LAGraph_Free ((void **) &t_walks_all, msg) ; \ + LAGraph_Free ((void **) &t_msbfs, msg) ; \ + LAGraph_Free ((void **) &t_walks_src, msg) ; \ + LAGraph_Free ((void **) &t_bfs, msg) ; \ +} + +int main (int argc, char **argv) +{ + char msg [LAGRAPH_MSG_LEN] ; + + LAGraph_Graph G = NULL ; + GrB_Matrix C = NULL ; + GrB_Matrix level = NULL, parent = NULL ; + GrB_Vector src_indicator = NULL ; // indicator for SS walks + GrB_Vector multisrc_vector = NULL ; // source list for MultiSourceBFS + GrB_Matrix SourceNodes = NULL ; + + double *t_walks_all = NULL ; // NumberOfWalks time per thread count + double *t_msbfs = NULL ; // MultiSourceBFS time per thread count + + double *t_walks_src = NULL ; // avg SS NumberOfWalks per thread count + double *t_bfs = NULL ; // avg BFS per thread count + + bool burble = false ; + demo_init (burble) ; + + //-------------------------------------------------------------------------- + // read walk length k from the WALK_K environment variable + //-------------------------------------------------------------------------- + + int64_t k = DEFAULT_K ; + const char *k_env = getenv ("WALK_K") ; + if (k_env != NULL && k_env [0] != '\0') k = atol (k_env) ; + if (k <= 0) k = DEFAULT_K ; + + //-------------------------------------------------------------------------- + // THREAD SETUP + //-------------------------------------------------------------------------- + + int nt = NTHREAD_LIST ; + int Nthreads [20] = { 0, THREAD_LIST } ; + int nthreads_max, nthreads_outer, nthreads_inner ; + LAGRAPH_TRY (LAGraph_GetNumThreads (&nthreads_outer, &nthreads_inner, msg)) ; + nthreads_max = nthreads_outer * nthreads_inner ; + if (Nthreads [1] == 0) + { + Nthreads [1] = nthreads_max ; + for (int t = 2 ; t <= nt ; t++) + { + Nthreads [t] = Nthreads [t-1] / 2 ; + if (Nthreads [t] == 0) nt = t-1 ; + } + } + printf ("threads to test:") ; + for (int t = 1 ; t <= nt ; t++) + { + int nthreads = Nthreads [t] ; + if (nthreads > nthreads_max) continue ; + printf (" %d", nthreads) ; + } + printf ("\n") ; + + LAGRAPH_TRY (LAGraph_Malloc ((void **) &t_walks_all, nthreads_max+1, + sizeof (double), msg)) ; + LAGRAPH_TRY (LAGraph_Malloc ((void **) &t_msbfs, nthreads_max+1, + sizeof (double), msg)) ; + LAGRAPH_TRY (LAGraph_Malloc ((void **) &t_walks_src, nthreads_max+1, + sizeof (double), msg)) ; + LAGRAPH_TRY (LAGraph_Malloc ((void **) &t_bfs, nthreads_max+1, + sizeof (double), msg)) ; + + //-------------------------------------------------------------------------- + // LOAD + //-------------------------------------------------------------------------- + + char *matrix_name = (argc > 1) ? argv [1] : "stdin" ; + LAGRAPH_TRY (readproblem (&G, &SourceNodes, + false, // make_symmetric: walks work on directed graphs + false, // remove_self_edges: self loops affect walk counts + false, // structural: need INT64 values, not bool + GrB_INT64, // typecast all entries to INT64 + false, // ensure_positive: walks can be zero + argc, argv)) ; + + LAGRAPH_TRY (LAGraph_Cached_OutDegree (G, msg)) ; + + GrB_Index n, nvals ; + GRB_TRY (GrB_Matrix_nrows (&n, G->A)) ; + GRB_TRY (GrB_Matrix_nvals (&nvals, G->A)) ; + + //-------------------------------------------------------------------------- + // build source node list + // If no source file was given, use the first N_DEFAULT_SOURCES nodes. + //-------------------------------------------------------------------------- + + GrB_Index ntrials ; + if (SourceNodes == NULL) + { + ntrials = (GrB_Index) N_DEFAULT_SOURCES ; + if (ntrials > n) ntrials = n ; + GRB_TRY (GrB_Matrix_new (&SourceNodes, GrB_INT64, ntrials, 1)) ; + for (GrB_Index i = 0 ; i < ntrials ; i++) + { + GRB_TRY (GrB_Matrix_setElement_INT64 (SourceNodes, + (int64_t)(i + 1), i, 0)) ; + } + } + else + { + GRB_TRY (GrB_Matrix_nrows (&ntrials, SourceNodes)) ; + } + if (ntrials > MAX_TRIALS) ntrials = MAX_TRIALS ; + + printf ("graph: %s n: %" PRIu64 " edges: %" PRIu64 " k: %" PRId64 "\n", + matrix_name, (uint64_t) n, (uint64_t) nvals, k) ; + printf ("source trials: %" PRIu64 "\n", (uint64_t) ntrials) ; + fflush (stdout) ; fflush (stderr) ; + + //-------------------------------------------------------------------------- + // build MultiSourceBFS source vector + // GrB_Vector where entry i = source node index (0-based) + //-------------------------------------------------------------------------- + + GRB_TRY (GrB_Vector_new (&multisrc_vector, GrB_INT64, ntrials)) ; + for (GrB_Index i = 0 ; i < ntrials ; i++) + { + int64_t src ; + GRB_TRY (GrB_Matrix_extractElement_INT64 (&src, SourceNodes, i, 0)) ; + src-- ; // convert 1-based (GAP format) to 0-based + GRB_TRY (GrB_Vector_setElement_INT64 (multisrc_vector, src, i)) ; + } + + //-------------------------------------------------------------------------- + // WARMUP + //-------------------------------------------------------------------------- + + printf ("\n--- warmup ---\n") ; + + // warmup: all pairs NumberOfWalks + double twarm = LAGraph_WallClockTime () ; + LAGRAPH_TRY (LAGraph_NumberOfWalks (&C, G->A, NULL, k)) ; + twarm = LAGraph_WallClockTime () - twarm ; + GRB_TRY (GrB_free (&C)) ; + printf ("NumberOfWalks (all-pairs): %g sec\n", twarm) ; + + // warmup: MultiSourceBFS + twarm = LAGraph_WallClockTime () ; + LAGRAPH_TRY (LAGraph_MultiSourceBFS (&level, NULL, G, multisrc_vector, msg)) ; + twarm = LAGraph_WallClockTime () - twarm ; + GRB_TRY (GrB_free (&level)) ; + printf ("MultiSourceBFS: %g sec\n", twarm) ; + + // warmup: SS BFS and NumberOfWalks + { + int64_t src ; + GRB_TRY (GrB_Matrix_extractElement_INT64 (&src, SourceNodes, 0, 0)) ; + src-- ; + + twarm = LAGraph_WallClockTime () ; + LAGRAPH_TRY (LAGr_BreadthFirstSearch (NULL, &parent, G, + (GrB_Index) src, msg)) ; + twarm = LAGraph_WallClockTime () - twarm ; + GRB_TRY (GrB_free (&parent)) ; + printf ("BFS (single-source): %g sec\n", twarm) ; + + GRB_TRY (GrB_Vector_new (&src_indicator, GrB_INT64, n)) ; + GRB_TRY (GrB_Vector_setElement_INT64 (src_indicator, 1, (GrB_Index) src)) ; + twarm = LAGraph_WallClockTime () ; + LAGRAPH_TRY (LAGraph_NumberOfWalks (&C, G->A, src_indicator, k)) ; + twarm = LAGraph_WallClockTime () - twarm ; + GRB_TRY (GrB_free (&C)) ; + GRB_TRY (GrB_free (&src_indicator)) ; + printf ("NumberOfWalks (single-source): %g sec\n", twarm) ; + } + fflush (stdout) ; fflush (stderr) ; + + //========================================================================== + // BENCHMARK + //========================================================================== + + for (int tt = 1 ; tt <= nt ; tt++) + { + int nthreads = Nthreads [tt] ; + if (nthreads > nthreads_max) continue ; + LAGRAPH_TRY (LAGraph_SetNumThreads (1, nthreads, msg)) ; + + printf ("\n=========================== nthreads: %2d ===========================\n", + nthreads) ; + + //---------------------------------------------------------------------- + // All pairs NumberOfWalks vs MultiSourceBFS + //---------------------------------------------------------------------- + + printf ("\n--- Part 1: All-pairs (k=%" PRId64 ", %" PRIu64 " sources) ---\n", + k, (uint64_t) ntrials) ; + + // NumberOfWalks: produces the full A^k matrix + { + double t_run = LAGraph_WallClockTime () ; + LAGRAPH_TRY (LAGraph_NumberOfWalks (&C, G->A, NULL, k)) ; + t_run = LAGraph_WallClockTime () - t_run ; + GRB_TRY (GrB_free (&C)) ; + t_walks_all [nthreads] = t_run ; + printf ("NumberOfWalks all-pairs k: %2" PRId64 + " threads: %2d time: %10.4f sec\n", + k, nthreads, t_run) ; + fflush (stdout) ; + } + + // MultiSourceBFS: over all ntrials source nodes + { + double t_run = LAGraph_WallClockTime () ; + LAGRAPH_TRY (LAGraph_MultiSourceBFS (&level, NULL, G, + multisrc_vector, msg)) ; + t_run = LAGraph_WallClockTime () - t_run ; + GRB_TRY (GrB_free (&level)) ; + t_msbfs [nthreads] = t_run ; + printf ("MultiSourceBFS sources: %" PRIu64 + " threads: %2d time: %10.4f sec\n", + (uint64_t) ntrials, nthreads, t_run) ; + fflush (stdout) ; + } + + //---------------------------------------------------------------------- + // SS NumberOfWalks vs BFS, averaged over all sources + //---------------------------------------------------------------------- + + printf ("\n--- Part 2: Single-source (k=%" PRId64 ") ---\n", k) ; + + double total_walks_src = 0, total_bfs = 0 ; + GRB_TRY (GrB_Vector_new (&src_indicator, GrB_INT64, n)) ; + + for (GrB_Index trial = 0 ; trial < ntrials ; trial++) + { + int64_t src ; + GRB_TRY (GrB_Matrix_extractElement_INT64 (&src, SourceNodes, + trial, 0)) ; + src-- ; + + // SS BFS + double tb = LAGraph_WallClockTime () ; + LAGRAPH_TRY (LAGr_BreadthFirstSearch (NULL, &parent, G, + (GrB_Index) src, msg)) ; + tb = LAGraph_WallClockTime () - tb ; + GRB_TRY (GrB_free (&parent)) ; + total_bfs += tb ; + + // SS NumberOfWalks + GRB_TRY (GrB_Vector_setElement_INT64 (src_indicator, 1, + (GrB_Index) src)) ; + double tw = LAGraph_WallClockTime () ; + LAGRAPH_TRY (LAGraph_NumberOfWalks (&C, G->A, src_indicator, k)) ; + tw = LAGraph_WallClockTime () - tw ; + GRB_TRY (GrB_free (&C)) ; + // clear for next trial + GRB_TRY (GrB_Vector_clear (src_indicator)) ; + total_walks_src += tw ; + + printf ("trial: %3" PRIu64 " src: %12" PRId64 + " walks: %8.4f sec bfs: %8.4f sec\n", + (uint64_t) trial, src, tw, tb) ; + fflush (stdout) ; + } + + GRB_TRY (GrB_free (&src_indicator)) ; + + t_walks_src [nthreads] = total_walks_src / (double) ntrials ; + t_bfs [nthreads] = total_bfs / (double) ntrials ; + + //---------------------------------------------------------------------- + // SUMMARY + //---------------------------------------------------------------------- + + printf ("\n") ; + + printf ( "Avg: NumberOfWalks all-pairs k: %2" PRId64 + " threads: %3d time: %10.3f sec graph: %s\n", + k, nthreads, t_walks_all [nthreads], matrix_name) ; + fprintf (stderr, "Avg: NumberOfWalks all-pairs k: %2" PRId64 + " threads: %3d time: %10.3f sec graph: %s\n", + k, nthreads, t_walks_all [nthreads], matrix_name) ; + + printf ( "Avg: MultiSourceBFS sources: %" PRIu64 + " threads: %3d time: %10.3f sec graph: %s\n", + (uint64_t) ntrials, nthreads, t_msbfs [nthreads], matrix_name) ; + fprintf (stderr, "Avg: MultiSourceBFS sources: %" PRIu64 + " threads: %3d time: %10.3f sec graph: %s\n", + (uint64_t) ntrials, nthreads, t_msbfs [nthreads], matrix_name) ; + + printf ( "Avg: NumberOfWalks single-src k: %2" PRId64 + " threads: %3d time: %10.3f sec graph: %s\n", + k, nthreads, t_walks_src [nthreads], matrix_name) ; + fprintf (stderr, "Avg: NumberOfWalks single-src k: %2" PRId64 + " threads: %3d time: %10.3f sec graph: %s\n", + k, nthreads, t_walks_src [nthreads], matrix_name) ; + + printf ( "Avg: BFS single-source threads: %3d" + " time: %10.3f sec graph: %s\n", + nthreads, t_bfs [nthreads], matrix_name) ; + fprintf (stderr, "Avg: BFS single-source threads: %3d" + " time: %10.3f sec graph: %s\n", + nthreads, t_bfs [nthreads], matrix_name) ; + + fflush (stdout) ; fflush (stderr) ; + } + + // restore default thread count + LAGRAPH_TRY (LAGraph_SetNumThreads (nthreads_outer, nthreads_inner, msg)) ; + + //-------------------------------------------------------------------------- + // CLEANUP + //-------------------------------------------------------------------------- + + LG_FREE_ALL ; + LAGRAPH_TRY (LAGraph_Finalize (msg)) ; + return (GrB_SUCCESS) ; +} diff --git a/experimental/test/test_NumberOfWalks.c b/experimental/test/test_NumberOfWalks.c new file mode 100644 index 0000000000..a489488602 --- /dev/null +++ b/experimental/test/test_NumberOfWalks.c @@ -0,0 +1,201 @@ +//------------------------------------------------------------------------------ +// LAGraph/experimental/test/test_NumberOfWalks.c: test for NumberOfWalks +//------------------------------------------------------------------------------ + +#include +#include + +#include +#include +#include "LG_Xtest.h" + +#define LEN 512 +char msg [LAGRAPH_MSG_LEN] ; +char filename [LEN+1] ; + +//------------------------------------------------------------------------------ +// Ground truth results (pre-computed with NetworkX) +//------------------------------------------------------------------------------ + +// A.mtx (7x7), A^2 walks of length 2 +int64_t A_mtx_walks_k2[49] = { + 3, 1, 2, 3, 2, 3, 1, + 1, 5, 3, 3, 1, 2, 5, + 2, 3, 5, 3, 3, 3, 3, + 3, 3, 3, 5, 2, 3, 3, + 2, 1, 3, 2, 3, 3, 1, + 3, 2, 3, 3, 3, 4, 2, + 1, 5, 3, 3, 1, 2, 5 +} ; + +// 4-node cycle, A^2 walks of length 2 +int64_t cycle_4node_walks_k2[16] = { + 2, 0, 2, 0, + 0, 2, 0, 2, + 2, 0, 2, 0, + 0, 2, 0, 2 +} ; + +// 3-node path (0-1-2), A^2 walks of length 2 +int64_t path_3node_walks_k2[9] = { + 1, 0, 1, + 0, 2, 0, + 1, 0, 1 +} ; + +// check matrix against ground truth +int64_t check_walks (GrB_Matrix result, int64_t *truth, GrB_Index n) +{ + int64_t max_diff = 0 ; + for (GrB_Index i = 0 ; i < n ; i++) + { + for (GrB_Index j = 0 ; j < n ; j++) + { + int64_t computed = 0 ; + int64_t expected = truth [i * n + j] ; + + GrB_Info info = GrB_Matrix_extractElement_INT64 (&computed, result, i, j) ; + if (info == GrB_NO_VALUE) computed = 0 ; + + int64_t diff = computed > expected ? computed - expected : expected - computed ; + if (diff > max_diff) max_diff = diff ; + } + } + return max_diff ; +} + +//------------------------------------------------------------------------------ +// test_NumberOfWalks_path_3node: simple path graph +//------------------------------------------------------------------------------ + +void test_NumberOfWalks_path_3node (void) +{ + LAGraph_Init (msg) ; + GrB_Matrix A = NULL, result = NULL ; + GrB_Index n = 3 ; + + OK (GrB_Matrix_new (&A, GrB_INT64, n, n)) ; + + GrB_Index rows[] = {0, 1, 1, 2} ; + GrB_Index cols[] = {1, 0, 2, 1} ; + int64_t vals[] = {1, 1, 1, 1} ; + + OK (GrB_Matrix_build_INT64 (A, rows, cols, vals, 4, GrB_PLUS_INT64)) ; + + // Compute A^2 + OK (LAGraph_NumberOfWalks (&result, A, NULL, 2)) ; + + // Compare with ground truth + int64_t err = check_walks (result, path_3node_walks_k2, n) ; + TEST_CHECK (err == 0) ; + + OK (GrB_free (&A)) ; + OK (GrB_free (&result)) ; + LAGraph_Finalize (msg) ; +} + +//------------------------------------------------------------------------------ +// test_NumberOfWalks_A_mtx: A.mtx graph with k=2 +//------------------------------------------------------------------------------ + +void test_NumberOfWalks_A_mtx (void) +{ + LAGraph_Init (msg) ; + GrB_Matrix A = NULL, result = NULL ; + GrB_Index n = 7 ; + + snprintf (filename, LEN, LG_DATA_DIR "%s", "A.mtx") ; + FILE *f = fopen (filename, "r") ; + TEST_CHECK (f != NULL) ; + OK (LAGraph_MMRead (&A, f, msg)) ; + OK (fclose (f)) ; + + OK (LAGraph_NumberOfWalks (&result, A, NULL, 2)) ; + + int64_t err = check_walks (result, A_mtx_walks_k2, n) ; + TEST_CHECK (err == 0) ; + + OK (GrB_free (&A)) ; + OK (GrB_free (&result)) ; + LAGraph_Finalize (msg) ; +} + +//------------------------------------------------------------------------------ +// test_NumberOfWalks_cycle_4: simple 4-cycle +//------------------------------------------------------------------------------ + +void test_NumberOfWalks_cycle_4 (void) +{ + LAGraph_Init (msg) ; + GrB_Matrix A = NULL, result = NULL ; + GrB_Index n = 4 ; + + // Build 4-cycle: 0-1-2-3-0 + OK (GrB_Matrix_new (&A, GrB_INT64, n, n)) ; + + GrB_Index rows[] = {0, 1, 1, 2, 2, 3, 3, 0} ; + GrB_Index cols[] = {1, 0, 2, 1, 3, 2, 0, 3} ; + int64_t vals[] = {1, 1, 1, 1, 1, 1, 1, 1} ; + + OK (GrB_Matrix_build_INT64 (A, rows, cols, vals, 8, GrB_PLUS_INT64)) ; + OK (LAGraph_NumberOfWalks (&result, A, NULL, 2)) ; + + int64_t err = check_walks (result, cycle_4node_walks_k2, n) ; + TEST_CHECK (err == 0) ; + + OK (GrB_free (&A)) ; + OK (GrB_free (&result)) ; + LAGraph_Finalize (msg) ; +} + +//------------------------------------------------------------------------------ +// test_NumberOfWalks_varying_k: different walk lengths +//------------------------------------------------------------------------------ + +void test_NumberOfWalks_varying_k (void) +{ + LAGraph_Init (msg) ; + GrB_Matrix A = NULL, result = NULL ; + GrB_Index n = 3 ; + + OK (GrB_Matrix_new (&A, GrB_INT64, n, n)) ; + + GrB_Index rows[] = {0, 1, 1, 2} ; + GrB_Index cols[] = {1, 0, 2, 1} ; + int64_t vals[] = {1, 1, 1, 1} ; + + OK (GrB_Matrix_build_INT64 (A, rows, cols, vals, 4, GrB_PLUS_INT64)) ; + + // k=1 + OK (LAGraph_NumberOfWalks (&result, A, NULL, 1)) ; + int64_t val = 0 ; + GrB_Matrix_extractElement_INT64 (&val, result, 0, 1) ; + TEST_CHECK (val == 1) ; + OK (GrB_free (&result)) ; + + // k=3 + OK (LAGraph_NumberOfWalks (&result, A, NULL, 3)) ; + val = 0 ; + GrB_Matrix_extractElement_INT64 (&val, result, 0, 2) ; + TEST_CHECK (val == 0) ; + OK (GrB_free (&result)) ; + + // k=4 + OK (LAGraph_NumberOfWalks (&result, A, NULL, 4)) ; + val = 0 ; + GrB_Matrix_extractElement_INT64 (&val, result, 0, 0) ; + TEST_CHECK (val == 2) ; + OK (GrB_free (&result)) ; + + OK (GrB_free (&A)) ; + LAGraph_Finalize (msg) ; +} + + +TEST_LIST = { + {"test_NumberOfWalks_path_3node", test_NumberOfWalks_path_3node}, + {"test_NumberOfWalks_A_mtx", test_NumberOfWalks_A_mtx}, + {"test_NumberOfWalks_cycle_4", test_NumberOfWalks_cycle_4}, + {"test_NumberOfWalks_varying_k", test_NumberOfWalks_varying_k}, + {NULL, NULL} +} ; diff --git a/include/LAGraphX.h b/include/LAGraphX.h index 689df55ad9..61c3a183ca 100644 --- a/include/LAGraphX.h +++ b/include/LAGraphX.h @@ -1719,6 +1719,21 @@ int LAGraph_IsolateSets( char *msg ); +//------------------------------------------------------------------------------ +// NumberOfWalks: compute walks of length k +//------------------------------------------------------------------------------ + +LAGRAPHX_PUBLIC +GrB_Info LAGraph_NumberOfWalks +( + // output: + GrB_Matrix *C, // A^k (n×n) for all-pairs, or 1×n for single-source + // input: + GrB_Matrix A, // adjacency matrix + GrB_Vector src, // source indicator vector (NULL = all pairs) + int64_t k // walk length +); + LAGRAPHX_PUBLIC int LAGraph_LouvainSeq( // output