From a6e4dbe1a0658f7eba0a0a39d4f5b02c5b19482e Mon Sep 17 00:00:00 2001 From: Sterkachov Date: Wed, 3 Dec 2025 09:28:14 +0300 Subject: [PATCH 01/25] feat: add RSM_reachability algorithm --- .../algorithm/LAGraph_RSM_reachability.c | 326 ++++++++++++++++++ include/LAGraphX.h | 31 +- 2 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 experimental/algorithm/LAGraph_RSM_reachability.c diff --git a/experimental/algorithm/LAGraph_RSM_reachability.c b/experimental/algorithm/LAGraph_RSM_reachability.c new file mode 100644 index 0000000000..afd3482a3c --- /dev/null +++ b/experimental/algorithm/LAGraph_RSM_reachability.c @@ -0,0 +1,326 @@ +//------------------------------------------------------------------------------ +// LAGraph_RSM_reachability.c +//------------------------------------------------------------------------------ + +// For and edge-labelled directed graph the algorithm computes the set of nodes +// for which these conditions are held: +// * The node is reachable by a path from one of the source nodes. +// * The concatenation of the labels over this path's edges is a word from the +// specified context-free language +// +// The context-free constraints are specified by a recursive state machine (RSM) +// over a subset of the graph edge labels. The algorithm assumes the labels are +// enumerated from 0 to some nl-1. The input graph and the RSM are defined by +// adjacency matrix decomposition. They are represented by arrays of graphs G and +// R both of length + +#include "GraphBLAS.h" +#define LG_FREE_WORK \ +{ \ + GrB_free (&frontier) ; \ + GrB_free (&next_frontier) ; \ + GrB_free (&symbol_frontier) ; \ + GrB_free (&visited) ; \ + GrB_free (&final_reducer) ; \ + LAGraph_Free ((void **) &A, GrB_NULL) ; \ + LAGraph_Free ((void **) &B, GrB_NULL) ; \ + LAGraph_Free ((void **) &BT, GrB_NULL) ; \ + LAGraph_Free ((void **) &B_call, GrB_NULL) ; \ + LAGraph_Free ((void **) &BT_call, GrB_NULL) ; \ +} + +#define LG_FREE_ALL \ +{ \ + LG_FREE_WORK ; \ + GrB_free (reachable) ; \ +} + +#include "LG_internal.h" +#include "LAGraphX.h" + +int LAGraph_RSM_reachability +( + // output: + GrB_Vector *reachable, // reachable(i) = true if node i is reachable + // from one of the starting nodes by a path + // satisfying regular constraints + + LAGraph_Graph *R, // input recursive state machine's + // adjacency matrix decomposition + size_t nl, // total label count, # of matrices graph and + // RSM adjacency matrix decomposition + const GrB_Index *QS, // starting states in RSM + size_t nqs, // number of starting states in RSM + const GrB_Index *QF, // final states in RSM + size_t nqf, // number of final states in RSM + + LAGraph_Graph *R_call, // recursive state machine's call/return nr*nr + // matrix, where (i, j) = 1 if there is transition + // from i state of one automata to j starting + // state of another automata + LAGraph_Graph *G, // input graph adjacency matrix decomposition + const GrB_Index *S, // source vertices to start searching paths + size_t ns, // number of source vertices + char *msg // LAGraph output message +) +{ + //-------------------------------------------------------------------------- + // check inputs + //-------------------------------------------------------------------------- + + LG_CLEAR_MSG ; + + GrB_Matrix frontier = GrB_NULL ; // traversal frontier representing + // correspondence between RSM states + // and graph vertices + GrB_Matrix symbol_frontier = GrB_NULL ; // part of the new frontier for the + // specific label + GrB_Matrix next_frontier = GrB_NULL ; // frontier value on the next + // traversal step + GrB_Matrix visited = GrB_NULL ; // visited pairs (state, vertex) + + GrB_Vector final_reducer = GrB_NULL ; // auxiliary vector for reducing the + // visited matrix to an answer + + GrB_Index ng = 0 ; // # nodes in the graph + GrB_Index nr = 0 ; // # states in the RSM + GrB_Index states = ns ; // # pairs in the current + // correspondence between the graph and + // the RSM + + GrB_Index rows = 0 ; // utility matrix row count + GrB_Index cols = 0 ; // utility matrix column count + GrB_Index vals = 0 ; // utility matrix valiue count + + GrB_Matrix *A = GrB_NULL ; + GrB_Matrix *B = GrB_NULL ; + GrB_Matrix *BT = GrB_NULL ; + + GrB_Matrix *B_call = GrB_NULL ; + GrB_Matrix *BT_call = GrB_NULL ; + + LG_ASSERT (reachable != GrB_NULL, GrB_NULL_POINTER) ; + LG_ASSERT (R != GrB_NULL, GrB_NULL_POINTER) ; + LG_ASSERT (G != GrB_NULL, GrB_NULL_POINTER) ; + LG_ASSERT (S != GrB_NULL, GrB_NULL_POINTER) ; + + LG_ASSERT (R_call != GrB_NULL, GrB_NULL_POINTER) ; + LG_ASSERT (*R_call != GrB_NULL, GrB_NULL_POINTER) ; + + (*reachable) = GrB_NULL ; + + for (size_t i = 0 ; i < nl ; ++i) + { + if (G[i] == GrB_NULL) continue ; + LG_TRY (LAGraph_CheckGraph (G[i], msg)) ; + } + + for (size_t i = 0 ; i < nl ; ++i) + { + if (R[i] == GrB_NULL) continue; + LG_TRY (LAGraph_CheckGraph (R[i], msg)) ; + } + + LG_TRY (LAGraph_Malloc ((void **) &A, nl, sizeof (GrB_Matrix), msg)) ; + + for (size_t i = 0 ; i < nl ; ++i) + { + if (G[i] == GrB_NULL) + { + A[i] = GrB_NULL ; + continue ; + } + + A[i] = G[i]->A ; + } + + LG_TRY (LAGraph_Malloc((void **) &B, nl, sizeof (GrB_Matrix), msg)) ; + LG_TRY (LAGraph_Malloc((void **) &BT, nl, sizeof (GrB_Matrix), msg)) ; + + for (size_t i = 0 ; i < nl ; ++i) + { + BT[i] = GrB_NULL ; + + if (R[i] == GrB_NULL) + { + B[i] = GrB_NULL ; + continue ; + } + + B[i] = R[i]->A ; + if (R[i]->is_symmetric_structure == LAGraph_TRUE) + { + BT[i] = B[i] ; + } else + { + // BT[i] could be NULL and the matrix will be transposed by a + // descriptor + BT[i] = R[i]->AT ; + } + } + + + + LG_TRY (LAGraph_Malloc((void **) &B_call, 1, sizeof (GrB_Matrix), msg)) ; + LG_TRY (LAGraph_Malloc((void **) &BT_call, 1, sizeof (GrB_Matrix), msg)) ; + + *B_call = (*R_call)->A ; + *BT_call = (*R_call)->AT ; + + + + for (size_t i = 0 ; i < nl ; ++i) + { + if (A[i] == GrB_NULL) continue ; + + GRB_TRY (GrB_Matrix_nrows (&ng, A[i])) ; + break ; + } + + for (size_t i = 0 ; i < nl ; ++i) + { + if (B[i] == GrB_NULL) continue ; + + GRB_TRY (GrB_Matrix_nrows (&nr, B[i])) ; + break ; + } + + // Check all the matrices in graph adjacency matrix decomposition are + // square and of the same dimensions + for (size_t i = 0 ; i < nl ; ++i) + { + if (A[i] == GrB_NULL) continue ; + + GRB_TRY (GrB_Matrix_nrows (&rows, A[i])) ; + GRB_TRY (GrB_Matrix_ncols (&cols, A[i])) ; + + LG_ASSERT_MSG (rows == ng && cols == ng, LAGRAPH_NOT_CACHED, + "all the matrices in the graph adjacency matrix decomposition " + "should have the same dimensions and be square") ; + } + + // Check all the matrices in RSM adjancency matrix decomposition are + // square and of the same dimensions + for (size_t i = 0 ; i < nl ; ++i) + { + if (B[i] == GrB_NULL) continue ; + + GRB_TRY (GrB_Matrix_nrows (&rows, B[i])) ; + GRB_TRY (GrB_Matrix_ncols (&cols, B[i])) ; + + LG_ASSERT_MSG (rows == nr && cols == nr, LAGRAPH_NOT_CACHED, + "all the matrices in the RSM adjacency matrix decomposition " + "should have the same dimensions and be square") ; + } + + // Check size of RSM call/return matrix + + GRB_TRY (GrB_Matrix_nrows (&rows, *B_call)) ; + GRB_TRY (GrB_Matrix_ncols (&cols, *B_call)) ; + + LG_ASSERT_MSG (rows == nr && cols == nr, LAGRAPH_NOT_CACHED, + "RSM's call/return matrix should have nr*nr size") ; + + // Check source nodes in the graph + for (size_t i = 0 ; i < ns ; ++i) + { + GrB_Index s = S[i] ; + LG_ASSERT_MSG(s < ng, GrB_INVALID_INDEX, "invalid graph source node") ; + } + + // Check starting states of the RSM + for (size_t i = 0 ; i < nqs ; ++i) + { + GrB_Index qs = QS[i] ; + LG_ASSERT_MSG (qs < nr, GrB_INVALID_INDEX, + "invalid RSM starting state") ; + } + + // Check final states of the RSM + for (size_t i = 0 ; i < nqf ; ++i) + { + GrB_Index qf = QF[i] ; + LG_ASSERT_MSG (qf < nr, GrB_INVALID_INDEX, "invalid RSM final state") ; + } + + // ------------------------------------------------------------------------- + // initialization + // ------------------------------------------------------------------------- + + GRB_TRY (GrB_Vector_new (reachable, GrB_BOOL, ng)) ; + GRB_TRY (GrB_Vector_new (&final_reducer, GrB_BOOL, nr)) ; + + // Initialize matrix for reducing the result + GrB_assign (final_reducer, GrB_NULL, GrB_NULL, true, QF, nqf, GrB_NULL) ; + + GRB_TRY (GrB_Matrix_new (&frontier, GrB_BOOL, nr, ng)) ; + GRB_TRY (GrB_Matrix_new (&next_frontier, GrB_BOOL, nr, ng)) ; + GRB_TRY (GrB_Matrix_new (&symbol_frontier, GrB_BOOL, nr, ng)) ; + GRB_TRY (GrB_Matrix_new (&visited, GrB_BOOL, nr, ng)) ; + + // Initialize frontier with the source nodes + GrB_assign (next_frontier, GrB_NULL, GrB_NULL, true, QS, nqs, S, ns, GrB_NULL) ; + GrB_assign (visited, GrB_NULL, GrB_NULL, true, QS, nqs, S, ns, GrB_NULL) ; + + // Main loop + while (states != 0) + { + GrB_Matrix old_frontier = frontier ; + frontier = next_frontier ; + next_frontier = old_frontier ; + + GRB_TRY (GrB_Matrix_clear (next_frontier)) ; + + // Obtain a new relation between the RSM states and the graph nodes + for (size_t i = 0 ; i < nl ; ++i) + { + if (A[i] == GrB_NULL || B[i] == GrB_NULL) continue ; + + // Traverse the RSM + // Try to use a provided transposed matrix or use the descriptor + + GRB_TRY (GrB_Matrix_clear (symbol_frontier)) ; + + if (BT[i] != GrB_NULL) + { + GRB_TRY (GrB_mxm (symbol_frontier, GrB_NULL, GrB_LOR, + GrB_LOR_LAND_SEMIRING_BOOL, BT[i], frontier, GrB_NULL)) ; + } + else + { + GRB_TRY (GrB_mxm (symbol_frontier, GrB_NULL, GrB_LOR, + GrB_LOR_LAND_SEMIRING_BOOL, B[i], frontier, GrB_DESC_T0)) ; + } + + if ((*BT_call) != GrB_NULL) + { + GRB_TRY (GrB_mxm (next_frontier, visited, GrB_LOR, + GrB_LOR_LAND_SEMIRING_BOOL, *BT_call, frontier, GrB_DESC_SC)) ; // не уверен по поводу дескриптора + } + else + { + GRB_TRY (GrB_mxm (next_frontier, visited, GrB_LOR, + GrB_LOR_LAND_SEMIRING_BOOL, *B_call, frontier, GrB_DESC_SCT0)) ; // не уверен по поводу дескриптора + } + + // я реально не понимаю как это работает, но окей + // Traverse the graph + GRB_TRY (GrB_mxm (next_frontier, visited, GrB_LOR, + GrB_LOR_LAND_SEMIRING_BOOL, symbol_frontier, A[i], GrB_DESC_SC)) ; + } + + // я реально не понимаю как это работает, но окей + // Accumulate the new state <-> node correspondence + GRB_TRY (GrB_assign (visited, visited, GrB_NULL, next_frontier, + GrB_ALL, nr, GrB_ALL, ng, GrB_DESC_SC)) ; + + GRB_TRY (GrB_Matrix_nvals (&states, next_frontier)) ; + } + + // Extract the nodes matching the final RSM states + GRB_TRY (GrB_vxm (*reachable, GrB_NULL, GrB_NULL, + GrB_LOR_LAND_SEMIRING_BOOL, final_reducer, visited, GrB_NULL)) ; + + LG_FREE_WORK ; + return (GrB_SUCCESS) ; +} diff --git a/include/LAGraphX.h b/include/LAGraphX.h index 3355addb2e..d7053d0244 100644 --- a/include/LAGraphX.h +++ b/include/LAGraphX.h @@ -854,7 +854,36 @@ int LAGraph_RegularPathQuery // nodes reachable from the starting by the const GrB_Index *S, // source vertices to start searching paths size_t ns, // number of source vertices char *msg // LAGraph output message -); +) ; + +//**************************************************************************** +LAGRAPHX_PUBLIC +int LAGraph_RSM_reachability +( + // output: + GrB_Vector *reachable, // reachable(i) = true if node i is reachable + // from one of the starting nodes by a path + // satisfying regular constraints + + LAGraph_Graph *R, // input recursive state machine's + // adjacency matrix decomposition + size_t nl, // total label count, # of matrices graph and + // RSM adjacency matrix decomposition + const GrB_Index *QS, // starting states in RSM + size_t nqs, // number of starting states in RSM + const GrB_Index *QF, // final states in RSM + size_t nqf, // number of final states in RSM + + LAGraph_Graph *R_call, // recursive state machine's call/return nr*nr + // matrix, where (i, j) = 1 if there is transition + // from i state of one automata to j starting + // state of another automata + LAGraph_Graph *G, // input graph adjacency matrix decomposition + const GrB_Index *S, // source vertices to start searching paths + size_t ns, // number of source vertices + char *msg // LAGraph output message +) ; + //**************************************************************************** LAGRAPHX_PUBLIC int LAGraph_VertexCentrality_Triangle // vertex triangle-centrality From 2dd9590f7ad0020feb4b9175bcb4d1df3dd2e237 Mon Sep 17 00:00:00 2001 From: Sterkachov Date: Wed, 3 Dec 2025 09:29:54 +0300 Subject: [PATCH 02/25] feat: add simple test for RSM_reachability algo --- data/rsm_data/1_a.mtx | 4 + data/rsm_data/1_call.mtx | 5 + data/rsm_data/1_meta.txt | 2 + data/rsm_data/1_sources.txt | 1 + data/rsm_data/a.mtx | 4 + data/rsm_data/b.mtx | 4 + experimental/test/test_RSM_reachability.c | 243 ++++++++++++++++++++++ 7 files changed, 263 insertions(+) create mode 100644 data/rsm_data/1_a.mtx create mode 100644 data/rsm_data/1_call.mtx create mode 100644 data/rsm_data/1_meta.txt create mode 100644 data/rsm_data/1_sources.txt create mode 100644 data/rsm_data/a.mtx create mode 100644 data/rsm_data/b.mtx create mode 100644 experimental/test/test_RSM_reachability.c diff --git a/data/rsm_data/1_a.mtx b/data/rsm_data/1_a.mtx new file mode 100644 index 0000000000..0430115a88 --- /dev/null +++ b/data/rsm_data/1_a.mtx @@ -0,0 +1,4 @@ +%%MatrixMarket matrix coordinate pattern general$ +%%GraphBLAS type bool$ +4 4 1 +3 4 diff --git a/data/rsm_data/1_call.mtx b/data/rsm_data/1_call.mtx new file mode 100644 index 0000000000..1340e468bd --- /dev/null +++ b/data/rsm_data/1_call.mtx @@ -0,0 +1,5 @@ +%%MatrixMarket matrix coordinate pattern general +%%GraphBLAS type bool +4 4 2 +1 3 +4 2 diff --git a/data/rsm_data/1_meta.txt b/data/rsm_data/1_meta.txt new file mode 100644 index 0000000000..1f25976c26 --- /dev/null +++ b/data/rsm_data/1_meta.txt @@ -0,0 +1,2 @@ +1 1 +1 2 diff --git a/data/rsm_data/1_sources.txt b/data/rsm_data/1_sources.txt new file mode 100644 index 0000000000..56a6051ca2 --- /dev/null +++ b/data/rsm_data/1_sources.txt @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/data/rsm_data/a.mtx b/data/rsm_data/a.mtx new file mode 100644 index 0000000000..3095351475 --- /dev/null +++ b/data/rsm_data/a.mtx @@ -0,0 +1,4 @@ +%%MatrixMarket matrix coordinate pattern general$ +%%GraphBLAS type bool$ +3 3 1 +1 2 diff --git a/data/rsm_data/b.mtx b/data/rsm_data/b.mtx new file mode 100644 index 0000000000..170cfb40dd --- /dev/null +++ b/data/rsm_data/b.mtx @@ -0,0 +1,4 @@ +%%MatrixMarket matrix coordinate pattern general$ +%%GraphBLAS type bool$ +3 3 1 +1 3 diff --git a/experimental/test/test_RSM_reachability.c b/experimental/test/test_RSM_reachability.c new file mode 100644 index 0000000000..121dc511f7 --- /dev/null +++ b/experimental/test/test_RSM_reachability.c @@ -0,0 +1,243 @@ +#include "GraphBLAS.h" +#include "LAGraph.h" +#include +#include +#include +#include +#include +#include + +#define LEN 512 +#define MAX_LABELS 3 +#define MAX_RESULTS 2000000 + +LAGraph_Graph G[MAX_LABELS] ; +LAGraph_Graph R[MAX_LABELS] ; +LAGraph_Graph R_call; + +GrB_Matrix A ; + +char testcase_name[LEN+1] ; +char filename[LEN+1] ; +char msg[LAGRAPH_MSG_LEN] ; + +typedef struct +{ + const char* name ; + const char* graphs[MAX_LABELS] ; + const char* rsm[MAX_LABELS] ; + const char* rsm_call; + const char* rsm_meta ; + const char* sources ; + const GrB_Index expected[MAX_RESULTS] ; + const size_t expected_count ; +} +matrix_info ; + +const matrix_info files [ ] = +{ + {"simple 1", + {"rsm_data/a.mtx", "rsm_data/b.mtx", NULL}, + {"rsm_data/1_a.mtx", NULL}, + "rsm_data/1_call.mtx", + "rsm_data/1_meta.txt", + "rsm_data/1_sources.txt", + {2}, + 1}, + {NULL, NULL, NULL, NULL}, +} ; + + +void test_RSM_reachability (void) +{ + LAGraph_Init (msg) ; + + for (int k = 0 ; ; ++k) + { + if (files[k].sources == NULL) break ; + + snprintf (testcase_name, LEN, "basic context-free path query %s", files[k].name) ; + TEST_CASE (testcase_name) ; + + for (int check_symmetry = 0 ; check_symmetry < 2 ; ++check_symmetry) + { + // Load graph from MTX files represention its adjacency matrix + // decomposition + for (int i = 0 ; ; ++i) + { + const char *name = files[k].graphs[i] ; + + if (name == NULL) break ; + if (strlen(name) == 0) continue ; + + snprintf (filename, LEN, LG_DATA_DIR "%s", name) ; + FILE *f = fopen (filename, "r") ; + TEST_CHECK (f != NULL) ; + OK (LAGraph_MMRead (&A, f, msg)) ; + OK (fclose (f)) ; + + OK (LAGraph_New (&(G[i]), &A, LAGraph_ADJACENCY_DIRECTED, msg)) ; + + TEST_CHECK (A == NULL) ; + } + + // Load RSM from MTX files representing its adjacency matrix + // decomposition + for (int i = 0 ; ; ++i) + { + const char *name = files[k].rsm[i] ; + + if (name == NULL) break ; + if (strlen(name) == 0) continue ; + + snprintf (filename, LEN, LG_DATA_DIR "%s", name) ; + FILE *f = fopen (filename, "r") ; + TEST_CHECK (f != NULL) ; + OK (LAGraph_MMRead (&A, f, msg)) ; + OK (fclose (f)) ; + + OK (LAGraph_New (&(R[i]), &A, LAGraph_ADJACENCY_DIRECTED, msg)) ; + + if (check_symmetry) + { + // Check if the pattern is symmetric - if it isn't make it. + // Note this also computes R[i]->AT + OK (LAGraph_Cached_IsSymmetricStructure (R[i], msg)) ; + } + + TEST_CHECK (A == NULL) ; + } + + { + const char *name = files[k].rsm_call ; + + snprintf (filename, LEN, LG_DATA_DIR "%s", name) ; + FILE *f = fopen (filename, "r") ; + TEST_CHECK (f != NULL) ; + OK (LAGraph_MMRead (&A, f, msg)) ; + OK (fclose (f)) ; + + OK (LAGraph_New (&(R_call), &A, LAGraph_ADJACENCY_DIRECTED, msg)) ; + + if (check_symmetry) + { + OK (LAGraph_Cached_IsSymmetricStructure(R_call, msg)) ; + } + + TEST_CHECK (A == NULL) ; // зачем нам это проверять?, почему оно NULL + } + + } + + // Note the matrix rows/cols are enumerated from 0 to n-1. + // Meanwhile, in MTX format they are enumerated from 1 to n. Thus, + // when loading/comparing the results these values should be + // decremented/incremented correspondingly. + + // Load graph source nodes from the sources file + GrB_Index s ; + GrB_Index S[16] ; + size_t ns = 0 ; + + const char *name = files[k].sources ; + snprintf (filename, LEN, LG_DATA_DIR "%s", name) ; + FILE *f = fopen (filename, "r") ; + TEST_CHECK (f != NULL) ; + + while (fscanf(f, "%" PRIu64, &s) != EOF) + { + S[ns++] = s - 1 ; + } + + OK (fclose(f)) ; + + // Load RSM starting states from the meta file + GrB_Index qs ; + GrB_Index QS[16] ; // почему именно 16, мб личный выбор + size_t nqs = 0 ; + + name = files[k].rsm_meta ; + snprintf (filename, LEN, LG_DATA_DIR "%s", name) ; + f = fopen (filename, "r") ; + TEST_CHECK (f != NULL) ; ; + + uint64_t nqs64 = 0 ; + TEST_CHECK (fscanf (f, "%" PRIu64, &nqs64) != EOF) ; + nqs = (size_t) nqs64 ; + + for (size_t i = 0 ; i < nqs ; ++i) + { + TEST_CHECK(fscanf (f, "%" PRIu64, &qs) != EOF) ; + QS[i] = qs - 1 ; + } + + // Load RSM final states from the same file + uint64_t qf ; + uint64_t QF[16] ; + size_t nqf = 0 ; + uint64_t nqf64 = 0 ; + + TEST_CHECK (fscanf (f, "%" PRIu64, &nqf64) != EOF) ; + nqf = (size_t) nqf64 ; + + for (size_t i = 0 ; i < nqf ; ++i) + { + TEST_CHECK (fscanf (f, "%" PRIu64, &qf) != EOF) ; + QF[i] = qf - 1 ; + } + + OK (fclose (f)) ; + + // Evaluate the algorithm + GrB_Vector r = NULL ; + + OK (LAGraph_RSM_reachability (&r, R, MAX_LABELS, QS, nqs, + QF, nqf, &R_call, G, S, ns, msg)) ; + + // Extract results from the output vector + GrB_Index *reachable ; + bool *values ; + + GrB_Index nvals ; + GrB_Vector_nvals (&nvals, r) ; + + OK (LAGraph_Malloc ((void **) &reachable, MAX_RESULTS, sizeof (GrB_Index), msg)) ;; + OK (LAGraph_Malloc ((void **) &values, MAX_RESULTS, sizeof (bool), msg)) ; + + GrB_Vector_extractTuples (reachable, values, &nvals, r) ; + + TEST_MSG("returned %lu values:\n", nvals); + for (uint64_t i = 0; i < nvals; i++) + TEST_MSG(" %lu\n", reachable[i] + 1); + + // Compare the results with expected values + TEST_CHECK (nvals == files[k].expected_count) ; + for (uint64_t i = 0 ; i < nvals ; ++i) + TEST_CHECK (reachable[i] + 1 == files[k].expected[i]) ; + + // Cleanup + OK (LAGraph_Free ((void **) &values, NULL)) ; + OK (LAGraph_Free ((void **) &reachable, NULL)) ; + + OK (GrB_free (&r)) ; + + for (uint64_t i = 0 ; i < MAX_LABELS ; ++i) + { + if (G[i] == NULL) continue ; + OK (LAGraph_Delete (&(G[i]), msg)) ; + } + + for (uint64_t i = 0; i < MAX_LABELS ; ++i) + { + if (R[i] == NULL) continue ; + OK (LAGraph_Delete (&(R[i]), msg)) ; + } + } + + LAGraph_Finalize (msg) ; +} + +TEST_LIST = { + {"RSM_reachability", test_RSM_reachability}, + {NULL, NULL} +} ; From f079f768dab1841f37e1d7540365d028dc0c11e6 Mon Sep 17 00:00:00 2001 From: Sterkachov Date: Fri, 20 Feb 2026 22:37:03 +0300 Subject: [PATCH 03/25] feat: implement the algorithm of rsm reachability --- data/rsm_data/1_a.mtx | 4 - data/rsm_data/1_call.mtx | 5 - data/rsm_data/1_meta.txt | 2 - data/rsm_data/1_sources.txt | 1 - data/rsm_data/a.mtx | 4 - data/rsm_data/b.mtx | 4 - .../algorithm/LAGraph_RSM_reachability.c | 781 ++++++++++++------ experimental/test/test_RSM_reachability.c | 488 ++++++----- include/LAGraphX.h | 48 +- 9 files changed, 859 insertions(+), 478 deletions(-) delete mode 100644 data/rsm_data/1_a.mtx delete mode 100644 data/rsm_data/1_call.mtx delete mode 100644 data/rsm_data/1_meta.txt delete mode 100644 data/rsm_data/1_sources.txt delete mode 100644 data/rsm_data/a.mtx delete mode 100644 data/rsm_data/b.mtx diff --git a/data/rsm_data/1_a.mtx b/data/rsm_data/1_a.mtx deleted file mode 100644 index 0430115a88..0000000000 --- a/data/rsm_data/1_a.mtx +++ /dev/null @@ -1,4 +0,0 @@ -%%MatrixMarket matrix coordinate pattern general$ -%%GraphBLAS type bool$ -4 4 1 -3 4 diff --git a/data/rsm_data/1_call.mtx b/data/rsm_data/1_call.mtx deleted file mode 100644 index 1340e468bd..0000000000 --- a/data/rsm_data/1_call.mtx +++ /dev/null @@ -1,5 +0,0 @@ -%%MatrixMarket matrix coordinate pattern general -%%GraphBLAS type bool -4 4 2 -1 3 -4 2 diff --git a/data/rsm_data/1_meta.txt b/data/rsm_data/1_meta.txt deleted file mode 100644 index 1f25976c26..0000000000 --- a/data/rsm_data/1_meta.txt +++ /dev/null @@ -1,2 +0,0 @@ -1 1 -1 2 diff --git a/data/rsm_data/1_sources.txt b/data/rsm_data/1_sources.txt deleted file mode 100644 index 56a6051ca2..0000000000 --- a/data/rsm_data/1_sources.txt +++ /dev/null @@ -1 +0,0 @@ -1 \ No newline at end of file diff --git a/data/rsm_data/a.mtx b/data/rsm_data/a.mtx deleted file mode 100644 index 3095351475..0000000000 --- a/data/rsm_data/a.mtx +++ /dev/null @@ -1,4 +0,0 @@ -%%MatrixMarket matrix coordinate pattern general$ -%%GraphBLAS type bool$ -3 3 1 -1 2 diff --git a/data/rsm_data/b.mtx b/data/rsm_data/b.mtx deleted file mode 100644 index 170cfb40dd..0000000000 --- a/data/rsm_data/b.mtx +++ /dev/null @@ -1,4 +0,0 @@ -%%MatrixMarket matrix coordinate pattern general$ -%%GraphBLAS type bool$ -3 3 1 -1 3 diff --git a/experimental/algorithm/LAGraph_RSM_reachability.c b/experimental/algorithm/LAGraph_RSM_reachability.c index afd3482a3c..8972116a4c 100644 --- a/experimental/algorithm/LAGraph_RSM_reachability.c +++ b/experimental/algorithm/LAGraph_RSM_reachability.c @@ -2,325 +2,636 @@ // LAGraph_RSM_reachability.c //------------------------------------------------------------------------------ -// For and edge-labelled directed graph the algorithm computes the set of nodes -// for which these conditions are held: -// * The node is reachable by a path from one of the source nodes. -// * The concatenation of the labels over this path's edges is a word from the -// specified context-free language -// -// The context-free constraints are specified by a recursive state machine (RSM) -// over a subset of the graph edge labels. The algorithm assumes the labels are -// enumerated from 0 to some nl-1. The input graph and the RSM are defined by -// adjacency matrix decomposition. They are represented by arrays of graphs G and -// R both of length - -#include "GraphBLAS.h" -#define LG_FREE_WORK \ -{ \ - GrB_free (&frontier) ; \ - GrB_free (&next_frontier) ; \ - GrB_free (&symbol_frontier) ; \ - GrB_free (&visited) ; \ - GrB_free (&final_reducer) ; \ - LAGraph_Free ((void **) &A, GrB_NULL) ; \ - LAGraph_Free ((void **) &B, GrB_NULL) ; \ - LAGraph_Free ((void **) &BT, GrB_NULL) ; \ - LAGraph_Free ((void **) &B_call, GrB_NULL) ; \ - LAGraph_Free ((void **) &BT_call, GrB_NULL) ; \ -} - -#define LG_FREE_ALL \ -{ \ - LG_FREE_WORK ; \ - GrB_free (reachable) ; \ -} +#include +#include +#include +#include +#include +#include "LAGraph.h" #include "LG_internal.h" -#include "LAGraphX.h" -int LAGraph_RSM_reachability -( +#define LG_FREE_WORK \ + { \ + GrB_free(&frontier); \ + GrB_free(&next_frontier); \ + GrB_free(&front_temp1); \ + GrB_free(&front_temp2); \ + GrB_free(&P); \ + GrB_free(&K); \ + GrB_free(&final_reducer); \ + GrB_free(&M_ret); \ + GrB_free(&M_call); \ + GrB_free(&M_call_new); \ + GrB_free(&G_S_new); \ + GrB_free(&step1); \ + LAGraph_Free((void **)&A_a, GrB_NULL); \ + LAGraph_Free((void **)&B_a, GrB_NULL); \ + LAGraph_Free((void **)&B_S, GrB_NULL); \ + LAGraph_Free((void **)&B_call, GrB_NULL); \ + LAGraph_Free((void **)&B_ret, GrB_NULL); \ + LAGraph_Free((void **)&B_S, GrB_NULL); \ + if (A_S != GrB_NULL) \ + { \ + for (size_t _i = 0; _i < num_nonterminals; ++_i) \ + GrB_free(&A_S[_i]); \ + LAGraph_Free((void **)&A_S, GrB_NULL); \ + } \ + } + +#define LG_FREE_ALL \ + { \ + LG_FREE_WORK; \ + GrB_free(reachable); \ + } + +int LAGraph_RSM_reachability( // output: - GrB_Vector *reachable, // reachable(i) = true if node i is reachable - // from one of the starting nodes by a path - // satisfying regular constraints - - LAGraph_Graph *R, // input recursive state machine's - // adjacency matrix decomposition - size_t nl, // total label count, # of matrices graph and - // RSM adjacency matrix decomposition - const GrB_Index *QS, // starting states in RSM - size_t nqs, // number of starting states in RSM - const GrB_Index *QF, // final states in RSM - size_t nqf, // number of final states in RSM - - LAGraph_Graph *R_call, // recursive state machine's call/return nr*nr - // matrix, where (i, j) = 1 if there is transition - // from i state of one automata to j starting - // state of another automata - LAGraph_Graph *G, // input graph adjacency matrix decomposition - const GrB_Index *S, // source vertices to start searching paths - size_t ns, // number of source vertices - char *msg // LAGraph output message -) + GrB_Vector *reachable, + + // input: + size_t num_terminals, // # terminal labels + LAGraph_Graph *G_a, // terminal transitions + LAGraph_Graph *N_a, // terminal transitions + + size_t num_nonterminals, // # nonterminal labels + LAGraph_Graph *N_S, // nonterminal RSM transitions + LAGraph_Graph *Call_S, // call transition matrix + LAGraph_Graph *Ret_S, // return transition matrix + + const GrB_Index *QS, // staring states in RSM + size_t nqs, // # starting states + const GrB_Index *QF, // final states in RSM + size_t nqf, // # final states + + const GrB_Index *Source, // sources vertices + size_t ns, // # source vertices + + char *msg + +) { //-------------------------------------------------------------------------- // check inputs //-------------------------------------------------------------------------- - LG_CLEAR_MSG ; + LG_CLEAR_MSG; + + // Working matrices + GrB_Matrix frontier = GrB_NULL; // traversal frontier + GrB_Matrix next_frontier = GrB_NULL; + GrB_Matrix front_temp1 = GrB_NULL; + GrB_Matrix front_temp2 = GrB_NULL; + GrB_Matrix P = GrB_NULL; // visited pairs (state, vertex) + GrB_Matrix K = GrB_NULL; // visited pairs in second loop + GrB_Vector final_reducer = GrB_NULL; // vector for reducing the visited matrix + + // Backward-phase working matrices + GrB_Matrix M_ret = GrB_NULL; + GrB_Matrix M_call = GrB_NULL; + GrB_Matrix M_call_new = GrB_NULL; + GrB_Matrix G_S_new = GrB_NULL; + GrB_Matrix step1 = GrB_NULL; - GrB_Matrix frontier = GrB_NULL ; // traversal frontier representing - // correspondence between RSM states - // and graph vertices - GrB_Matrix symbol_frontier = GrB_NULL ; // part of the new frontier for the - // specific label - GrB_Matrix next_frontier = GrB_NULL ; // frontier value on the next - // traversal step - GrB_Matrix visited = GrB_NULL ; // visited pairs (state, vertex) + GrB_Index ng = 0; // # nodes in the graph + GrB_Index nr = 0; // # states in the RSM + GrB_Index states = ns; // # pairs in current frontier - GrB_Vector final_reducer = GrB_NULL ; // auxiliary vector for reducing the - // visited matrix to an answer + GrB_Index rows = 0, cols = 0, vals = 0; - GrB_Index ng = 0 ; // # nodes in the graph - GrB_Index nr = 0 ; // # states in the RSM - GrB_Index states = ns ; // # pairs in the current - // correspondence between the graph and - // the RSM + GrB_Matrix *A_a = GrB_NULL; // for G_a + GrB_Matrix *B_a = GrB_NULL; // for N_a - GrB_Index rows = 0 ; // utility matrix row count - GrB_Index cols = 0 ; // utility matrix column count - GrB_Index vals = 0 ; // utility matrix valiue count + GrB_Matrix *A_S = GrB_NULL; // Derived graph edge matrices + GrB_Matrix *B_S = GrB_NULL; // for N_S + GrB_Matrix *B_call = GrB_NULL; // for Call_S + GrB_Matrix *B_ret = GrB_NULL; // for Ret_S - GrB_Matrix *A = GrB_NULL ; - GrB_Matrix *B = GrB_NULL ; - GrB_Matrix *BT = GrB_NULL ; + LG_ASSERT(reachable != GrB_NULL, GrB_NULL_POINTER); - GrB_Matrix *B_call = GrB_NULL ; - GrB_Matrix *BT_call = GrB_NULL ; + LG_ASSERT(G_a != GrB_NULL, GrB_NULL_POINTER); + LG_ASSERT(N_a != GrB_NULL, GrB_NULL_POINTER); + LG_ASSERT(N_S != GrB_NULL, GrB_NULL_POINTER); + LG_ASSERT(Call_S != GrB_NULL, GrB_NULL_POINTER); + LG_ASSERT(Ret_S != GrB_NULL, GrB_NULL_POINTER); + LG_ASSERT(QS != GrB_NULL, GrB_NULL_POINTER); + LG_ASSERT(QF != GrB_NULL, GrB_NULL_POINTER); + LG_ASSERT(Source != GrB_NULL, GrB_NULL_POINTER); - LG_ASSERT (reachable != GrB_NULL, GrB_NULL_POINTER) ; - LG_ASSERT (R != GrB_NULL, GrB_NULL_POINTER) ; - LG_ASSERT (G != GrB_NULL, GrB_NULL_POINTER) ; - LG_ASSERT (S != GrB_NULL, GrB_NULL_POINTER) ; + (*reachable) = GrB_NULL; - LG_ASSERT (R_call != GrB_NULL, GrB_NULL_POINTER) ; - LG_ASSERT (*R_call != GrB_NULL, GrB_NULL_POINTER) ; + //-------------------------------------------------------------------------- + // validate input graphs + //-------------------------------------------------------------------------- + + for (size_t i = 0; i < num_terminals; ++i) + if (G_a[i] != GrB_NULL) + LG_TRY(LAGraph_CheckGraph(G_a[i], msg)) + + for (size_t i = 0; i < num_terminals; ++i) + if (N_a[i] != GrB_NULL) + LG_TRY(LAGraph_CheckGraph(N_a[i], msg)) - (*reachable) = GrB_NULL ; + for (size_t i = 0; i < num_nonterminals; ++i) + if (N_S[i] != GrB_NULL) + LG_TRY(LAGraph_CheckGraph(N_S[i], msg)) - for (size_t i = 0 ; i < nl ; ++i) + for (size_t i = 0; i < num_nonterminals; ++i) + if (Call_S[i] != GrB_NULL) + LG_TRY(LAGraph_CheckGraph(Call_S[i], msg)) + + for (size_t i = 0; i < num_nonterminals; ++i) + if (Ret_S[i] != GrB_NULL) + LG_TRY(LAGraph_CheckGraph(Ret_S[i], msg)) + + LG_TRY(LAGraph_Malloc((void **)&A_a, num_terminals, sizeof(GrB_Matrix), msg)) + for (size_t i = 0; i < num_terminals; ++i) { - if (G[i] == GrB_NULL) continue ; - LG_TRY (LAGraph_CheckGraph (G[i], msg)) ; + if (G_a[i] == GrB_NULL) + A_a[i] = GrB_NULL; + else + A_a[i] = G_a[i]->A; } - - for (size_t i = 0 ; i < nl ; ++i) + LG_TRY(LAGraph_Malloc((void **)&B_a, num_terminals, sizeof(GrB_Matrix), msg)) + for (size_t i = 0; i < num_terminals; ++i) { - if (R[i] == GrB_NULL) continue; - LG_TRY (LAGraph_CheckGraph (R[i], msg)) ; + if (N_a[i] == GrB_NULL) + B_a[i] = GrB_NULL; + else + B_a[i] = N_a[i]->A; } - - LG_TRY (LAGraph_Malloc ((void **) &A, nl, sizeof (GrB_Matrix), msg)) ; - - for (size_t i = 0 ; i < nl ; ++i) + LG_TRY(LAGraph_Malloc((void **)&B_S, num_nonterminals, sizeof(GrB_Matrix), msg)) + for (size_t i = 0; i < num_nonterminals; ++i) { - if (G[i] == GrB_NULL) - { - A[i] = GrB_NULL ; - continue ; - } - - A[i] = G[i]->A ; + if (N_S[i] == GrB_NULL) + B_S[i] = GrB_NULL; + else + B_S[i] = N_S[i]->A; + } + LG_TRY(LAGraph_Malloc((void **)&B_call, num_nonterminals, sizeof(GrB_Matrix), msg)) + for (size_t i = 0; i < num_nonterminals; ++i) + { + if (Call_S[i] == GrB_NULL) + B_call[i] = GrB_NULL; + else + B_call[i] = Call_S[i]->A; + } + LG_TRY(LAGraph_Malloc((void **)&B_ret, num_nonterminals, sizeof(GrB_Matrix), msg)) + for (size_t i = 0; i < num_nonterminals; ++i) + { + if (Ret_S[i] == GrB_NULL) + B_ret[i] = GrB_NULL; + else + B_ret[i] = Ret_S[i]->A; } - LG_TRY (LAGraph_Malloc((void **) &B, nl, sizeof (GrB_Matrix), msg)) ; - LG_TRY (LAGraph_Malloc((void **) &BT, nl, sizeof (GrB_Matrix), msg)) ; + //-------------------------------------------------------------------------- + // determine ng (graph size) and nr (RSM state count) + //-------------------------------------------------------------------------- - for (size_t i = 0 ; i < nl ; ++i) + for (size_t i = 0; i < num_terminals; ++i) { - BT[i] = GrB_NULL ; - - if (R[i] == GrB_NULL) - { - B[i] = GrB_NULL ; - continue ; - } + if (A_a[i] == GrB_NULL) + continue; - B[i] = R[i]->A ; - if (R[i]->is_symmetric_structure == LAGraph_TRUE) - { - BT[i] = B[i] ; - } else - { - // BT[i] could be NULL and the matrix will be transposed by a - // descriptor - BT[i] = R[i]->AT ; - } + GRB_TRY(GrB_Matrix_nrows(&ng, A_a[i])); + break; } - + for (size_t i = 0; i < num_terminals; ++i) + { + if (B_a[i] == GrB_NULL) + continue; - LG_TRY (LAGraph_Malloc((void **) &B_call, 1, sizeof (GrB_Matrix), msg)) ; - LG_TRY (LAGraph_Malloc((void **) &BT_call, 1, sizeof (GrB_Matrix), msg)) ; + GRB_TRY(GrB_Matrix_nrows(&nr, B_a[i])); + break; + } - *B_call = (*R_call)->A ; - *BT_call = (*R_call)->AT ; + //-------------------------------------------------------------------------- + // dimension checks + //-------------------------------------------------------------------------- + for (size_t i = 0; i < num_terminals; ++i) + { + if (A_a[i] == GrB_NULL) + continue; + GRB_TRY(GrB_Matrix_nrows(&rows, A_a[i])); + GRB_TRY(GrB_Matrix_ncols(&cols, A_a[i])); - for (size_t i = 0 ; i < nl ; ++i) + LG_ASSERT_MSG(rows == ng && cols == ng, LAGRAPH_NOT_CACHED, + "all the matrices in the graph adjacency matrix decomposition " + "should have the same dimensions and be square"); + } + for (size_t i = 0; i < num_terminals; ++i) { - if (A[i] == GrB_NULL) continue ; + if (B_a[i] == GrB_NULL) + continue; - GRB_TRY (GrB_Matrix_nrows (&ng, A[i])) ; - break ; - } + GRB_TRY(GrB_Matrix_nrows(&rows, B_a[i])); + GRB_TRY(GrB_Matrix_ncols(&cols, B_a[i])); - for (size_t i = 0 ; i < nl ; ++i) + LG_ASSERT_MSG(rows == nr && cols == nr, LAGRAPH_NOT_CACHED, + "all the matrices in the RSM adjacency matrix decomposition " + "should have the same dimensions and be square") + } + for (size_t i = 0; i < num_nonterminals; ++i) { - if (B[i] == GrB_NULL) continue ; + if (B_S[i] == GrB_NULL) + continue; - GRB_TRY (GrB_Matrix_nrows (&nr, B[i])) ; - break ; - } + GRB_TRY(GrB_Matrix_nrows(&rows, B_S[i])); + GRB_TRY(GrB_Matrix_ncols(&cols, B_S[i])); - // Check all the matrices in graph adjacency matrix decomposition are - // square and of the same dimensions - for (size_t i = 0 ; i < nl ; ++i) + LG_ASSERT_MSG(rows == nr && cols == nr, LAGRAPH_NOT_CACHED, + "all the matrices in the RSM nonterminal matrix decomposition " + "should have the same dimensions and be square") + } + for (size_t i = 0; i < num_nonterminals; ++i) { - if (A[i] == GrB_NULL) continue ; + if (B_call[i] == GrB_NULL) + continue; - GRB_TRY (GrB_Matrix_nrows (&rows, A[i])) ; - GRB_TRY (GrB_Matrix_ncols (&cols, A[i])) ; + GRB_TRY(GrB_Matrix_nrows(&rows, B_call[i])); + GRB_TRY(GrB_Matrix_ncols(&cols, B_call[i])); - LG_ASSERT_MSG (rows == ng && cols == ng, LAGRAPH_NOT_CACHED, - "all the matrices in the graph adjacency matrix decomposition " - "should have the same dimensions and be square") ; + LG_ASSERT_MSG(rows == nr && cols == nr, LAGRAPH_NOT_CACHED, + "all the matrices in the RSM call matrix decomposition " + "should have the same dimensions and be square") } - - // Check all the matrices in RSM adjancency matrix decomposition are - // square and of the same dimensions - for (size_t i = 0 ; i < nl ; ++i) + for (size_t i = 0; i < num_nonterminals; ++i) { - if (B[i] == GrB_NULL) continue ; - - GRB_TRY (GrB_Matrix_nrows (&rows, B[i])) ; - GRB_TRY (GrB_Matrix_ncols (&cols, B[i])) ; - - LG_ASSERT_MSG (rows == nr && cols == nr, LAGRAPH_NOT_CACHED, - "all the matrices in the RSM adjacency matrix decomposition " - "should have the same dimensions and be square") ; - } - - // Check size of RSM call/return matrix + if (B_ret[i] == GrB_NULL) + continue; - GRB_TRY (GrB_Matrix_nrows (&rows, *B_call)) ; - GRB_TRY (GrB_Matrix_ncols (&cols, *B_call)) ; + GRB_TRY(GrB_Matrix_nrows(&rows, B_ret[i])); + GRB_TRY(GrB_Matrix_ncols(&cols, B_ret[i])); - LG_ASSERT_MSG (rows == nr && cols == nr, LAGRAPH_NOT_CACHED, - "RSM's call/return matrix should have nr*nr size") ; + LG_ASSERT_MSG(rows == nr && cols == nr, LAGRAPH_NOT_CACHED, + "all the matrices in the RSM return matrix decomposition " + "should have the same dimensions and be square") + } // Check source nodes in the graph - for (size_t i = 0 ; i < ns ; ++i) + for (size_t i = 0; i < ns; ++i) { - GrB_Index s = S[i] ; - LG_ASSERT_MSG(s < ng, GrB_INVALID_INDEX, "invalid graph source node") ; + GrB_Index s = Source[i]; + LG_ASSERT_MSG(s < ng, GrB_INVALID_INDEX, "invalid graph source node"); } - // Check starting states of the RSM - for (size_t i = 0 ; i < nqs ; ++i) + for (size_t i = 0; i < nqs; ++i) { - GrB_Index qs = QS[i] ; - LG_ASSERT_MSG (qs < nr, GrB_INVALID_INDEX, - "invalid RSM starting state") ; + GrB_Index qs = QS[i]; + LG_ASSERT_MSG(qs < nr, GrB_INVALID_INDEX, "invalid RSM starting state"); } - // Check final states of the RSM - for (size_t i = 0 ; i < nqf ; ++i) + for (size_t i = 0; i < nqf; ++i) { - GrB_Index qf = QF[i] ; - LG_ASSERT_MSG (qf < nr, GrB_INVALID_INDEX, "invalid RSM final state") ; + GrB_Index qf = QF[i]; + LG_ASSERT_MSG(qf < nr, GrB_INVALID_INDEX, "invalid RSM final state"); } + //-------------------------------------------------------------------------- + // allocate derived graph-edge matrices A_S[s] (ng x ng, initially empty) + //-------------------------------------------------------------------------- + + LG_TRY(LAGraph_Malloc((void **)&A_S, num_nonterminals, sizeof(GrB_Matrix), msg)) + for (size_t i = 0; i < num_nonterminals; ++i) + GRB_TRY(GrB_Matrix_new(&A_S[i], GrB_BOOL, ng, ng)); + // ------------------------------------------------------------------------- // initialization // ------------------------------------------------------------------------- - GRB_TRY (GrB_Vector_new (reachable, GrB_BOOL, ng)) ; - GRB_TRY (GrB_Vector_new (&final_reducer, GrB_BOOL, nr)) ; + GRB_TRY(GrB_Vector_new(reachable, GrB_BOOL, ng)); + GRB_TRY(GrB_Vector_new(&final_reducer, GrB_BOOL, nr)); + + // final_reducer[QF] = true + GrB_assign(final_reducer, GrB_NULL, GrB_NULL, true, QF, nqf, GrB_NULL); - // Initialize matrix for reducing the result - GrB_assign (final_reducer, GrB_NULL, GrB_NULL, true, QF, nqf, GrB_NULL) ; + // frontier / visited matrices (nr x ng) + GRB_TRY(GrB_Matrix_new(&frontier, GrB_BOOL, nr, ng)); + GRB_TRY(GrB_Matrix_new(&next_frontier, GrB_BOOL, nr, ng)); + GRB_TRY(GrB_Matrix_new(&front_temp1, GrB_BOOL, nr, ng)); + GRB_TRY(GrB_Matrix_new(&front_temp2, GrB_BOOL, nr, ng)); + GRB_TRY(GrB_Matrix_new(&P, GrB_BOOL, nr, ng)); + GRB_TRY(GrB_Matrix_new(&K, GrB_BOOL, nr, ng)) - GRB_TRY (GrB_Matrix_new (&frontier, GrB_BOOL, nr, ng)) ; - GRB_TRY (GrB_Matrix_new (&next_frontier, GrB_BOOL, nr, ng)) ; - GRB_TRY (GrB_Matrix_new (&symbol_frontier, GrB_BOOL, nr, ng)) ; - GRB_TRY (GrB_Matrix_new (&visited, GrB_BOOL, nr, ng)) ; + GRB_TRY(GrB_Matrix_new(&M_ret, GrB_BOOL, nr, ng)); + GRB_TRY(GrB_Matrix_new(&M_call, GrB_BOOL, nr, ng)); + GRB_TRY(GrB_Matrix_new(&M_call_new, GrB_BOOL, nr, ng)); + GRB_TRY(GrB_Matrix_new(&G_S_new, GrB_BOOL, ng, ng)); + GRB_TRY(GrB_Matrix_new(&step1, GrB_BOOL, ng, nr)); + + // Seed next_frontier, P, K from (start-states × source-vertices) + GrB_assign(next_frontier, GrB_NULL, GrB_NULL, true, QS, nqs, Source, ns, GrB_NULL); + GrB_assign(P, GrB_NULL, GrB_NULL, true, QS, nqs, Source, ns, GrB_NULL); + GrB_assign(K, GrB_NULL, GrB_NULL, true, QS, nqs, Source, ns, GrB_NULL); + + GRB_TRY(GrB_Matrix_nvals(&states, next_frontier)); + + int iteration = 0; - // Initialize frontier with the source nodes - GrB_assign (next_frontier, GrB_NULL, GrB_NULL, true, QS, nqs, S, ns, GrB_NULL) ; - GrB_assign (visited, GrB_NULL, GrB_NULL, true, QS, nqs, S, ns, GrB_NULL) ; - // Main loop while (states != 0) { - GrB_Matrix old_frontier = frontier ; - frontier = next_frontier ; - next_frontier = old_frontier ; + iteration++; + + GrB_Matrix old_frontier = frontier; + frontier = next_frontier; + next_frontier = old_frontier; + GRB_TRY(GrB_Matrix_clear(next_frontier)); - GRB_TRY (GrB_Matrix_clear (next_frontier)) ; + //---------------------------------------------------------------------- + // FORWARD PHASE + //---------------------------------------------------------------------- - // Obtain a new relation between the RSM states and the graph nodes - for (size_t i = 0 ; i < nl ; ++i) + // Terminals + for (size_t i = 0; i < num_terminals; ++i) { - if (A[i] == GrB_NULL || B[i] == GrB_NULL) continue ; + if (A_a[i] == GrB_NULL || B_a[i] == GrB_NULL) + continue; - // Traverse the RSM - // Try to use a provided transposed matrix or use the descriptor + GRB_TRY(GrB_Matrix_clear(front_temp1)); + GRB_TRY(GrB_Matrix_clear(front_temp2)); - GRB_TRY (GrB_Matrix_clear (symbol_frontier)) ; + // front_temp1 = N_a[a]^T * M + GRB_TRY(GrB_mxm(front_temp1, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, B_a[i], + frontier, GrB_DESC_T0)); - if (BT[i] != GrB_NULL) - { - GRB_TRY (GrB_mxm (symbol_frontier, GrB_NULL, GrB_LOR, - GrB_LOR_LAND_SEMIRING_BOOL, BT[i], frontier, GrB_NULL)) ; - } - else - { - GRB_TRY (GrB_mxm (symbol_frontier, GrB_NULL, GrB_LOR, - GrB_LOR_LAND_SEMIRING_BOOL, B[i], frontier, GrB_DESC_T0)) ; - } + // front_temp2 = front_temp1 * G_a[a] + GRB_TRY(GrB_mxm(front_temp2, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, front_temp1, + A_a[i], GrB_NULL)); - if ((*BT_call) != GrB_NULL) - { - GRB_TRY (GrB_mxm (next_frontier, visited, GrB_LOR, - GrB_LOR_LAND_SEMIRING_BOOL, *BT_call, frontier, GrB_DESC_SC)) ; // не уверен по поводу дескриптора - } - else + // M_new |= front_temp2 & ~P + GRB_TRY(GrB_eWiseAdd(next_frontier, P, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, + next_frontier, front_temp2, GrB_DESC_SC)); + } + + // Nonterminals (derived A_S edges) + for (size_t i = 0; i < num_nonterminals; ++i) + { + if (B_S[i] == GrB_NULL) + continue; + + GRB_TRY(GrB_Matrix_clear(front_temp1)); + GRB_TRY(GrB_Matrix_clear(front_temp2)); + + // front_temp1 = N_S[i]^T * frontier + GRB_TRY(GrB_mxm(front_temp1, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, B_S[i], + frontier, GrB_DESC_T0)); + + // front_temp2 = front_temp1 * A_S[i] + GRB_TRY(GrB_mxm(front_temp2, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, + front_temp1, A_S[i], GrB_NULL)); + + // next_frontier |= symbol_frontier & ~P + GRB_TRY(GrB_eWiseAdd(next_frontier, P, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, + next_frontier, front_temp2, GrB_DESC_SC)); + } + + // Call edges + for (size_t i = 0; i < num_nonterminals; ++i) + { + if (B_call[i] == GrB_NULL) + continue; + + GRB_TRY(GrB_Matrix_clear(front_temp1)); + + // front_temp1 = B_call[s]^T * frontier + GRB_TRY(GrB_mxm(front_temp1, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, B_call[i], + frontier, GrB_DESC_T0)); + + // next_frontier |= front_temp1 & ~P + GRB_TRY(GrB_eWiseAdd(next_frontier, P, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, + next_frontier, front_temp1, GrB_DESC_SC)); + } + + //---------------------------------------------------------------------- + // BACKWARD PHASE – match return edges back to their call sites + //---------------------------------------------------------------------- + + for (size_t i = 0; i < num_nonterminals; ++i) + { + if (B_ret[i] == GrB_NULL || B_call[i] == GrB_NULL || B_S[i] == GrB_NULL) + continue; + + GRB_TRY(GrB_Matrix_clear(M_ret)); + GRB_TRY(GrB_Matrix_clear(M_call)); + + // M_ret = B_ret[s]^T * frontier (positions that hit a return state) + GRB_TRY(GrB_mxm(M_ret, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, B_ret[i], + frontier, GrB_DESC_T0)); + + GrB_Index ret_nvals; + GRB_TRY(GrB_Matrix_nvals(&ret_nvals, M_ret)); + if (ret_nvals == 0) + continue; + + // M_call = (B_ret[s] * M_ret) & frontier + GRB_TRY(GrB_mxm(M_call, frontier, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, B_ret[i], M_ret, + GrB_DESC_S)); + + // walk backwards through visited set P to find the + // matching call-site positions. + + GrB_Index call_nvals; + GRB_TRY(GrB_Matrix_nvals(&call_nvals, M_call)); + while (call_nvals > 0) { - GRB_TRY (GrB_mxm (next_frontier, visited, GrB_LOR, - GrB_LOR_LAND_SEMIRING_BOOL, *B_call, frontier, GrB_DESC_SCT0)) ; // не уверен по поводу дескриптора + GRB_TRY(GrB_Matrix_clear(M_call_new)); + + // Backward terminal step: M_call_new |= (N_a[i] * M_call * G_a[i]^T) & P + for (size_t i = 0; i < num_terminals; ++i) + { + if (A_a[i] == GrB_NULL || B_a[i] == GrB_NULL) + continue; + + GRB_TRY(GrB_Matrix_clear(front_temp1)); + GRB_TRY(GrB_Matrix_clear(front_temp2)); + + // front_temp1 = N_a[a] * M_call + GRB_TRY(GrB_mxm(front_temp1, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, + B_a[i], M_call, GrB_NULL)); + + // front_temp2 = front_temp1 * G_a[a]^T + GRB_TRY(GrB_mxm(front_temp2, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, + front_temp1, A_a[i], GrB_DESC_T1)); + + // M_call_new |= front_temp2 & P + GRB_TRY(GrB_eWiseAdd(M_call_new, P, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, + M_call_new, front_temp2, GrB_DESC_S)); + } + // Backward nonterminal step: M_call_new |= (N_S[i] * M_call * G_S[i]^T) & P + for (size_t i = 0; i < num_nonterminals; ++i) + { + if (A_S[i] == GrB_NULL) + continue; + + GRB_TRY(GrB_Matrix_clear(front_temp1)); + GRB_TRY(GrB_Matrix_clear(front_temp2)); + + // front_temp1 = N_S[S] * M_call + GRB_TRY(GrB_mxm(front_temp1, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, + B_S[i], M_call, GrB_NULL)); + + // front_temp2 = front_temp1 * G_S[S]^T + GRB_TRY(GrB_mxm(front_temp2, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, + front_temp1, A_S[i], GrB_DESC_T1)); + + // M_call_new |= front_temp2 & P + GRB_TRY(GrB_eWiseAdd(M_call_new, P, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, + M_call_new, front_temp2, GrB_DESC_S)); + } + + GRB_TRY(GrB_Matrix_nvals(&call_nvals, M_call_new)); + if (call_nvals == 0) + break; + + GRB_TRY(GrB_Matrix_clear(M_call)); + GRB_TRY(GrB_eWiseAdd(M_call, GrB_NULL, GrB_NULL, GrB_LOR, M_call, M_call_new, + GrB_NULL)); } - // я реально не понимаю как это работает, но окей - // Traverse the graph - GRB_TRY (GrB_mxm (next_frontier, visited, GrB_LOR, - GrB_LOR_LAND_SEMIRING_BOOL, symbol_frontier, A[i], GrB_DESC_SC)) ; + // need to check if we reach call pos + // M_call = (B_call[s] * M_call) & P + GRB_TRY(GrB_Matrix_clear(front_temp1)); + GRB_TRY(GrB_mxm(front_temp1, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, B_call[i], + M_call, GrB_NULL)); + GRB_TRY(GrB_Matrix_clear(M_call)); + GRB_TRY(GrB_eWiseAdd(M_call, P, GrB_NULL, GrB_LOR, M_call, front_temp1, GrB_DESC_S)); + + GRB_TRY(GrB_Matrix_nvals(&call_nvals, M_call)); + if (call_nvals == 0) + continue; + + // we found some call positions + // but still not sure if they are correct ones + // G_S_new = (M_call.T @ N_S[S] @ M_ret) & ~G_S[S] + GRB_TRY(GrB_Matrix_clear(step1)); + GRB_TRY(GrB_Matrix_clear(front_temp2)); + GRB_TRY(GrB_Matrix_clear(G_S_new)); + + GRB_TRY(GrB_mxm(step1, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, M_call, B_S[i], + GrB_DESC_T0)); // step1 is ng*nr + + GRB_TRY(GrB_Matrix_clear(G_S_new)); + GRB_TRY(GrB_mxm(G_S_new, A_S[i], GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, step1, M_ret, + GrB_DESC_SC)); + + GrB_Index new_edge_nvals; + GRB_TRY(GrB_Matrix_nvals(&new_edge_nvals, G_S_new)); + if (new_edge_nvals == 0) + continue; + + /* + call positions are correct + we derived some edges for graph + now we need to traverse this part again */ + + // G_S[S] |= G_S_new + GRB_TRY(GrB_eWiseAdd(A_S[i], GrB_NULL, GrB_NULL, GrB_LOR, A_S[i], G_S_new, GrB_NULL)); + + // P &= ~M_ret + GRB_TRY(GrB_Matrix_clear(front_temp1)); + GRB_TRY( + GrB_eWiseAdd(front_temp1, GrB_NULL, GrB_NULL, GrB_LOR, front_temp1, P, GrB_NULL)); + GRB_TRY(GrB_Matrix_clear(P)); + GRB_TRY(GrB_eWiseAdd(P, M_ret, GrB_NULL, GrB_LOR, P, front_temp1, GrB_DESC_SC)); + + // next_frontier |= M_call + GRB_TRY(GrB_eWiseAdd(next_frontier, GrB_NULL, GrB_NULL, GrB_LOR, next_frontier, M_call, + GrB_NULL)); + } + + //---------------------------------------------------------------------- + // ADVANCE: P |= next_frontier + //---------------------------------------------------------------------- + + GRB_TRY(GrB_eWiseAdd(P, GrB_NULL, GrB_NULL, GrB_LOR, P, next_frontier, GrB_NULL)); + GRB_TRY(GrB_Matrix_nvals(&states, next_frontier)); + + if (iteration > 3000) + { + printf("Warning: Maximum iterations reached\n"); + break; } + } + + GRB_TRY(GrB_Matrix_clear(next_frontier)); + GrB_assign(next_frontier, GrB_NULL, GrB_NULL, true, QS, nqs, Source, ns, GrB_NULL); + + GRB_TRY(GrB_Matrix_nvals(&states, next_frontier)); + // second loop + while (states != 0) + { + iteration++; + GrB_Matrix old_frontier = frontier; + frontier = next_frontier; + next_frontier = old_frontier; + GRB_TRY(GrB_Matrix_clear(next_frontier)); + + // Terminals + for (size_t i = 0; i < num_terminals; ++i) + { + if (A_a[i] == GrB_NULL || B_a[i] == GrB_NULL) + continue; + + GRB_TRY(GrB_Matrix_clear(front_temp1)); + GRB_TRY(GrB_Matrix_clear(front_temp2)); - // я реально не понимаю как это работает, но окей - // Accumulate the new state <-> node correspondence - GRB_TRY (GrB_assign (visited, visited, GrB_NULL, next_frontier, - GrB_ALL, nr, GrB_ALL, ng, GrB_DESC_SC)) ; + // front_temp1 = N_a[a]^T * M + GRB_TRY(GrB_mxm(front_temp1, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, B_a[i], + frontier, GrB_DESC_T0)); - GRB_TRY (GrB_Matrix_nvals (&states, next_frontier)) ; + // front_temp2 = front_temp1 * G_a[a] + GRB_TRY(GrB_mxm(front_temp2, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, front_temp1, + A_a[i], GrB_NULL)); + + // M_new |= front_temp2 & ~K + GRB_TRY(GrB_eWiseAdd(next_frontier, K, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, + next_frontier, front_temp2, GrB_DESC_SC)); + } + + // Nonterminals (derived A_S edges) + for (size_t i = 0; i < num_nonterminals; ++i) + { + if (B_S[i] == GrB_NULL) + continue; + + GRB_TRY(GrB_Matrix_clear(front_temp1)); + GRB_TRY(GrB_Matrix_clear(front_temp2)); + + // front_temp1 = N_S[i]^T * frontier + GRB_TRY(GrB_mxm(front_temp1, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, B_S[i], + frontier, GrB_DESC_T0)); + + // front_temp2 = front_temp1 * G_S[i] + GRB_TRY(GrB_mxm(front_temp2, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, + front_temp1, A_S[i], GrB_NULL)); + + // next_frontier |= symbol_frontier & ~K + GRB_TRY(GrB_eWiseAdd(next_frontier, K, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, + next_frontier, front_temp2, GrB_DESC_SC)); + } + + //---------------------------------------------------------------------- + // ADVANCE: P |= next_frontier + //---------------------------------------------------------------------- + + GRB_TRY(GrB_eWiseAdd(K, GrB_NULL, GrB_NULL, GrB_LOR, K, next_frontier, GrB_NULL)); + GRB_TRY(GrB_Matrix_nvals(&states, next_frontier)); + + if (iteration > 3000) + { + printf("Warning: Maximum iterations reached\n"); + break; + } } - // Extract the nodes matching the final RSM states - GRB_TRY (GrB_vxm (*reachable, GrB_NULL, GrB_NULL, - GrB_LOR_LAND_SEMIRING_BOOL, final_reducer, visited, GrB_NULL)) ; + GRB_TRY(GrB_vxm(*reachable, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, final_reducer, K, + GrB_NULL)) - LG_FREE_WORK ; - return (GrB_SUCCESS) ; + LG_FREE_WORK; + return GrB_SUCCESS; } diff --git a/experimental/test/test_RSM_reachability.c b/experimental/test/test_RSM_reachability.c index 121dc511f7..bf10a389de 100644 --- a/experimental/test/test_RSM_reachability.c +++ b/experimental/test/test_RSM_reachability.c @@ -1,243 +1,333 @@ -#include "GraphBLAS.h" -#include "LAGraph.h" +#include +#include #include -#include #include -#include +#include + +#include "LAGraph.h" + #include #include +#include +#include #define LEN 512 #define MAX_LABELS 3 #define MAX_RESULTS 2000000 -LAGraph_Graph G[MAX_LABELS] ; -LAGraph_Graph R[MAX_LABELS] ; -LAGraph_Graph R_call; - -GrB_Matrix A ; +#define CHECK(info) \ + do \ + { \ + GrB_Info _i = (info); \ + if (_i != GrB_SUCCESS && _i != GrB_NO_VALUE) \ + { \ + fprintf(stderr, "GraphBLAS error %d at %s:%d\n", _i, __FILE__, __LINE__); \ + exit(EXIT_FAILURE); \ + } \ + } while (0) -char testcase_name[LEN+1] ; -char filename[LEN+1] ; -char msg[LAGRAPH_MSG_LEN] ; +char msg[LAGRAPH_MSG_LEN]; -typedef struct +static void setup(void) { - const char* name ; - const char* graphs[MAX_LABELS] ; - const char* rsm[MAX_LABELS] ; - const char* rsm_call; - const char* rsm_meta ; - const char* sources ; - const GrB_Index expected[MAX_RESULTS] ; - const size_t expected_count ; + LAGraph_Init(msg); } -matrix_info ; -const matrix_info files [ ] = +static void teardown(void) { - {"simple 1", - {"rsm_data/a.mtx", "rsm_data/b.mtx", NULL}, - {"rsm_data/1_a.mtx", NULL}, - "rsm_data/1_call.mtx", - "rsm_data/1_meta.txt", - "rsm_data/1_sources.txt", - {2}, - 1}, - {NULL, NULL, NULL, NULL}, -} ; - - -void test_RSM_reachability (void) -{ - LAGraph_Init (msg) ; - - for (int k = 0 ; ; ++k) - { - if (files[k].sources == NULL) break ; + LAGraph_Finalize(msg); +} - snprintf (testcase_name, LEN, "basic context-free path query %s", files[k].name) ; - TEST_CASE (testcase_name) ; +static GrB_Matrix make_bool_matrix(GrB_Index rows, GrB_Index cols, const GrB_Index *I, + const GrB_Index *J, GrB_Index nvals) +{ + GrB_Matrix A = GrB_NULL; + TEST_CHECK(GrB_SUCCESS == GrB_Matrix_new(&A, GrB_BOOL, rows, cols)); - for (int check_symmetry = 0 ; check_symmetry < 2 ; ++check_symmetry) - { - // Load graph from MTX files represention its adjacency matrix - // decomposition - for (int i = 0 ; ; ++i) - { - const char *name = files[k].graphs[i] ; - - if (name == NULL) break ; - if (strlen(name) == 0) continue ; - - snprintf (filename, LEN, LG_DATA_DIR "%s", name) ; - FILE *f = fopen (filename, "r") ; - TEST_CHECK (f != NULL) ; - OK (LAGraph_MMRead (&A, f, msg)) ; - OK (fclose (f)) ; - - OK (LAGraph_New (&(G[i]), &A, LAGraph_ADJACENCY_DIRECTED, msg)) ; - - TEST_CHECK (A == NULL) ; - } - - // Load RSM from MTX files representing its adjacency matrix - // decomposition - for (int i = 0 ; ; ++i) - { - const char *name = files[k].rsm[i] ; - - if (name == NULL) break ; - if (strlen(name) == 0) continue ; - - snprintf (filename, LEN, LG_DATA_DIR "%s", name) ; - FILE *f = fopen (filename, "r") ; - TEST_CHECK (f != NULL) ; - OK (LAGraph_MMRead (&A, f, msg)) ; - OK (fclose (f)) ; - - OK (LAGraph_New (&(R[i]), &A, LAGraph_ADJACENCY_DIRECTED, msg)) ; - - if (check_symmetry) - { - // Check if the pattern is symmetric - if it isn't make it. - // Note this also computes R[i]->AT - OK (LAGraph_Cached_IsSymmetricStructure (R[i], msg)) ; - } - - TEST_CHECK (A == NULL) ; - } - - { - const char *name = files[k].rsm_call ; - - snprintf (filename, LEN, LG_DATA_DIR "%s", name) ; - FILE *f = fopen (filename, "r") ; - TEST_CHECK (f != NULL) ; - OK (LAGraph_MMRead (&A, f, msg)) ; - OK (fclose (f)) ; - - OK (LAGraph_New (&(R_call), &A, LAGraph_ADJACENCY_DIRECTED, msg)) ; - - if (check_symmetry) - { - OK (LAGraph_Cached_IsSymmetricStructure(R_call, msg)) ; - } - - TEST_CHECK (A == NULL) ; // зачем нам это проверять?, почему оно NULL - } + for (GrB_Index k = 0; k < nvals; k++) + TEST_CHECK(GrB_SUCCESS == GrB_Matrix_setElement_BOOL(A, true, I[k], J[k])); + return A; +} - } +static LAGraph_Graph make_graph(GrB_Matrix *pA) +{ + LAGraph_Graph G = GrB_NULL; + int retval = LAGraph_New(&G, pA, LAGraph_ADJACENCY_DIRECTED, msg); + TEST_CHECK(retval == 0); + TEST_MSG("LAGraph_New failed: %s", msg); + return G; +} - // Note the matrix rows/cols are enumerated from 0 to n-1. - // Meanwhile, in MTX format they are enumerated from 1 to n. Thus, - // when loading/comparing the results these values should be - // decremented/incremented correspondingly. +static void free_graphs(LAGraph_Graph *arr, size_t n) +{ + for (size_t i = 0; i < n; ++i) + if (arr[i] != GrB_NULL) + LAGraph_Delete(&arr[i], msg); +} - // Load graph source nodes from the sources file - GrB_Index s ; - GrB_Index S[16] ; - size_t ns = 0 ; +static void check_result(GrB_Vector result, GrB_Index V, const bool *expected, + const char *test_name) +{ + GrB_Index n = 0; + TEST_CHECK(GrB_SUCCESS == GrB_Vector_size(&n, result)); + TEST_CHECK_(n == V, "%s: result vector size %llu != %llu", test_name, (unsigned long long)n, + (unsigned long long)V); - const char *name = files[k].sources ; - snprintf (filename, LEN, LG_DATA_DIR "%s", name) ; - FILE *f = fopen (filename, "r") ; - TEST_CHECK (f != NULL) ; + for (GrB_Index v = 0; v < V; v++) + { + bool val = false; + GrB_Info info = GrB_Vector_extractElement_BOOL(&val, result, v); - while (fscanf(f, "%" PRIu64, &s) != EOF) + if (expected[v]) { - S[ns++] = s - 1 ; + TEST_CHECK_(info == GrB_SUCCESS && val, + "%s: vertex %llu should be reachable but is not", test_name, + (unsigned long long)v); } + else + { + bool absent = (info == GrB_NO_VALUE) || (!val); + TEST_CHECK_(absent, "%s: vertex %llu should not be reachable but is", test_name, + (unsigned long long)v); + } + } +} - OK (fclose(f)) ; - - // Load RSM starting states from the meta file - GrB_Index qs ; - GrB_Index QS[16] ; // почему именно 16, мб личный выбор - size_t nqs = 0 ; +//============================================================================== +// TEST CASES +//============================================================================== - name = files[k].rsm_meta ; - snprintf (filename, LEN, LG_DATA_DIR "%s", name) ; - f = fopen (filename, "r") ; - TEST_CHECK (f != NULL) ; ; +void test_based(void) +{ + setup(); - uint64_t nqs64 = 0 ; - TEST_CHECK (fscanf (f, "%" PRIu64, &nqs64) != EOF) ; - nqs = (size_t) nqs64 ; + const GrB_Index Q = 4, V = 6; - for (size_t i = 0 ; i < nqs ; ++i) - { - TEST_CHECK(fscanf (f, "%" PRIu64, &qs) != EOF) ; - QS[i] = qs - 1 ; - } + // Graph: 0 -a-> 1 -a-> 2 -b-> 3 -b-> 4 -b-> 5 + GrB_Index Ga_I[] = {0, 1}, Ga_J[] = {1, 2}; + GrB_Index Gb_I[] = {2, 3, 4}, Gb_J[] = {3, 4, 5}; - // Load RSM final states from the same file - uint64_t qf ; - uint64_t QF[16] ; - size_t nqf = 0 ; - uint64_t nqf64 = 0 ; + // RSM: S: q0 -a-> q1 -S-> q2 -b-> q3 - TEST_CHECK (fscanf (f, "%" PRIu64, &nqf64) != EOF) ; - nqf = (size_t) nqf64 ; + GrB_Index Na_I[] = {0}, Na_J[] = {1}; + GrB_Index Nb_I[] = {1, 2}, Nb_J[] = {3, 3}; // q1->q3, q2->q3 - for (size_t i = 0 ; i < nqf ; ++i) - { - TEST_CHECK (fscanf (f, "%" PRIu64, &qf) != EOF) ; - QF[i] = qf - 1 ; - } + GrB_Index Call_I[] = {1}, Call_J[] = {0}; + GrB_Index Ret_I[] = {3}, Ret_J[] = {2}; + GrB_Index Ns_I[] = {1}, Ns_J[] = {2}; // N_S: q1->q2 - OK (fclose (f)) ; + GrB_Matrix Ga_raw = make_bool_matrix(V, V, Ga_I, Ga_J, 2); + GrB_Matrix Gb_raw = make_bool_matrix(V, V, Gb_I, Gb_J, 3); + GrB_Matrix Na_raw = make_bool_matrix(Q, Q, Na_I, Na_J, 1); + GrB_Matrix Nb_raw = make_bool_matrix(Q, Q, Nb_I, Nb_J, 2); + GrB_Matrix Cs_raw = make_bool_matrix(Q, Q, Call_I, Call_J, 1); + GrB_Matrix Rs_raw = make_bool_matrix(Q, Q, Ret_I, Ret_J, 1); + GrB_Matrix Ns_raw = make_bool_matrix(Q, Q, Ns_I, Ns_J, 1); - // Evaluate the algorithm - GrB_Vector r = NULL ; + LAGraph_Graph Ga[2], Na[2], Cs[1], Rs[1], Ns[1]; + Ga[0] = make_graph(&Ga_raw); + Ga[1] = make_graph(&Gb_raw); + Na[0] = make_graph(&Na_raw); + Na[1] = make_graph(&Nb_raw); + Cs[0] = make_graph(&Cs_raw); + Rs[0] = make_graph(&Rs_raw); + Ns[0] = make_graph(&Ns_raw); - OK (LAGraph_RSM_reachability (&r, R, MAX_LABELS, QS, nqs, - QF, nqf, &R_call, G, S, ns, msg)) ; - - // Extract results from the output vector - GrB_Index *reachable ; - bool *values ; - - GrB_Index nvals ; - GrB_Vector_nvals (&nvals, r) ; + GrB_Index QS[] = {0}, QF[] = {3}, Source[] = {0}; + GrB_Vector result = GrB_NULL; - OK (LAGraph_Malloc ((void **) &reachable, MAX_RESULTS, sizeof (GrB_Index), msg)) ;; - OK (LAGraph_Malloc ((void **) &values, MAX_RESULTS, sizeof (bool), msg)) ; + int retval = + LAGraph_RSM_reachability(&result, 2, Ga, Na, 1, Ns, Cs, Rs, QS, 1, QF, 1, Source, 1, msg); - GrB_Vector_extractTuples (reachable, values, &nvals, r) ; - - TEST_MSG("returned %lu values:\n", nvals); - for (uint64_t i = 0; i < nvals; i++) - TEST_MSG(" %lu\n", reachable[i] + 1); + TEST_CHECK(GrB_SUCCESS == retval); + TEST_MSG("test_based: retval = %d (%s)", retval, msg); - // Compare the results with expected values - TEST_CHECK (nvals == files[k].expected_count) ; - for (uint64_t i = 0 ; i < nvals ; ++i) - TEST_CHECK (reachable[i] + 1 == files[k].expected[i]) ; + const bool expected[6] = {false, false, false, false, true, false}; + check_result(result, V, expected, "test_based"); - // Cleanup - OK (LAGraph_Free ((void **) &values, NULL)) ; - OK (LAGraph_Free ((void **) &reachable, NULL)) ; + GrB_free(&result); + free_graphs(Ga, 2); + free_graphs(Na, 2); + free_graphs(Cs, 1); + free_graphs(Rs, 1); + free_graphs(Ns, 1); - OK (GrB_free (&r)) ; + teardown(); +} - for (uint64_t i = 0 ; i < MAX_LABELS ; ++i) - { - if (G[i] == NULL) continue ; - OK (LAGraph_Delete (&(G[i]), msg)) ; - } +void test_balanced_paren(void) +{ + setup(); + + const GrB_Index Q = 5, V = 6; + + // Graph: 0 -a-> 1 -a-> 2 -b-> 3 -b-> 4 -b-> 5 + GrB_Index Ga_I[] = {0, 1, 4}, Ga_J[] = {1, 2, 5}; + GrB_Index Gb_I[] = {2, 3}, Gb_J[] = {3, 4}; + + // RSM: S: q0 -(-> q1 -S-> q2 -)-> q3 -S-> q4 + GrB_Index Na_I[] = {0}, Na_J[] = {1}; + GrB_Index Nb_I[] = {1, 1, 2, 2}, Nb_J[] = {3, 4, 3, 4}; + + GrB_Index Call_I[] = {1, 3}, Call_J[] = {0, 0}; + GrB_Index Ret_I[] = {4, 4}, Ret_J[] = {2, 4}; + GrB_Index Ns_I[] = {1, 3}, Ns_J[] = {2, 4}; + + GrB_Matrix Ga_raw = make_bool_matrix(V, V, Ga_I, Ga_J, 3); + GrB_Matrix Gb_raw = make_bool_matrix(V, V, Gb_I, Gb_J, 2); + GrB_Matrix Na_raw = make_bool_matrix(Q, Q, Na_I, Na_J, 1); + GrB_Matrix Nb_raw = make_bool_matrix(Q, Q, Nb_I, Nb_J, 4); + GrB_Matrix Cs_raw = make_bool_matrix(Q, Q, Call_I, Call_J, 2); + GrB_Matrix Rs_raw = make_bool_matrix(Q, Q, Ret_I, Ret_J, 2); + GrB_Matrix Ns_raw = make_bool_matrix(Q, Q, Ns_I, Ns_J, 2); + + LAGraph_Graph Ga[2], Na[2], Cs[1], Rs[1], Ns[1]; + Ga[0] = make_graph(&Ga_raw); + Ga[1] = make_graph(&Gb_raw); + Na[0] = make_graph(&Na_raw); + Na[1] = make_graph(&Nb_raw); + Cs[0] = make_graph(&Cs_raw); + Rs[0] = make_graph(&Rs_raw); + Ns[0] = make_graph(&Ns_raw); + + GrB_Index QS[] = {0}, QF[] = {4}, Source[] = {0}; + GrB_Vector result = GrB_NULL; + + int retval = + LAGraph_RSM_reachability(&result, 2, Ga, Na, 1, Ns, Cs, Rs, QS, 1, QF, 1, Source, 1, msg); + + TEST_CHECK(GrB_SUCCESS == retval); + TEST_MSG("test_based: retval = %d (%s)", retval, msg); + + const bool expected[6] = {false, false, false, false, true, false}; + check_result(result, V, expected, "test_based"); + + GrB_free(&result); + free_graphs(Ga, 2); + free_graphs(Na, 2); + free_graphs(Cs, 1); + free_graphs(Rs, 1); + free_graphs(Ns, 1); + + teardown(); +} - for (uint64_t i = 0; i < MAX_LABELS ; ++i) - { - if (R[i] == NULL) continue ; - OK (LAGraph_Delete (&(R[i]), msg)) ; - } - } +void parens_lrlrlr(void) +{ + setup(); + + const GrB_Index Q = 5, V = 7; + + // Graph 0 -(-> 1 -)-> 2 -(-> 3 -)-> 4 -(-> 5 -)-> 6 + GrB_Index Ga_I[] = {0, 2, 4}, Ga_J[] = {1, 3, 5}; + GrB_Index Gb_I[] = {1, 3, 5}, Gb_J[] = {2, 4, 6}; + + // RSM: S: q0 -(-> q1 -S-> q2 -)-> q3 -S-> q4 + GrB_Index Na_I[] = {0}, Na_J[] = {1}; + GrB_Index Nb_I[] = {1, 1, 2, 2}, Nb_J[] = {3, 4, 3, 4}; + + GrB_Index Call_I[] = {1, 3}, Call_J[] = {0, 0}; + GrB_Index Ret_I[] = {4, 4}, Ret_J[] = {2, 4}; + GrB_Index Ns_I[] = {1, 3}, Ns_J[] = {2, 4}; + + GrB_Matrix Ga_raw = make_bool_matrix(V, V, Ga_I, Ga_J, 3); + GrB_Matrix Gb_raw = make_bool_matrix(V, V, Gb_I, Gb_J, 3); + GrB_Matrix Na_raw = make_bool_matrix(Q, Q, Na_I, Na_J, 1); + GrB_Matrix Nb_raw = make_bool_matrix(Q, Q, Nb_I, Nb_J, 4); + GrB_Matrix Cs_raw = make_bool_matrix(Q, Q, Call_I, Call_J, 2); + GrB_Matrix Rs_raw = make_bool_matrix(Q, Q, Ret_I, Ret_J, 2); + GrB_Matrix Ns_raw = make_bool_matrix(Q, Q, Ns_I, Ns_J, 2); + + LAGraph_Graph Ga[2], Na[2], Cs[1], Rs[1], Ns[1]; + Ga[0] = make_graph(&Ga_raw); + Ga[1] = make_graph(&Gb_raw); + Na[0] = make_graph(&Na_raw); + Na[1] = make_graph(&Nb_raw); + Cs[0] = make_graph(&Cs_raw); + Rs[0] = make_graph(&Rs_raw); + Ns[0] = make_graph(&Ns_raw); + + GrB_Index QS[] = {0}, QF[] = {4}, Source[] = {0}; + GrB_Vector result = GrB_NULL; + + int retval = + LAGraph_RSM_reachability(&result, 2, Ga, Na, 1, Ns, Cs, Rs, QS, 1, QF, 1, Source, 1, msg); + + TEST_CHECK(GrB_SUCCESS == retval); + TEST_MSG("test_based: retval = %d (%s)", retval, msg); + + const bool expected[7] = {0, 0, 1, 0, 1, 0, 1}; + check_result(result, V, expected, "test_based"); + + GrB_free(&result); + free_graphs(Ga, 2); + free_graphs(Na, 2); + free_graphs(Cs, 1); + free_graphs(Rs, 1); + free_graphs(Ns, 1); + + teardown(); +} - LAGraph_Finalize (msg) ; +void very_big_parens(void) +{ + setup(); + + const GrB_Index Q = 5, V = 13; + + // Graph (()(()()))() + GrB_Index Ga_I[] = {0, 1, 3, 4, 6, 10}, Ga_J[] = {1, 2, 4, 5, 7, 11}; + GrB_Index Gb_I[] = {2, 5, 7, 8, 9, 11}, Gb_J[] = {3, 6, 8, 9, 10, 12}; + + // RSM: S: q0 -(-> q1 -S-> q2 -)-> q3 -S-> q4 + GrB_Index Na_I[] = {0}, Na_J[] = {1}; + GrB_Index Nb_I[] = {1, 1, 2, 2}, Nb_J[] = {3, 4, 3, 4}; + + GrB_Index Call_I[] = {1, 3}, Call_J[] = {0, 0}; + GrB_Index Ret_I[] = {4, 4}, Ret_J[] = {2, 4}; + GrB_Index Ns_I[] = {1, 3}, Ns_J[] = {2, 4}; + + GrB_Matrix Ga_raw = make_bool_matrix(V, V, Ga_I, Ga_J, 6); + GrB_Matrix Gb_raw = make_bool_matrix(V, V, Gb_I, Gb_J, 6); + GrB_Matrix Na_raw = make_bool_matrix(Q, Q, Na_I, Na_J, 1); + GrB_Matrix Nb_raw = make_bool_matrix(Q, Q, Nb_I, Nb_J, 4); + GrB_Matrix Cs_raw = make_bool_matrix(Q, Q, Call_I, Call_J, 2); + GrB_Matrix Rs_raw = make_bool_matrix(Q, Q, Ret_I, Ret_J, 2); + GrB_Matrix Ns_raw = make_bool_matrix(Q, Q, Ns_I, Ns_J, 2); + + LAGraph_Graph Ga[2], Na[2], Cs[1], Rs[1], Ns[1]; + Ga[0] = make_graph(&Ga_raw); + Ga[1] = make_graph(&Gb_raw); + Na[0] = make_graph(&Na_raw); + Na[1] = make_graph(&Nb_raw); + Cs[0] = make_graph(&Cs_raw); + Rs[0] = make_graph(&Rs_raw); + Ns[0] = make_graph(&Ns_raw); + + GrB_Index QS[] = {0}, QF[] = {4}, Source[] = {0}; + GrB_Vector result = GrB_NULL; + + int retval = + LAGraph_RSM_reachability(&result, 2, Ga, Na, 1, Ns, Cs, Rs, QS, 1, QF, 1, Source, 1, msg); + + TEST_CHECK(GrB_SUCCESS == retval); + TEST_MSG("test_based: retval = %d (%s)", retval, msg); + + const bool expected[13] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1}; + check_result(result, V, expected, "test_based"); + + GrB_free(&result); + free_graphs(Ga, 2); + free_graphs(Na, 2); + free_graphs(Cs, 1); + free_graphs(Rs, 1); + free_graphs(Ns, 1); + + teardown(); } -TEST_LIST = { - {"RSM_reachability", test_RSM_reachability}, - {NULL, NULL} -} ; +TEST_LIST = {{"simple", test_based}, + {"test_balanced_paren", test_balanced_paren}, + {"parens_lrlrlr", parens_lrlrlr}, + {"very_big_parens", very_big_parens}, + {NULL, NULL}}; diff --git a/include/LAGraphX.h b/include/LAGraphX.h index d7053d0244..daf5d5727d 100644 --- a/include/LAGraphX.h +++ b/include/LAGraphX.h @@ -858,31 +858,31 @@ int LAGraph_RegularPathQuery // nodes reachable from the starting by the //**************************************************************************** LAGRAPHX_PUBLIC -int LAGraph_RSM_reachability -( +int LAGraph_RSM_reachability( // output: - GrB_Vector *reachable, // reachable(i) = true if node i is reachable - // from one of the starting nodes by a path - // satisfying regular constraints - - LAGraph_Graph *R, // input recursive state machine's - // adjacency matrix decomposition - size_t nl, // total label count, # of matrices graph and - // RSM adjacency matrix decomposition - const GrB_Index *QS, // starting states in RSM - size_t nqs, // number of starting states in RSM - const GrB_Index *QF, // final states in RSM - size_t nqf, // number of final states in RSM - - LAGraph_Graph *R_call, // recursive state machine's call/return nr*nr - // matrix, where (i, j) = 1 if there is transition - // from i state of one automata to j starting - // state of another automata - LAGraph_Graph *G, // input graph adjacency matrix decomposition - const GrB_Index *S, // source vertices to start searching paths - size_t ns, // number of source vertices - char *msg // LAGraph output message -) ; + GrB_Vector *reachable, + + // input: + size_t num_terminals, // # terminal labels + LAGraph_Graph *G_a, // terminal transitions + LAGraph_Graph *N_a, // terminal transitions + + size_t num_nonterminals, // # nonterminal labels + LAGraph_Graph *N_S, // nonterminal RSM transitions + LAGraph_Graph *Call_S, // call transition matrix + LAGraph_Graph *Ret_S, // return transition matrix + + const GrB_Index *QS, // staring states in RSM + size_t nqs, // # starting states + const GrB_Index *QF, // final states in RSM + size_t nqf, // # final states + + const GrB_Index *Source, // sources vertices + size_t ns, // # source vertices + + char *msg + +); //**************************************************************************** LAGRAPHX_PUBLIC From 767268b75eb8c0759fd476fc0db9590fdad2f76d Mon Sep 17 00:00:00 2001 From: Sterkachov Date: Tue, 3 Mar 2026 16:22:50 +0300 Subject: [PATCH 04/25] feat: add the algorithm's description --- .../algorithm/LAGraph_RSM_reachability.c | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/experimental/algorithm/LAGraph_RSM_reachability.c b/experimental/algorithm/LAGraph_RSM_reachability.c index 8972116a4c..cf6faf523d 100644 --- a/experimental/algorithm/LAGraph_RSM_reachability.c +++ b/experimental/algorithm/LAGraph_RSM_reachability.c @@ -2,6 +2,118 @@ // LAGraph_RSM_reachability.c //------------------------------------------------------------------------------ +/* +Let: + + Q = {0, …, Q-1} — set of RSM states + V = {0, …, V-1} — set of graph vertices + Σ — terminal alphabet + N — set of nonterminals + +Boolean matrices: + + G_a ∈ {0,1}^{V×V} — graph edges labeled by a ∈ Σ + N_a ∈ {0,1}^{Q×Q} — RSM transitions labeled by a ∈ Σ + Call_S ∈ {0,1}^{Q×Q} — call transitions for S ∈ N + Ret_S ∈ {0,1}^{Q×Q} — return transitions for S ∈ N + N_S ∈ {0,1}^{Q×Q} — internal nonterminal transitions + G_S ∈ {0,1}^{V×V} — derived graph edges for S + + M ∈ {0,1}^{Q×V} + P ∈ {0,1}^{Q×V} + + +------------------------------------------------------------ +Phase 1: Saturation (computation of reachable configurations) +------------------------------------------------------------ + +Initialization: + M[q₀, v₀] = 1 for all q₀ ∈ start_states + P[q₀, v₀] = 1 for all q₀ ∈ start_states + +while M != 0: + + Forward propagation: + M_new = 0 + for all a ∈ Σ: + M_new ← M_new ∪ (N_aᵀ · M · G_a) ∧ ~P + + For all S ∈ N: + M_new ← M_new ∪ (N_Sᵀ · M · G_S) ∧ ~P + M_new ← M_new ∪ (Call_Sᵀ · M) ∧ ~P + + Backward propagation (summary edge construction): + + + For all S ∈ N: + M_ret = Ret_Sᵀ · M + + if M_ret = 0: continue + + Restore paths to matching call positions by computing + the least fixed point of: + + X₀ = (Ret_S · M_ret) ∧ P + X_{i+1} = + ((⋁_{a∈Σ} N_a · X_i · G_aᵀ) + ∨ (⋁_{S1∈N} N_{S1} · X_i · G_{S1}ᵀ)) ∧ ~P + + After reaching fixed point X: + + X_call = (Call_S · X) ∧ P + + if X_call = 0: continue + + New summary edges: + + G_S_new = + (X_callᵀ · N_S · M_ret) ∧ ~G_S + + Update: + G_S ← G_S ∪ G_S_new + M_new = M_new ∪ M_ret + + M = M_new + P = P ∪ M + +Iterate until no new configurations are added to P. + +------------------------------------------------------------ +Phase 2: Reachability restricted to valid CF paths +------------------------------------------------------------ + +M = 0 + +Compute K ⊆ Q × V: + M[q₀, v₀] = 1 + K[q₀, v₀] = 1 for start configurations + +while M != 0: + + Forward propagation: + M_new = 0 + for all a ∈ Σ: + M_new ← M_new ∪ (N_aᵀ · M · G_a) + + For all S ∈ N: + M_new ← M_new ∪ (N_Sᵀ · M · G_S) + + M = M_new + P = P ∪ M + +until fixed point. + +------------------------------------------------------------ +Result +------------------------------------------------------------ + +Let F ∈ {0,1}^{1×Q} be final-state selector. + +Output: + result = F · K + +*/ + #include #include #include From 745f2157ab03d6dfadbc3380b869f6d08b8fbdfb Mon Sep 17 00:00:00 2001 From: Sterkachov Date: Wed, 8 Apr 2026 19:16:30 +0300 Subject: [PATCH 05/25] Implement second version of new algorithm --- .../algorithm/LAGraph_RSM_reachability.c | 141 ++++++------------ 1 file changed, 48 insertions(+), 93 deletions(-) diff --git a/experimental/algorithm/LAGraph_RSM_reachability.c b/experimental/algorithm/LAGraph_RSM_reachability.c index cf6faf523d..aa5336aa33 100644 --- a/experimental/algorithm/LAGraph_RSM_reachability.c +++ b/experimental/algorithm/LAGraph_RSM_reachability.c @@ -3,114 +3,69 @@ //------------------------------------------------------------------------------ /* -Let: +Initialize: + P := 0 + M := 0 + M[q_start, vs·V + vs] := 1 - Q = {0, …, Q-1} — set of RSM states - V = {0, …, V-1} — set of graph vertices - Σ — terminal alphabet - N — set of nonterminals + For each S: + Stack[S] := 0 // (q_ret, v_before, v_entry) + graph_nt[S] := 0 // (v_entry, v_final) -Boolean matrices: +while M != 0 do - G_a ∈ {0,1}^{V×V} — graph edges labeled by a ∈ Σ - N_a ∈ {0,1}^{Q×Q} — RSM transitions labeled by a ∈ Σ - Call_S ∈ {0,1}^{Q×Q} — call transitions for S ∈ N - Ret_S ∈ {0,1}^{Q×Q} — return transitions for S ∈ N - N_S ∈ {0,1}^{Q×Q} — internal nonterminal transitions - G_S ∈ {0,1}^{V×V} — derived graph edges for S + // -------------------------------------------------- + // 1. TERMINAL TRANSITIONS + // -------------------------------------------------- + M_term := 0 + for each label a do + for each q' do + M_term |= N_a^T @ M @ G_a - M ∈ {0,1}^{Q×V} - P ∈ {0,1}^{Q×V} + // -------------------------------------------------- + // 2. CALL TRANSITIONS + // -------------------------------------------------- + M_call := 0 + for each box S do + // select call states + mask_call := call[S]^T @ M // (q_start(S), v_start, v_entry) ------------------------------------------------------------- -Phase 1: Saturation (computation of reachable configurations) ------------------------------------------------------------- + if mask_call != 0: -Initialization: - M[q₀, v₀] = 1 for all q₀ ∈ start_states - P[q₀, v₀] = 1 for all q₀ ∈ start_states + // ---- push frames ---- + frame := rsm_nt[S]^T @ M // (q_ret, v_before, v_entry) + Stack[S] |= frame -while M != 0: + // ---- activate callee ---- + entry_vec := OR_rows(M[q_start]) // bcs exist only one q_start for each box + D := diag(entry_vec) + M_call[q_start] |= D - Forward propagation: - M_new = 0 - for all a ∈ Σ: - M_new ← M_new ∪ (N_aᵀ · M · G_a) ∧ ~P + // -------------------------------------------------- + // 3. RETURN TRANSITIONS + // -------------------------------------------------- + M_return := 0 + for each box S do - For all S ∈ N: - M_new ← M_new ∪ (N_Sᵀ · M · G_S) ∧ ~P - M_new ← M_new ∪ (Call_Sᵀ · M) ∧ ~P + M contains (q_final_S + other states, ..., ...) + Mask them with final_states and get (q_final_S, v_start_S, v_final_S) + derive (v_start_S, v_final_S) edges to graph_nt[S] + M_return |= Stack[S] @ graph_nt[S] // restore traviersion from point (q_return, v_before, +v_final_S) - Backward propagation (summary edge construction): + // -------------------------------------------------- + // 4. MERGE + // -------------------------------------------------- + M_new := (M_term | M_call | M_return) & ~P - For all S ∈ N: - M_ret = Ret_Sᵀ · M + P |= M_new + M := M_new - if M_ret = 0: continue +end while - Restore paths to matching call positions by computing - the least fixed point of: - - X₀ = (Ret_S · M_ret) ∧ P - X_{i+1} = - ((⋁_{a∈Σ} N_a · X_i · G_aᵀ) - ∨ (⋁_{S1∈N} N_{S1} · X_i · G_{S1}ᵀ)) ∧ ~P - - After reaching fixed point X: - - X_call = (Call_S · X) ∧ P - - if X_call = 0: continue - - New summary edges: - - G_S_new = - (X_callᵀ · N_S · M_ret) ∧ ~G_S - - Update: - G_S ← G_S ∪ G_S_new - M_new = M_new ∪ M_ret - - M = M_new - P = P ∪ M - -Iterate until no new configurations are added to P. - ------------------------------------------------------------- -Phase 2: Reachability restricted to valid CF paths ------------------------------------------------------------- - -M = 0 - -Compute K ⊆ Q × V: - M[q₀, v₀] = 1 - K[q₀, v₀] = 1 for start configurations - -while M != 0: - - Forward propagation: - M_new = 0 - for all a ∈ Σ: - M_new ← M_new ∪ (N_aᵀ · M · G_a) - - For all S ∈ N: - M_new ← M_new ∪ (N_Sᵀ · M · G_S) - - M = M_new - P = P ∪ M - -until fixed point. - ------------------------------------------------------------- -Result ------------------------------------------------------------- - -Let F ∈ {0,1}^{1×Q} be final-state selector. - -Output: - result = F · K +return P */ From f4581377a504bf3d04e2793720ddb3b003591e84 Mon Sep 17 00:00:00 2001 From: Sterkachov Date: Fri, 17 Apr 2026 05:20:15 +0300 Subject: [PATCH 06/25] CFPQ RSM algorithm v3 --- experimental/algorithm/LAGraph_CFPQ_RSM.c | 350 +++++++++ .../algorithm/LAGraph_RSM_reachability.c | 704 ----------------- experimental/test/test_CFPQ_RSM.c | 709 ++++++++++++++++++ experimental/test/test_RSM_reachability.c | 333 -------- include/LAGraphX.h | 45 +- 5 files changed, 1076 insertions(+), 1065 deletions(-) create mode 100644 experimental/algorithm/LAGraph_CFPQ_RSM.c delete mode 100644 experimental/algorithm/LAGraph_RSM_reachability.c create mode 100644 experimental/test/test_CFPQ_RSM.c delete mode 100644 experimental/test/test_RSM_reachability.c diff --git a/experimental/algorithm/LAGraph_CFPQ_RSM.c b/experimental/algorithm/LAGraph_CFPQ_RSM.c new file mode 100644 index 0000000000..5d107fc412 --- /dev/null +++ b/experimental/algorithm/LAGraph_CFPQ_RSM.c @@ -0,0 +1,350 @@ +#include "LAGraph.h" +#include "LG_internal.h" +#include + +#include +#include +#include + +#define LG_FREE_WORK \ + { \ + GrB_free(&M); \ + GrB_free(&P); \ + GrB_free(&M_term); \ + GrB_free(&M_nonterm); \ + GrB_free(&M_call); \ + GrB_free(&M_return); \ + GrB_free(&M_new); \ + GrB_free(&temp1); \ + GrB_free(&temp2); \ + GrB_free(&mask_call); \ + GrB_free(&frame); \ + GrB_free(&new_edges); \ + GrB_free(&diag_entry); \ + GrB_free(&outer_result); \ + GrB_free(&entry_vec); \ + if (Stack != NULL) \ + { \ + for (size_t _i = 0; _i < num_nonterminals; ++_i) \ + GrB_free(&Stack[_i]); \ + LAGraph_Free((void **)&Stack, GrB_NULL); \ + } \ + if (graph_nt != NULL) \ + { \ + for (size_t _i = 0; _i < num_nonterminals; ++_i) \ + GrB_free(&graph_nt[_i]); \ + LAGraph_Free((void **)&graph_nt, GrB_NULL); \ + } \ + } +#define LG_FREE_ALL \ + { \ + LG_FREE_WORK; \ + GrB_free(reachable); \ + } + +#define H_TRY(expr) \ + { \ + GrB_Info _hi = (expr); \ + if (_hi != GrB_SUCCESS) \ + return _hi; \ + } + +static GrB_Info s_mxm_chain(GrB_Matrix C, // output accumulator |Q|*|V*V| + GrB_Matrix A, // RSM matrix |Q|*|Q| + GrB_Matrix M, // frontier |Q|*|V*V| + GrB_Matrix B, // graph matrix |V|*|V| + GrB_Matrix temp1, // scratch |Q|*|V*V| + GrB_Matrix temp2, // scratch |Q|*|V*V| + GrB_Index Q, GrB_Index V) +{ + H_TRY(GrB_Matrix_clear(temp1)); + H_TRY(GrB_Matrix_clear(temp2)); + + // temp1 = A^T * M -> |Q|*|V*V| + H_TRY(GrB_mxm(temp1, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, A, M, GrB_DESC_T0)); + + // reshape: |Q| × |V*V| -> |Q*V|*|V| + H_TRY(GxB_Matrix_reshape(temp1, false, Q * V, V, GrB_NULL)); + H_TRY(GxB_Matrix_reshape(temp2, false, Q * V, V, GrB_NULL)); + + // temp2 = temp1 * B -> |Q*V|*|V| + H_TRY(GrB_mxm(temp2, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, temp1, B, GrB_NULL)); + + // reshape: |Q*V|*|V| -> |Q|*|V*V| + H_TRY(GxB_Matrix_reshape(temp1, false, Q, V * V, GrB_NULL)); + H_TRY(GxB_Matrix_reshape(temp2, false, Q, V * V, GrB_NULL)); + + // C |= temp2 + H_TRY(GrB_eWiseAdd(C, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, C, temp2, GrB_NULL)); + + return GrB_SUCCESS; +} +#undef H_TRY + +int LAGraph_CFPQ_RSM( + // output: + GrB_Vector *reachable, + + // RSM terminal input: + size_t num_terminals, GrB_Matrix *rsm_term, GrB_Matrix *graph_term, + + // RSM non-terminal input: + size_t num_nonterminals, GrB_Matrix *rsm_nonterm, GrB_Matrix *rsm_call, GrB_Vector *rsm_start, + GrB_Vector *rsm_final, + + size_t start_nonterm, GrB_Index start_vertex, + + GrB_Index Q, GrB_Index V, + + char *msg) +{ + LG_CLEAR_MSG; + + // working matrices / vectors + GrB_Matrix M = GrB_NULL; + GrB_Matrix P = GrB_NULL; + GrB_Matrix M_term = GrB_NULL; + GrB_Matrix M_nonterm = GrB_NULL; + GrB_Matrix M_call = GrB_NULL; + GrB_Matrix M_return = GrB_NULL; + GrB_Matrix M_new = GrB_NULL; + GrB_Matrix temp1 = GrB_NULL; + GrB_Matrix temp2 = GrB_NULL; + GrB_Matrix mask_call = GrB_NULL; + GrB_Matrix frame = GrB_NULL; + GrB_Matrix new_edges = GrB_NULL; + GrB_Matrix diag_entry = GrB_NULL; // add diagonal matrix for entry_vec + GrB_Matrix outer_result = GrB_NULL; // additional matrix + GrB_Vector entry_vec = GrB_NULL; + + GrB_Matrix *Stack = NULL; + GrB_Matrix *graph_nt = NULL; + + LG_ASSERT(reachable != NULL, GrB_NULL_POINTER); + LG_ASSERT(rsm_term != NULL, GrB_NULL_POINTER); + LG_ASSERT(rsm_nonterm != NULL, GrB_NULL_POINTER); + LG_ASSERT(rsm_call != NULL, GrB_NULL_POINTER); + LG_ASSERT(rsm_start != NULL, GrB_NULL_POINTER); + LG_ASSERT(rsm_final != NULL, GrB_NULL_POINTER); + LG_ASSERT(start_nonterm < num_nonterminals, GrB_INVALID_VALUE); + LG_ASSERT(start_vertex < V, GrB_INVALID_VALUE); + + *reachable = GrB_NULL; + + GrB_Index VV = V * V; + + // allocate per-nonterminal arrays + LG_TRY(LAGraph_Malloc((void **)&Stack, num_nonterminals, sizeof(GrB_Matrix), msg)); + LG_TRY(LAGraph_Malloc((void **)&graph_nt, num_nonterminals, sizeof(GrB_Matrix), msg)); + + for (size_t i = 0; i < num_nonterminals; ++i) + { + Stack[i] = GrB_NULL; + graph_nt[i] = GrB_NULL; + } + + for (size_t i = 0; i < num_nonterminals; ++i) + { + GRB_TRY(GrB_Matrix_new(&Stack[i], GrB_BOOL, Q, VV)); + GRB_TRY(GrB_Matrix_new(&graph_nt[i], GrB_BOOL, V, V)); + } + + // allocate working matrices + GRB_TRY(GrB_Matrix_new(&M, GrB_BOOL, Q, VV)); + GRB_TRY(GrB_Matrix_new(&P, GrB_BOOL, Q, VV)); + GRB_TRY(GrB_Matrix_new(&M_term, GrB_BOOL, Q, VV)); + GRB_TRY(GrB_Matrix_new(&M_nonterm, GrB_BOOL, Q, VV)); + GRB_TRY(GrB_Matrix_new(&M_call, GrB_BOOL, Q, VV)); + GRB_TRY(GrB_Matrix_new(&M_return, GrB_BOOL, Q, VV)); + GRB_TRY(GrB_Matrix_new(&M_new, GrB_BOOL, Q, VV)); + GRB_TRY(GrB_Matrix_new(&temp1, GrB_BOOL, Q, VV)); + GRB_TRY(GrB_Matrix_new(&temp2, GrB_BOOL, Q, VV)); + GRB_TRY(GrB_Matrix_new(&mask_call, GrB_BOOL, Q, VV)); + GRB_TRY(GrB_Matrix_new(&frame, GrB_BOOL, Q, VV)); + GRB_TRY(GrB_Matrix_new(&new_edges, GrB_BOOL, 1, VV)); + // GRB_TRY(GrB_Matrix_new(&diag_entry, GrB_BOOL, V, V)); + GRB_TRY(GrB_Matrix_new(&outer_result, GrB_BOOL, Q, VV)); + GRB_TRY(GrB_Vector_new(&entry_vec, GrB_BOOL, V)); + GRB_TRY(GrB_Vector_new(reachable, GrB_BOOL, V)); + + // seed initial frontier M and Stack + { + GrB_Index seed_col = start_vertex * V + start_vertex; + + GrB_Index nstart = 0; + GRB_TRY(GrB_Vector_nvals(&nstart, rsm_start[start_nonterm])); + + GrB_Index *start_states = NULL; + bool *start_vals = NULL; + LG_TRY(LAGraph_Malloc((void **)&start_states, nstart, sizeof(GrB_Index), msg)); + LG_TRY(LAGraph_Malloc((void **)&start_vals, nstart, sizeof(bool), msg)); + + GrB_Info info = GrB_Vector_extractTuples_BOOL(start_states, start_vals, &nstart, + rsm_start[start_nonterm]); + LAGraph_Free((void **)&start_vals, GrB_NULL); + + if (info != GrB_SUCCESS) + { + LAGraph_Free((void **)&start_states, GrB_NULL); + LG_FREE_ALL; + return info; + } + + for (GrB_Index k = 0; k < nstart; ++k) + { + GrB_Index q = start_states[k]; + GRB_TRY(GrB_Matrix_setElement_BOOL(M, true, q, seed_col)); + GRB_TRY(GrB_Matrix_setElement_BOOL(Stack[start_nonterm], true, q, seed_col)); + } + + LAGraph_Free((void **)&start_states, GrB_NULL); + } + + // main fixed-point loop + GrB_Index m_nvals = 0; + GRB_TRY(GrB_Matrix_nvals(&m_nvals, M)); + + while (m_nvals > 0) + { + + // PHASE 1: TERMINAL TRANSITIONS + GRB_TRY(GrB_Matrix_clear(M_term)); + for (size_t i = 0; i < num_terminals; ++i) + { + if (rsm_term[i] == GrB_NULL || graph_term[i] == GrB_NULL) + continue; + LG_TRY(s_mxm_chain(M_term, rsm_term[i], M, graph_term[i], temp1, temp2, Q, V)); + } + + // PHASE 2: NON-TERMINAL TRANSITIONS + GRB_TRY(GrB_Matrix_clear(M_nonterm)); + for (size_t i = 0; i < num_nonterminals; ++i) + { + if (rsm_nonterm[i] == GrB_NULL) + continue; + LG_TRY(s_mxm_chain(M_nonterm, rsm_nonterm[i], M, graph_nt[i], temp1, temp2, Q, V)); + } + + // PHASE 3: CALL TRANSITIONS + GRB_TRY(GrB_Matrix_clear(M_call)); + for (size_t i = 0; i < num_nonterminals; ++i) + { + if (rsm_call[i] == GrB_NULL || rsm_nonterm[i] == GrB_NULL) + continue; + + // mask_call = rsm_call[i]^T * M -> |Q|*|V*V| + GRB_TRY(GrB_Matrix_clear(mask_call)); + GRB_TRY(GrB_mxm(mask_call, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, rsm_call[i], + M, GrB_DESC_T0)); + + GrB_Index mc_nvals = 0; + GRB_TRY(GrB_Matrix_nvals(&mc_nvals, mask_call)); + if (mc_nvals == 0) + continue; + + // frame = rsm_nonterm[i]^T * M, accumulated into Stack[i] + GRB_TRY(GrB_Matrix_clear(frame)); + GRB_TRY(GrB_mxm(frame, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, rsm_nonterm[i], M, + GrB_DESC_T0)); + GRB_TRY(GrB_eWiseAdd(Stack[i], GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, Stack[i], + frame, GrB_NULL)); + + // entry_vec = column-OR of mask_call reshaped to |Q*V|*|V| + GRB_TRY(GrB_Matrix_clear(temp1)); + GRB_TRY(GrB_eWiseAdd(temp1, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, temp1, + mask_call, GrB_NULL)); + GRB_TRY(GxB_Matrix_reshape(temp1, false, Q * V, V, GrB_NULL)); + + GRB_TRY(GrB_Vector_clear(entry_vec)); + // column OR = row OR of transpose + GRB_TRY( + GrB_reduce(entry_vec, GrB_NULL, GrB_LOR, GrB_LOR_MONOID_BOOL, temp1, GrB_DESC_T0)); + + GRB_TRY(GxB_Matrix_reshape(temp1, false, Q, VV, GrB_NULL)); + + // Build diag(entry_vec) reshaped to 1*|V*V| + diag_entry = GrB_NULL; + GRB_TRY(GrB_Matrix_diag(&diag_entry, entry_vec, 0)); // |V|*|V| + GRB_TRY(GxB_Matrix_reshape(diag_entry, false, 1, VV, GrB_NULL)); + + GRB_TRY(GrB_Matrix_clear(outer_result)); + // |Q|*1 * 1*|V*V| -> |Q|*|V*V| + GRB_TRY(GrB_mxm(outer_result, GrB_NULL, GrB_NULL, GxB_LOR_LAND_BOOL, + (GrB_Matrix)rsm_start[i], diag_entry, GrB_NULL)); + + // M_call |= outer_result + GRB_TRY(GrB_eWiseAdd(M_call, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, M_call, + outer_result, GrB_NULL)); + + GRB_TRY(GrB_Matrix_free(&diag_entry)); + } + + // PHASE 4: RETURN TRANSITIONS + GRB_TRY(GrB_Matrix_clear(M_return)); + for (size_t i = 0; i < num_nonterminals; ++i) + { + if (rsm_final[i] == GrB_NULL) + continue; + + // 1*|Q| * |Q|*|V*V| -> 1*|V*V| + GRB_TRY(GrB_Matrix_clear(new_edges)); + GRB_TRY(GrB_mxm(new_edges, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, + (GrB_Matrix)rsm_final[i], M, GrB_DESC_T0)); + + GRB_TRY(GxB_Matrix_reshape(new_edges, false, V, V, GrB_NULL)); + + GRB_TRY(GrB_eWiseAdd(graph_nt[i], GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, + graph_nt[i], new_edges, GrB_NULL)); + + GRB_TRY(GrB_Matrix_clear(temp1)); + GRB_TRY(GrB_eWiseAdd(temp1, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, temp1, + Stack[i], GrB_NULL)); + GRB_TRY(GxB_Matrix_reshape(temp1, false, Q * V, V, GrB_NULL)); + + GRB_TRY(GrB_Matrix_clear(temp2)); + GRB_TRY(GxB_Matrix_reshape(temp2, false, Q * V, V, GrB_NULL)); + // |Q*V|*|V| * |V|*|V| -> |Q*V|*|V| + GRB_TRY(GrB_mxm(temp2, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, temp1, new_edges, + GrB_NULL)); + + GRB_TRY(GxB_Matrix_reshape(temp1, false, Q, VV, GrB_NULL)); + GRB_TRY(GxB_Matrix_reshape(temp2, false, Q, VV, GrB_NULL)); + + GRB_TRY(GrB_eWiseAdd(M_return, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, M_return, + temp2, GrB_NULL)); + GRB_TRY(GxB_Matrix_reshape(new_edges, false, 1, VV, GrB_NULL)); + } + + // PHASE 5: MERGE & MARK VISITED + // GrB_assign has big overhead + // M_new = (M_term | M_nonterm | M_call | M_return) & ~P + GRB_TRY(GrB_Matrix_clear(M_new)); + GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, M_new, M_term, + GrB_NULL)); + GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, M_new, M_nonterm, + GrB_NULL)); + GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, M_new, M_call, + GrB_NULL)); + GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, M_new, M_return, + GrB_NULL)); + + // M_new &= ~P + GRB_TRY(GrB_assign(M_new, P, GrB_NULL, M_new, GrB_ALL, Q, GrB_ALL, VV, GrB_DESC_RSC)); + + // P |= M_new + GRB_TRY(GrB_eWiseAdd(P, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, P, M_new, GrB_NULL)); + + GrB_Matrix swap = M; + M = M_new; + M_new = swap; + + GRB_TRY(GrB_Matrix_nvals(&m_nvals, M)); + } + + // GrB_DESC_T0 transposes graph_nt so Col_extract gives row start_vertex + GRB_TRY(GrB_Col_extract(*reachable, GrB_NULL, GrB_NULL, graph_nt[start_nonterm], GrB_ALL, V, + start_vertex, GrB_DESC_T0)); + + LG_FREE_WORK; + return GrB_SUCCESS; +} diff --git a/experimental/algorithm/LAGraph_RSM_reachability.c b/experimental/algorithm/LAGraph_RSM_reachability.c deleted file mode 100644 index aa5336aa33..0000000000 --- a/experimental/algorithm/LAGraph_RSM_reachability.c +++ /dev/null @@ -1,704 +0,0 @@ -//------------------------------------------------------------------------------ -// LAGraph_RSM_reachability.c -//------------------------------------------------------------------------------ - -/* -Initialize: - P := 0 - M := 0 - M[q_start, vs·V + vs] := 1 - - For each S: - Stack[S] := 0 // (q_ret, v_before, v_entry) - graph_nt[S] := 0 // (v_entry, v_final) - -while M != 0 do - - // -------------------------------------------------- - // 1. TERMINAL TRANSITIONS - // -------------------------------------------------- - M_term := 0 - for each label a do - for each q' do - M_term |= N_a^T @ M @ G_a - - // -------------------------------------------------- - // 2. CALL TRANSITIONS - // -------------------------------------------------- - M_call := 0 - for each box S do - - // select call states - mask_call := call[S]^T @ M // (q_start(S), v_start, v_entry) - - if mask_call != 0: - - // ---- push frames ---- - frame := rsm_nt[S]^T @ M // (q_ret, v_before, v_entry) - Stack[S] |= frame - - // ---- activate callee ---- - entry_vec := OR_rows(M[q_start]) // bcs exist only one q_start for each box - D := diag(entry_vec) - M_call[q_start] |= D - - // -------------------------------------------------- - // 3. RETURN TRANSITIONS - // -------------------------------------------------- - M_return := 0 - for each box S do - - M contains (q_final_S + other states, ..., ...) - Mask them with final_states and get (q_final_S, v_start_S, v_final_S) - derive (v_start_S, v_final_S) edges to graph_nt[S] - M_return |= Stack[S] @ graph_nt[S] // restore traviersion from point (q_return, v_before, -v_final_S) - - - // -------------------------------------------------- - // 4. MERGE - // -------------------------------------------------- - M_new := (M_term | M_call | M_return) & ~P - - P |= M_new - M := M_new - -end while - -return P - -*/ - -#include -#include -#include -#include -#include - -#include "LAGraph.h" -#include "LG_internal.h" - -#define LG_FREE_WORK \ - { \ - GrB_free(&frontier); \ - GrB_free(&next_frontier); \ - GrB_free(&front_temp1); \ - GrB_free(&front_temp2); \ - GrB_free(&P); \ - GrB_free(&K); \ - GrB_free(&final_reducer); \ - GrB_free(&M_ret); \ - GrB_free(&M_call); \ - GrB_free(&M_call_new); \ - GrB_free(&G_S_new); \ - GrB_free(&step1); \ - LAGraph_Free((void **)&A_a, GrB_NULL); \ - LAGraph_Free((void **)&B_a, GrB_NULL); \ - LAGraph_Free((void **)&B_S, GrB_NULL); \ - LAGraph_Free((void **)&B_call, GrB_NULL); \ - LAGraph_Free((void **)&B_ret, GrB_NULL); \ - LAGraph_Free((void **)&B_S, GrB_NULL); \ - if (A_S != GrB_NULL) \ - { \ - for (size_t _i = 0; _i < num_nonterminals; ++_i) \ - GrB_free(&A_S[_i]); \ - LAGraph_Free((void **)&A_S, GrB_NULL); \ - } \ - } - -#define LG_FREE_ALL \ - { \ - LG_FREE_WORK; \ - GrB_free(reachable); \ - } - -int LAGraph_RSM_reachability( - // output: - GrB_Vector *reachable, - - // input: - size_t num_terminals, // # terminal labels - LAGraph_Graph *G_a, // terminal transitions - LAGraph_Graph *N_a, // terminal transitions - - size_t num_nonterminals, // # nonterminal labels - LAGraph_Graph *N_S, // nonterminal RSM transitions - LAGraph_Graph *Call_S, // call transition matrix - LAGraph_Graph *Ret_S, // return transition matrix - - const GrB_Index *QS, // staring states in RSM - size_t nqs, // # starting states - const GrB_Index *QF, // final states in RSM - size_t nqf, // # final states - - const GrB_Index *Source, // sources vertices - size_t ns, // # source vertices - - char *msg - -) -{ - //-------------------------------------------------------------------------- - // check inputs - //-------------------------------------------------------------------------- - - LG_CLEAR_MSG; - - // Working matrices - GrB_Matrix frontier = GrB_NULL; // traversal frontier - GrB_Matrix next_frontier = GrB_NULL; - GrB_Matrix front_temp1 = GrB_NULL; - GrB_Matrix front_temp2 = GrB_NULL; - GrB_Matrix P = GrB_NULL; // visited pairs (state, vertex) - GrB_Matrix K = GrB_NULL; // visited pairs in second loop - GrB_Vector final_reducer = GrB_NULL; // vector for reducing the visited matrix - - // Backward-phase working matrices - GrB_Matrix M_ret = GrB_NULL; - GrB_Matrix M_call = GrB_NULL; - GrB_Matrix M_call_new = GrB_NULL; - GrB_Matrix G_S_new = GrB_NULL; - GrB_Matrix step1 = GrB_NULL; - - GrB_Index ng = 0; // # nodes in the graph - GrB_Index nr = 0; // # states in the RSM - GrB_Index states = ns; // # pairs in current frontier - - GrB_Index rows = 0, cols = 0, vals = 0; - - GrB_Matrix *A_a = GrB_NULL; // for G_a - GrB_Matrix *B_a = GrB_NULL; // for N_a - - GrB_Matrix *A_S = GrB_NULL; // Derived graph edge matrices - GrB_Matrix *B_S = GrB_NULL; // for N_S - GrB_Matrix *B_call = GrB_NULL; // for Call_S - GrB_Matrix *B_ret = GrB_NULL; // for Ret_S - - LG_ASSERT(reachable != GrB_NULL, GrB_NULL_POINTER); - - LG_ASSERT(G_a != GrB_NULL, GrB_NULL_POINTER); - LG_ASSERT(N_a != GrB_NULL, GrB_NULL_POINTER); - LG_ASSERT(N_S != GrB_NULL, GrB_NULL_POINTER); - LG_ASSERT(Call_S != GrB_NULL, GrB_NULL_POINTER); - LG_ASSERT(Ret_S != GrB_NULL, GrB_NULL_POINTER); - LG_ASSERT(QS != GrB_NULL, GrB_NULL_POINTER); - LG_ASSERT(QF != GrB_NULL, GrB_NULL_POINTER); - LG_ASSERT(Source != GrB_NULL, GrB_NULL_POINTER); - - (*reachable) = GrB_NULL; - - //-------------------------------------------------------------------------- - // validate input graphs - //-------------------------------------------------------------------------- - - for (size_t i = 0; i < num_terminals; ++i) - if (G_a[i] != GrB_NULL) - LG_TRY(LAGraph_CheckGraph(G_a[i], msg)) - - for (size_t i = 0; i < num_terminals; ++i) - if (N_a[i] != GrB_NULL) - LG_TRY(LAGraph_CheckGraph(N_a[i], msg)) - - for (size_t i = 0; i < num_nonterminals; ++i) - if (N_S[i] != GrB_NULL) - LG_TRY(LAGraph_CheckGraph(N_S[i], msg)) - - for (size_t i = 0; i < num_nonterminals; ++i) - if (Call_S[i] != GrB_NULL) - LG_TRY(LAGraph_CheckGraph(Call_S[i], msg)) - - for (size_t i = 0; i < num_nonterminals; ++i) - if (Ret_S[i] != GrB_NULL) - LG_TRY(LAGraph_CheckGraph(Ret_S[i], msg)) - - LG_TRY(LAGraph_Malloc((void **)&A_a, num_terminals, sizeof(GrB_Matrix), msg)) - for (size_t i = 0; i < num_terminals; ++i) - { - if (G_a[i] == GrB_NULL) - A_a[i] = GrB_NULL; - else - A_a[i] = G_a[i]->A; - } - LG_TRY(LAGraph_Malloc((void **)&B_a, num_terminals, sizeof(GrB_Matrix), msg)) - for (size_t i = 0; i < num_terminals; ++i) - { - if (N_a[i] == GrB_NULL) - B_a[i] = GrB_NULL; - else - B_a[i] = N_a[i]->A; - } - LG_TRY(LAGraph_Malloc((void **)&B_S, num_nonterminals, sizeof(GrB_Matrix), msg)) - for (size_t i = 0; i < num_nonterminals; ++i) - { - if (N_S[i] == GrB_NULL) - B_S[i] = GrB_NULL; - else - B_S[i] = N_S[i]->A; - } - LG_TRY(LAGraph_Malloc((void **)&B_call, num_nonterminals, sizeof(GrB_Matrix), msg)) - for (size_t i = 0; i < num_nonterminals; ++i) - { - if (Call_S[i] == GrB_NULL) - B_call[i] = GrB_NULL; - else - B_call[i] = Call_S[i]->A; - } - LG_TRY(LAGraph_Malloc((void **)&B_ret, num_nonterminals, sizeof(GrB_Matrix), msg)) - for (size_t i = 0; i < num_nonterminals; ++i) - { - if (Ret_S[i] == GrB_NULL) - B_ret[i] = GrB_NULL; - else - B_ret[i] = Ret_S[i]->A; - } - - //-------------------------------------------------------------------------- - // determine ng (graph size) and nr (RSM state count) - //-------------------------------------------------------------------------- - - for (size_t i = 0; i < num_terminals; ++i) - { - if (A_a[i] == GrB_NULL) - continue; - - GRB_TRY(GrB_Matrix_nrows(&ng, A_a[i])); - break; - } - - for (size_t i = 0; i < num_terminals; ++i) - { - if (B_a[i] == GrB_NULL) - continue; - - GRB_TRY(GrB_Matrix_nrows(&nr, B_a[i])); - break; - } - - //-------------------------------------------------------------------------- - // dimension checks - //-------------------------------------------------------------------------- - - for (size_t i = 0; i < num_terminals; ++i) - { - if (A_a[i] == GrB_NULL) - continue; - - GRB_TRY(GrB_Matrix_nrows(&rows, A_a[i])); - GRB_TRY(GrB_Matrix_ncols(&cols, A_a[i])); - - LG_ASSERT_MSG(rows == ng && cols == ng, LAGRAPH_NOT_CACHED, - "all the matrices in the graph adjacency matrix decomposition " - "should have the same dimensions and be square"); - } - for (size_t i = 0; i < num_terminals; ++i) - { - if (B_a[i] == GrB_NULL) - continue; - - GRB_TRY(GrB_Matrix_nrows(&rows, B_a[i])); - GRB_TRY(GrB_Matrix_ncols(&cols, B_a[i])); - - LG_ASSERT_MSG(rows == nr && cols == nr, LAGRAPH_NOT_CACHED, - "all the matrices in the RSM adjacency matrix decomposition " - "should have the same dimensions and be square") - } - for (size_t i = 0; i < num_nonterminals; ++i) - { - if (B_S[i] == GrB_NULL) - continue; - - GRB_TRY(GrB_Matrix_nrows(&rows, B_S[i])); - GRB_TRY(GrB_Matrix_ncols(&cols, B_S[i])); - - LG_ASSERT_MSG(rows == nr && cols == nr, LAGRAPH_NOT_CACHED, - "all the matrices in the RSM nonterminal matrix decomposition " - "should have the same dimensions and be square") - } - for (size_t i = 0; i < num_nonterminals; ++i) - { - if (B_call[i] == GrB_NULL) - continue; - - GRB_TRY(GrB_Matrix_nrows(&rows, B_call[i])); - GRB_TRY(GrB_Matrix_ncols(&cols, B_call[i])); - - LG_ASSERT_MSG(rows == nr && cols == nr, LAGRAPH_NOT_CACHED, - "all the matrices in the RSM call matrix decomposition " - "should have the same dimensions and be square") - } - for (size_t i = 0; i < num_nonterminals; ++i) - { - if (B_ret[i] == GrB_NULL) - continue; - - GRB_TRY(GrB_Matrix_nrows(&rows, B_ret[i])); - GRB_TRY(GrB_Matrix_ncols(&cols, B_ret[i])); - - LG_ASSERT_MSG(rows == nr && cols == nr, LAGRAPH_NOT_CACHED, - "all the matrices in the RSM return matrix decomposition " - "should have the same dimensions and be square") - } - - // Check source nodes in the graph - for (size_t i = 0; i < ns; ++i) - { - GrB_Index s = Source[i]; - LG_ASSERT_MSG(s < ng, GrB_INVALID_INDEX, "invalid graph source node"); - } - // Check starting states of the RSM - for (size_t i = 0; i < nqs; ++i) - { - GrB_Index qs = QS[i]; - LG_ASSERT_MSG(qs < nr, GrB_INVALID_INDEX, "invalid RSM starting state"); - } - // Check final states of the RSM - for (size_t i = 0; i < nqf; ++i) - { - GrB_Index qf = QF[i]; - LG_ASSERT_MSG(qf < nr, GrB_INVALID_INDEX, "invalid RSM final state"); - } - - //-------------------------------------------------------------------------- - // allocate derived graph-edge matrices A_S[s] (ng x ng, initially empty) - //-------------------------------------------------------------------------- - - LG_TRY(LAGraph_Malloc((void **)&A_S, num_nonterminals, sizeof(GrB_Matrix), msg)) - for (size_t i = 0; i < num_nonterminals; ++i) - GRB_TRY(GrB_Matrix_new(&A_S[i], GrB_BOOL, ng, ng)); - - // ------------------------------------------------------------------------- - // initialization - // ------------------------------------------------------------------------- - - GRB_TRY(GrB_Vector_new(reachable, GrB_BOOL, ng)); - GRB_TRY(GrB_Vector_new(&final_reducer, GrB_BOOL, nr)); - - // final_reducer[QF] = true - GrB_assign(final_reducer, GrB_NULL, GrB_NULL, true, QF, nqf, GrB_NULL); - - // frontier / visited matrices (nr x ng) - GRB_TRY(GrB_Matrix_new(&frontier, GrB_BOOL, nr, ng)); - GRB_TRY(GrB_Matrix_new(&next_frontier, GrB_BOOL, nr, ng)); - GRB_TRY(GrB_Matrix_new(&front_temp1, GrB_BOOL, nr, ng)); - GRB_TRY(GrB_Matrix_new(&front_temp2, GrB_BOOL, nr, ng)); - GRB_TRY(GrB_Matrix_new(&P, GrB_BOOL, nr, ng)); - GRB_TRY(GrB_Matrix_new(&K, GrB_BOOL, nr, ng)) - - GRB_TRY(GrB_Matrix_new(&M_ret, GrB_BOOL, nr, ng)); - GRB_TRY(GrB_Matrix_new(&M_call, GrB_BOOL, nr, ng)); - GRB_TRY(GrB_Matrix_new(&M_call_new, GrB_BOOL, nr, ng)); - GRB_TRY(GrB_Matrix_new(&G_S_new, GrB_BOOL, ng, ng)); - GRB_TRY(GrB_Matrix_new(&step1, GrB_BOOL, ng, nr)); - - // Seed next_frontier, P, K from (start-states × source-vertices) - GrB_assign(next_frontier, GrB_NULL, GrB_NULL, true, QS, nqs, Source, ns, GrB_NULL); - GrB_assign(P, GrB_NULL, GrB_NULL, true, QS, nqs, Source, ns, GrB_NULL); - GrB_assign(K, GrB_NULL, GrB_NULL, true, QS, nqs, Source, ns, GrB_NULL); - - GRB_TRY(GrB_Matrix_nvals(&states, next_frontier)); - - int iteration = 0; - - // Main loop - while (states != 0) - { - iteration++; - - GrB_Matrix old_frontier = frontier; - frontier = next_frontier; - next_frontier = old_frontier; - GRB_TRY(GrB_Matrix_clear(next_frontier)); - - //---------------------------------------------------------------------- - // FORWARD PHASE - //---------------------------------------------------------------------- - - // Terminals - for (size_t i = 0; i < num_terminals; ++i) - { - if (A_a[i] == GrB_NULL || B_a[i] == GrB_NULL) - continue; - - GRB_TRY(GrB_Matrix_clear(front_temp1)); - GRB_TRY(GrB_Matrix_clear(front_temp2)); - - // front_temp1 = N_a[a]^T * M - GRB_TRY(GrB_mxm(front_temp1, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, B_a[i], - frontier, GrB_DESC_T0)); - - // front_temp2 = front_temp1 * G_a[a] - GRB_TRY(GrB_mxm(front_temp2, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, front_temp1, - A_a[i], GrB_NULL)); - - // M_new |= front_temp2 & ~P - GRB_TRY(GrB_eWiseAdd(next_frontier, P, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, - next_frontier, front_temp2, GrB_DESC_SC)); - } - - // Nonterminals (derived A_S edges) - for (size_t i = 0; i < num_nonterminals; ++i) - { - if (B_S[i] == GrB_NULL) - continue; - - GRB_TRY(GrB_Matrix_clear(front_temp1)); - GRB_TRY(GrB_Matrix_clear(front_temp2)); - - // front_temp1 = N_S[i]^T * frontier - GRB_TRY(GrB_mxm(front_temp1, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, B_S[i], - frontier, GrB_DESC_T0)); - - // front_temp2 = front_temp1 * A_S[i] - GRB_TRY(GrB_mxm(front_temp2, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, - front_temp1, A_S[i], GrB_NULL)); - - // next_frontier |= symbol_frontier & ~P - GRB_TRY(GrB_eWiseAdd(next_frontier, P, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, - next_frontier, front_temp2, GrB_DESC_SC)); - } - - // Call edges - for (size_t i = 0; i < num_nonterminals; ++i) - { - if (B_call[i] == GrB_NULL) - continue; - - GRB_TRY(GrB_Matrix_clear(front_temp1)); - - // front_temp1 = B_call[s]^T * frontier - GRB_TRY(GrB_mxm(front_temp1, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, B_call[i], - frontier, GrB_DESC_T0)); - - // next_frontier |= front_temp1 & ~P - GRB_TRY(GrB_eWiseAdd(next_frontier, P, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, - next_frontier, front_temp1, GrB_DESC_SC)); - } - - //---------------------------------------------------------------------- - // BACKWARD PHASE – match return edges back to their call sites - //---------------------------------------------------------------------- - - for (size_t i = 0; i < num_nonterminals; ++i) - { - if (B_ret[i] == GrB_NULL || B_call[i] == GrB_NULL || B_S[i] == GrB_NULL) - continue; - - GRB_TRY(GrB_Matrix_clear(M_ret)); - GRB_TRY(GrB_Matrix_clear(M_call)); - - // M_ret = B_ret[s]^T * frontier (positions that hit a return state) - GRB_TRY(GrB_mxm(M_ret, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, B_ret[i], - frontier, GrB_DESC_T0)); - - GrB_Index ret_nvals; - GRB_TRY(GrB_Matrix_nvals(&ret_nvals, M_ret)); - if (ret_nvals == 0) - continue; - - // M_call = (B_ret[s] * M_ret) & frontier - GRB_TRY(GrB_mxm(M_call, frontier, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, B_ret[i], M_ret, - GrB_DESC_S)); - - // walk backwards through visited set P to find the - // matching call-site positions. - - GrB_Index call_nvals; - GRB_TRY(GrB_Matrix_nvals(&call_nvals, M_call)); - while (call_nvals > 0) - { - GRB_TRY(GrB_Matrix_clear(M_call_new)); - - // Backward terminal step: M_call_new |= (N_a[i] * M_call * G_a[i]^T) & P - for (size_t i = 0; i < num_terminals; ++i) - { - if (A_a[i] == GrB_NULL || B_a[i] == GrB_NULL) - continue; - - GRB_TRY(GrB_Matrix_clear(front_temp1)); - GRB_TRY(GrB_Matrix_clear(front_temp2)); - - // front_temp1 = N_a[a] * M_call - GRB_TRY(GrB_mxm(front_temp1, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, - B_a[i], M_call, GrB_NULL)); - - // front_temp2 = front_temp1 * G_a[a]^T - GRB_TRY(GrB_mxm(front_temp2, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, - front_temp1, A_a[i], GrB_DESC_T1)); - - // M_call_new |= front_temp2 & P - GRB_TRY(GrB_eWiseAdd(M_call_new, P, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, - M_call_new, front_temp2, GrB_DESC_S)); - } - // Backward nonterminal step: M_call_new |= (N_S[i] * M_call * G_S[i]^T) & P - for (size_t i = 0; i < num_nonterminals; ++i) - { - if (A_S[i] == GrB_NULL) - continue; - - GRB_TRY(GrB_Matrix_clear(front_temp1)); - GRB_TRY(GrB_Matrix_clear(front_temp2)); - - // front_temp1 = N_S[S] * M_call - GRB_TRY(GrB_mxm(front_temp1, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, - B_S[i], M_call, GrB_NULL)); - - // front_temp2 = front_temp1 * G_S[S]^T - GRB_TRY(GrB_mxm(front_temp2, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, - front_temp1, A_S[i], GrB_DESC_T1)); - - // M_call_new |= front_temp2 & P - GRB_TRY(GrB_eWiseAdd(M_call_new, P, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, - M_call_new, front_temp2, GrB_DESC_S)); - } - - GRB_TRY(GrB_Matrix_nvals(&call_nvals, M_call_new)); - if (call_nvals == 0) - break; - - GRB_TRY(GrB_Matrix_clear(M_call)); - GRB_TRY(GrB_eWiseAdd(M_call, GrB_NULL, GrB_NULL, GrB_LOR, M_call, M_call_new, - GrB_NULL)); - } - - // need to check if we reach call pos - // M_call = (B_call[s] * M_call) & P - GRB_TRY(GrB_Matrix_clear(front_temp1)); - GRB_TRY(GrB_mxm(front_temp1, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, B_call[i], - M_call, GrB_NULL)); - GRB_TRY(GrB_Matrix_clear(M_call)); - GRB_TRY(GrB_eWiseAdd(M_call, P, GrB_NULL, GrB_LOR, M_call, front_temp1, GrB_DESC_S)); - - GRB_TRY(GrB_Matrix_nvals(&call_nvals, M_call)); - if (call_nvals == 0) - continue; - - // we found some call positions - // but still not sure if they are correct ones - // G_S_new = (M_call.T @ N_S[S] @ M_ret) & ~G_S[S] - GRB_TRY(GrB_Matrix_clear(step1)); - GRB_TRY(GrB_Matrix_clear(front_temp2)); - GRB_TRY(GrB_Matrix_clear(G_S_new)); - - GRB_TRY(GrB_mxm(step1, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, M_call, B_S[i], - GrB_DESC_T0)); // step1 is ng*nr - - GRB_TRY(GrB_Matrix_clear(G_S_new)); - GRB_TRY(GrB_mxm(G_S_new, A_S[i], GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, step1, M_ret, - GrB_DESC_SC)); - - GrB_Index new_edge_nvals; - GRB_TRY(GrB_Matrix_nvals(&new_edge_nvals, G_S_new)); - if (new_edge_nvals == 0) - continue; - - /* - call positions are correct - we derived some edges for graph - now we need to traverse this part again */ - - // G_S[S] |= G_S_new - GRB_TRY(GrB_eWiseAdd(A_S[i], GrB_NULL, GrB_NULL, GrB_LOR, A_S[i], G_S_new, GrB_NULL)); - - // P &= ~M_ret - GRB_TRY(GrB_Matrix_clear(front_temp1)); - GRB_TRY( - GrB_eWiseAdd(front_temp1, GrB_NULL, GrB_NULL, GrB_LOR, front_temp1, P, GrB_NULL)); - GRB_TRY(GrB_Matrix_clear(P)); - GRB_TRY(GrB_eWiseAdd(P, M_ret, GrB_NULL, GrB_LOR, P, front_temp1, GrB_DESC_SC)); - - // next_frontier |= M_call - GRB_TRY(GrB_eWiseAdd(next_frontier, GrB_NULL, GrB_NULL, GrB_LOR, next_frontier, M_call, - GrB_NULL)); - } - - //---------------------------------------------------------------------- - // ADVANCE: P |= next_frontier - //---------------------------------------------------------------------- - - GRB_TRY(GrB_eWiseAdd(P, GrB_NULL, GrB_NULL, GrB_LOR, P, next_frontier, GrB_NULL)); - GRB_TRY(GrB_Matrix_nvals(&states, next_frontier)); - - if (iteration > 3000) - { - printf("Warning: Maximum iterations reached\n"); - break; - } - } - - GRB_TRY(GrB_Matrix_clear(next_frontier)); - GrB_assign(next_frontier, GrB_NULL, GrB_NULL, true, QS, nqs, Source, ns, GrB_NULL); - - GRB_TRY(GrB_Matrix_nvals(&states, next_frontier)); - // second loop - while (states != 0) - { - iteration++; - GrB_Matrix old_frontier = frontier; - frontier = next_frontier; - next_frontier = old_frontier; - GRB_TRY(GrB_Matrix_clear(next_frontier)); - - // Terminals - for (size_t i = 0; i < num_terminals; ++i) - { - if (A_a[i] == GrB_NULL || B_a[i] == GrB_NULL) - continue; - - GRB_TRY(GrB_Matrix_clear(front_temp1)); - GRB_TRY(GrB_Matrix_clear(front_temp2)); - - // front_temp1 = N_a[a]^T * M - GRB_TRY(GrB_mxm(front_temp1, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, B_a[i], - frontier, GrB_DESC_T0)); - - // front_temp2 = front_temp1 * G_a[a] - GRB_TRY(GrB_mxm(front_temp2, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, front_temp1, - A_a[i], GrB_NULL)); - - // M_new |= front_temp2 & ~K - GRB_TRY(GrB_eWiseAdd(next_frontier, K, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, - next_frontier, front_temp2, GrB_DESC_SC)); - } - - // Nonterminals (derived A_S edges) - for (size_t i = 0; i < num_nonterminals; ++i) - { - if (B_S[i] == GrB_NULL) - continue; - - GRB_TRY(GrB_Matrix_clear(front_temp1)); - GRB_TRY(GrB_Matrix_clear(front_temp2)); - - // front_temp1 = N_S[i]^T * frontier - GRB_TRY(GrB_mxm(front_temp1, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, B_S[i], - frontier, GrB_DESC_T0)); - - // front_temp2 = front_temp1 * G_S[i] - GRB_TRY(GrB_mxm(front_temp2, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, - front_temp1, A_S[i], GrB_NULL)); - - // next_frontier |= symbol_frontier & ~K - GRB_TRY(GrB_eWiseAdd(next_frontier, K, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, - next_frontier, front_temp2, GrB_DESC_SC)); - } - - //---------------------------------------------------------------------- - // ADVANCE: P |= next_frontier - //---------------------------------------------------------------------- - - GRB_TRY(GrB_eWiseAdd(K, GrB_NULL, GrB_NULL, GrB_LOR, K, next_frontier, GrB_NULL)); - GRB_TRY(GrB_Matrix_nvals(&states, next_frontier)); - - if (iteration > 3000) - { - printf("Warning: Maximum iterations reached\n"); - break; - } - } - - GRB_TRY(GrB_vxm(*reachable, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, final_reducer, K, - GrB_NULL)) - - LG_FREE_WORK; - return GrB_SUCCESS; -} diff --git a/experimental/test/test_CFPQ_RSM.c b/experimental/test/test_CFPQ_RSM.c new file mode 100644 index 0000000000..8dd9953bd6 --- /dev/null +++ b/experimental/test/test_CFPQ_RSM.c @@ -0,0 +1,709 @@ +#include "LAGraph.h" +#include "LAGraphX.h" +#include "LAGraph_test.h" +#include + +char msg[LAGRAPH_MSG_LEN]; + +//============================================================================== +// test setup / teardown +//============================================================================== + +static void setup(void) +{ + OK(LAGraph_Init(msg)); +} + +static void teardown(void) +{ + OK(LAGraph_Finalize(msg)); +} + +static GrB_Info build_call_matrix(GrB_Matrix *call, GrB_Matrix rsm_nt, GrB_Vector rsm_start, + GrB_Index Q) +{ + GrB_Vector q_call_mask = GrB_NULL; + + GRB_TRY(GrB_Matrix_new(call, GrB_BOOL, Q, Q)); + GRB_TRY(GrB_Vector_new(&q_call_mask, GrB_BOOL, Q)); + + // [q_call, q_ret] -> [q_call] + GRB_TRY(GrB_reduce(q_call_mask, GrB_NULL, GrB_NULL, GrB_LOR_MONOID_BOOL, rsm_nt, GrB_NULL)); + // |Q|*1 * 1*|Q| -> |Q|*|Q| + GRB_TRY(GrB_mxm(*call, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, (GrB_Matrix)q_call_mask, + (GrB_Matrix)rsm_start, GrB_DESC_T1)); + + GRB_TRY(GrB_Vector_free(&q_call_mask)); + + return GrB_SUCCESS; +} + +static void check_reachable(GrB_Vector result, GrB_Index *expected, GrB_Index n_expected, + GrB_Index V, const char *test_name) +{ + GrB_Index nvals = 0; + OK(GrB_Vector_nvals(&nvals, result)); + + bool *got = (bool *)calloc(V, sizeof(bool)); + TEST_CHECK(got != NULL); + + GrB_Index *idx = GrB_NULL; + bool *val = GrB_NULL; + + if (nvals > 0) + { + idx = (GrB_Index *)malloc(nvals * sizeof(GrB_Index)); + val = (bool *)malloc(nvals * sizeof(bool)); + if (!TEST_CHECK(idx != NULL && val != NULL)) + { + free(got); + free(idx); + free(val); + return; + } + OK(GrB_Vector_extractTuples_BOOL(idx, val, &nvals, result)); + for (GrB_Index k = 0; k < nvals; ++k) + if (val[k]) + got[idx[k]] = true; + } + + // every expected vertex must be reachable + for (GrB_Index k = 0; k < n_expected; ++k) + { + TEST_CHECK(got[expected[k]]); + TEST_MSG("%s: vertex %llu should be reachable", test_name, (unsigned long long)expected[k]); + } + + GrB_Index actual_count = 0; + for (GrB_Index v = 0; v < V; ++v) + if (got[v]) + actual_count++; + + TEST_CHECK(actual_count == n_expected); + TEST_MSG("%s: expected %llu reachable vertices, got %llu", test_name, + (unsigned long long)n_expected, (unsigned long long)actual_count); + + free(got); + free(idx); + free(val); +} + +//============================================================================== +// RSM builders +//============================================================================== +// Grammar 1: S -> a S b | a b +static void rsm_aSb_ab(GrB_Matrix *term, GrB_Matrix *nt, GrB_Matrix *call, GrB_Vector *start, + GrB_Vector *final) +{ + GrB_Index Q = 4; + OK(GrB_Matrix_new(&term[0], GrB_BOOL, Q, Q)); + OK(GrB_Matrix_new(&term[1], GrB_BOOL, Q, Q)); + OK(GrB_Matrix_new(&nt[0], GrB_BOOL, Q, Q)); + OK(GrB_Vector_new(&start[0], GrB_BOOL, Q)); + OK(GrB_Vector_new(&final[0], GrB_BOOL, Q)); + + OK(GrB_Matrix_setElement_BOOL(term[0], true, 0, 1)); // 0 -a-> 1 + OK(GrB_Matrix_setElement_BOOL(term[1], true, 1, 3)); // 1 -b-> 3 + OK(GrB_Matrix_setElement_BOOL(term[1], true, 2, 3)); // 2 -b-> 3 + OK(GrB_Matrix_setElement_BOOL(nt[0], true, 1, 2)); // 1 -S-> 2 + OK(GrB_Vector_setElement_BOOL(start[0], true, 0)); + OK(GrB_Vector_setElement_BOOL(final[0], true, 3)); + + OK(build_call_matrix(call, *nt, *start, Q)); +} + +// Grammar 2: S -> a S b | c +static void rsm_aSb_c(GrB_Matrix *term, GrB_Matrix *nt, GrB_Matrix *call, GrB_Vector *start, + GrB_Vector *final) +{ + GrB_Index Q = 4; + OK(GrB_Matrix_new(&term[0], GrB_BOOL, Q, Q)); + OK(GrB_Matrix_new(&term[1], GrB_BOOL, Q, Q)); + OK(GrB_Matrix_new(&term[2], GrB_BOOL, Q, Q)); + OK(GrB_Matrix_new(&nt[0], GrB_BOOL, Q, Q)); + OK(GrB_Vector_new(&start[0], GrB_BOOL, Q)); + OK(GrB_Vector_new(&final[0], GrB_BOOL, Q)); + + OK(GrB_Matrix_setElement_BOOL(term[0], true, 0, 1)); // 0 -a-> 1 + OK(GrB_Matrix_setElement_BOOL(term[2], true, 0, 3)); // 1 -c-> 3 + OK(GrB_Matrix_setElement_BOOL(term[1], true, 2, 3)); // 2 -b-> 3 + OK(GrB_Matrix_setElement_BOOL(nt[0], true, 1, 2)); // 1 -S-> 2 + OK(GrB_Vector_setElement_BOOL(start[0], true, 0)); + OK(GrB_Vector_setElement_BOOL(final[0], true, 3)); + + OK(build_call_matrix(call, *nt, *start, Q)); +} + +// Grammar 3: S -> X | Y | S X | S Y +// X -> a S b | a b +// Y -> c S d | c d +static void rsm_complex(GrB_Matrix *term, GrB_Matrix *nt, GrB_Matrix *call, GrB_Vector *start, + GrB_Vector *final) +{ + GrB_Index Q = 11; + for (int t = 0; t < 4; ++t) + OK(GrB_Matrix_new(&term[t], GrB_BOOL, Q, Q)); + for (int n = 0; n < 3; ++n) + { + OK(GrB_Matrix_new(&nt[n], GrB_BOOL, Q, Q)); + OK(GrB_Vector_new(&start[n], GrB_BOOL, Q)); + OK(GrB_Vector_new(&final[n], GrB_BOOL, Q)); + } + + // --- S Component (States 0-2) --- + // S -> X | Y | S X | S Y + OK(GrB_Matrix_setElement_BOOL(nt[1], true, 0, 2)); // 0 -X-> 2 + OK(GrB_Matrix_setElement_BOOL(nt[2], true, 0, 2)); // 0 -Y-> 2 + OK(GrB_Matrix_setElement_BOOL(nt[0], true, 0, 1)); // 0 -S-> 1 + OK(GrB_Matrix_setElement_BOOL(nt[1], true, 1, 2)); // 1 -X-> 2 + OK(GrB_Matrix_setElement_BOOL(nt[2], true, 1, 2)); // 1 -Y-> 2 + OK(GrB_Vector_setElement_BOOL(start[0], true, 0)); + OK(GrB_Vector_setElement_BOOL(final[0], true, 2)); + + // --- X Component (States 3-6) --- + OK(GrB_Matrix_setElement_BOOL(term[0], true, 3, 4)); // 3 -a-> 4 + OK(GrB_Matrix_setElement_BOOL(term[1], true, 4, 6)); // 4 -b-> 6 + OK(GrB_Matrix_setElement_BOOL(term[1], true, 5, 6)); // 5 -b-> 6 + OK(GrB_Matrix_setElement_BOOL(nt[0], true, 4, 5)); // 4 -S-> 5 + OK(GrB_Vector_setElement_BOOL(start[1], true, 3)); + OK(GrB_Vector_setElement_BOOL(final[1], true, 6)); + + // --- Y Component (States 7-10) + OK(GrB_Matrix_setElement_BOOL(term[2], true, 7, 8)); // 7 -c-> 8 + OK(GrB_Matrix_setElement_BOOL(term[3], true, 8, 10)); // 8 -d-> 10 + OK(GrB_Matrix_setElement_BOOL(term[3], true, 9, 10)); // 9 -d-> 10 + OK(GrB_Matrix_setElement_BOOL(nt[0], true, 8, 9)); // 8 -S-> 9 + OK(GrB_Vector_setElement_BOOL(start[2], true, 7)); + OK(GrB_Vector_setElement_BOOL(final[2], true, 10)); + + for (int n = 0; n < 3; n++) + OK(build_call_matrix(&call[n], nt[n], start[n], Q)); +} + +//============================================================================== +// test cases +//============================================================================== + +//------------------------------------------------------------------------------ +// TC1: S -> a S b | a b +// Graph: 0-a->1; 1-a->2; 2-a->0; 0-b->3; 3-b->0 +// Source: 1 Expected: {0, 3} +//------------------------------------------------------------------------------ +void test_TC1_cyclic_ab(void) +{ + setup(); + + GrB_Matrix term[2], nt[1], call[1]; + GrB_Vector start[1], final[1]; + + term[0] = term[1] = GrB_NULL; + nt[0] = GrB_NULL; + call[0] = GrB_NULL; + start[0] = final[0] = GrB_NULL; + + rsm_aSb_ab(term, nt, call, start, final); + + GrB_Index V = 4; + GrB_Matrix gterm[2]; + gterm[0] = gterm[1] = GrB_NULL; + OK(GrB_Matrix_new(>erm[0], GrB_BOOL, V, V)); + OK(GrB_Matrix_new(>erm[1], GrB_BOOL, V, V)); + + OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 0, 1)); + OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 1, 2)); + OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 2, 0)); + OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 0, 3)); + OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 3, 0)); + + GrB_Vector result = GrB_NULL; + + GrB_Info info = + LAGraph_CFPQ_RSM(&result, 2, term, gterm, 1, nt, call, start, final, 0, 1, 4, V, msg); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {0, 3}; + check_reachable(result, expected, 2, V, "TC1"); + } + + GrB_free(&result); + for (int i = 0; i < 2; ++i) + { + GrB_free(&term[i]); + GrB_free(>erm[i]); + } + GrB_free(&nt[0]); + GrB_free(&call[0]); + GrB_free(&start[0]); + GrB_free(&final[0]); + + OK(info); + + teardown(); +} + +//------------------------------------------------------------------------------ +// TC2: S -> a S b | a b +// Graph: 0-a->0; 0-a->1; 1-b->1 +// Source: 0 Expected: {1} +//------------------------------------------------------------------------------ +void test_TC2_self_loops(void) +{ + setup(); + + GrB_Matrix term[2], nt[1], call[1]; + GrB_Vector start[1], final[1]; + + term[0] = term[1] = GrB_NULL; + nt[0] = GrB_NULL; + call[0] = GrB_NULL; + start[0] = final[0] = GrB_NULL; + + rsm_aSb_ab(term, nt, call, start, final); + + GrB_Index V = 2; + GrB_Matrix gterm[2]; + gterm[0] = gterm[1] = GrB_NULL; + OK(GrB_Matrix_new(>erm[0], GrB_BOOL, V, V)); + OK(GrB_Matrix_new(>erm[1], GrB_BOOL, V, V)); + + OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 0, 0)); + OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 0, 1)); + OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 1, 1)); + + GrB_Vector result = GrB_NULL; + + GrB_Info info = + LAGraph_CFPQ_RSM(&result, 2, term, gterm, 1, nt, call, start, final, 0, 0, 4, V, msg); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {1}; + check_reachable(result, expected, 1, V, "TC2"); + } + + GrB_free(&result); + for (int i = 0; i < 2; ++i) + { + GrB_free(&term[i]); + GrB_free(>erm[i]); + } + GrB_free(&nt[0]); + GrB_free(&call[0]); + GrB_free(&start[0]); + GrB_free(&final[0]); + + OK(info); + + teardown(); +} + +//------------------------------------------------------------------------------ +// TC3: S -> a S b | a b +// Graph: 0-a->0; 0-b->0 +// Source: 0 Expected: {0} +//------------------------------------------------------------------------------ +void test_TC3_single_node(void) +{ + setup(); + + GrB_Matrix term[2], nt[1], call[1]; + GrB_Vector start[1], final[1]; + + term[0] = term[1] = GrB_NULL; + nt[0] = GrB_NULL; + call[0] = GrB_NULL; + start[0] = final[0] = GrB_NULL; + + rsm_aSb_ab(term, nt, call, start, final); + + GrB_Index V = 1; + GrB_Matrix gterm[2]; + gterm[0] = gterm[1] = GrB_NULL; + OK(GrB_Matrix_new(>erm[0], GrB_BOOL, V, V)); + OK(GrB_Matrix_new(>erm[1], GrB_BOOL, V, V)); + + OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 0, 0)); + OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 0, 0)); + + GrB_Vector result = GrB_NULL; + + GrB_Info info = + LAGraph_CFPQ_RSM(&result, 2, term, gterm, 1, nt, call, start, final, 0, 0, 4, V, msg); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {0}; + check_reachable(result, expected, 1, V, "TC3"); + } + + GrB_free(&result); + for (int i = 0; i < 2; ++i) + { + GrB_free(&term[i]); + GrB_free(>erm[i]); + } + GrB_free(&nt[0]); + GrB_free(&call[0]); + GrB_free(&start[0]); + GrB_free(&final[0]); + + OK(info); + + teardown(); +} + +//------------------------------------------------------------------------------ +// TC4: S -> a S b | c +// Graph: 0-a->0; 0-c->1; 1-b->1 +// Source: 0 Expected: {1} +//------------------------------------------------------------------------------ +void test_TC4_aSb_c_loops(void) +{ + setup(); + + GrB_Matrix term[3], nt[1], call[1]; + GrB_Vector start[1], final[1]; + + term[0] = term[1] = term[2] = GrB_NULL; + nt[0] = GrB_NULL; + call[0] = GrB_NULL; + start[0] = final[0] = GrB_NULL; + + rsm_aSb_c(term, nt, call, start, final); + + GrB_Index V = 2; + GrB_Matrix gterm[3]; + gterm[0] = gterm[1] = gterm[2] = GrB_NULL; + for (int i = 0; i < 3; ++i) + OK(GrB_Matrix_new(>erm[i], GrB_BOOL, V, V)); + + OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 0, 0)); + OK(GrB_Matrix_setElement_BOOL(gterm[2], true, 0, 1)); + OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 1, 1)); + + GrB_Vector result = GrB_NULL; + + GrB_Info info = + LAGraph_CFPQ_RSM(&result, 3, term, gterm, 1, nt, call, start, final, 0, 0, 4, V, msg); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {1}; + check_reachable(result, expected, 1, V, "TC4"); + } + + GrB_free(&result); + for (int i = 0; i < 3; ++i) + { + GrB_free(&term[i]); + GrB_free(>erm[i]); + } + GrB_free(&nt[0]); + GrB_free(&call[0]); + GrB_free(&start[0]); + GrB_free(&final[0]); + + OK(info); + + teardown(); +} + +//------------------------------------------------------------------------------ +// TC5: S -> a S b | c +// Graph: 0-a->1; 1-a->0; 0-c->2; 2-b->3; 3-b->2 +// Source: 1 Expected: {3} +//------------------------------------------------------------------------------ +void test_TC5_aSb_c_cycle1(void) +{ + setup(); + + GrB_Matrix term[3], nt[1], call[1]; + GrB_Vector start[1], final[1]; + + term[0] = term[1] = term[2] = GrB_NULL; + nt[0] = GrB_NULL; + call[0] = GrB_NULL; + start[0] = final[0] = GrB_NULL; + + rsm_aSb_c(term, nt, call, start, final); + + GrB_Index V = 4; + GrB_Matrix gterm[3]; + gterm[0] = gterm[1] = gterm[2] = GrB_NULL; + for (int i = 0; i < 3; ++i) + OK(GrB_Matrix_new(>erm[i], GrB_BOOL, V, V)); + + OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 0, 1)); + OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 1, 0)); + OK(GrB_Matrix_setElement_BOOL(gterm[2], true, 0, 2)); + OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 2, 3)); + OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 3, 2)); + + GrB_Vector result = GrB_NULL; + + GrB_Info info = + LAGraph_CFPQ_RSM(&result, 3, term, gterm, 1, nt, call, start, final, 0, 1, 4, V, msg); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {3}; + check_reachable(result, expected, 1, V, "TC5"); + } + + GrB_free(&result); + for (int i = 0; i < 3; ++i) + { + GrB_free(&term[i]); + GrB_free(>erm[i]); + } + GrB_free(&nt[0]); + GrB_free(&call[0]); + GrB_free(&start[0]); + GrB_free(&final[0]); + + OK(info); + + teardown(); +} +//------------------------------------------------------------------------------ +// TC6: S -> a S b | c +// Graph: 0-a->1; 1-a->0; 0-c->2; 2-b->2 +// Source: 1 Expected: {2} +//------------------------------------------------------------------------------ +void test_TC6_aSb_c_cycle2(void) +{ + setup(); + + GrB_Matrix term[3], nt[1], call[1]; + GrB_Vector start[1], final[1]; + + term[0] = term[1] = term[2] = GrB_NULL; + nt[0] = GrB_NULL; + call[0] = GrB_NULL; + start[0] = final[0] = GrB_NULL; + + rsm_aSb_c(term, nt, call, start, final); + + GrB_Index V = 3; + GrB_Matrix gterm[3]; + gterm[0] = gterm[1] = gterm[2] = GrB_NULL; + for (int i = 0; i < 3; ++i) + OK(GrB_Matrix_new(>erm[i], GrB_BOOL, V, V)); + + OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 0, 1)); + OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 1, 0)); + OK(GrB_Matrix_setElement_BOOL(gterm[2], true, 0, 2)); + OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 2, 2)); + + GrB_Vector result = GrB_NULL; + + GrB_Info info = + LAGraph_CFPQ_RSM(&result, 3, term, gterm, 1, nt, call, start, final, 0, 1, 4, V, msg); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {2}; + check_reachable(result, expected, 1, V, "TC6"); + } + + GrB_free(&result); + for (int i = 0; i < 3; ++i) + { + GrB_free(&term[i]); + GrB_free(>erm[i]); + } + GrB_free(&nt[0]); + GrB_free(&call[0]); + GrB_free(&start[0]); + GrB_free(&final[0]); + + OK(info); + + teardown(); +} + +// Graph: 0-a->1; 1-a->0; 0-c->2; 2-b->3; 3-b->4; 4-b->2 +static void graph_tc7_8(GrB_Matrix gterm[3]) +{ + GrB_Index V = 5; + for (int i = 0; i < 3; ++i) + OK(GrB_Matrix_new(>erm[i], GrB_BOOL, V, V)); + + OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 0, 1)); + OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 1, 0)); + OK(GrB_Matrix_setElement_BOOL(gterm[2], true, 0, 2)); + OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 2, 3)); + OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 3, 4)); + OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 4, 2)); +} + +//------------------------------------------------------------------------------ +// TC7: S -> a S b | c +// same graph as TC7/8 above +// Source: 0 Expected: {2, 3, 4} +//------------------------------------------------------------------------------ +void test_TC7_aSb_c_cycle3(void) +{ + setup(); + + GrB_Matrix term[3], nt[1], call[1]; + GrB_Vector start[1], final[1]; + + term[0] = term[1] = term[2] = GrB_NULL; + nt[0] = GrB_NULL; + call[0] = GrB_NULL; + start[0] = final[0] = GrB_NULL; + + rsm_aSb_c(term, nt, call, start, final); + + GrB_Index V = 5; + GrB_Matrix gterm[3]; + gterm[0] = gterm[1] = gterm[2] = GrB_NULL; + graph_tc7_8(gterm); + + GrB_Vector result = GrB_NULL; + + GrB_Info info = + LAGraph_CFPQ_RSM(&result, 3, term, gterm, 1, nt, call, start, final, 0, 0, 4, V, msg); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {2, 3, 4}; + check_reachable(result, expected, 3, V, "TC7"); + } + + GrB_free(&result); + for (int i = 0; i < 3; ++i) + { + GrB_free(&term[i]); + GrB_free(>erm[i]); + } + GrB_free(&nt[0]); + GrB_free(&call[0]); + GrB_free(&start[0]); + GrB_free(&final[0]); + + OK(info); + + teardown(); +} + +//------------------------------------------------------------------------------ +// TC8: S -> a S b | c +// same graph, different source vertex +// Source: 1 Expected: {2, 3, 4} +//------------------------------------------------------------------------------ +void test_TC8_aSb_c_cycle4(void) +{ + setup(); + + GrB_Matrix term[3], nt[1], call[1]; + GrB_Vector start[1], final[1]; + + term[0] = term[1] = term[2] = GrB_NULL; + nt[0] = GrB_NULL; + call[0] = GrB_NULL; + start[0] = final[0] = GrB_NULL; + + rsm_aSb_c(term, nt, call, start, final); + + GrB_Index V = 5; + GrB_Matrix gterm[3]; + gterm[0] = gterm[1] = gterm[2] = GrB_NULL; + graph_tc7_8(gterm); + + GrB_Vector result = GrB_NULL; + + GrB_Info info = + LAGraph_CFPQ_RSM(&result, 3, term, gterm, 1, nt, call, start, final, 0, 1, 4, V, msg); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {2, 3, 4}; + check_reachable(result, expected, 3, V, "TC8"); + } + + GrB_free(&result); + for (int i = 0; i < 3; ++i) + { + GrB_free(&term[i]); + GrB_free(>erm[i]); + } + GrB_free(&nt[0]); + GrB_free(&call[0]); + GrB_free(&start[0]); + GrB_free(&final[0]); + + OK(info); + + teardown(); +} + +void test_TC9_mult_recursive_cycles(void) +{ + setup(); + + GrB_Matrix term[4], nt[3], call[3]; + GrB_Vector start[3], final[3]; + + term[0] = term[1] = term[2] = term[3] = GrB_NULL; + nt[0] = nt[1] = nt[2] = GrB_NULL; + call[0] = call[1] = call[2] = GrB_NULL; + start[0] = start[1] = start[2] = GrB_NULL; + final[0] = final[1] = final[2] = GrB_NULL; + + rsm_complex(term, nt, call, start, final); + + GrB_Index V = 6; + GrB_Matrix gterm[4]; + gterm[0] = gterm[1] = gterm[2] = gterm[3] = GrB_NULL; + for (int i = 0; i < 4; ++i) + OK(GrB_Matrix_new(>erm[i], GrB_BOOL, V, V)); + + // a-cycle (0,1,2) + OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 0, 1)); + OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 1, 2)); + OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 2, 0)); + // b-cycle (2,3) + OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 2, 3)); + OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 3, 2)); + // c-cycle (3,4,5) + OK(GrB_Matrix_setElement_BOOL(gterm[2], true, 3, 4)); + OK(GrB_Matrix_setElement_BOOL(gterm[2], true, 4, 5)); + OK(GrB_Matrix_setElement_BOOL(gterm[2], true, 5, 3)); + // d-cycle + OK(GrB_Matrix_setElement_BOOL(gterm[3], true, 5, 2)); + OK(GrB_Matrix_setElement_BOOL(gterm[3], true, 2, 5)); + + GrB_Vector result = GrB_NULL; + + GrB_Info info = + LAGraph_CFPQ_RSM(&result, 4, term, gterm, 3, nt, call, start, final, 0, 0, 11, V, msg); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {2, 3, 5}; + check_reachable(result, expected, 3, V, "TC9"); + } + + GrB_free(&result); + for (int i = 0; i < 4; ++i) + { + GrB_free(&term[i]); + GrB_free(>erm[i]); + } + for (int i = 0; i < 3; ++i) + { + GrB_free(&nt[i]); + GrB_free(&call[i]); + GrB_free(&start[i]); + GrB_free(&final[i]); + } + + OK(info); + + teardown(); +} + +TEST_LIST = {{"TC1_cyclic_ab", test_TC1_cyclic_ab}, + {"TC2_self_loops", test_TC2_self_loops}, + {"TC3_single_node", test_TC3_single_node}, + {"TC4_aSb_c_loops", test_TC4_aSb_c_loops}, + {"TC5_aSb_c_cycle1", test_TC5_aSb_c_cycle1}, + {"TC4_aSb_c_loops", test_TC4_aSb_c_loops}, + {"TC7_aSb_c_cycle3", test_TC7_aSb_c_cycle3}, + {"TC8_aSb_c_cycle4", test_TC8_aSb_c_cycle4}, + {"TC9_mult_recursive_cycles", test_TC9_mult_recursive_cycles}, + {NULL, NULL}}; diff --git a/experimental/test/test_RSM_reachability.c b/experimental/test/test_RSM_reachability.c deleted file mode 100644 index bf10a389de..0000000000 --- a/experimental/test/test_RSM_reachability.c +++ /dev/null @@ -1,333 +0,0 @@ -#include -#include -#include -#include -#include - -#include "LAGraph.h" - -#include -#include -#include -#include - -#define LEN 512 -#define MAX_LABELS 3 -#define MAX_RESULTS 2000000 - -#define CHECK(info) \ - do \ - { \ - GrB_Info _i = (info); \ - if (_i != GrB_SUCCESS && _i != GrB_NO_VALUE) \ - { \ - fprintf(stderr, "GraphBLAS error %d at %s:%d\n", _i, __FILE__, __LINE__); \ - exit(EXIT_FAILURE); \ - } \ - } while (0) - -char msg[LAGRAPH_MSG_LEN]; - -static void setup(void) -{ - LAGraph_Init(msg); -} - -static void teardown(void) -{ - LAGraph_Finalize(msg); -} - -static GrB_Matrix make_bool_matrix(GrB_Index rows, GrB_Index cols, const GrB_Index *I, - const GrB_Index *J, GrB_Index nvals) -{ - GrB_Matrix A = GrB_NULL; - TEST_CHECK(GrB_SUCCESS == GrB_Matrix_new(&A, GrB_BOOL, rows, cols)); - - for (GrB_Index k = 0; k < nvals; k++) - TEST_CHECK(GrB_SUCCESS == GrB_Matrix_setElement_BOOL(A, true, I[k], J[k])); - return A; -} - -static LAGraph_Graph make_graph(GrB_Matrix *pA) -{ - LAGraph_Graph G = GrB_NULL; - int retval = LAGraph_New(&G, pA, LAGraph_ADJACENCY_DIRECTED, msg); - TEST_CHECK(retval == 0); - TEST_MSG("LAGraph_New failed: %s", msg); - return G; -} - -static void free_graphs(LAGraph_Graph *arr, size_t n) -{ - for (size_t i = 0; i < n; ++i) - if (arr[i] != GrB_NULL) - LAGraph_Delete(&arr[i], msg); -} - -static void check_result(GrB_Vector result, GrB_Index V, const bool *expected, - const char *test_name) -{ - GrB_Index n = 0; - TEST_CHECK(GrB_SUCCESS == GrB_Vector_size(&n, result)); - TEST_CHECK_(n == V, "%s: result vector size %llu != %llu", test_name, (unsigned long long)n, - (unsigned long long)V); - - for (GrB_Index v = 0; v < V; v++) - { - bool val = false; - GrB_Info info = GrB_Vector_extractElement_BOOL(&val, result, v); - - if (expected[v]) - { - TEST_CHECK_(info == GrB_SUCCESS && val, - "%s: vertex %llu should be reachable but is not", test_name, - (unsigned long long)v); - } - else - { - bool absent = (info == GrB_NO_VALUE) || (!val); - TEST_CHECK_(absent, "%s: vertex %llu should not be reachable but is", test_name, - (unsigned long long)v); - } - } -} - -//============================================================================== -// TEST CASES -//============================================================================== - -void test_based(void) -{ - setup(); - - const GrB_Index Q = 4, V = 6; - - // Graph: 0 -a-> 1 -a-> 2 -b-> 3 -b-> 4 -b-> 5 - GrB_Index Ga_I[] = {0, 1}, Ga_J[] = {1, 2}; - GrB_Index Gb_I[] = {2, 3, 4}, Gb_J[] = {3, 4, 5}; - - // RSM: S: q0 -a-> q1 -S-> q2 -b-> q3 - - GrB_Index Na_I[] = {0}, Na_J[] = {1}; - GrB_Index Nb_I[] = {1, 2}, Nb_J[] = {3, 3}; // q1->q3, q2->q3 - - GrB_Index Call_I[] = {1}, Call_J[] = {0}; - GrB_Index Ret_I[] = {3}, Ret_J[] = {2}; - GrB_Index Ns_I[] = {1}, Ns_J[] = {2}; // N_S: q1->q2 - - GrB_Matrix Ga_raw = make_bool_matrix(V, V, Ga_I, Ga_J, 2); - GrB_Matrix Gb_raw = make_bool_matrix(V, V, Gb_I, Gb_J, 3); - GrB_Matrix Na_raw = make_bool_matrix(Q, Q, Na_I, Na_J, 1); - GrB_Matrix Nb_raw = make_bool_matrix(Q, Q, Nb_I, Nb_J, 2); - GrB_Matrix Cs_raw = make_bool_matrix(Q, Q, Call_I, Call_J, 1); - GrB_Matrix Rs_raw = make_bool_matrix(Q, Q, Ret_I, Ret_J, 1); - GrB_Matrix Ns_raw = make_bool_matrix(Q, Q, Ns_I, Ns_J, 1); - - LAGraph_Graph Ga[2], Na[2], Cs[1], Rs[1], Ns[1]; - Ga[0] = make_graph(&Ga_raw); - Ga[1] = make_graph(&Gb_raw); - Na[0] = make_graph(&Na_raw); - Na[1] = make_graph(&Nb_raw); - Cs[0] = make_graph(&Cs_raw); - Rs[0] = make_graph(&Rs_raw); - Ns[0] = make_graph(&Ns_raw); - - GrB_Index QS[] = {0}, QF[] = {3}, Source[] = {0}; - GrB_Vector result = GrB_NULL; - - int retval = - LAGraph_RSM_reachability(&result, 2, Ga, Na, 1, Ns, Cs, Rs, QS, 1, QF, 1, Source, 1, msg); - - TEST_CHECK(GrB_SUCCESS == retval); - TEST_MSG("test_based: retval = %d (%s)", retval, msg); - - const bool expected[6] = {false, false, false, false, true, false}; - check_result(result, V, expected, "test_based"); - - GrB_free(&result); - free_graphs(Ga, 2); - free_graphs(Na, 2); - free_graphs(Cs, 1); - free_graphs(Rs, 1); - free_graphs(Ns, 1); - - teardown(); -} - -void test_balanced_paren(void) -{ - setup(); - - const GrB_Index Q = 5, V = 6; - - // Graph: 0 -a-> 1 -a-> 2 -b-> 3 -b-> 4 -b-> 5 - GrB_Index Ga_I[] = {0, 1, 4}, Ga_J[] = {1, 2, 5}; - GrB_Index Gb_I[] = {2, 3}, Gb_J[] = {3, 4}; - - // RSM: S: q0 -(-> q1 -S-> q2 -)-> q3 -S-> q4 - GrB_Index Na_I[] = {0}, Na_J[] = {1}; - GrB_Index Nb_I[] = {1, 1, 2, 2}, Nb_J[] = {3, 4, 3, 4}; - - GrB_Index Call_I[] = {1, 3}, Call_J[] = {0, 0}; - GrB_Index Ret_I[] = {4, 4}, Ret_J[] = {2, 4}; - GrB_Index Ns_I[] = {1, 3}, Ns_J[] = {2, 4}; - - GrB_Matrix Ga_raw = make_bool_matrix(V, V, Ga_I, Ga_J, 3); - GrB_Matrix Gb_raw = make_bool_matrix(V, V, Gb_I, Gb_J, 2); - GrB_Matrix Na_raw = make_bool_matrix(Q, Q, Na_I, Na_J, 1); - GrB_Matrix Nb_raw = make_bool_matrix(Q, Q, Nb_I, Nb_J, 4); - GrB_Matrix Cs_raw = make_bool_matrix(Q, Q, Call_I, Call_J, 2); - GrB_Matrix Rs_raw = make_bool_matrix(Q, Q, Ret_I, Ret_J, 2); - GrB_Matrix Ns_raw = make_bool_matrix(Q, Q, Ns_I, Ns_J, 2); - - LAGraph_Graph Ga[2], Na[2], Cs[1], Rs[1], Ns[1]; - Ga[0] = make_graph(&Ga_raw); - Ga[1] = make_graph(&Gb_raw); - Na[0] = make_graph(&Na_raw); - Na[1] = make_graph(&Nb_raw); - Cs[0] = make_graph(&Cs_raw); - Rs[0] = make_graph(&Rs_raw); - Ns[0] = make_graph(&Ns_raw); - - GrB_Index QS[] = {0}, QF[] = {4}, Source[] = {0}; - GrB_Vector result = GrB_NULL; - - int retval = - LAGraph_RSM_reachability(&result, 2, Ga, Na, 1, Ns, Cs, Rs, QS, 1, QF, 1, Source, 1, msg); - - TEST_CHECK(GrB_SUCCESS == retval); - TEST_MSG("test_based: retval = %d (%s)", retval, msg); - - const bool expected[6] = {false, false, false, false, true, false}; - check_result(result, V, expected, "test_based"); - - GrB_free(&result); - free_graphs(Ga, 2); - free_graphs(Na, 2); - free_graphs(Cs, 1); - free_graphs(Rs, 1); - free_graphs(Ns, 1); - - teardown(); -} - -void parens_lrlrlr(void) -{ - setup(); - - const GrB_Index Q = 5, V = 7; - - // Graph 0 -(-> 1 -)-> 2 -(-> 3 -)-> 4 -(-> 5 -)-> 6 - GrB_Index Ga_I[] = {0, 2, 4}, Ga_J[] = {1, 3, 5}; - GrB_Index Gb_I[] = {1, 3, 5}, Gb_J[] = {2, 4, 6}; - - // RSM: S: q0 -(-> q1 -S-> q2 -)-> q3 -S-> q4 - GrB_Index Na_I[] = {0}, Na_J[] = {1}; - GrB_Index Nb_I[] = {1, 1, 2, 2}, Nb_J[] = {3, 4, 3, 4}; - - GrB_Index Call_I[] = {1, 3}, Call_J[] = {0, 0}; - GrB_Index Ret_I[] = {4, 4}, Ret_J[] = {2, 4}; - GrB_Index Ns_I[] = {1, 3}, Ns_J[] = {2, 4}; - - GrB_Matrix Ga_raw = make_bool_matrix(V, V, Ga_I, Ga_J, 3); - GrB_Matrix Gb_raw = make_bool_matrix(V, V, Gb_I, Gb_J, 3); - GrB_Matrix Na_raw = make_bool_matrix(Q, Q, Na_I, Na_J, 1); - GrB_Matrix Nb_raw = make_bool_matrix(Q, Q, Nb_I, Nb_J, 4); - GrB_Matrix Cs_raw = make_bool_matrix(Q, Q, Call_I, Call_J, 2); - GrB_Matrix Rs_raw = make_bool_matrix(Q, Q, Ret_I, Ret_J, 2); - GrB_Matrix Ns_raw = make_bool_matrix(Q, Q, Ns_I, Ns_J, 2); - - LAGraph_Graph Ga[2], Na[2], Cs[1], Rs[1], Ns[1]; - Ga[0] = make_graph(&Ga_raw); - Ga[1] = make_graph(&Gb_raw); - Na[0] = make_graph(&Na_raw); - Na[1] = make_graph(&Nb_raw); - Cs[0] = make_graph(&Cs_raw); - Rs[0] = make_graph(&Rs_raw); - Ns[0] = make_graph(&Ns_raw); - - GrB_Index QS[] = {0}, QF[] = {4}, Source[] = {0}; - GrB_Vector result = GrB_NULL; - - int retval = - LAGraph_RSM_reachability(&result, 2, Ga, Na, 1, Ns, Cs, Rs, QS, 1, QF, 1, Source, 1, msg); - - TEST_CHECK(GrB_SUCCESS == retval); - TEST_MSG("test_based: retval = %d (%s)", retval, msg); - - const bool expected[7] = {0, 0, 1, 0, 1, 0, 1}; - check_result(result, V, expected, "test_based"); - - GrB_free(&result); - free_graphs(Ga, 2); - free_graphs(Na, 2); - free_graphs(Cs, 1); - free_graphs(Rs, 1); - free_graphs(Ns, 1); - - teardown(); -} - -void very_big_parens(void) -{ - setup(); - - const GrB_Index Q = 5, V = 13; - - // Graph (()(()()))() - GrB_Index Ga_I[] = {0, 1, 3, 4, 6, 10}, Ga_J[] = {1, 2, 4, 5, 7, 11}; - GrB_Index Gb_I[] = {2, 5, 7, 8, 9, 11}, Gb_J[] = {3, 6, 8, 9, 10, 12}; - - // RSM: S: q0 -(-> q1 -S-> q2 -)-> q3 -S-> q4 - GrB_Index Na_I[] = {0}, Na_J[] = {1}; - GrB_Index Nb_I[] = {1, 1, 2, 2}, Nb_J[] = {3, 4, 3, 4}; - - GrB_Index Call_I[] = {1, 3}, Call_J[] = {0, 0}; - GrB_Index Ret_I[] = {4, 4}, Ret_J[] = {2, 4}; - GrB_Index Ns_I[] = {1, 3}, Ns_J[] = {2, 4}; - - GrB_Matrix Ga_raw = make_bool_matrix(V, V, Ga_I, Ga_J, 6); - GrB_Matrix Gb_raw = make_bool_matrix(V, V, Gb_I, Gb_J, 6); - GrB_Matrix Na_raw = make_bool_matrix(Q, Q, Na_I, Na_J, 1); - GrB_Matrix Nb_raw = make_bool_matrix(Q, Q, Nb_I, Nb_J, 4); - GrB_Matrix Cs_raw = make_bool_matrix(Q, Q, Call_I, Call_J, 2); - GrB_Matrix Rs_raw = make_bool_matrix(Q, Q, Ret_I, Ret_J, 2); - GrB_Matrix Ns_raw = make_bool_matrix(Q, Q, Ns_I, Ns_J, 2); - - LAGraph_Graph Ga[2], Na[2], Cs[1], Rs[1], Ns[1]; - Ga[0] = make_graph(&Ga_raw); - Ga[1] = make_graph(&Gb_raw); - Na[0] = make_graph(&Na_raw); - Na[1] = make_graph(&Nb_raw); - Cs[0] = make_graph(&Cs_raw); - Rs[0] = make_graph(&Rs_raw); - Ns[0] = make_graph(&Ns_raw); - - GrB_Index QS[] = {0}, QF[] = {4}, Source[] = {0}; - GrB_Vector result = GrB_NULL; - - int retval = - LAGraph_RSM_reachability(&result, 2, Ga, Na, 1, Ns, Cs, Rs, QS, 1, QF, 1, Source, 1, msg); - - TEST_CHECK(GrB_SUCCESS == retval); - TEST_MSG("test_based: retval = %d (%s)", retval, msg); - - const bool expected[13] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1}; - check_result(result, V, expected, "test_based"); - - GrB_free(&result); - free_graphs(Ga, 2); - free_graphs(Na, 2); - free_graphs(Cs, 1); - free_graphs(Rs, 1); - free_graphs(Ns, 1); - - teardown(); -} - -TEST_LIST = {{"simple", test_based}, - {"test_balanced_paren", test_balanced_paren}, - {"parens_lrlrlr", parens_lrlrlr}, - {"very_big_parens", very_big_parens}, - {NULL, NULL}}; diff --git a/include/LAGraphX.h b/include/LAGraphX.h index daf5d5727d..ea81c2589e 100644 --- a/include/LAGraphX.h +++ b/include/LAGraphX.h @@ -856,34 +856,6 @@ int LAGraph_RegularPathQuery // nodes reachable from the starting by the char *msg // LAGraph output message ) ; -//**************************************************************************** -LAGRAPHX_PUBLIC -int LAGraph_RSM_reachability( - // output: - GrB_Vector *reachable, - - // input: - size_t num_terminals, // # terminal labels - LAGraph_Graph *G_a, // terminal transitions - LAGraph_Graph *N_a, // terminal transitions - - size_t num_nonterminals, // # nonterminal labels - LAGraph_Graph *N_S, // nonterminal RSM transitions - LAGraph_Graph *Call_S, // call transition matrix - LAGraph_Graph *Ret_S, // return transition matrix - - const GrB_Index *QS, // staring states in RSM - size_t nqs, // # starting states - const GrB_Index *QF, // final states in RSM - size_t nqf, // # final states - - const GrB_Index *Source, // sources vertices - size_t ns, // # source vertices - - char *msg - -); - //**************************************************************************** LAGRAPHX_PUBLIC int LAGraph_VertexCentrality_Triangle // vertex triangle-centrality @@ -1125,6 +1097,23 @@ GrB_Info LAGraph_CFL_reachability char *msg // Message string for error reporting. ) ; +int LAGraph_CFPQ_RSM( + // output: + GrB_Vector *reachable, + + // RSM terminal input: + size_t num_terminals, GrB_Matrix *rsm_term, GrB_Matrix *graph_term, + + // RSM non-terminal input: + size_t num_nonterminals, GrB_Matrix *rsm_nonterm, GrB_Matrix *rsm_call, GrB_Vector *rsm_start, + GrB_Vector *rsm_final, + + size_t start_nonterm, GrB_Index start_vertex, + + GrB_Index Q, GrB_Index V, + + char *msg); + //------------------------------------------------------------------------------ // a simple example of an algorithm //------------------------------------------------------------------------------ From 530be6c9b0775ea4ee361fe10bd9ced36f51da09 Mon Sep 17 00:00:00 2001 From: Sterkachov Date: Fri, 17 Apr 2026 22:48:24 +0300 Subject: [PATCH 07/25] refactor: built rsm_call matrix internally instead of accepting as parameter --- experimental/algorithm/LAGraph_CFPQ_RSM.c | 34 ++++++- experimental/test/test_CFPQ_RSM.c | 111 ++++++---------------- include/LAGraphX.h | 2 +- 3 files changed, 61 insertions(+), 86 deletions(-) diff --git a/experimental/algorithm/LAGraph_CFPQ_RSM.c b/experimental/algorithm/LAGraph_CFPQ_RSM.c index 5d107fc412..c04adc4050 100644 --- a/experimental/algorithm/LAGraph_CFPQ_RSM.c +++ b/experimental/algorithm/LAGraph_CFPQ_RSM.c @@ -35,6 +35,12 @@ GrB_free(&graph_nt[_i]); \ LAGraph_Free((void **)&graph_nt, GrB_NULL); \ } \ + if (rsm_call != NULL) \ + { \ + for (size_t _i = 0; _i < num_nonterminals; ++_i) \ + GrB_free(&rsm_call[_i]); \ + LAGraph_Free((void **)&rsm_call, GrB_NULL); \ + } \ } #define LG_FREE_ALL \ { \ @@ -79,6 +85,26 @@ static GrB_Info s_mxm_chain(GrB_Matrix C, // output accumulator |Q|*|V*V| return GrB_SUCCESS; } + +static GrB_Info build_call_matrix(GrB_Matrix *call, GrB_Matrix rsm_nt, GrB_Vector rsm_start, + GrB_Index Q) +{ + GrB_Vector q_call_mask = GrB_NULL; + + H_TRY(GrB_Matrix_new(call, GrB_BOOL, Q, Q)); + H_TRY(GrB_Vector_new(&q_call_mask, GrB_BOOL, Q)); + + // [q_call, q_ret] -> [q_call] + H_TRY(GrB_reduce(q_call_mask, GrB_NULL, GrB_NULL, GrB_LOR_MONOID_BOOL, rsm_nt, GrB_NULL)); + // |Q|*1 * 1*|Q| -> |Q|*|Q| + H_TRY(GrB_mxm(*call, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, (GrB_Matrix)q_call_mask, + (GrB_Matrix)rsm_start, GrB_DESC_T1)); + + H_TRY(GrB_Vector_free(&q_call_mask)); + + return GrB_SUCCESS; +} + #undef H_TRY int LAGraph_CFPQ_RSM( @@ -89,8 +115,7 @@ int LAGraph_CFPQ_RSM( size_t num_terminals, GrB_Matrix *rsm_term, GrB_Matrix *graph_term, // RSM non-terminal input: - size_t num_nonterminals, GrB_Matrix *rsm_nonterm, GrB_Matrix *rsm_call, GrB_Vector *rsm_start, - GrB_Vector *rsm_final, + size_t num_nonterminals, GrB_Matrix *rsm_nonterm, GrB_Vector *rsm_start, GrB_Vector *rsm_final, size_t start_nonterm, GrB_Index start_vertex, @@ -119,11 +144,11 @@ int LAGraph_CFPQ_RSM( GrB_Matrix *Stack = NULL; GrB_Matrix *graph_nt = NULL; + GrB_Matrix *rsm_call = NULL; LG_ASSERT(reachable != NULL, GrB_NULL_POINTER); LG_ASSERT(rsm_term != NULL, GrB_NULL_POINTER); LG_ASSERT(rsm_nonterm != NULL, GrB_NULL_POINTER); - LG_ASSERT(rsm_call != NULL, GrB_NULL_POINTER); LG_ASSERT(rsm_start != NULL, GrB_NULL_POINTER); LG_ASSERT(rsm_final != NULL, GrB_NULL_POINTER); LG_ASSERT(start_nonterm < num_nonterminals, GrB_INVALID_VALUE); @@ -136,17 +161,20 @@ int LAGraph_CFPQ_RSM( // allocate per-nonterminal arrays LG_TRY(LAGraph_Malloc((void **)&Stack, num_nonterminals, sizeof(GrB_Matrix), msg)); LG_TRY(LAGraph_Malloc((void **)&graph_nt, num_nonterminals, sizeof(GrB_Matrix), msg)); + LG_TRY(LAGraph_Malloc((void **)&rsm_call, num_nonterminals, sizeof(GrB_Matrix), msg)); for (size_t i = 0; i < num_nonterminals; ++i) { Stack[i] = GrB_NULL; graph_nt[i] = GrB_NULL; + rsm_call[i] = GrB_NULL; } for (size_t i = 0; i < num_nonterminals; ++i) { GRB_TRY(GrB_Matrix_new(&Stack[i], GrB_BOOL, Q, VV)); GRB_TRY(GrB_Matrix_new(&graph_nt[i], GrB_BOOL, V, V)); + GRB_TRY(build_call_matrix(&rsm_call[i], rsm_nonterm[i], rsm_start[i], Q)); } // allocate working matrices diff --git a/experimental/test/test_CFPQ_RSM.c b/experimental/test/test_CFPQ_RSM.c index 8dd9953bd6..763a0aad70 100644 --- a/experimental/test/test_CFPQ_RSM.c +++ b/experimental/test/test_CFPQ_RSM.c @@ -19,25 +19,6 @@ static void teardown(void) OK(LAGraph_Finalize(msg)); } -static GrB_Info build_call_matrix(GrB_Matrix *call, GrB_Matrix rsm_nt, GrB_Vector rsm_start, - GrB_Index Q) -{ - GrB_Vector q_call_mask = GrB_NULL; - - GRB_TRY(GrB_Matrix_new(call, GrB_BOOL, Q, Q)); - GRB_TRY(GrB_Vector_new(&q_call_mask, GrB_BOOL, Q)); - - // [q_call, q_ret] -> [q_call] - GRB_TRY(GrB_reduce(q_call_mask, GrB_NULL, GrB_NULL, GrB_LOR_MONOID_BOOL, rsm_nt, GrB_NULL)); - // |Q|*1 * 1*|Q| -> |Q|*|Q| - GRB_TRY(GrB_mxm(*call, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, (GrB_Matrix)q_call_mask, - (GrB_Matrix)rsm_start, GrB_DESC_T1)); - - GRB_TRY(GrB_Vector_free(&q_call_mask)); - - return GrB_SUCCESS; -} - static void check_reachable(GrB_Vector result, GrB_Index *expected, GrB_Index n_expected, GrB_Index V, const char *test_name) { @@ -92,8 +73,7 @@ static void check_reachable(GrB_Vector result, GrB_Index *expected, GrB_Index n_ // RSM builders //============================================================================== // Grammar 1: S -> a S b | a b -static void rsm_aSb_ab(GrB_Matrix *term, GrB_Matrix *nt, GrB_Matrix *call, GrB_Vector *start, - GrB_Vector *final) +static void rsm_aSb_ab(GrB_Matrix *term, GrB_Matrix *nt, GrB_Vector *start, GrB_Vector *final) { GrB_Index Q = 4; OK(GrB_Matrix_new(&term[0], GrB_BOOL, Q, Q)); @@ -108,13 +88,10 @@ static void rsm_aSb_ab(GrB_Matrix *term, GrB_Matrix *nt, GrB_Matrix *call, GrB_V OK(GrB_Matrix_setElement_BOOL(nt[0], true, 1, 2)); // 1 -S-> 2 OK(GrB_Vector_setElement_BOOL(start[0], true, 0)); OK(GrB_Vector_setElement_BOOL(final[0], true, 3)); - - OK(build_call_matrix(call, *nt, *start, Q)); } // Grammar 2: S -> a S b | c -static void rsm_aSb_c(GrB_Matrix *term, GrB_Matrix *nt, GrB_Matrix *call, GrB_Vector *start, - GrB_Vector *final) +static void rsm_aSb_c(GrB_Matrix *term, GrB_Matrix *nt, GrB_Vector *start, GrB_Vector *final) { GrB_Index Q = 4; OK(GrB_Matrix_new(&term[0], GrB_BOOL, Q, Q)); @@ -130,15 +107,12 @@ static void rsm_aSb_c(GrB_Matrix *term, GrB_Matrix *nt, GrB_Matrix *call, GrB_Ve OK(GrB_Matrix_setElement_BOOL(nt[0], true, 1, 2)); // 1 -S-> 2 OK(GrB_Vector_setElement_BOOL(start[0], true, 0)); OK(GrB_Vector_setElement_BOOL(final[0], true, 3)); - - OK(build_call_matrix(call, *nt, *start, Q)); } // Grammar 3: S -> X | Y | S X | S Y // X -> a S b | a b // Y -> c S d | c d -static void rsm_complex(GrB_Matrix *term, GrB_Matrix *nt, GrB_Matrix *call, GrB_Vector *start, - GrB_Vector *final) +static void rsm_complex(GrB_Matrix *term, GrB_Matrix *nt, GrB_Vector *start, GrB_Vector *final) { GrB_Index Q = 11; for (int t = 0; t < 4; ++t) @@ -175,9 +149,6 @@ static void rsm_complex(GrB_Matrix *term, GrB_Matrix *nt, GrB_Matrix *call, GrB_ OK(GrB_Matrix_setElement_BOOL(nt[0], true, 8, 9)); // 8 -S-> 9 OK(GrB_Vector_setElement_BOOL(start[2], true, 7)); OK(GrB_Vector_setElement_BOOL(final[2], true, 10)); - - for (int n = 0; n < 3; n++) - OK(build_call_matrix(&call[n], nt[n], start[n], Q)); } //============================================================================== @@ -193,15 +164,14 @@ void test_TC1_cyclic_ab(void) { setup(); - GrB_Matrix term[2], nt[1], call[1]; + GrB_Matrix term[2], nt[1]; GrB_Vector start[1], final[1]; term[0] = term[1] = GrB_NULL; nt[0] = GrB_NULL; - call[0] = GrB_NULL; start[0] = final[0] = GrB_NULL; - rsm_aSb_ab(term, nt, call, start, final); + rsm_aSb_ab(term, nt, start, final); GrB_Index V = 4; GrB_Matrix gterm[2]; @@ -217,8 +187,7 @@ void test_TC1_cyclic_ab(void) GrB_Vector result = GrB_NULL; - GrB_Info info = - LAGraph_CFPQ_RSM(&result, 2, term, gterm, 1, nt, call, start, final, 0, 1, 4, V, msg); + GrB_Info info = LAGraph_CFPQ_RSM(&result, 2, term, gterm, 1, nt, start, final, 0, 1, 4, V, msg); if (info == GrB_SUCCESS) { GrB_Index expected[] = {0, 3}; @@ -232,7 +201,6 @@ void test_TC1_cyclic_ab(void) GrB_free(>erm[i]); } GrB_free(&nt[0]); - GrB_free(&call[0]); GrB_free(&start[0]); GrB_free(&final[0]); @@ -250,15 +218,14 @@ void test_TC2_self_loops(void) { setup(); - GrB_Matrix term[2], nt[1], call[1]; + GrB_Matrix term[2], nt[1]; GrB_Vector start[1], final[1]; term[0] = term[1] = GrB_NULL; nt[0] = GrB_NULL; - call[0] = GrB_NULL; start[0] = final[0] = GrB_NULL; - rsm_aSb_ab(term, nt, call, start, final); + rsm_aSb_ab(term, nt, start, final); GrB_Index V = 2; GrB_Matrix gterm[2]; @@ -272,8 +239,7 @@ void test_TC2_self_loops(void) GrB_Vector result = GrB_NULL; - GrB_Info info = - LAGraph_CFPQ_RSM(&result, 2, term, gterm, 1, nt, call, start, final, 0, 0, 4, V, msg); + GrB_Info info = LAGraph_CFPQ_RSM(&result, 2, term, gterm, 1, nt, start, final, 0, 0, 4, V, msg); if (info == GrB_SUCCESS) { GrB_Index expected[] = {1}; @@ -287,7 +253,6 @@ void test_TC2_self_loops(void) GrB_free(>erm[i]); } GrB_free(&nt[0]); - GrB_free(&call[0]); GrB_free(&start[0]); GrB_free(&final[0]); @@ -305,15 +270,14 @@ void test_TC3_single_node(void) { setup(); - GrB_Matrix term[2], nt[1], call[1]; + GrB_Matrix term[2], nt[1]; GrB_Vector start[1], final[1]; term[0] = term[1] = GrB_NULL; nt[0] = GrB_NULL; - call[0] = GrB_NULL; start[0] = final[0] = GrB_NULL; - rsm_aSb_ab(term, nt, call, start, final); + rsm_aSb_ab(term, nt, start, final); GrB_Index V = 1; GrB_Matrix gterm[2]; @@ -326,8 +290,7 @@ void test_TC3_single_node(void) GrB_Vector result = GrB_NULL; - GrB_Info info = - LAGraph_CFPQ_RSM(&result, 2, term, gterm, 1, nt, call, start, final, 0, 0, 4, V, msg); + GrB_Info info = LAGraph_CFPQ_RSM(&result, 2, term, gterm, 1, nt, start, final, 0, 0, 4, V, msg); if (info == GrB_SUCCESS) { GrB_Index expected[] = {0}; @@ -341,7 +304,6 @@ void test_TC3_single_node(void) GrB_free(>erm[i]); } GrB_free(&nt[0]); - GrB_free(&call[0]); GrB_free(&start[0]); GrB_free(&final[0]); @@ -359,15 +321,14 @@ void test_TC4_aSb_c_loops(void) { setup(); - GrB_Matrix term[3], nt[1], call[1]; + GrB_Matrix term[3], nt[1]; GrB_Vector start[1], final[1]; term[0] = term[1] = term[2] = GrB_NULL; nt[0] = GrB_NULL; - call[0] = GrB_NULL; start[0] = final[0] = GrB_NULL; - rsm_aSb_c(term, nt, call, start, final); + rsm_aSb_c(term, nt, start, final); GrB_Index V = 2; GrB_Matrix gterm[3]; @@ -381,8 +342,7 @@ void test_TC4_aSb_c_loops(void) GrB_Vector result = GrB_NULL; - GrB_Info info = - LAGraph_CFPQ_RSM(&result, 3, term, gterm, 1, nt, call, start, final, 0, 0, 4, V, msg); + GrB_Info info = LAGraph_CFPQ_RSM(&result, 3, term, gterm, 1, nt, start, final, 0, 0, 4, V, msg); if (info == GrB_SUCCESS) { GrB_Index expected[] = {1}; @@ -396,7 +356,6 @@ void test_TC4_aSb_c_loops(void) GrB_free(>erm[i]); } GrB_free(&nt[0]); - GrB_free(&call[0]); GrB_free(&start[0]); GrB_free(&final[0]); @@ -414,15 +373,14 @@ void test_TC5_aSb_c_cycle1(void) { setup(); - GrB_Matrix term[3], nt[1], call[1]; + GrB_Matrix term[3], nt[1]; GrB_Vector start[1], final[1]; term[0] = term[1] = term[2] = GrB_NULL; nt[0] = GrB_NULL; - call[0] = GrB_NULL; start[0] = final[0] = GrB_NULL; - rsm_aSb_c(term, nt, call, start, final); + rsm_aSb_c(term, nt, start, final); GrB_Index V = 4; GrB_Matrix gterm[3]; @@ -438,8 +396,7 @@ void test_TC5_aSb_c_cycle1(void) GrB_Vector result = GrB_NULL; - GrB_Info info = - LAGraph_CFPQ_RSM(&result, 3, term, gterm, 1, nt, call, start, final, 0, 1, 4, V, msg); + GrB_Info info = LAGraph_CFPQ_RSM(&result, 3, term, gterm, 1, nt, start, final, 0, 1, 4, V, msg); if (info == GrB_SUCCESS) { GrB_Index expected[] = {3}; @@ -453,7 +410,6 @@ void test_TC5_aSb_c_cycle1(void) GrB_free(>erm[i]); } GrB_free(&nt[0]); - GrB_free(&call[0]); GrB_free(&start[0]); GrB_free(&final[0]); @@ -470,15 +426,14 @@ void test_TC6_aSb_c_cycle2(void) { setup(); - GrB_Matrix term[3], nt[1], call[1]; + GrB_Matrix term[3], nt[1]; GrB_Vector start[1], final[1]; term[0] = term[1] = term[2] = GrB_NULL; nt[0] = GrB_NULL; - call[0] = GrB_NULL; start[0] = final[0] = GrB_NULL; - rsm_aSb_c(term, nt, call, start, final); + rsm_aSb_c(term, nt, start, final); GrB_Index V = 3; GrB_Matrix gterm[3]; @@ -493,8 +448,7 @@ void test_TC6_aSb_c_cycle2(void) GrB_Vector result = GrB_NULL; - GrB_Info info = - LAGraph_CFPQ_RSM(&result, 3, term, gterm, 1, nt, call, start, final, 0, 1, 4, V, msg); + GrB_Info info = LAGraph_CFPQ_RSM(&result, 3, term, gterm, 1, nt, start, final, 0, 1, 4, V, msg); if (info == GrB_SUCCESS) { GrB_Index expected[] = {2}; @@ -508,7 +462,6 @@ void test_TC6_aSb_c_cycle2(void) GrB_free(>erm[i]); } GrB_free(&nt[0]); - GrB_free(&call[0]); GrB_free(&start[0]); GrB_free(&final[0]); @@ -541,15 +494,14 @@ void test_TC7_aSb_c_cycle3(void) { setup(); - GrB_Matrix term[3], nt[1], call[1]; + GrB_Matrix term[3], nt[1]; GrB_Vector start[1], final[1]; term[0] = term[1] = term[2] = GrB_NULL; nt[0] = GrB_NULL; - call[0] = GrB_NULL; start[0] = final[0] = GrB_NULL; - rsm_aSb_c(term, nt, call, start, final); + rsm_aSb_c(term, nt, start, final); GrB_Index V = 5; GrB_Matrix gterm[3]; @@ -558,8 +510,7 @@ void test_TC7_aSb_c_cycle3(void) GrB_Vector result = GrB_NULL; - GrB_Info info = - LAGraph_CFPQ_RSM(&result, 3, term, gterm, 1, nt, call, start, final, 0, 0, 4, V, msg); + GrB_Info info = LAGraph_CFPQ_RSM(&result, 3, term, gterm, 1, nt, start, final, 0, 0, 4, V, msg); if (info == GrB_SUCCESS) { GrB_Index expected[] = {2, 3, 4}; @@ -573,7 +524,6 @@ void test_TC7_aSb_c_cycle3(void) GrB_free(>erm[i]); } GrB_free(&nt[0]); - GrB_free(&call[0]); GrB_free(&start[0]); GrB_free(&final[0]); @@ -599,7 +549,7 @@ void test_TC8_aSb_c_cycle4(void) call[0] = GrB_NULL; start[0] = final[0] = GrB_NULL; - rsm_aSb_c(term, nt, call, start, final); + rsm_aSb_c(term, nt, start, final); GrB_Index V = 5; GrB_Matrix gterm[3]; @@ -608,8 +558,7 @@ void test_TC8_aSb_c_cycle4(void) GrB_Vector result = GrB_NULL; - GrB_Info info = - LAGraph_CFPQ_RSM(&result, 3, term, gterm, 1, nt, call, start, final, 0, 1, 4, V, msg); + GrB_Info info = LAGraph_CFPQ_RSM(&result, 3, term, gterm, 1, nt, start, final, 0, 1, 4, V, msg); if (info == GrB_SUCCESS) { GrB_Index expected[] = {2, 3, 4}; @@ -636,16 +585,15 @@ void test_TC9_mult_recursive_cycles(void) { setup(); - GrB_Matrix term[4], nt[3], call[3]; + GrB_Matrix term[4], nt[3]; GrB_Vector start[3], final[3]; term[0] = term[1] = term[2] = term[3] = GrB_NULL; nt[0] = nt[1] = nt[2] = GrB_NULL; - call[0] = call[1] = call[2] = GrB_NULL; start[0] = start[1] = start[2] = GrB_NULL; final[0] = final[1] = final[2] = GrB_NULL; - rsm_complex(term, nt, call, start, final); + rsm_complex(term, nt, start, final); GrB_Index V = 6; GrB_Matrix gterm[4]; @@ -671,7 +619,7 @@ void test_TC9_mult_recursive_cycles(void) GrB_Vector result = GrB_NULL; GrB_Info info = - LAGraph_CFPQ_RSM(&result, 4, term, gterm, 3, nt, call, start, final, 0, 0, 11, V, msg); + LAGraph_CFPQ_RSM(&result, 4, term, gterm, 3, nt, start, final, 0, 0, 11, V, msg); if (info == GrB_SUCCESS) { GrB_Index expected[] = {2, 3, 5}; @@ -687,7 +635,6 @@ void test_TC9_mult_recursive_cycles(void) for (int i = 0; i < 3; ++i) { GrB_free(&nt[i]); - GrB_free(&call[i]); GrB_free(&start[i]); GrB_free(&final[i]); } diff --git a/include/LAGraphX.h b/include/LAGraphX.h index ea81c2589e..2a5a7249a0 100644 --- a/include/LAGraphX.h +++ b/include/LAGraphX.h @@ -1105,7 +1105,7 @@ int LAGraph_CFPQ_RSM( size_t num_terminals, GrB_Matrix *rsm_term, GrB_Matrix *graph_term, // RSM non-terminal input: - size_t num_nonterminals, GrB_Matrix *rsm_nonterm, GrB_Matrix *rsm_call, GrB_Vector *rsm_start, + size_t num_nonterminals, GrB_Matrix *rsm_nonterm, GrB_Vector *rsm_start, GrB_Vector *rsm_final, size_t start_nonterm, GrB_Index start_vertex, From fca24cacdeef5efa01cb7bc857f9c1d328cd0802 Mon Sep 17 00:00:00 2001 From: Sterkachov Date: Sun, 19 Apr 2026 15:32:01 +0300 Subject: [PATCH 08/25] refactor: consolidate RSM parameters into struct and modularize tests --- experimental/algorithm/LAGraph_CFPQ_RSM.c | 107 ++-- experimental/test/test_CFPQ_RSM.c | 698 ++++++++++------------ include/LAGraphX.h | 30 +- 3 files changed, 386 insertions(+), 449 deletions(-) diff --git a/experimental/algorithm/LAGraph_CFPQ_RSM.c b/experimental/algorithm/LAGraph_CFPQ_RSM.c index c04adc4050..35ac23be58 100644 --- a/experimental/algorithm/LAGraph_CFPQ_RSM.c +++ b/experimental/algorithm/LAGraph_CFPQ_RSM.c @@ -25,21 +25,27 @@ GrB_free(&entry_vec); \ if (Stack != NULL) \ { \ - for (size_t _i = 0; _i < num_nonterminals; ++_i) \ + for (size_t _i = 0; _i < num_nonterm; ++_i) \ GrB_free(&Stack[_i]); \ - LAGraph_Free((void **)&Stack, GrB_NULL); \ + LAGraph_Free((void **)&Stack, msg); \ } \ if (graph_nt != NULL) \ { \ - for (size_t _i = 0; _i < num_nonterminals; ++_i) \ + for (size_t _i = 0; _i < num_nonterm; ++_i) \ GrB_free(&graph_nt[_i]); \ - LAGraph_Free((void **)&graph_nt, GrB_NULL); \ + LAGraph_Free((void **)&graph_nt, msg); \ + } \ + if (rsm_start != NULL) \ + { \ + for (size_t _i = 0; _i < num_nonterm; ++_i) \ + GrB_free(&rsm_start[_i]); \ + LAGraph_Free((void **)&rsm_start, msg); \ } \ if (rsm_call != NULL) \ { \ - for (size_t _i = 0; _i < num_nonterminals; ++_i) \ + for (size_t _i = 0; _i < num_nonterm; ++_i) \ GrB_free(&rsm_call[_i]); \ - LAGraph_Free((void **)&rsm_call, GrB_NULL); \ + LAGraph_Free((void **)&rsm_call, msg); \ } \ } #define LG_FREE_ALL \ @@ -89,42 +95,51 @@ static GrB_Info s_mxm_chain(GrB_Matrix C, // output accumulator |Q|*|V*V| static GrB_Info build_call_matrix(GrB_Matrix *call, GrB_Matrix rsm_nt, GrB_Vector rsm_start, GrB_Index Q) { - GrB_Vector q_call_mask = GrB_NULL; + GrB_Vector call_mask = GrB_NULL; H_TRY(GrB_Matrix_new(call, GrB_BOOL, Q, Q)); - H_TRY(GrB_Vector_new(&q_call_mask, GrB_BOOL, Q)); + H_TRY(GrB_Vector_new(&call_mask, GrB_BOOL, Q)); // [q_call, q_ret] -> [q_call] - H_TRY(GrB_reduce(q_call_mask, GrB_NULL, GrB_NULL, GrB_LOR_MONOID_BOOL, rsm_nt, GrB_NULL)); + H_TRY(GrB_reduce(call_mask, GrB_NULL, GrB_NULL, GrB_LOR_MONOID_BOOL, rsm_nt, GrB_NULL)); // |Q|*1 * 1*|Q| -> |Q|*|Q| - H_TRY(GrB_mxm(*call, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, (GrB_Matrix)q_call_mask, + H_TRY(GrB_mxm(*call, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, (GrB_Matrix)call_mask, (GrB_Matrix)rsm_start, GrB_DESC_T1)); - H_TRY(GrB_Vector_free(&q_call_mask)); + H_TRY(GrB_Vector_free(&call_mask)); return GrB_SUCCESS; } #undef H_TRY - -int LAGraph_CFPQ_RSM( - // output: - GrB_Vector *reachable, - - // RSM terminal input: - size_t num_terminals, GrB_Matrix *rsm_term, GrB_Matrix *graph_term, - - // RSM non-terminal input: - size_t num_nonterminals, GrB_Matrix *rsm_nonterm, GrB_Vector *rsm_start, GrB_Vector *rsm_final, - - size_t start_nonterm, GrB_Index start_vertex, - - GrB_Index Q, GrB_Index V, - - char *msg) +typedef struct +{ + GrB_Index state_count; // Q + GrB_Index terminal_count; // num_terminals + GrB_Index nonterminal_count; // num_nonterminals + GrB_Index start_nonterminal; // start_nonterm + GrB_Matrix *terminal_matrices; // rsm_term + GrB_Matrix *nonterminal_matrices; // rsm_nonterm + GrB_Index *start_states; // rsm_start + GrB_Vector *final_states; // rsm_final +} RSM; + +int LAGraph_CFPQ_RSM(GrB_Vector *reachable, RSM *rsm, GrB_Matrix *graph_term, + GrB_Index start_vertex, GrB_Index V, char *msg) { LG_CLEAR_MSG; + // shortcuts + GrB_Index VV = V * V; + GrB_Index Q = rsm->state_count; + GrB_Index num_term = rsm->terminal_count; + GrB_Index num_nonterm = rsm->nonterminal_count; + GrB_Index start_nonterm = rsm->start_nonterminal; + GrB_Matrix *rsm_term = rsm->terminal_matrices; + GrB_Matrix *rsm_nonterm = rsm->nonterminal_matrices; + GrB_Index *start_states = rsm->start_states; + GrB_Vector *final_states = rsm->final_states; + // working matrices / vectors GrB_Matrix M = GrB_NULL; GrB_Matrix P = GrB_NULL; @@ -145,35 +160,40 @@ int LAGraph_CFPQ_RSM( GrB_Matrix *Stack = NULL; GrB_Matrix *graph_nt = NULL; GrB_Matrix *rsm_call = NULL; + GrB_Vector *rsm_start = NULL; // rsm_start[S][i] = true, iff start_states[S] = i LG_ASSERT(reachable != NULL, GrB_NULL_POINTER); LG_ASSERT(rsm_term != NULL, GrB_NULL_POINTER); LG_ASSERT(rsm_nonterm != NULL, GrB_NULL_POINTER); - LG_ASSERT(rsm_start != NULL, GrB_NULL_POINTER); - LG_ASSERT(rsm_final != NULL, GrB_NULL_POINTER); - LG_ASSERT(start_nonterm < num_nonterminals, GrB_INVALID_VALUE); + LG_ASSERT(start_states != NULL, GrB_NULL_POINTER); + LG_ASSERT(final_states != NULL, GrB_NULL_POINTER); + LG_ASSERT(start_nonterm < num_nonterm, GrB_INVALID_VALUE); LG_ASSERT(start_vertex < V, GrB_INVALID_VALUE); *reachable = GrB_NULL; - GrB_Index VV = V * V; - // allocate per-nonterminal arrays - LG_TRY(LAGraph_Malloc((void **)&Stack, num_nonterminals, sizeof(GrB_Matrix), msg)); - LG_TRY(LAGraph_Malloc((void **)&graph_nt, num_nonterminals, sizeof(GrB_Matrix), msg)); - LG_TRY(LAGraph_Malloc((void **)&rsm_call, num_nonterminals, sizeof(GrB_Matrix), msg)); + LG_TRY(LAGraph_Malloc((void **)&Stack, rsm->nonterminal_count, sizeof(GrB_Matrix), msg)); + LG_TRY(LAGraph_Malloc((void **)&graph_nt, rsm->nonterminal_count, sizeof(GrB_Matrix), msg)); + LG_TRY(LAGraph_Malloc((void **)&rsm_start, rsm->nonterminal_count, sizeof(GrB_Vector), msg)); + LG_TRY(LAGraph_Malloc((void **)&rsm_call, rsm->nonterminal_count, sizeof(GrB_Matrix), msg)); - for (size_t i = 0; i < num_nonterminals; ++i) + for (size_t i = 0; i < num_nonterm; ++i) { Stack[i] = GrB_NULL; graph_nt[i] = GrB_NULL; + rsm_start[i] = GrB_NULL; rsm_call[i] = GrB_NULL; } - for (size_t i = 0; i < num_nonterminals; ++i) + for (size_t i = 0; i < num_nonterm; ++i) { GRB_TRY(GrB_Matrix_new(&Stack[i], GrB_BOOL, Q, VV)); GRB_TRY(GrB_Matrix_new(&graph_nt[i], GrB_BOOL, V, V)); + + GRB_TRY(GrB_Vector_new(&rsm_start[i], GrB_BOOL, Q)); + GrB_Vector_setElement_BOOL(rsm_start[i], true, start_states[i]); + GRB_TRY(build_call_matrix(&rsm_call[i], rsm_nonterm[i], rsm_start[i], Q)); } @@ -231,13 +251,12 @@ int LAGraph_CFPQ_RSM( // main fixed-point loop GrB_Index m_nvals = 0; GRB_TRY(GrB_Matrix_nvals(&m_nvals, M)); - while (m_nvals > 0) { // PHASE 1: TERMINAL TRANSITIONS GRB_TRY(GrB_Matrix_clear(M_term)); - for (size_t i = 0; i < num_terminals; ++i) + for (size_t i = 0; i < num_term; ++i) { if (rsm_term[i] == GrB_NULL || graph_term[i] == GrB_NULL) continue; @@ -246,7 +265,7 @@ int LAGraph_CFPQ_RSM( // PHASE 2: NON-TERMINAL TRANSITIONS GRB_TRY(GrB_Matrix_clear(M_nonterm)); - for (size_t i = 0; i < num_nonterminals; ++i) + for (size_t i = 0; i < num_nonterm; ++i) { if (rsm_nonterm[i] == GrB_NULL) continue; @@ -255,7 +274,7 @@ int LAGraph_CFPQ_RSM( // PHASE 3: CALL TRANSITIONS GRB_TRY(GrB_Matrix_clear(M_call)); - for (size_t i = 0; i < num_nonterminals; ++i) + for (size_t i = 0; i < num_nonterm; ++i) { if (rsm_call[i] == GrB_NULL || rsm_nonterm[i] == GrB_NULL) continue; @@ -309,15 +328,15 @@ int LAGraph_CFPQ_RSM( // PHASE 4: RETURN TRANSITIONS GRB_TRY(GrB_Matrix_clear(M_return)); - for (size_t i = 0; i < num_nonterminals; ++i) + for (size_t i = 0; i < num_nonterm; ++i) { - if (rsm_final[i] == GrB_NULL) + if (final_states[i] == GrB_NULL) continue; // 1*|Q| * |Q|*|V*V| -> 1*|V*V| GRB_TRY(GrB_Matrix_clear(new_edges)); GRB_TRY(GrB_mxm(new_edges, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, - (GrB_Matrix)rsm_final[i], M, GrB_DESC_T0)); + (GrB_Matrix)final_states[i], M, GrB_DESC_T0)); GRB_TRY(GxB_Matrix_reshape(new_edges, false, V, V, GrB_NULL)); diff --git a/experimental/test/test_CFPQ_RSM.c b/experimental/test/test_CFPQ_RSM.c index 763a0aad70..344c87ccc5 100644 --- a/experimental/test/test_CFPQ_RSM.c +++ b/experimental/test/test_CFPQ_RSM.c @@ -2,25 +2,101 @@ #include "LAGraphX.h" #include "LAGraph_test.h" #include +#include char msg[LAGRAPH_MSG_LEN]; +static RSM *rsm = NULL; +static size_t V = 0; +static size_t n_graph_term = 0; +static GrB_Matrix *graph_term = NULL; +static GrB_Vector result = GrB_NULL; + //============================================================================== // test setup / teardown //============================================================================== static void setup(void) { - OK(LAGraph_Init(msg)); + LAGraph_Init(msg); } static void teardown(void) { - OK(LAGraph_Finalize(msg)); + LAGraph_Finalize(msg); +} + +static RSM *alloc_rsm(GrB_Index state_count, GrB_Index term_count, GrB_Index nonterm_count, + GrB_Index start_nonterm) +{ + RSM *r = NULL; + LAGraph_Malloc((void **)&r, 1, sizeof(RSM), msg); + if (r == NULL) + return NULL; + + r->state_count = state_count; + r->terminal_count = term_count; + r->nonterminal_count = nonterm_count; + r->start_nonterminal = start_nonterm; + + LAGraph_Calloc((void **)&r->terminal_matrices, term_count, sizeof(GrB_Matrix), msg); + LAGraph_Calloc((void **)&r->nonterminal_matrices, nonterm_count, sizeof(GrB_Matrix), msg); + LAGraph_Calloc((void **)&r->start_states, nonterm_count, sizeof(GrB_Index), msg); + LAGraph_Calloc((void **)&r->final_states, nonterm_count, sizeof(GrB_Vector), msg); + + return r; +} + +static void free_rsm(void) +{ + if (rsm == NULL) + return; + + if (rsm->terminal_matrices != NULL) + { + for (size_t i = 0; i < rsm->terminal_count; ++i) + GrB_free(&rsm->terminal_matrices[i]); + LAGraph_Free((void **)&rsm->terminal_matrices, msg); + } + if (rsm->nonterminal_matrices != NULL) + { + for (size_t i = 0; i < rsm->nonterminal_count; ++i) + GrB_free(&rsm->nonterminal_matrices[i]); + LAGraph_Free((void **)&rsm->nonterminal_matrices, msg); + } + + if (rsm->nonterminal_matrices != NULL) + { + for (size_t i = 0; i < rsm->nonterminal_count; ++i) + { + GrB_free(&rsm->final_states[i]); + } + LAGraph_Free((void **)&rsm->final_states, msg); + } + + LAGraph_Free((void **)&rsm->start_states, msg); + LAGraph_Free((void **)&rsm, msg); +} + +static void free_graph(void) +{ + if (graph_term == NULL) + return; + for (size_t i = 0; i < n_graph_term; ++i) + GrB_free(&graph_term[i]); + LAGraph_Free((void **)&graph_term, msg); + n_graph_term = 0; + V = 0; +} + +static void free_workspace(void) +{ + GrB_free(&result); + free_rsm(); + free_graph(); } -static void check_reachable(GrB_Vector result, GrB_Index *expected, GrB_Index n_expected, - GrB_Index V, const char *test_name) +static void check_reachable(GrB_Index *expected, GrB_Index n_expected, const char *test_name) { GrB_Index nvals = 0; OK(GrB_Vector_nvals(&nvals, result)); @@ -46,6 +122,8 @@ static void check_reachable(GrB_Vector result, GrB_Index *expected, GrB_Index n_ for (GrB_Index k = 0; k < nvals; ++k) if (val[k]) got[idx[k]] = true; + free(idx); + free(val); } // every expected vertex must be reachable @@ -65,92 +143,210 @@ static void check_reachable(GrB_Vector result, GrB_Index *expected, GrB_Index n_ (unsigned long long)n_expected, (unsigned long long)actual_count); free(got); - free(idx); - free(val); } //============================================================================== // RSM builders //============================================================================== // Grammar 1: S -> a S b | a b -static void rsm_aSb_ab(GrB_Matrix *term, GrB_Matrix *nt, GrB_Vector *start, GrB_Vector *final) +static void init_rsm_aSb_ab(void) { GrB_Index Q = 4; - OK(GrB_Matrix_new(&term[0], GrB_BOOL, Q, Q)); - OK(GrB_Matrix_new(&term[1], GrB_BOOL, Q, Q)); - OK(GrB_Matrix_new(&nt[0], GrB_BOOL, Q, Q)); - OK(GrB_Vector_new(&start[0], GrB_BOOL, Q)); - OK(GrB_Vector_new(&final[0], GrB_BOOL, Q)); - - OK(GrB_Matrix_setElement_BOOL(term[0], true, 0, 1)); // 0 -a-> 1 - OK(GrB_Matrix_setElement_BOOL(term[1], true, 1, 3)); // 1 -b-> 3 - OK(GrB_Matrix_setElement_BOOL(term[1], true, 2, 3)); // 2 -b-> 3 - OK(GrB_Matrix_setElement_BOOL(nt[0], true, 1, 2)); // 1 -S-> 2 - OK(GrB_Vector_setElement_BOOL(start[0], true, 0)); - OK(GrB_Vector_setElement_BOOL(final[0], true, 3)); + rsm = alloc_rsm(Q, /*term_count=*/2, /*nonterm_count=*/1, /*start_nonterm=*/0); + + OK(GrB_Matrix_new(&rsm->terminal_matrices[0], GrB_BOOL, Q, Q)); + OK(GrB_Matrix_new(&rsm->terminal_matrices[1], GrB_BOOL, Q, Q)); + OK(GrB_Matrix_new(&rsm->nonterminal_matrices[0], GrB_BOOL, Q, Q)); + OK(GrB_Vector_new(&rsm->final_states[0], GrB_BOOL, Q)); + + OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[0], true, 0, 1)); // 0 -a-> 1 + OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[1], true, 1, 3)); // 1 -b-> 3 + OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[1], true, 2, 3)); // 2 -b-> 3 + OK(GrB_Matrix_setElement_BOOL(rsm->nonterminal_matrices[0], true, 1, 2)); // 1 -S-> 2 + rsm->start_states[0] = 0; + OK(GrB_Vector_setElement_BOOL(rsm->final_states[0], true, 3)); } // Grammar 2: S -> a S b | c -static void rsm_aSb_c(GrB_Matrix *term, GrB_Matrix *nt, GrB_Vector *start, GrB_Vector *final) +static void init_rsm_aSb_c(void) { GrB_Index Q = 4; - OK(GrB_Matrix_new(&term[0], GrB_BOOL, Q, Q)); - OK(GrB_Matrix_new(&term[1], GrB_BOOL, Q, Q)); - OK(GrB_Matrix_new(&term[2], GrB_BOOL, Q, Q)); - OK(GrB_Matrix_new(&nt[0], GrB_BOOL, Q, Q)); - OK(GrB_Vector_new(&start[0], GrB_BOOL, Q)); - OK(GrB_Vector_new(&final[0], GrB_BOOL, Q)); - - OK(GrB_Matrix_setElement_BOOL(term[0], true, 0, 1)); // 0 -a-> 1 - OK(GrB_Matrix_setElement_BOOL(term[2], true, 0, 3)); // 1 -c-> 3 - OK(GrB_Matrix_setElement_BOOL(term[1], true, 2, 3)); // 2 -b-> 3 - OK(GrB_Matrix_setElement_BOOL(nt[0], true, 1, 2)); // 1 -S-> 2 - OK(GrB_Vector_setElement_BOOL(start[0], true, 0)); - OK(GrB_Vector_setElement_BOOL(final[0], true, 3)); + rsm = alloc_rsm(Q, /*term_count=*/3, /*nonterm_count=*/1, /*start_nonterm=*/0); + + OK(GrB_Matrix_new(&rsm->terminal_matrices[0], GrB_BOOL, Q, Q)); + OK(GrB_Matrix_new(&rsm->terminal_matrices[1], GrB_BOOL, Q, Q)); + OK(GrB_Matrix_new(&rsm->terminal_matrices[2], GrB_BOOL, Q, Q)); + OK(GrB_Matrix_new(&rsm->nonterminal_matrices[0], GrB_BOOL, Q, Q)); + + OK(GrB_Vector_new(&rsm->final_states[0], GrB_BOOL, Q)); + + OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[0], true, 0, 1)); // 0 -a-> 1 + OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[2], true, 0, 3)); // 0 -c-> 3 + OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[1], true, 2, 3)); // 2 -b-> 3 + OK(GrB_Matrix_setElement_BOOL(rsm->nonterminal_matrices[0], true, 1, 2)); // 1 -S-> 2 + rsm->start_states[0] = 0; + OK(GrB_Vector_setElement_BOOL(rsm->final_states[0], true, 3)); } // Grammar 3: S -> X | Y | S X | S Y // X -> a S b | a b // Y -> c S d | c d -static void rsm_complex(GrB_Matrix *term, GrB_Matrix *nt, GrB_Vector *start, GrB_Vector *final) +static void init_rsm_complex(void) { GrB_Index Q = 11; + rsm = alloc_rsm(Q, /*term_count=*/4, /*nonterm_count=*/3, /*start_nonterm=*/0); for (int t = 0; t < 4; ++t) - OK(GrB_Matrix_new(&term[t], GrB_BOOL, Q, Q)); + OK(GrB_Matrix_new(&rsm->terminal_matrices[t], GrB_BOOL, Q, Q)); for (int n = 0; n < 3; ++n) { - OK(GrB_Matrix_new(&nt[n], GrB_BOOL, Q, Q)); - OK(GrB_Vector_new(&start[n], GrB_BOOL, Q)); - OK(GrB_Vector_new(&final[n], GrB_BOOL, Q)); + OK(GrB_Matrix_new(&rsm->nonterminal_matrices[n], GrB_BOOL, Q, Q)); + OK(GrB_Vector_new(&rsm->final_states[n], GrB_BOOL, Q)); } // --- S Component (States 0-2) --- // S -> X | Y | S X | S Y - OK(GrB_Matrix_setElement_BOOL(nt[1], true, 0, 2)); // 0 -X-> 2 - OK(GrB_Matrix_setElement_BOOL(nt[2], true, 0, 2)); // 0 -Y-> 2 - OK(GrB_Matrix_setElement_BOOL(nt[0], true, 0, 1)); // 0 -S-> 1 - OK(GrB_Matrix_setElement_BOOL(nt[1], true, 1, 2)); // 1 -X-> 2 - OK(GrB_Matrix_setElement_BOOL(nt[2], true, 1, 2)); // 1 -Y-> 2 - OK(GrB_Vector_setElement_BOOL(start[0], true, 0)); - OK(GrB_Vector_setElement_BOOL(final[0], true, 2)); + OK(GrB_Matrix_setElement_BOOL(rsm->nonterminal_matrices[1], true, 0, 2)); // 0 -X-> 2 + OK(GrB_Matrix_setElement_BOOL(rsm->nonterminal_matrices[2], true, 0, 2)); // 0 -Y-> 2 + OK(GrB_Matrix_setElement_BOOL(rsm->nonterminal_matrices[0], true, 0, 1)); // 0 -S-> 1 + OK(GrB_Matrix_setElement_BOOL(rsm->nonterminal_matrices[1], true, 1, 2)); // 1 -X-> 2 + OK(GrB_Matrix_setElement_BOOL(rsm->nonterminal_matrices[2], true, 1, 2)); // 1 -Y-> 2 + rsm->start_states[0] = 0; + OK(GrB_Vector_setElement_BOOL(rsm->final_states[0], true, 2)); // --- X Component (States 3-6) --- - OK(GrB_Matrix_setElement_BOOL(term[0], true, 3, 4)); // 3 -a-> 4 - OK(GrB_Matrix_setElement_BOOL(term[1], true, 4, 6)); // 4 -b-> 6 - OK(GrB_Matrix_setElement_BOOL(term[1], true, 5, 6)); // 5 -b-> 6 - OK(GrB_Matrix_setElement_BOOL(nt[0], true, 4, 5)); // 4 -S-> 5 - OK(GrB_Vector_setElement_BOOL(start[1], true, 3)); - OK(GrB_Vector_setElement_BOOL(final[1], true, 6)); + OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[0], true, 3, 4)); // 3 -a-> 4 + OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[1], true, 4, 6)); // 4 -b-> 6 + OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[1], true, 5, 6)); // 5 -b-> 6 + OK(GrB_Matrix_setElement_BOOL(rsm->nonterminal_matrices[0], true, 4, 5)); // 4 -S-> 5 + rsm->start_states[1] = 3; + OK(GrB_Vector_setElement_BOOL(rsm->final_states[1], true, 6)); // --- Y Component (States 7-10) - OK(GrB_Matrix_setElement_BOOL(term[2], true, 7, 8)); // 7 -c-> 8 - OK(GrB_Matrix_setElement_BOOL(term[3], true, 8, 10)); // 8 -d-> 10 - OK(GrB_Matrix_setElement_BOOL(term[3], true, 9, 10)); // 9 -d-> 10 - OK(GrB_Matrix_setElement_BOOL(nt[0], true, 8, 9)); // 8 -S-> 9 - OK(GrB_Vector_setElement_BOOL(start[2], true, 7)); - OK(GrB_Vector_setElement_BOOL(final[2], true, 10)); + OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[2], true, 7, 8)); // 7 -c-> 8 + OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[3], true, 8, 10)); // 8 -d-> 10 + OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[3], true, 9, 10)); // 9 -d-> 10 + OK(GrB_Matrix_setElement_BOOL(rsm->nonterminal_matrices[0], true, 8, 9)); // 8 -S-> 9 + rsm->start_states[2] = 7; + OK(GrB_Vector_setElement_BOOL(rsm->final_states[2], true, 10)); } +//============================================================================== +// Graph builders +//============================================================================== +// 0-a->1; 1-a->2; 2-a->0; 0-b->3; 3-b->0 (V=4, 2 labels: a=0, b=1) +static void init_graph_double_cycle_ab(void) +{ + V = 4; + n_graph_term = 2; + LAGraph_Calloc((void **)&graph_term, n_graph_term, sizeof(GrB_Matrix), msg); + for (size_t i = 0; i < n_graph_term; ++i) + OK(GrB_Matrix_new(&graph_term[i], GrB_BOOL, V, V)); + + OK(GrB_Matrix_setElement_BOOL(graph_term[0], true, 0, 1)); + OK(GrB_Matrix_setElement_BOOL(graph_term[0], true, 1, 2)); + OK(GrB_Matrix_setElement_BOOL(graph_term[0], true, 2, 0)); + OK(GrB_Matrix_setElement_BOOL(graph_term[1], true, 0, 3)); + OK(GrB_Matrix_setElement_BOOL(graph_term[1], true, 3, 0)); +} + +// 0-a->0; 0-a->1; 1-b->1 (V=2, 2 labels: a=0, b=1) +static void init_graph_self_loops(void) +{ + V = 2; + n_graph_term = 2; + LAGraph_Calloc((void **)&graph_term, n_graph_term, sizeof(GrB_Matrix), msg); + for (size_t i = 0; i < n_graph_term; ++i) + OK(GrB_Matrix_new(&graph_term[i], GrB_BOOL, V, V)); + + OK(GrB_Matrix_setElement_BOOL(graph_term[0], true, 0, 0)); + OK(GrB_Matrix_setElement_BOOL(graph_term[0], true, 0, 1)); + OK(GrB_Matrix_setElement_BOOL(graph_term[1], true, 1, 1)); +} + +// 0-a->0; 0-b->0 (V=2, 2 labels: a=0, b=1) +static void init_graph_single_node(void) +{ + V = 2; + n_graph_term = 2; + LAGraph_Calloc((void **)&graph_term, n_graph_term, sizeof(GrB_Matrix), msg); + for (size_t i = 0; i < n_graph_term; ++i) + OK(GrB_Matrix_new(&graph_term[i], GrB_BOOL, V, V)); + + OK(GrB_Matrix_setElement_BOOL(graph_term[0], true, 0, 0)); + OK(GrB_Matrix_setElement_BOOL(graph_term[1], true, 0, 0)); +} + +// 0-a->0; 0-c->1; 1-b->1 (V=2, 3 labels: a=0, b=1, c=2) +static void init_graph_loops_abc(void) +{ + V = 2; + n_graph_term = 3; + LAGraph_Calloc((void **)&graph_term, n_graph_term, sizeof(GrB_Matrix), msg); + for (size_t i = 0; i < n_graph_term; ++i) + OK(GrB_Matrix_new(&graph_term[i], GrB_BOOL, V, V)); + + OK(GrB_Matrix_setElement_BOOL(graph_term[0], true, 0, 0)); + OK(GrB_Matrix_setElement_BOOL(graph_term[2], true, 0, 1)); + OK(GrB_Matrix_setElement_BOOL(graph_term[1], true, 0, 0)); +} + +// 0-a->1; 1-a->0; 0-c->2; 2-b->3; 3-b->2 (V=4, 3 labels: a=0, b=1, c=2) +static void init_graph_loops_abc_1(void) +{ + V = 4; + n_graph_term = 3; + LAGraph_Calloc((void **)&graph_term, n_graph_term, sizeof(GrB_Matrix), msg); + for (size_t i = 0; i < n_graph_term; ++i) + OK(GrB_Matrix_new(&graph_term[i], GrB_BOOL, V, V)); + + OK(GrB_Matrix_setElement_BOOL(graph_term[0], true, 0, 1)); + OK(GrB_Matrix_setElement_BOOL(graph_term[0], true, 1, 0)); + OK(GrB_Matrix_setElement_BOOL(graph_term[2], true, 0, 2)); + OK(GrB_Matrix_setElement_BOOL(graph_term[1], true, 2, 3)); + OK(GrB_Matrix_setElement_BOOL(graph_term[1], true, 3, 2)); +} + +// 0-a->1; 1-a->0; 0-c->2; 2-b->3; 3-b->4; 4-b->2 (V=5, 3 labels: a=0, b=1, c=2) +static void init_graph_loops_abc_2(void) +{ + V = 5; + n_graph_term = 3; + LAGraph_Calloc((void **)&graph_term, n_graph_term, sizeof(GrB_Matrix), msg); + for (size_t i = 0; i < n_graph_term; ++i) + OK(GrB_Matrix_new(&graph_term[i], GrB_BOOL, V, V)); + + OK(GrB_Matrix_setElement_BOOL(graph_term[0], true, 0, 1)); + OK(GrB_Matrix_setElement_BOOL(graph_term[0], true, 1, 0)); + OK(GrB_Matrix_setElement_BOOL(graph_term[2], true, 0, 2)); + OK(GrB_Matrix_setElement_BOOL(graph_term[1], true, 2, 3)); + OK(GrB_Matrix_setElement_BOOL(graph_term[1], true, 3, 4)); + OK(GrB_Matrix_setElement_BOOL(graph_term[1], true, 4, 2)); +} + +// a-cycle(0,1,2), b-cycle(2,3), c-cycle(3,4,5), d-edges(2<->5) (V=6, 4 labels) +static void init_graph_multi_cycle(void) +{ + V = 6; + n_graph_term = 4; + LAGraph_Calloc((void **)&graph_term, n_graph_term, sizeof(GrB_Matrix), msg); + for (size_t i = 0; i < n_graph_term; ++i) + OK(GrB_Matrix_new(&graph_term[i], GrB_BOOL, V, V)); + + OK(GrB_Matrix_setElement_BOOL(graph_term[0], true, 0, 1)); + OK(GrB_Matrix_setElement_BOOL(graph_term[0], true, 1, 2)); + OK(GrB_Matrix_setElement_BOOL(graph_term[0], true, 2, 0)); + OK(GrB_Matrix_setElement_BOOL(graph_term[1], true, 2, 3)); + OK(GrB_Matrix_setElement_BOOL(graph_term[1], true, 3, 2)); + OK(GrB_Matrix_setElement_BOOL(graph_term[2], true, 3, 4)); + OK(GrB_Matrix_setElement_BOOL(graph_term[2], true, 4, 5)); + OK(GrB_Matrix_setElement_BOOL(graph_term[2], true, 5, 3)); + OK(GrB_Matrix_setElement_BOOL(graph_term[3], true, 5, 2)); + OK(GrB_Matrix_setElement_BOOL(graph_term[3], true, 2, 5)); +} + +#define run_algorithm(start_vertex) \ + LAGraph_CFPQ_RSM(&result, rsm, graph_term, (start_vertex), V, msg); + //============================================================================== // test cases //============================================================================== @@ -164,48 +360,18 @@ void test_TC1_cyclic_ab(void) { setup(); - GrB_Matrix term[2], nt[1]; - GrB_Vector start[1], final[1]; - - term[0] = term[1] = GrB_NULL; - nt[0] = GrB_NULL; - start[0] = final[0] = GrB_NULL; - - rsm_aSb_ab(term, nt, start, final); - - GrB_Index V = 4; - GrB_Matrix gterm[2]; - gterm[0] = gterm[1] = GrB_NULL; - OK(GrB_Matrix_new(>erm[0], GrB_BOOL, V, V)); - OK(GrB_Matrix_new(>erm[1], GrB_BOOL, V, V)); - - OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 0, 1)); - OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 1, 2)); - OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 2, 0)); - OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 0, 3)); - OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 3, 0)); - - GrB_Vector result = GrB_NULL; + init_rsm_aSb_ab(); + init_graph_double_cycle_ab(); - GrB_Info info = LAGraph_CFPQ_RSM(&result, 2, term, gterm, 1, nt, start, final, 0, 1, 4, V, msg); + GrB_Info info = run_algorithm(/*start_vertex=*/1); if (info == GrB_SUCCESS) { GrB_Index expected[] = {0, 3}; - check_reachable(result, expected, 2, V, "TC1"); + check_reachable(expected, /*n_expected=*/2, "TC1"); } - GrB_free(&result); - for (int i = 0; i < 2; ++i) - { - GrB_free(&term[i]); - GrB_free(>erm[i]); - } - GrB_free(&nt[0]); - GrB_free(&start[0]); - GrB_free(&final[0]); - + free_workspace(); OK(info); - teardown(); } @@ -217,47 +383,18 @@ void test_TC1_cyclic_ab(void) void test_TC2_self_loops(void) { setup(); + init_rsm_aSb_ab(); + init_graph_self_loops(); - GrB_Matrix term[2], nt[1]; - GrB_Vector start[1], final[1]; - - term[0] = term[1] = GrB_NULL; - nt[0] = GrB_NULL; - start[0] = final[0] = GrB_NULL; - - rsm_aSb_ab(term, nt, start, final); - - GrB_Index V = 2; - GrB_Matrix gterm[2]; - gterm[0] = gterm[1] = GrB_NULL; - OK(GrB_Matrix_new(>erm[0], GrB_BOOL, V, V)); - OK(GrB_Matrix_new(>erm[1], GrB_BOOL, V, V)); - - OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 0, 0)); - OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 0, 1)); - OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 1, 1)); - - GrB_Vector result = GrB_NULL; - - GrB_Info info = LAGraph_CFPQ_RSM(&result, 2, term, gterm, 1, nt, start, final, 0, 0, 4, V, msg); + GrB_Info info = run_algorithm(/*start_vertex=*/0); if (info == GrB_SUCCESS) { GrB_Index expected[] = {1}; - check_reachable(result, expected, 1, V, "TC2"); + check_reachable(expected, /*n_expected=*/1, "TC2"); } - GrB_free(&result); - for (int i = 0; i < 2; ++i) - { - GrB_free(&term[i]); - GrB_free(>erm[i]); - } - GrB_free(&nt[0]); - GrB_free(&start[0]); - GrB_free(&final[0]); - + free_workspace(); OK(info); - teardown(); } @@ -269,46 +406,18 @@ void test_TC2_self_loops(void) void test_TC3_single_node(void) { setup(); + init_rsm_aSb_ab(); + init_graph_single_node(); - GrB_Matrix term[2], nt[1]; - GrB_Vector start[1], final[1]; - - term[0] = term[1] = GrB_NULL; - nt[0] = GrB_NULL; - start[0] = final[0] = GrB_NULL; - - rsm_aSb_ab(term, nt, start, final); - - GrB_Index V = 1; - GrB_Matrix gterm[2]; - gterm[0] = gterm[1] = GrB_NULL; - OK(GrB_Matrix_new(>erm[0], GrB_BOOL, V, V)); - OK(GrB_Matrix_new(>erm[1], GrB_BOOL, V, V)); - - OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 0, 0)); - OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 0, 0)); - - GrB_Vector result = GrB_NULL; - - GrB_Info info = LAGraph_CFPQ_RSM(&result, 2, term, gterm, 1, nt, start, final, 0, 0, 4, V, msg); + GrB_Info info = run_algorithm(/*start_vertex=*/0); if (info == GrB_SUCCESS) { GrB_Index expected[] = {0}; - check_reachable(result, expected, 1, V, "TC3"); - } - - GrB_free(&result); - for (int i = 0; i < 2; ++i) - { - GrB_free(&term[i]); - GrB_free(>erm[i]); + check_reachable(expected, /*n_expected=*/1, "TC3"); } - GrB_free(&nt[0]); - GrB_free(&start[0]); - GrB_free(&final[0]); + free_workspace(); OK(info); - teardown(); } @@ -320,47 +429,18 @@ void test_TC3_single_node(void) void test_TC4_aSb_c_loops(void) { setup(); + init_rsm_aSb_c(); + init_graph_loops_abc(); - GrB_Matrix term[3], nt[1]; - GrB_Vector start[1], final[1]; - - term[0] = term[1] = term[2] = GrB_NULL; - nt[0] = GrB_NULL; - start[0] = final[0] = GrB_NULL; - - rsm_aSb_c(term, nt, start, final); - - GrB_Index V = 2; - GrB_Matrix gterm[3]; - gterm[0] = gterm[1] = gterm[2] = GrB_NULL; - for (int i = 0; i < 3; ++i) - OK(GrB_Matrix_new(>erm[i], GrB_BOOL, V, V)); - - OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 0, 0)); - OK(GrB_Matrix_setElement_BOOL(gterm[2], true, 0, 1)); - OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 1, 1)); - - GrB_Vector result = GrB_NULL; - - GrB_Info info = LAGraph_CFPQ_RSM(&result, 3, term, gterm, 1, nt, start, final, 0, 0, 4, V, msg); + GrB_Info info = run_algorithm(/*start_vertex=*/0); if (info == GrB_SUCCESS) { GrB_Index expected[] = {1}; - check_reachable(result, expected, 1, V, "TC4"); - } - - GrB_free(&result); - for (int i = 0; i < 3; ++i) - { - GrB_free(&term[i]); - GrB_free(>erm[i]); + check_reachable(expected, 1, "TC4"); } - GrB_free(&nt[0]); - GrB_free(&start[0]); - GrB_free(&final[0]); + free_workspace(); OK(info); - teardown(); } @@ -372,49 +452,18 @@ void test_TC4_aSb_c_loops(void) void test_TC5_aSb_c_cycle1(void) { setup(); + init_rsm_aSb_c(); + init_graph_loops_abc_1(); - GrB_Matrix term[3], nt[1]; - GrB_Vector start[1], final[1]; - - term[0] = term[1] = term[2] = GrB_NULL; - nt[0] = GrB_NULL; - start[0] = final[0] = GrB_NULL; - - rsm_aSb_c(term, nt, start, final); - - GrB_Index V = 4; - GrB_Matrix gterm[3]; - gterm[0] = gterm[1] = gterm[2] = GrB_NULL; - for (int i = 0; i < 3; ++i) - OK(GrB_Matrix_new(>erm[i], GrB_BOOL, V, V)); - - OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 0, 1)); - OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 1, 0)); - OK(GrB_Matrix_setElement_BOOL(gterm[2], true, 0, 2)); - OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 2, 3)); - OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 3, 2)); - - GrB_Vector result = GrB_NULL; - - GrB_Info info = LAGraph_CFPQ_RSM(&result, 3, term, gterm, 1, nt, start, final, 0, 1, 4, V, msg); + GrB_Info info = run_algorithm(/*start_vertex=*/1); if (info == GrB_SUCCESS) { GrB_Index expected[] = {3}; - check_reachable(result, expected, 1, V, "TC5"); - } - - GrB_free(&result); - for (int i = 0; i < 3; ++i) - { - GrB_free(&term[i]); - GrB_free(>erm[i]); + check_reachable(expected, 1, "TC5"); } - GrB_free(&nt[0]); - GrB_free(&start[0]); - GrB_free(&final[0]); + free_workspace(); OK(info); - teardown(); } //------------------------------------------------------------------------------ @@ -425,110 +474,51 @@ void test_TC5_aSb_c_cycle1(void) void test_TC6_aSb_c_cycle2(void) { setup(); + init_rsm_aSb_c(); - GrB_Matrix term[3], nt[1]; - GrB_Vector start[1], final[1]; - - term[0] = term[1] = term[2] = GrB_NULL; - nt[0] = GrB_NULL; - start[0] = final[0] = GrB_NULL; - - rsm_aSb_c(term, nt, start, final); + V = 3; + n_graph_term = 3; + LAGraph_Calloc((void **)&graph_term, n_graph_term, sizeof(GrB_Matrix), msg); + for (size_t i = 0; i < n_graph_term; ++i) + OK(GrB_Matrix_new(&graph_term[i], GrB_BOOL, V, V)); - GrB_Index V = 3; - GrB_Matrix gterm[3]; - gterm[0] = gterm[1] = gterm[2] = GrB_NULL; - for (int i = 0; i < 3; ++i) - OK(GrB_Matrix_new(>erm[i], GrB_BOOL, V, V)); + OK(GrB_Matrix_setElement_BOOL(graph_term[0], true, 0, 1)); + OK(GrB_Matrix_setElement_BOOL(graph_term[0], true, 1, 0)); + OK(GrB_Matrix_setElement_BOOL(graph_term[2], true, 0, 2)); + OK(GrB_Matrix_setElement_BOOL(graph_term[1], true, 2, 2)); - OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 0, 1)); - OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 1, 0)); - OK(GrB_Matrix_setElement_BOOL(gterm[2], true, 0, 2)); - OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 2, 2)); - - GrB_Vector result = GrB_NULL; - - GrB_Info info = LAGraph_CFPQ_RSM(&result, 3, term, gterm, 1, nt, start, final, 0, 1, 4, V, msg); + GrB_Info info = run_algorithm(/*start_vertex=*/1); if (info == GrB_SUCCESS) { GrB_Index expected[] = {2}; - check_reachable(result, expected, 1, V, "TC6"); - } - - GrB_free(&result); - for (int i = 0; i < 3; ++i) - { - GrB_free(&term[i]); - GrB_free(>erm[i]); + check_reachable(expected, 1, "TC6"); } - GrB_free(&nt[0]); - GrB_free(&start[0]); - GrB_free(&final[0]); + free_workspace(); OK(info); - teardown(); } -// Graph: 0-a->1; 1-a->0; 0-c->2; 2-b->3; 3-b->4; 4-b->2 -static void graph_tc7_8(GrB_Matrix gterm[3]) -{ - GrB_Index V = 5; - for (int i = 0; i < 3; ++i) - OK(GrB_Matrix_new(>erm[i], GrB_BOOL, V, V)); - - OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 0, 1)); - OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 1, 0)); - OK(GrB_Matrix_setElement_BOOL(gterm[2], true, 0, 2)); - OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 2, 3)); - OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 3, 4)); - OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 4, 2)); -} - //------------------------------------------------------------------------------ // TC7: S -> a S b | c -// same graph as TC7/8 above +// Graph: 0-a->1; 1-a->0; 0-c->2; 2-b->3; 3-b->4; 4-b->2 // Source: 0 Expected: {2, 3, 4} //------------------------------------------------------------------------------ void test_TC7_aSb_c_cycle3(void) { setup(); + init_rsm_aSb_c(); + init_graph_loops_abc_2(); - GrB_Matrix term[3], nt[1]; - GrB_Vector start[1], final[1]; - - term[0] = term[1] = term[2] = GrB_NULL; - nt[0] = GrB_NULL; - start[0] = final[0] = GrB_NULL; - - rsm_aSb_c(term, nt, start, final); - - GrB_Index V = 5; - GrB_Matrix gterm[3]; - gterm[0] = gterm[1] = gterm[2] = GrB_NULL; - graph_tc7_8(gterm); - - GrB_Vector result = GrB_NULL; - - GrB_Info info = LAGraph_CFPQ_RSM(&result, 3, term, gterm, 1, nt, start, final, 0, 0, 4, V, msg); + GrB_Info info = run_algorithm(/*start_vertex=*/0); if (info == GrB_SUCCESS) { GrB_Index expected[] = {2, 3, 4}; - check_reachable(result, expected, 3, V, "TC7"); + check_reachable(expected, 3, "TC7"); } - GrB_free(&result); - for (int i = 0; i < 3; ++i) - { - GrB_free(&term[i]); - GrB_free(>erm[i]); - } - GrB_free(&nt[0]); - GrB_free(&start[0]); - GrB_free(&final[0]); - + free_workspace(); OK(info); - teardown(); } @@ -540,44 +530,18 @@ void test_TC7_aSb_c_cycle3(void) void test_TC8_aSb_c_cycle4(void) { setup(); + init_rsm_aSb_c(); + init_graph_loops_abc_2(); - GrB_Matrix term[3], nt[1], call[1]; - GrB_Vector start[1], final[1]; - - term[0] = term[1] = term[2] = GrB_NULL; - nt[0] = GrB_NULL; - call[0] = GrB_NULL; - start[0] = final[0] = GrB_NULL; - - rsm_aSb_c(term, nt, start, final); - - GrB_Index V = 5; - GrB_Matrix gterm[3]; - gterm[0] = gterm[1] = gterm[2] = GrB_NULL; - graph_tc7_8(gterm); - - GrB_Vector result = GrB_NULL; - - GrB_Info info = LAGraph_CFPQ_RSM(&result, 3, term, gterm, 1, nt, start, final, 0, 1, 4, V, msg); + GrB_Info info = run_algorithm(/*start_vertex=*/1); if (info == GrB_SUCCESS) { GrB_Index expected[] = {2, 3, 4}; - check_reachable(result, expected, 3, V, "TC8"); + check_reachable(expected, 3, "TC8"); } - GrB_free(&result); - for (int i = 0; i < 3; ++i) - { - GrB_free(&term[i]); - GrB_free(>erm[i]); - } - GrB_free(&nt[0]); - GrB_free(&call[0]); - GrB_free(&start[0]); - GrB_free(&final[0]); - + free_workspace(); OK(info); - teardown(); } @@ -585,62 +549,18 @@ void test_TC9_mult_recursive_cycles(void) { setup(); - GrB_Matrix term[4], nt[3]; - GrB_Vector start[3], final[3]; - - term[0] = term[1] = term[2] = term[3] = GrB_NULL; - nt[0] = nt[1] = nt[2] = GrB_NULL; - start[0] = start[1] = start[2] = GrB_NULL; - final[0] = final[1] = final[2] = GrB_NULL; - - rsm_complex(term, nt, start, final); - - GrB_Index V = 6; - GrB_Matrix gterm[4]; - gterm[0] = gterm[1] = gterm[2] = gterm[3] = GrB_NULL; - for (int i = 0; i < 4; ++i) - OK(GrB_Matrix_new(>erm[i], GrB_BOOL, V, V)); - - // a-cycle (0,1,2) - OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 0, 1)); - OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 1, 2)); - OK(GrB_Matrix_setElement_BOOL(gterm[0], true, 2, 0)); - // b-cycle (2,3) - OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 2, 3)); - OK(GrB_Matrix_setElement_BOOL(gterm[1], true, 3, 2)); - // c-cycle (3,4,5) - OK(GrB_Matrix_setElement_BOOL(gterm[2], true, 3, 4)); - OK(GrB_Matrix_setElement_BOOL(gterm[2], true, 4, 5)); - OK(GrB_Matrix_setElement_BOOL(gterm[2], true, 5, 3)); - // d-cycle - OK(GrB_Matrix_setElement_BOOL(gterm[3], true, 5, 2)); - OK(GrB_Matrix_setElement_BOOL(gterm[3], true, 2, 5)); - - GrB_Vector result = GrB_NULL; - - GrB_Info info = - LAGraph_CFPQ_RSM(&result, 4, term, gterm, 3, nt, start, final, 0, 0, 11, V, msg); + init_rsm_complex(); + init_graph_multi_cycle(); + + GrB_Info info = run_algorithm(/*start_vertex=*/0); if (info == GrB_SUCCESS) { GrB_Index expected[] = {2, 3, 5}; - check_reachable(result, expected, 3, V, "TC9"); - } - - GrB_free(&result); - for (int i = 0; i < 4; ++i) - { - GrB_free(&term[i]); - GrB_free(>erm[i]); - } - for (int i = 0; i < 3; ++i) - { - GrB_free(&nt[i]); - GrB_free(&start[i]); - GrB_free(&final[i]); + check_reachable(expected, 3, "TC9"); } + free_workspace(); OK(info); - teardown(); } @@ -649,7 +569,7 @@ TEST_LIST = {{"TC1_cyclic_ab", test_TC1_cyclic_ab}, {"TC3_single_node", test_TC3_single_node}, {"TC4_aSb_c_loops", test_TC4_aSb_c_loops}, {"TC5_aSb_c_cycle1", test_TC5_aSb_c_cycle1}, - {"TC4_aSb_c_loops", test_TC4_aSb_c_loops}, + {"TC6_aSb_c_cycle2", test_TC6_aSb_c_cycle2}, {"TC7_aSb_c_cycle3", test_TC7_aSb_c_cycle3}, {"TC8_aSb_c_cycle4", test_TC8_aSb_c_cycle4}, {"TC9_mult_recursive_cycles", test_TC9_mult_recursive_cycles}, diff --git a/include/LAGraphX.h b/include/LAGraphX.h index 2a5a7249a0..f0278fb704 100644 --- a/include/LAGraphX.h +++ b/include/LAGraphX.h @@ -1097,22 +1097,20 @@ GrB_Info LAGraph_CFL_reachability char *msg // Message string for error reporting. ) ; -int LAGraph_CFPQ_RSM( - // output: - GrB_Vector *reachable, - - // RSM terminal input: - size_t num_terminals, GrB_Matrix *rsm_term, GrB_Matrix *graph_term, - - // RSM non-terminal input: - size_t num_nonterminals, GrB_Matrix *rsm_nonterm, GrB_Vector *rsm_start, - GrB_Vector *rsm_final, - - size_t start_nonterm, GrB_Index start_vertex, - - GrB_Index Q, GrB_Index V, - - char *msg); +typedef struct +{ + GrB_Index state_count; + GrB_Index terminal_count; + GrB_Index nonterminal_count; + GrB_Index start_nonterminal; + GrB_Matrix *terminal_matrices; + GrB_Matrix *nonterminal_matrices; + GrB_Index *start_states; + GrB_Vector *final_states; +} RSM; + +int LAGraph_CFPQ_RSM(GrB_Vector *reachable, RSM *rsm, GrB_Matrix *graph_term, + GrB_Index start_vertex, GrB_Index V, char *msg); //------------------------------------------------------------------------------ // a simple example of an algorithm From c9efb510a07e599a4a1514de6056540760ff3c4e Mon Sep 17 00:00:00 2001 From: Sterkachov Date: Sun, 19 Apr 2026 22:08:48 +0300 Subject: [PATCH 09/25] feat: add multiple source support to CFPQ algorithm --- experimental/algorithm/LAGraph_CFPQ_RSM.c | 57 +++++++++++++++-------- experimental/test/test_CFPQ_RSM.c | 33 ++++++++----- include/LAGraphX.h | 4 +- 3 files changed, 61 insertions(+), 33 deletions(-) diff --git a/experimental/algorithm/LAGraph_CFPQ_RSM.c b/experimental/algorithm/LAGraph_CFPQ_RSM.c index 35ac23be58..5eb6317eba 100644 --- a/experimental/algorithm/LAGraph_CFPQ_RSM.c +++ b/experimental/algorithm/LAGraph_CFPQ_RSM.c @@ -23,6 +23,7 @@ GrB_free(&diag_entry); \ GrB_free(&outer_result); \ GrB_free(&entry_vec); \ + GrB_free(&source_mask); \ if (Stack != NULL) \ { \ for (size_t _i = 0; _i < num_nonterm; ++_i) \ @@ -124,8 +125,8 @@ typedef struct GrB_Vector *final_states; // rsm_final } RSM; -int LAGraph_CFPQ_RSM(GrB_Vector *reachable, RSM *rsm, GrB_Matrix *graph_term, - GrB_Index start_vertex, GrB_Index V, char *msg) +int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *graph_term, + const GrB_Index *sources, size_t num_sources, GrB_Index V, char *msg) { LG_CLEAR_MSG; @@ -156,6 +157,7 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, RSM *rsm, GrB_Matrix *graph_term, GrB_Matrix diag_entry = GrB_NULL; // add diagonal matrix for entry_vec GrB_Matrix outer_result = GrB_NULL; // additional matrix GrB_Vector entry_vec = GrB_NULL; + GrB_Vector source_mask = GrB_NULL; GrB_Matrix *Stack = NULL; GrB_Matrix *graph_nt = NULL; @@ -168,7 +170,7 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, RSM *rsm, GrB_Matrix *graph_term, LG_ASSERT(start_states != NULL, GrB_NULL_POINTER); LG_ASSERT(final_states != NULL, GrB_NULL_POINTER); LG_ASSERT(start_nonterm < num_nonterm, GrB_INVALID_VALUE); - LG_ASSERT(start_vertex < V, GrB_INVALID_VALUE); + LG_ASSERT(num_sources <= V, GrB_INVALID_VALUE); *reachable = GrB_NULL; @@ -213,39 +215,43 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, RSM *rsm, GrB_Matrix *graph_term, // GRB_TRY(GrB_Matrix_new(&diag_entry, GrB_BOOL, V, V)); GRB_TRY(GrB_Matrix_new(&outer_result, GrB_BOOL, Q, VV)); GRB_TRY(GrB_Vector_new(&entry_vec, GrB_BOOL, V)); + GRB_TRY(GrB_Vector_new(&source_mask, GrB_BOOL, V)); GRB_TRY(GrB_Vector_new(reachable, GrB_BOOL, V)); // seed initial frontier M and Stack { - GrB_Index seed_col = start_vertex * V + start_vertex; GrB_Index nstart = 0; GRB_TRY(GrB_Vector_nvals(&nstart, rsm_start[start_nonterm])); - GrB_Index *start_states = NULL; - bool *start_vals = NULL; - LG_TRY(LAGraph_Malloc((void **)&start_states, nstart, sizeof(GrB_Index), msg)); - LG_TRY(LAGraph_Malloc((void **)&start_vals, nstart, sizeof(bool), msg)); + GrB_Index *seed_states = NULL; + bool *seed_vals = NULL; + LG_TRY(LAGraph_Malloc((void **)&seed_states, nstart, sizeof(GrB_Index), msg)); + LG_TRY(LAGraph_Malloc((void **)&seed_vals, nstart, sizeof(bool), msg)); - GrB_Info info = GrB_Vector_extractTuples_BOOL(start_states, start_vals, &nstart, + GrB_Info info = GrB_Vector_extractTuples_BOOL(seed_states, seed_vals, &nstart, rsm_start[start_nonterm]); - LAGraph_Free((void **)&start_vals, GrB_NULL); + LAGraph_Free((void **)&seed_vals, GrB_NULL); if (info != GrB_SUCCESS) { - LAGraph_Free((void **)&start_states, GrB_NULL); + LAGraph_Free((void **)&seed_states, GrB_NULL); LG_FREE_ALL; return info; } - for (GrB_Index k = 0; k < nstart; ++k) + for (size_t i = 0; i < num_sources; ++i) { - GrB_Index q = start_states[k]; - GRB_TRY(GrB_Matrix_setElement_BOOL(M, true, q, seed_col)); - GRB_TRY(GrB_Matrix_setElement_BOOL(Stack[start_nonterm], true, q, seed_col)); + GrB_Index seed_col = sources[i] * V + sources[i]; + for (GrB_Index k = 0; k < nstart; ++k) + { + GrB_Index q = seed_states[k]; + GRB_TRY(GrB_Matrix_setElement_BOOL(M, true, q, seed_col)); + GRB_TRY(GrB_Matrix_setElement_BOOL(Stack[start_nonterm], true, q, seed_col)); + } } - LAGraph_Free((void **)&start_states, GrB_NULL); + LAGraph_Free((void **)&seed_states, GrB_NULL); } // main fixed-point loop @@ -388,9 +394,22 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, RSM *rsm, GrB_Matrix *graph_term, GRB_TRY(GrB_Matrix_nvals(&m_nvals, M)); } - // GrB_DESC_T0 transposes graph_nt so Col_extract gives row start_vertex - GRB_TRY(GrB_Col_extract(*reachable, GrB_NULL, GrB_NULL, graph_nt[start_nonterm], GrB_ALL, V, - start_vertex, GrB_DESC_T0)); + // Extract all reachable vertices + + { + bool *vals = NULL; + LG_TRY(LAGraph_Malloc((void **)&vals, num_sources, sizeof(bool), msg)); + for (size_t i = 0; i < num_sources; ++i) + vals[i] = true; + + GrB_Info _bi = + GrB_Vector_build_BOOL(source_mask, sources, vals, num_sources, GrB_SECOND_BOOL); + LAGraph_Free((void **)&vals, GrB_NULL); + GRB_TRY(_bi); + } + + GRB_TRY(GrB_mxv(*reachable, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, + graph_nt[start_nonterm], source_mask, GrB_DESC_T0)); LG_FREE_WORK; return GrB_SUCCESS; diff --git a/experimental/test/test_CFPQ_RSM.c b/experimental/test/test_CFPQ_RSM.c index 344c87ccc5..2ac746ead4 100644 --- a/experimental/test/test_CFPQ_RSM.c +++ b/experimental/test/test_CFPQ_RSM.c @@ -65,7 +65,7 @@ static void free_rsm(void) LAGraph_Free((void **)&rsm->nonterminal_matrices, msg); } - if (rsm->nonterminal_matrices != NULL) + if (rsm->final_states != NULL) { for (size_t i = 0; i < rsm->nonterminal_count; ++i) { @@ -344,8 +344,8 @@ static void init_graph_multi_cycle(void) OK(GrB_Matrix_setElement_BOOL(graph_term[3], true, 2, 5)); } -#define run_algorithm(start_vertex) \ - LAGraph_CFPQ_RSM(&result, rsm, graph_term, (start_vertex), V, msg); +#define run_algorithm(sources, num_sources) \ + LAGraph_CFPQ_RSM(&result, rsm, graph_term, (sources), (num_sources), V, msg); //============================================================================== // test cases @@ -363,7 +363,8 @@ void test_TC1_cyclic_ab(void) init_rsm_aSb_ab(); init_graph_double_cycle_ab(); - GrB_Info info = run_algorithm(/*start_vertex=*/1); + GrB_Index sources = {1}; + GrB_Info info = run_algorithm(&sources, 1); if (info == GrB_SUCCESS) { GrB_Index expected[] = {0, 3}; @@ -386,7 +387,8 @@ void test_TC2_self_loops(void) init_rsm_aSb_ab(); init_graph_self_loops(); - GrB_Info info = run_algorithm(/*start_vertex=*/0); + GrB_Index sources = {0}; + GrB_Info info = run_algorithm(&sources, 1); if (info == GrB_SUCCESS) { GrB_Index expected[] = {1}; @@ -409,7 +411,8 @@ void test_TC3_single_node(void) init_rsm_aSb_ab(); init_graph_single_node(); - GrB_Info info = run_algorithm(/*start_vertex=*/0); + GrB_Index sources = {0}; + GrB_Info info = run_algorithm(&sources, 1); if (info == GrB_SUCCESS) { GrB_Index expected[] = {0}; @@ -432,7 +435,8 @@ void test_TC4_aSb_c_loops(void) init_rsm_aSb_c(); init_graph_loops_abc(); - GrB_Info info = run_algorithm(/*start_vertex=*/0); + GrB_Index sources = {0}; + GrB_Info info = run_algorithm(&sources, 1); if (info == GrB_SUCCESS) { GrB_Index expected[] = {1}; @@ -455,7 +459,8 @@ void test_TC5_aSb_c_cycle1(void) init_rsm_aSb_c(); init_graph_loops_abc_1(); - GrB_Info info = run_algorithm(/*start_vertex=*/1); + GrB_Index sources = {1}; + GrB_Info info = run_algorithm(&sources, 1); if (info == GrB_SUCCESS) { GrB_Index expected[] = {3}; @@ -487,7 +492,8 @@ void test_TC6_aSb_c_cycle2(void) OK(GrB_Matrix_setElement_BOOL(graph_term[2], true, 0, 2)); OK(GrB_Matrix_setElement_BOOL(graph_term[1], true, 2, 2)); - GrB_Info info = run_algorithm(/*start_vertex=*/1); + GrB_Index sources = {1}; + GrB_Info info = run_algorithm(&sources, 1); if (info == GrB_SUCCESS) { GrB_Index expected[] = {2}; @@ -510,7 +516,8 @@ void test_TC7_aSb_c_cycle3(void) init_rsm_aSb_c(); init_graph_loops_abc_2(); - GrB_Info info = run_algorithm(/*start_vertex=*/0); + GrB_Index sources = {0}; + GrB_Info info = run_algorithm(&sources, 1); if (info == GrB_SUCCESS) { GrB_Index expected[] = {2, 3, 4}; @@ -533,7 +540,8 @@ void test_TC8_aSb_c_cycle4(void) init_rsm_aSb_c(); init_graph_loops_abc_2(); - GrB_Info info = run_algorithm(/*start_vertex=*/1); + GrB_Index sources = {1}; + GrB_Info info = run_algorithm(&sources, 1); if (info == GrB_SUCCESS) { GrB_Index expected[] = {2, 3, 4}; @@ -552,7 +560,8 @@ void test_TC9_mult_recursive_cycles(void) init_rsm_complex(); init_graph_multi_cycle(); - GrB_Info info = run_algorithm(/*start_vertex=*/0); + GrB_Index sources = {1}; + GrB_Info info = run_algorithm(&sources, 1); if (info == GrB_SUCCESS) { GrB_Index expected[] = {2, 3, 5}; diff --git a/include/LAGraphX.h b/include/LAGraphX.h index f0278fb704..18171ed555 100644 --- a/include/LAGraphX.h +++ b/include/LAGraphX.h @@ -1109,8 +1109,8 @@ typedef struct GrB_Vector *final_states; } RSM; -int LAGraph_CFPQ_RSM(GrB_Vector *reachable, RSM *rsm, GrB_Matrix *graph_term, - GrB_Index start_vertex, GrB_Index V, char *msg); +int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *graph_term, + const GrB_Index *sources, size_t num_sources, GrB_Index V, char *msg); //------------------------------------------------------------------------------ // a simple example of an algorithm From 35bee56067b09a3e0338723058246683afdefa74 Mon Sep 17 00:00:00 2001 From: Sterkachov Date: Thu, 23 Apr 2026 23:45:48 +0300 Subject: [PATCH 10/25] fix: Stack structure is redundant, use P instead --- experimental/algorithm/LAGraph_CFPQ_RSM.c | 75 +++++++++++------------ experimental/test/test_CFPQ_RSM.c | 28 +++------ 2 files changed, 44 insertions(+), 59 deletions(-) diff --git a/experimental/algorithm/LAGraph_CFPQ_RSM.c b/experimental/algorithm/LAGraph_CFPQ_RSM.c index 5eb6317eba..2f47f58caa 100644 --- a/experimental/algorithm/LAGraph_CFPQ_RSM.c +++ b/experimental/algorithm/LAGraph_CFPQ_RSM.c @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -247,6 +248,7 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr { GrB_Index q = seed_states[k]; GRB_TRY(GrB_Matrix_setElement_BOOL(M, true, q, seed_col)); + GRB_TRY(GrB_Matrix_setElement_BOOL(P, true, q, seed_col)); GRB_TRY(GrB_Matrix_setElement_BOOL(Stack[start_nonterm], true, q, seed_col)); } } @@ -275,6 +277,12 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr { if (rsm_nonterm[i] == GrB_NULL) continue; + + GrB_Index gnt_nvals = 0; + GRB_TRY(GrB_Matrix_nvals(&gnt_nvals, graph_nt[i])); + if (gnt_nvals == 0) + continue; + LG_TRY(s_mxm_chain(M_nonterm, rsm_nonterm[i], M, graph_nt[i], temp1, temp2, Q, V)); } @@ -287,7 +295,7 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr // mask_call = rsm_call[i]^T * M -> |Q|*|V*V| GRB_TRY(GrB_Matrix_clear(mask_call)); - GRB_TRY(GrB_mxm(mask_call, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, rsm_call[i], + GRB_TRY(GrB_mxm(mask_call, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, rsm_call[i], M, GrB_DESC_T0)); GrB_Index mc_nvals = 0; @@ -295,27 +303,26 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr if (mc_nvals == 0) continue; - // frame = rsm_nonterm[i]^T * M, accumulated into Stack[i] - GRB_TRY(GrB_Matrix_clear(frame)); - GRB_TRY(GrB_mxm(frame, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, rsm_nonterm[i], M, - GrB_DESC_T0)); - GRB_TRY(GrB_eWiseAdd(Stack[i], GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, Stack[i], - frame, GrB_NULL)); - // entry_vec = column-OR of mask_call reshaped to |Q*V|*|V| GRB_TRY(GrB_Matrix_clear(temp1)); - GRB_TRY(GrB_eWiseAdd(temp1, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, temp1, + GRB_TRY(GrB_eWiseAdd(temp1, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, temp1, mask_call, GrB_NULL)); GRB_TRY(GxB_Matrix_reshape(temp1, false, Q * V, V, GrB_NULL)); GRB_TRY(GrB_Vector_clear(entry_vec)); // column OR = row OR of transpose GRB_TRY( - GrB_reduce(entry_vec, GrB_NULL, GrB_LOR, GrB_LOR_MONOID_BOOL, temp1, GrB_DESC_T0)); + GrB_reduce(entry_vec, GrB_NULL, GrB_NULL, GrB_LOR_MONOID_BOOL, temp1, GrB_DESC_T0)); GRB_TRY(GxB_Matrix_reshape(temp1, false, Q, VV, GrB_NULL)); + GrB_Index ev_nvals = 0; + GRB_TRY(GrB_Vector_nvals(&ev_nvals, entry_vec)); + if (ev_nvals == 0) + continue; + // Build diag(entry_vec) reshaped to 1*|V*V| + GRB_TRY(GrB_Matrix_free(&diag_entry)); diag_entry = GrB_NULL; GRB_TRY(GrB_Matrix_diag(&diag_entry, entry_vec, 0)); // |V|*|V| GRB_TRY(GxB_Matrix_reshape(diag_entry, false, 1, VV, GrB_NULL)); @@ -326,17 +333,15 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr (GrB_Matrix)rsm_start[i], diag_entry, GrB_NULL)); // M_call |= outer_result - GRB_TRY(GrB_eWiseAdd(M_call, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, M_call, - outer_result, GrB_NULL)); - - GRB_TRY(GrB_Matrix_free(&diag_entry)); + GRB_TRY( + GrB_eWiseAdd(M_call, GrB_NULL, GrB_LOR, GrB_LOR, M_call, outer_result, GrB_NULL)); } // PHASE 4: RETURN TRANSITIONS GRB_TRY(GrB_Matrix_clear(M_return)); for (size_t i = 0; i < num_nonterm; ++i) { - if (final_states[i] == GrB_NULL) + if (final_states[i] == GrB_NULL || rsm_nonterm[i] == GrB_NULL) continue; // 1*|Q| * |Q|*|V*V| -> 1*|V*V| @@ -346,25 +351,19 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GRB_TRY(GxB_Matrix_reshape(new_edges, false, V, V, GrB_NULL)); - GRB_TRY(GrB_eWiseAdd(graph_nt[i], GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, - graph_nt[i], new_edges, GrB_NULL)); - - GRB_TRY(GrB_Matrix_clear(temp1)); - GRB_TRY(GrB_eWiseAdd(temp1, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, temp1, - Stack[i], GrB_NULL)); - GRB_TRY(GxB_Matrix_reshape(temp1, false, Q * V, V, GrB_NULL)); + GrB_Index ne_vals = 0; + GRB_TRY(GrB_Matrix_nvals(&ne_vals, new_edges)); + if (ne_vals == 0) + { + GRB_TRY(GxB_Matrix_reshape(new_edges, false, 1, VV, GrB_NULL)); + continue; + } - GRB_TRY(GrB_Matrix_clear(temp2)); - GRB_TRY(GxB_Matrix_reshape(temp2, false, Q * V, V, GrB_NULL)); - // |Q*V|*|V| * |V|*|V| -> |Q*V|*|V| - GRB_TRY(GrB_mxm(temp2, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, temp1, new_edges, - GrB_NULL)); + GRB_TRY(GrB_eWiseAdd(graph_nt[i], GrB_NULL, GrB_LOR, GrB_LOR, graph_nt[i], new_edges, + GrB_NULL)); - GRB_TRY(GxB_Matrix_reshape(temp1, false, Q, VV, GrB_NULL)); - GRB_TRY(GxB_Matrix_reshape(temp2, false, Q, VV, GrB_NULL)); + LG_TRY(s_mxm_chain(M_return, rsm_nonterm[i], P, new_edges, temp1, temp2, Q, V)); - GRB_TRY(GrB_eWiseAdd(M_return, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, M_return, - temp2, GrB_NULL)); GRB_TRY(GxB_Matrix_reshape(new_edges, false, 1, VV, GrB_NULL)); } @@ -372,20 +371,16 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr // GrB_assign has big overhead // M_new = (M_term | M_nonterm | M_call | M_return) & ~P GRB_TRY(GrB_Matrix_clear(M_new)); - GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, M_new, M_term, - GrB_NULL)); - GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, M_new, M_nonterm, - GrB_NULL)); - GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, M_new, M_call, - GrB_NULL)); - GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, M_new, M_return, - GrB_NULL)); + GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_LOR, GrB_LOR, M_new, M_term, GrB_NULL)); + GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_LOR, GrB_LOR, M_new, M_nonterm, GrB_NULL)); + GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_LOR, GrB_LOR, M_new, M_call, GrB_NULL)); + GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_LOR, GrB_LOR, M_new, M_return, GrB_NULL)); // M_new &= ~P GRB_TRY(GrB_assign(M_new, P, GrB_NULL, M_new, GrB_ALL, Q, GrB_ALL, VV, GrB_DESC_RSC)); // P |= M_new - GRB_TRY(GrB_eWiseAdd(P, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, P, M_new, GrB_NULL)); + GRB_TRY(GrB_eWiseAdd(P, GrB_NULL, GrB_LOR, GrB_LOR, P, M_new, GrB_NULL)); GrB_Matrix swap = M; M = M_new; diff --git a/experimental/test/test_CFPQ_RSM.c b/experimental/test/test_CFPQ_RSM.c index 2ac746ead4..6a37fff856 100644 --- a/experimental/test/test_CFPQ_RSM.c +++ b/experimental/test/test_CFPQ_RSM.c @@ -44,6 +44,15 @@ static RSM *alloc_rsm(GrB_Index state_count, GrB_Index term_count, GrB_Index non LAGraph_Calloc((void **)&r->start_states, nonterm_count, sizeof(GrB_Index), msg); LAGraph_Calloc((void **)&r->final_states, nonterm_count, sizeof(GrB_Vector), msg); + for (size_t i = 0; i < term_count; ++i) + GrB_Matrix_new(&r->terminal_matrices[i], GrB_BOOL, state_count, state_count); + + for (size_t i = 0; i < nonterm_count; ++i) + { + GrB_Matrix_new(&r->nonterminal_matrices[i], GrB_BOOL, state_count, state_count); + GrB_Vector_new(&r->final_states[i], GrB_BOOL, state_count); + } + return r; } @@ -154,11 +163,6 @@ static void init_rsm_aSb_ab(void) GrB_Index Q = 4; rsm = alloc_rsm(Q, /*term_count=*/2, /*nonterm_count=*/1, /*start_nonterm=*/0); - OK(GrB_Matrix_new(&rsm->terminal_matrices[0], GrB_BOOL, Q, Q)); - OK(GrB_Matrix_new(&rsm->terminal_matrices[1], GrB_BOOL, Q, Q)); - OK(GrB_Matrix_new(&rsm->nonterminal_matrices[0], GrB_BOOL, Q, Q)); - OK(GrB_Vector_new(&rsm->final_states[0], GrB_BOOL, Q)); - OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[0], true, 0, 1)); // 0 -a-> 1 OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[1], true, 1, 3)); // 1 -b-> 3 OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[1], true, 2, 3)); // 2 -b-> 3 @@ -173,13 +177,6 @@ static void init_rsm_aSb_c(void) GrB_Index Q = 4; rsm = alloc_rsm(Q, /*term_count=*/3, /*nonterm_count=*/1, /*start_nonterm=*/0); - OK(GrB_Matrix_new(&rsm->terminal_matrices[0], GrB_BOOL, Q, Q)); - OK(GrB_Matrix_new(&rsm->terminal_matrices[1], GrB_BOOL, Q, Q)); - OK(GrB_Matrix_new(&rsm->terminal_matrices[2], GrB_BOOL, Q, Q)); - OK(GrB_Matrix_new(&rsm->nonterminal_matrices[0], GrB_BOOL, Q, Q)); - - OK(GrB_Vector_new(&rsm->final_states[0], GrB_BOOL, Q)); - OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[0], true, 0, 1)); // 0 -a-> 1 OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[2], true, 0, 3)); // 0 -c-> 3 OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[1], true, 2, 3)); // 2 -b-> 3 @@ -195,13 +192,6 @@ static void init_rsm_complex(void) { GrB_Index Q = 11; rsm = alloc_rsm(Q, /*term_count=*/4, /*nonterm_count=*/3, /*start_nonterm=*/0); - for (int t = 0; t < 4; ++t) - OK(GrB_Matrix_new(&rsm->terminal_matrices[t], GrB_BOOL, Q, Q)); - for (int n = 0; n < 3; ++n) - { - OK(GrB_Matrix_new(&rsm->nonterminal_matrices[n], GrB_BOOL, Q, Q)); - OK(GrB_Vector_new(&rsm->final_states[n], GrB_BOOL, Q)); - } // --- S Component (States 0-2) --- // S -> X | Y | S X | S Y From e7f0b951b07cff04b86e1334229b62f1a777e3fe Mon Sep 17 00:00:00 2001 From: Sterkachov Date: Tue, 28 Apr 2026 22:43:13 +0300 Subject: [PATCH 11/25] fix: use matrix_reshape_dup to avoid 32-bit overflow Matrix reshape from |V|*|V| to 1*|V*V| triggered 32-bit indexing overflow when V*V > 2^32. The library correctly detects potential overflow (Cj_is_32=false), but the in-place reshape operation (in_place=true) inherits T->j_is_32=true from the original matrix, causing J_work_is_32 to become true and overflow to occur. Solution: use matrix_reshape_dup instead of matrix_reshape to create a new matrix (in_place=false), so J_work_is_32 correctly evaluates to false and 64-bit indexing is used. --- experimental/algorithm/LAGraph_CFPQ_RSM.c | 129 ++++++++++++++++++---- experimental/test/test_CFPQ_RSM.c | 81 ++++++++++++++ 2 files changed, 190 insertions(+), 20 deletions(-) diff --git a/experimental/algorithm/LAGraph_CFPQ_RSM.c b/experimental/algorithm/LAGraph_CFPQ_RSM.c index 2f47f58caa..68052ea3f7 100644 --- a/experimental/algorithm/LAGraph_CFPQ_RSM.c +++ b/experimental/algorithm/LAGraph_CFPQ_RSM.c @@ -25,12 +25,15 @@ GrB_free(&outer_result); \ GrB_free(&entry_vec); \ GrB_free(&source_mask); \ +<<<<<<< Updated upstream if (Stack != NULL) \ { \ for (size_t _i = 0; _i < num_nonterm; ++_i) \ GrB_free(&Stack[_i]); \ LAGraph_Free((void **)&Stack, msg); \ } \ +======= +>>>>>>> Stashed changes if (graph_nt != NULL) \ { \ for (size_t _i = 0; _i < num_nonterm; ++_i) \ @@ -63,6 +66,7 @@ return _hi; \ } +<<<<<<< Updated upstream static GrB_Info s_mxm_chain(GrB_Matrix C, // output accumulator |Q|*|V*V| GrB_Matrix A, // RSM matrix |Q|*|Q| GrB_Matrix M, // frontier |Q|*|V*V| @@ -76,20 +80,62 @@ static GrB_Info s_mxm_chain(GrB_Matrix C, // output accumulator |Q|*|V*V| // temp1 = A^T * M -> |Q|*|V*V| H_TRY(GrB_mxm(temp1, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, A, M, GrB_DESC_T0)); +======= +static inline GrB_Info reshape( // input/output: + GrB_Matrix *C, // input/output matrix, reshaped in place + bool by_col, // true if reshape by column, false if by row + GrB_Index nrows_new, // new number of rows of C + GrB_Index ncols_new, // new number of columns of C + const GrB_Descriptor desc // to control # of threads used +) +{ + if (C == NULL || *C == NULL) + return GrB_NULL_POINTER; + + GrB_Matrix temp = GrB_NULL; + H_TRY(GxB_Matrix_reshapeDup(&temp, *C, by_col, nrows_new, ncols_new, desc)); + H_TRY(GrB_Matrix_free(C)); + + *C = temp; + return GrB_SUCCESS; +} + +static GrB_Info s_mxm_chain(GrB_Matrix C, // output accumulator |Q|*|V*V| + GrB_Matrix A, // RSM matrix |Q|*|Q| + GrB_Matrix M, // frontier |Q|*|V*V| + GrB_Matrix B, // graph matrix |V|*|V| + GrB_Matrix *temp1, // scratch |Q|*|V*V| + GrB_Matrix *temp2, // scratch |Q|*|V*V| + GrB_Index Q, GrB_Index V) +{ + H_TRY(GrB_Matrix_clear(*temp1)); + H_TRY(GrB_Matrix_clear(*temp2)); + + // temp1 = A^T * M -> |Q|*|V*V| + H_TRY(GrB_mxm(*temp1, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, A, M, GrB_DESC_T0)); +>>>>>>> Stashed changes // reshape: |Q| × |V*V| -> |Q*V|*|V| - H_TRY(GxB_Matrix_reshape(temp1, false, Q * V, V, GrB_NULL)); - H_TRY(GxB_Matrix_reshape(temp2, false, Q * V, V, GrB_NULL)); + H_TRY(reshape(temp1, false, Q * V, V, GrB_NULL)); + H_TRY(reshape(temp2, false, Q * V, V, GrB_NULL)); // temp2 = temp1 * B -> |Q*V|*|V| +<<<<<<< Updated upstream H_TRY(GrB_mxm(temp2, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, temp1, B, GrB_NULL)); +======= + H_TRY(GrB_mxm(*temp2, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, *temp1, B, GrB_NULL)); +>>>>>>> Stashed changes // reshape: |Q*V|*|V| -> |Q|*|V*V| - H_TRY(GxB_Matrix_reshape(temp1, false, Q, V * V, GrB_NULL)); - H_TRY(GxB_Matrix_reshape(temp2, false, Q, V * V, GrB_NULL)); + H_TRY(reshape(temp1, false, Q, V * V, GrB_NULL)); + H_TRY(reshape(temp2, false, Q, V * V, GrB_NULL)); // C |= temp2 +<<<<<<< Updated upstream H_TRY(GrB_eWiseAdd(C, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, C, temp2, GrB_NULL)); +======= + H_TRY(GrB_eWiseAdd(C, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, C, *temp2, GrB_NULL)); +>>>>>>> Stashed changes return GrB_SUCCESS; } @@ -155,12 +201,11 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GrB_Matrix mask_call = GrB_NULL; GrB_Matrix frame = GrB_NULL; GrB_Matrix new_edges = GrB_NULL; - GrB_Matrix diag_entry = GrB_NULL; // add diagonal matrix for entry_vec - GrB_Matrix outer_result = GrB_NULL; // additional matrix + GrB_Matrix diag_entry = GrB_NULL; + GrB_Matrix outer_result = GrB_NULL; GrB_Vector entry_vec = GrB_NULL; GrB_Vector source_mask = GrB_NULL; - GrB_Matrix *Stack = NULL; GrB_Matrix *graph_nt = NULL; GrB_Matrix *rsm_call = NULL; GrB_Vector *rsm_start = NULL; // rsm_start[S][i] = true, iff start_states[S] = i @@ -176,14 +221,20 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr *reachable = GrB_NULL; // allocate per-nonterminal arrays +<<<<<<< Updated upstream LG_TRY(LAGraph_Malloc((void **)&Stack, rsm->nonterminal_count, sizeof(GrB_Matrix), msg)); +======= +>>>>>>> Stashed changes LG_TRY(LAGraph_Malloc((void **)&graph_nt, rsm->nonterminal_count, sizeof(GrB_Matrix), msg)); LG_TRY(LAGraph_Malloc((void **)&rsm_start, rsm->nonterminal_count, sizeof(GrB_Vector), msg)); LG_TRY(LAGraph_Malloc((void **)&rsm_call, rsm->nonterminal_count, sizeof(GrB_Matrix), msg)); for (size_t i = 0; i < num_nonterm; ++i) { +<<<<<<< Updated upstream Stack[i] = GrB_NULL; +======= +>>>>>>> Stashed changes graph_nt[i] = GrB_NULL; rsm_start[i] = GrB_NULL; rsm_call[i] = GrB_NULL; @@ -191,12 +242,13 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr for (size_t i = 0; i < num_nonterm; ++i) { +<<<<<<< Updated upstream GRB_TRY(GrB_Matrix_new(&Stack[i], GrB_BOOL, Q, VV)); +======= +>>>>>>> Stashed changes GRB_TRY(GrB_Matrix_new(&graph_nt[i], GrB_BOOL, V, V)); - GRB_TRY(GrB_Vector_new(&rsm_start[i], GrB_BOOL, Q)); GrB_Vector_setElement_BOOL(rsm_start[i], true, start_states[i]); - GRB_TRY(build_call_matrix(&rsm_call[i], rsm_nonterm[i], rsm_start[i], Q)); } @@ -213,13 +265,13 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GRB_TRY(GrB_Matrix_new(&mask_call, GrB_BOOL, Q, VV)); GRB_TRY(GrB_Matrix_new(&frame, GrB_BOOL, Q, VV)); GRB_TRY(GrB_Matrix_new(&new_edges, GrB_BOOL, 1, VV)); - // GRB_TRY(GrB_Matrix_new(&diag_entry, GrB_BOOL, V, V)); + // there is no need to create matrix for diag_entry GRB_TRY(GrB_Matrix_new(&outer_result, GrB_BOOL, Q, VV)); GRB_TRY(GrB_Vector_new(&entry_vec, GrB_BOOL, V)); GRB_TRY(GrB_Vector_new(&source_mask, GrB_BOOL, V)); GRB_TRY(GrB_Vector_new(reachable, GrB_BOOL, V)); - // seed initial frontier M and Stack + // seed initial frontier M and visited P { GrB_Index nstart = 0; @@ -249,7 +301,10 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GrB_Index q = seed_states[k]; GRB_TRY(GrB_Matrix_setElement_BOOL(M, true, q, seed_col)); GRB_TRY(GrB_Matrix_setElement_BOOL(P, true, q, seed_col)); +<<<<<<< Updated upstream GRB_TRY(GrB_Matrix_setElement_BOOL(Stack[start_nonterm], true, q, seed_col)); +======= +>>>>>>> Stashed changes } } @@ -261,14 +316,21 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GRB_TRY(GrB_Matrix_nvals(&m_nvals, M)); while (m_nvals > 0) { +<<<<<<< Updated upstream +======= +>>>>>>> Stashed changes // PHASE 1: TERMINAL TRANSITIONS GRB_TRY(GrB_Matrix_clear(M_term)); for (size_t i = 0; i < num_term; ++i) { if (rsm_term[i] == GrB_NULL || graph_term[i] == GrB_NULL) continue; +<<<<<<< Updated upstream LG_TRY(s_mxm_chain(M_term, rsm_term[i], M, graph_term[i], temp1, temp2, Q, V)); +======= + LG_TRY(s_mxm_chain(M_term, rsm_term[i], M, graph_term[i], &temp1, &temp2, Q, V)); +>>>>>>> Stashed changes } // PHASE 2: NON-TERMINAL TRANSITIONS @@ -283,7 +345,11 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr if (gnt_nvals == 0) continue; +<<<<<<< Updated upstream LG_TRY(s_mxm_chain(M_nonterm, rsm_nonterm[i], M, graph_nt[i], temp1, temp2, Q, V)); +======= + LG_TRY(s_mxm_chain(M_nonterm, rsm_nonterm[i], M, graph_nt[i], &temp1, &temp2, Q, V)); +>>>>>>> Stashed changes } // PHASE 3: CALL TRANSITIONS @@ -305,27 +371,32 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr // entry_vec = column-OR of mask_call reshaped to |Q*V|*|V| GRB_TRY(GrB_Matrix_clear(temp1)); +<<<<<<< Updated upstream GRB_TRY(GrB_eWiseAdd(temp1, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, temp1, mask_call, GrB_NULL)); GRB_TRY(GxB_Matrix_reshape(temp1, false, Q * V, V, GrB_NULL)); +======= + GRB_TRY(GrB_eWiseAdd(temp1, GrB_NULL, GrB_NULL, GrB_LOR, temp1, mask_call, GrB_NULL)); + GRB_TRY(reshape(&temp1, false, Q * V, V, GrB_NULL)); +>>>>>>> Stashed changes GRB_TRY(GrB_Vector_clear(entry_vec)); // column OR = row OR of transpose GRB_TRY( GrB_reduce(entry_vec, GrB_NULL, GrB_NULL, GrB_LOR_MONOID_BOOL, temp1, GrB_DESC_T0)); - GRB_TRY(GxB_Matrix_reshape(temp1, false, Q, VV, GrB_NULL)); + GRB_TRY(reshape(&temp1, false, Q, VV, GrB_NULL)); GrB_Index ev_nvals = 0; GRB_TRY(GrB_Vector_nvals(&ev_nvals, entry_vec)); if (ev_nvals == 0) continue; - // Build diag(entry_vec) reshaped to 1*|V*V| + // Build diag(entry_vec) and flatten to 1*|V*V| GRB_TRY(GrB_Matrix_free(&diag_entry)); diag_entry = GrB_NULL; GRB_TRY(GrB_Matrix_diag(&diag_entry, entry_vec, 0)); // |V|*|V| - GRB_TRY(GxB_Matrix_reshape(diag_entry, false, 1, VV, GrB_NULL)); + GRB_TRY(reshape(&diag_entry, false, 1, VV, GrB_NULL)); GRB_TRY(GrB_Matrix_clear(outer_result)); // |Q|*1 * 1*|V*V| -> |Q|*|V*V| @@ -334,7 +405,11 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr // M_call |= outer_result GRB_TRY( +<<<<<<< Updated upstream GrB_eWiseAdd(M_call, GrB_NULL, GrB_LOR, GrB_LOR, M_call, outer_result, GrB_NULL)); +======= + GrB_eWiseAdd(M_call, GrB_NULL, GrB_NULL, GrB_LOR, M_call, outer_result, GrB_NULL)); +>>>>>>> Stashed changes } // PHASE 4: RETURN TRANSITIONS @@ -349,38 +424,53 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GRB_TRY(GrB_mxm(new_edges, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, (GrB_Matrix)final_states[i], M, GrB_DESC_T0)); - GRB_TRY(GxB_Matrix_reshape(new_edges, false, V, V, GrB_NULL)); - GrB_Index ne_vals = 0; GRB_TRY(GrB_Matrix_nvals(&ne_vals, new_edges)); if (ne_vals == 0) +<<<<<<< Updated upstream { GRB_TRY(GxB_Matrix_reshape(new_edges, false, 1, VV, GrB_NULL)); +======= +>>>>>>> Stashed changes continue; - } +<<<<<<< Updated upstream GRB_TRY(GrB_eWiseAdd(graph_nt[i], GrB_NULL, GrB_LOR, GrB_LOR, graph_nt[i], new_edges, GrB_NULL)); LG_TRY(s_mxm_chain(M_return, rsm_nonterm[i], P, new_edges, temp1, temp2, Q, V)); +======= + GRB_TRY(reshape(&new_edges, false, V, V, GrB_NULL)); + GRB_TRY(GrB_eWiseAdd(graph_nt[i], GrB_NULL, GrB_NULL, GrB_LOR, graph_nt[i], new_edges, + GrB_NULL)); - GRB_TRY(GxB_Matrix_reshape(new_edges, false, 1, VV, GrB_NULL)); + LG_TRY(s_mxm_chain(M_return, rsm_nonterm[i], P, new_edges, &temp1, &temp2, Q, V)); +>>>>>>> Stashed changes + + GRB_TRY(reshape(&new_edges, false, 1, VV, GrB_NULL)); } // PHASE 5: MERGE & MARK VISITED // GrB_assign has big overhead // M_new = (M_term | M_nonterm | M_call | M_return) & ~P GRB_TRY(GrB_Matrix_clear(M_new)); +<<<<<<< Updated upstream GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_LOR, GrB_LOR, M_new, M_term, GrB_NULL)); GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_LOR, GrB_LOR, M_new, M_nonterm, GrB_NULL)); GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_LOR, GrB_LOR, M_new, M_call, GrB_NULL)); GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_LOR, GrB_LOR, M_new, M_return, GrB_NULL)); +======= + GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_NULL, GrB_LOR, M_new, M_term, GrB_NULL)); + GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_NULL, GrB_LOR, M_new, M_nonterm, GrB_NULL)); + GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_NULL, GrB_LOR, M_new, M_call, GrB_NULL)); + GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_NULL, GrB_LOR, M_new, M_return, GrB_NULL)); +>>>>>>> Stashed changes // M_new &= ~P GRB_TRY(GrB_assign(M_new, P, GrB_NULL, M_new, GrB_ALL, Q, GrB_ALL, VV, GrB_DESC_RSC)); // P |= M_new - GRB_TRY(GrB_eWiseAdd(P, GrB_NULL, GrB_LOR, GrB_LOR, P, M_new, GrB_NULL)); + GRB_TRY(GrB_eWiseAdd(P, GrB_NULL, GrB_NULL, GrB_LOR, P, M_new, GrB_NULL)); GrB_Matrix swap = M; M = M_new; @@ -390,7 +480,6 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr } // Extract all reachable vertices - { bool *vals = NULL; LG_TRY(LAGraph_Malloc((void **)&vals, num_sources, sizeof(bool), msg)); diff --git a/experimental/test/test_CFPQ_RSM.c b/experimental/test/test_CFPQ_RSM.c index 6a37fff856..e19b604bae 100644 --- a/experimental/test/test_CFPQ_RSM.c +++ b/experimental/test/test_CFPQ_RSM.c @@ -19,6 +19,12 @@ static GrB_Vector result = GrB_NULL; static void setup(void) { LAGraph_Init(msg); +<<<<<<< Updated upstream +======= + // All new matrices will use 64-bit indices by default: + GxB_Global_Option_set(GxB_ROWINDEX_INTEGER_BITS, 64); + GxB_Global_Option_set(GxB_COLINDEX_INTEGER_BITS, 64); +>>>>>>> Stashed changes } static void teardown(void) @@ -220,6 +226,37 @@ static void init_rsm_complex(void) OK(GrB_Vector_setElement_BOOL(rsm->final_states[2], true, 10)); } +static void init_c_alias_rsm(void) +{ + GrB_Index Q = 8; + rsm = alloc_rsm(Q, /*term_count=*/4, /*nonterm_count=*/2, /*start_nonterm=*/0); + // alphabet: d_r=0; d=1, a=2, a_r=3 + + // --- S Component (States 0-3) --- + // S -> d_r V d + OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[0], true, 0, 1)); // 0 -d_r-> 1 + OK(GrB_Matrix_setElement_BOOL(rsm->nonterminal_matrices[1], true, 1, 2)); // 1 -V-> 2 + OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[1], true, 2, 3)); // 2 -d-> 3 + rsm->start_states[0] = 0; + OK(GrB_Vector_setElement_BOOL(rsm->final_states[0], true, 3)); + + // --- V Component (States 4-7) --- + OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[2], true, 4, 6)); // 4 -a-> 6 + OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[3], true, 4, 4)); // 4 -a_r-> 4 + OK(GrB_Matrix_setElement_BOOL(rsm->nonterminal_matrices[0], true, 4, 5)); // 4 -S-> 5 + OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[3], true, 5, 4)); // 5 -a_r-> 4 + OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[2], true, 5, 6)); // 5 -a-> 6 + OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[2], true, 6, 6)); // 6 -a-> 6 + OK(GrB_Matrix_setElement_BOOL(rsm->nonterminal_matrices[0], true, 6, 7)); // 6 -S-> 7 + OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[2], true, 7, 6)); // 7 -a-> 6 + rsm->start_states[1] = 4; + // All states are final + OK(GrB_Vector_setElement_BOOL(rsm->final_states[1], true, 4)); + OK(GrB_Vector_setElement_BOOL(rsm->final_states[1], true, 5)); + OK(GrB_Vector_setElement_BOOL(rsm->final_states[1], true, 6)); + OK(GrB_Vector_setElement_BOOL(rsm->final_states[1], true, 7)); +} + //============================================================================== // Graph builders //============================================================================== @@ -334,6 +371,21 @@ static void init_graph_multi_cycle(void) OK(GrB_Matrix_setElement_BOOL(graph_term[3], true, 2, 5)); } +<<<<<<< Updated upstream +======= +static void init_graph_c_alias_overflow(void) +{ + V = 65537; + n_graph_term = 4; + LAGraph_Calloc((void **)&graph_term, n_graph_term, sizeof(GrB_Matrix), msg); + for (size_t i = 0; i < n_graph_term; ++i) + OK(GrB_Matrix_new(&graph_term[i], GrB_BOOL, V, V)); + + OK(GrB_Matrix_setElement_BOOL(graph_term[0], true, 0, 65536)); + OK(GrB_Matrix_setElement_BOOL(graph_term[1], true, 65536, 0)); +} + +>>>>>>> Stashed changes #define run_algorithm(sources, num_sources) \ LAGraph_CFPQ_RSM(&result, rsm, graph_term, (sources), (num_sources), V, msg); @@ -563,6 +615,30 @@ void test_TC9_mult_recursive_cycles(void) teardown(); } +//------------------------------------------------------------------------------ +// TC11: c_alias_rsm +// Graph: 65536 -d-> 0, 0 -d_r-> 65536 +// Source: 0 Expected: {0} +//------------------------------------------------------------------------------ +void test_TC10_c_alias_overflow(void) +{ + setup(); + init_c_alias_rsm(); + init_graph_c_alias_overflow(); + + GrB_Index sources = {0}; + GrB_Info info = run_algorithm(&sources, /*num_sources=*/1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {0}; + check_reachable(expected, /*n_expected=*/1, "TC10"); + } + + free_workspace(); + OK(info); + teardown(); +} + TEST_LIST = {{"TC1_cyclic_ab", test_TC1_cyclic_ab}, {"TC2_self_loops", test_TC2_self_loops}, {"TC3_single_node", test_TC3_single_node}, @@ -572,4 +648,9 @@ TEST_LIST = {{"TC1_cyclic_ab", test_TC1_cyclic_ab}, {"TC7_aSb_c_cycle3", test_TC7_aSb_c_cycle3}, {"TC8_aSb_c_cycle4", test_TC8_aSb_c_cycle4}, {"TC9_mult_recursive_cycles", test_TC9_mult_recursive_cycles}, +<<<<<<< Updated upstream + {NULL, NULL}}; +======= + {"TC10_c_alias_overflow", test_TC10_c_alias_overflow}, {NULL, NULL}}; +>>>>>>> Stashed changes From bb7dc96e66a99ca7628374c177f04d5dab36bd92 Mon Sep 17 00:00:00 2001 From: Sterkachov Date: Wed, 29 Apr 2026 10:28:00 +0300 Subject: [PATCH 12/25] fix: resolve stash conflict markers from accidental push --- experimental/algorithm/LAGraph_CFPQ_RSM.c | 94 +---------------------- experimental/test/test_CFPQ_RSM.c | 13 ---- 2 files changed, 2 insertions(+), 105 deletions(-) diff --git a/experimental/algorithm/LAGraph_CFPQ_RSM.c b/experimental/algorithm/LAGraph_CFPQ_RSM.c index 68052ea3f7..abe58b6855 100644 --- a/experimental/algorithm/LAGraph_CFPQ_RSM.c +++ b/experimental/algorithm/LAGraph_CFPQ_RSM.c @@ -25,15 +25,6 @@ GrB_free(&outer_result); \ GrB_free(&entry_vec); \ GrB_free(&source_mask); \ -<<<<<<< Updated upstream - if (Stack != NULL) \ - { \ - for (size_t _i = 0; _i < num_nonterm; ++_i) \ - GrB_free(&Stack[_i]); \ - LAGraph_Free((void **)&Stack, msg); \ - } \ -======= ->>>>>>> Stashed changes if (graph_nt != NULL) \ { \ for (size_t _i = 0; _i < num_nonterm; ++_i) \ @@ -66,21 +57,6 @@ return _hi; \ } -<<<<<<< Updated upstream -static GrB_Info s_mxm_chain(GrB_Matrix C, // output accumulator |Q|*|V*V| - GrB_Matrix A, // RSM matrix |Q|*|Q| - GrB_Matrix M, // frontier |Q|*|V*V| - GrB_Matrix B, // graph matrix |V|*|V| - GrB_Matrix temp1, // scratch |Q|*|V*V| - GrB_Matrix temp2, // scratch |Q|*|V*V| - GrB_Index Q, GrB_Index V) -{ - H_TRY(GrB_Matrix_clear(temp1)); - H_TRY(GrB_Matrix_clear(temp2)); - - // temp1 = A^T * M -> |Q|*|V*V| - H_TRY(GrB_mxm(temp1, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, A, M, GrB_DESC_T0)); -======= static inline GrB_Info reshape( // input/output: GrB_Matrix *C, // input/output matrix, reshaped in place bool by_col, // true if reshape by column, false if by row @@ -112,30 +88,21 @@ static GrB_Info s_mxm_chain(GrB_Matrix C, // output accumulator |Q|*|V*V| H_TRY(GrB_Matrix_clear(*temp2)); // temp1 = A^T * M -> |Q|*|V*V| - H_TRY(GrB_mxm(*temp1, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, A, M, GrB_DESC_T0)); ->>>>>>> Stashed changes + H_TRY(GrB_mxm(*temp1, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, A, M, GrB_DESC_T0)); // reshape: |Q| × |V*V| -> |Q*V|*|V| H_TRY(reshape(temp1, false, Q * V, V, GrB_NULL)); H_TRY(reshape(temp2, false, Q * V, V, GrB_NULL)); // temp2 = temp1 * B -> |Q*V|*|V| -<<<<<<< Updated upstream - H_TRY(GrB_mxm(temp2, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, temp1, B, GrB_NULL)); -======= H_TRY(GrB_mxm(*temp2, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, *temp1, B, GrB_NULL)); ->>>>>>> Stashed changes // reshape: |Q*V|*|V| -> |Q|*|V*V| H_TRY(reshape(temp1, false, Q, V * V, GrB_NULL)); H_TRY(reshape(temp2, false, Q, V * V, GrB_NULL)); // C |= temp2 -<<<<<<< Updated upstream - H_TRY(GrB_eWiseAdd(C, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, C, temp2, GrB_NULL)); -======= - H_TRY(GrB_eWiseAdd(C, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, C, *temp2, GrB_NULL)); ->>>>>>> Stashed changes + H_TRY(GrB_eWiseAdd(C, GrB_NULL, GrB_NULL, GrB_LOR, C, *temp2, GrB_NULL)); return GrB_SUCCESS; } @@ -221,20 +188,12 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr *reachable = GrB_NULL; // allocate per-nonterminal arrays -<<<<<<< Updated upstream - LG_TRY(LAGraph_Malloc((void **)&Stack, rsm->nonterminal_count, sizeof(GrB_Matrix), msg)); -======= ->>>>>>> Stashed changes LG_TRY(LAGraph_Malloc((void **)&graph_nt, rsm->nonterminal_count, sizeof(GrB_Matrix), msg)); LG_TRY(LAGraph_Malloc((void **)&rsm_start, rsm->nonterminal_count, sizeof(GrB_Vector), msg)); LG_TRY(LAGraph_Malloc((void **)&rsm_call, rsm->nonterminal_count, sizeof(GrB_Matrix), msg)); for (size_t i = 0; i < num_nonterm; ++i) { -<<<<<<< Updated upstream - Stack[i] = GrB_NULL; -======= ->>>>>>> Stashed changes graph_nt[i] = GrB_NULL; rsm_start[i] = GrB_NULL; rsm_call[i] = GrB_NULL; @@ -242,10 +201,6 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr for (size_t i = 0; i < num_nonterm; ++i) { -<<<<<<< Updated upstream - GRB_TRY(GrB_Matrix_new(&Stack[i], GrB_BOOL, Q, VV)); -======= ->>>>>>> Stashed changes GRB_TRY(GrB_Matrix_new(&graph_nt[i], GrB_BOOL, V, V)); GRB_TRY(GrB_Vector_new(&rsm_start[i], GrB_BOOL, Q)); GrB_Vector_setElement_BOOL(rsm_start[i], true, start_states[i]); @@ -301,10 +256,6 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GrB_Index q = seed_states[k]; GRB_TRY(GrB_Matrix_setElement_BOOL(M, true, q, seed_col)); GRB_TRY(GrB_Matrix_setElement_BOOL(P, true, q, seed_col)); -<<<<<<< Updated upstream - GRB_TRY(GrB_Matrix_setElement_BOOL(Stack[start_nonterm], true, q, seed_col)); -======= ->>>>>>> Stashed changes } } @@ -316,21 +267,13 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GRB_TRY(GrB_Matrix_nvals(&m_nvals, M)); while (m_nvals > 0) { -<<<<<<< Updated upstream - -======= ->>>>>>> Stashed changes // PHASE 1: TERMINAL TRANSITIONS GRB_TRY(GrB_Matrix_clear(M_term)); for (size_t i = 0; i < num_term; ++i) { if (rsm_term[i] == GrB_NULL || graph_term[i] == GrB_NULL) continue; -<<<<<<< Updated upstream - LG_TRY(s_mxm_chain(M_term, rsm_term[i], M, graph_term[i], temp1, temp2, Q, V)); -======= LG_TRY(s_mxm_chain(M_term, rsm_term[i], M, graph_term[i], &temp1, &temp2, Q, V)); ->>>>>>> Stashed changes } // PHASE 2: NON-TERMINAL TRANSITIONS @@ -345,11 +288,7 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr if (gnt_nvals == 0) continue; -<<<<<<< Updated upstream - LG_TRY(s_mxm_chain(M_nonterm, rsm_nonterm[i], M, graph_nt[i], temp1, temp2, Q, V)); -======= LG_TRY(s_mxm_chain(M_nonterm, rsm_nonterm[i], M, graph_nt[i], &temp1, &temp2, Q, V)); ->>>>>>> Stashed changes } // PHASE 3: CALL TRANSITIONS @@ -371,14 +310,8 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr // entry_vec = column-OR of mask_call reshaped to |Q*V|*|V| GRB_TRY(GrB_Matrix_clear(temp1)); -<<<<<<< Updated upstream - GRB_TRY(GrB_eWiseAdd(temp1, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, temp1, - mask_call, GrB_NULL)); - GRB_TRY(GxB_Matrix_reshape(temp1, false, Q * V, V, GrB_NULL)); -======= GRB_TRY(GrB_eWiseAdd(temp1, GrB_NULL, GrB_NULL, GrB_LOR, temp1, mask_call, GrB_NULL)); GRB_TRY(reshape(&temp1, false, Q * V, V, GrB_NULL)); ->>>>>>> Stashed changes GRB_TRY(GrB_Vector_clear(entry_vec)); // column OR = row OR of transpose @@ -405,11 +338,7 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr // M_call |= outer_result GRB_TRY( -<<<<<<< Updated upstream - GrB_eWiseAdd(M_call, GrB_NULL, GrB_LOR, GrB_LOR, M_call, outer_result, GrB_NULL)); -======= GrB_eWiseAdd(M_call, GrB_NULL, GrB_NULL, GrB_LOR, M_call, outer_result, GrB_NULL)); ->>>>>>> Stashed changes } // PHASE 4: RETURN TRANSITIONS @@ -427,25 +356,13 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GrB_Index ne_vals = 0; GRB_TRY(GrB_Matrix_nvals(&ne_vals, new_edges)); if (ne_vals == 0) -<<<<<<< Updated upstream - { - GRB_TRY(GxB_Matrix_reshape(new_edges, false, 1, VV, GrB_NULL)); -======= ->>>>>>> Stashed changes continue; -<<<<<<< Updated upstream - GRB_TRY(GrB_eWiseAdd(graph_nt[i], GrB_NULL, GrB_LOR, GrB_LOR, graph_nt[i], new_edges, - GrB_NULL)); - - LG_TRY(s_mxm_chain(M_return, rsm_nonterm[i], P, new_edges, temp1, temp2, Q, V)); -======= GRB_TRY(reshape(&new_edges, false, V, V, GrB_NULL)); GRB_TRY(GrB_eWiseAdd(graph_nt[i], GrB_NULL, GrB_NULL, GrB_LOR, graph_nt[i], new_edges, GrB_NULL)); LG_TRY(s_mxm_chain(M_return, rsm_nonterm[i], P, new_edges, &temp1, &temp2, Q, V)); ->>>>>>> Stashed changes GRB_TRY(reshape(&new_edges, false, 1, VV, GrB_NULL)); } @@ -454,17 +371,10 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr // GrB_assign has big overhead // M_new = (M_term | M_nonterm | M_call | M_return) & ~P GRB_TRY(GrB_Matrix_clear(M_new)); -<<<<<<< Updated upstream - GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_LOR, GrB_LOR, M_new, M_term, GrB_NULL)); - GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_LOR, GrB_LOR, M_new, M_nonterm, GrB_NULL)); - GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_LOR, GrB_LOR, M_new, M_call, GrB_NULL)); - GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_LOR, GrB_LOR, M_new, M_return, GrB_NULL)); -======= GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_NULL, GrB_LOR, M_new, M_term, GrB_NULL)); GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_NULL, GrB_LOR, M_new, M_nonterm, GrB_NULL)); GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_NULL, GrB_LOR, M_new, M_call, GrB_NULL)); GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_NULL, GrB_LOR, M_new, M_return, GrB_NULL)); ->>>>>>> Stashed changes // M_new &= ~P GRB_TRY(GrB_assign(M_new, P, GrB_NULL, M_new, GrB_ALL, Q, GrB_ALL, VV, GrB_DESC_RSC)); diff --git a/experimental/test/test_CFPQ_RSM.c b/experimental/test/test_CFPQ_RSM.c index e19b604bae..7d741b3837 100644 --- a/experimental/test/test_CFPQ_RSM.c +++ b/experimental/test/test_CFPQ_RSM.c @@ -19,12 +19,6 @@ static GrB_Vector result = GrB_NULL; static void setup(void) { LAGraph_Init(msg); -<<<<<<< Updated upstream -======= - // All new matrices will use 64-bit indices by default: - GxB_Global_Option_set(GxB_ROWINDEX_INTEGER_BITS, 64); - GxB_Global_Option_set(GxB_COLINDEX_INTEGER_BITS, 64); ->>>>>>> Stashed changes } static void teardown(void) @@ -371,8 +365,6 @@ static void init_graph_multi_cycle(void) OK(GrB_Matrix_setElement_BOOL(graph_term[3], true, 2, 5)); } -<<<<<<< Updated upstream -======= static void init_graph_c_alias_overflow(void) { V = 65537; @@ -385,7 +377,6 @@ static void init_graph_c_alias_overflow(void) OK(GrB_Matrix_setElement_BOOL(graph_term[1], true, 65536, 0)); } ->>>>>>> Stashed changes #define run_algorithm(sources, num_sources) \ LAGraph_CFPQ_RSM(&result, rsm, graph_term, (sources), (num_sources), V, msg); @@ -648,9 +639,5 @@ TEST_LIST = {{"TC1_cyclic_ab", test_TC1_cyclic_ab}, {"TC7_aSb_c_cycle3", test_TC7_aSb_c_cycle3}, {"TC8_aSb_c_cycle4", test_TC8_aSb_c_cycle4}, {"TC9_mult_recursive_cycles", test_TC9_mult_recursive_cycles}, -<<<<<<< Updated upstream - {NULL, NULL}}; -======= {"TC10_c_alias_overflow", test_TC10_c_alias_overflow}, {NULL, NULL}}; ->>>>>>> Stashed changes From d5c1dc6bf8ae1fd3fdd3fa782d72a6a8c55b529b Mon Sep 17 00:00:00 2001 From: Tim Davis Date: Mon, 20 Jul 2026 09:15:11 -0500 Subject: [PATCH 13/25] fix workflow on macos ... again ... because of changes in brew --- .github/workflows/build.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ff5b7ff06e..e72aecfb18 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,13 +18,13 @@ jobs: config: # if there are multiple items in this list, only use should # deployit=true for just one of them. - - {grb_version: 9.0.0, deployit: false} - - {grb_version: 10.1.0, deployit: true} + #- {grb_version: 9.0.0, deployit: false} + - {grb_version: 10.3.0, deployit: true} steps: - name: Checkout - uses: actions/checkout@v2.0.0 + uses: actions/checkout@v7 - name: Install tools for build run: | @@ -68,19 +68,20 @@ jobs: strategy: matrix: config: - - {grb_version: 9.0.0} - - {grb_version: 10.1.0} + # - {grb_version: 9.0.0} + - {grb_version: 10.3.0} steps: - name: Checkout - uses: actions/checkout@v2.0.0 + uses: actions/checkout@v7 - name: Install dependencies run: | brew uninstall cmake brew tap-new libomp/cask brew extract --version=14.0.6 libomp libomp/cask + brew trust libomp/cask brew install libomp@14.0.6 - name: Build GraphBLAS @@ -99,5 +100,5 @@ jobs: cd build cmake .. JOBS=2 make - ctest . + ctest . --rerun-failed --output-on-failure From 72c7bfab63ec94bc3754d4f099bf8a2cc4b38898 Mon Sep 17 00:00:00 2001 From: Tim Davis Date: Mon, 20 Jul 2026 15:26:02 -0500 Subject: [PATCH 14/25] v1.2.2: fix for MSVC which has no stdalign.h --- CMakeLists.txt | 17 ++++++++++++++--- Config/LAGraph.h.in | 13 +++++++------ experimental/algorithm/LAGraph_cdlp.c | 11 +++++++++-- include/LAGraph.h | 17 +++++++++-------- 4 files changed, 39 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1aad95a819..91beb26b75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,13 +36,13 @@ # get the version #------------------------------------------------------------------------------- -cmake_minimum_required ( VERSION 3.20 ) # LAGraph can be built stand-alone +cmake_minimum_required ( VERSION 3.23 ) # version of LAGraph -set ( LAGraph_DATE "Sept 8, 2025" ) +set ( LAGraph_DATE "July 21, 2026" ) set ( LAGraph_VERSION_MAJOR 1 CACHE STRING "" FORCE ) set ( LAGraph_VERSION_MINOR 2 CACHE STRING "" FORCE ) -set ( LAGraph_VERSION_SUB 1 CACHE STRING "" FORCE ) +set ( LAGraph_VERSION_SUB 2 CACHE STRING "" FORCE ) message ( STATUS "Building LAGraph version: v" ${LAGraph_VERSION_MAJOR}. @@ -70,6 +70,17 @@ include ( SuiteSparsePolicy ) enable_language ( C ) +# check if stdalign.h exists +include ( CheckIncludeFile ) +check_include_file ( stdalign.h HAVE_STDALIGN_H ) +if ( HAVE_STDALIGN_H ) + set ( LAGraph_HAS_STDALIGN_H 1 ) + message ( STATUS "stdalign.h is available" ) +else ( ) + message ( STATUS "stdalign.h is not available" ) + set ( LAGraph_HAS_STDALIGN_H 0 ) +endif ( ) + # configure LAGraph.h with the LAGraph date and version configure_file ( "Config/LAGraph.h.in" diff --git a/Config/LAGraph.h.in b/Config/LAGraph.h.in index a46c06c8eb..b47e20b68f 100644 --- a/Config/LAGraph.h.in +++ b/Config/LAGraph.h.in @@ -41,6 +41,7 @@ #define LAGRAPH_VERSION_MAJOR @LAGraph_VERSION_MAJOR@ #define LAGRAPH_VERSION_MINOR @LAGraph_VERSION_MINOR@ #define LAGRAPH_VERSION_UPDATE @LAGraph_VERSION_SUB@ +#define LAGRAPH_HAS_STDALIGN_H @LAGraph_HAS_STDALIGN_H@ //============================================================================== // include files and helper macros @@ -104,8 +105,8 @@ #if ( !LAGRAPH_VANILLA ) && defined ( GxB_SUITESPARSE_GRAPHBLAS ) // use SuiteSparse, and its GxB* extensions - #if GxB_IMPLEMENTATION < GxB_VERSION (9,0,0) - #error "If using SuiteSparse::GraphBLAS, version 9.0.0 or later is required" + #if GxB_IMPLEMENTATION < GxB_VERSION (10,0,0) + #error "SuiteSparse::GraphBLAS v10.0.0 or later is required" #endif #define LAGRAPH_SUITESPARSE 1 #else @@ -2410,11 +2411,11 @@ int LAGr_SingleSourceShortestPath ) ; //------------------------------------------------------------------------------ -// LAGr_Betweenness: betweeness centrality metric +// LAGr_Betweenness: betweenness centrality metric //------------------------------------------------------------------------------ -/** LAGr_Betweenness: betweeness centrality metric. This methods computes an - * approximation of the betweeness-centrality metric of all nodes in the graph. +/** LAGr_Betweenness: betweenness centrality metric. This methods computes an + * approximation of the betweenness-centrality metric of all nodes in the graph. * Only a few given source nodes are used for the approximation. This is an * Advanced algorithm (G->AT is required). * @@ -2442,7 +2443,7 @@ LAGRAPH_PUBLIC int LAGr_Betweenness ( // output: - GrB_Vector *centrality, // centrality(i): betweeness centrality of i + GrB_Vector *centrality, // centrality(i): betweenness centrality of i // input: const LAGraph_Graph G, // input graph const GrB_Index *sources, // source vertices to compute shortest paths diff --git a/experimental/algorithm/LAGraph_cdlp.c b/experimental/algorithm/LAGraph_cdlp.c index b8aceb624d..a6f2ba8f97 100644 --- a/experimental/algorithm/LAGraph_cdlp.c +++ b/experimental/algorithm/LAGraph_cdlp.c @@ -73,7 +73,13 @@ #include #include -#include +#if LAGRAPH_HAS_STDALIGN_H + #include + #define ALIGNAS_64 alignas(64) +#else + // the alignas keyword/macro is not available + #define ALIGNAS_64 +#endif #include "LG_internal.h" // A Go-style slice / Lisp-style property list @@ -141,7 +147,7 @@ void counts_reducer(GrB_Index* e1, GrB_Index* c1, GrB_Index e2, GrB_Index c2) { #define bucket_shift (64llu - bucket_bits) typedef struct { - alignas(64) plist buckets[nof_buckets]; + ALIGNAS_64 plist buckets[nof_buckets]; } ptable; void ptable_free(ptable* table) { @@ -187,6 +193,7 @@ void ptable_reduce(ptable* table, GrB_Index* entry, GrB_Index* count, plist_redu } //**************************************************************************** + int LAGraph_cdlp ( GrB_Vector *CDLP_handle, // output vector diff --git a/include/LAGraph.h b/include/LAGraph.h index ea88c7c7dc..0a2fca849a 100644 --- a/include/LAGraph.h +++ b/include/LAGraph.h @@ -37,10 +37,11 @@ // See also the LAGraph_Version utility method, which returns these values. // These definitions are derived from LAGraph/CMakeLists.txt. -#define LAGRAPH_DATE "Sept 8, 2025" +#define LAGRAPH_DATE "July 21, 2026" #define LAGRAPH_VERSION_MAJOR 1 #define LAGRAPH_VERSION_MINOR 2 -#define LAGRAPH_VERSION_UPDATE 1 +#define LAGRAPH_VERSION_UPDATE 2 +#define LAGRAPH_HAS_STDALIGN_H 1 //============================================================================== // include files and helper macros @@ -104,8 +105,8 @@ #if ( !LAGRAPH_VANILLA ) && defined ( GxB_SUITESPARSE_GRAPHBLAS ) // use SuiteSparse, and its GxB* extensions - #if GxB_IMPLEMENTATION < GxB_VERSION (9,0,0) - #error "If using SuiteSparse::GraphBLAS, version 9.0.0 or later is required" + #if GxB_IMPLEMENTATION < GxB_VERSION (10,0,0) + #error "SuiteSparse::GraphBLAS v10.0.0 or later is required" #endif #define LAGRAPH_SUITESPARSE 1 #else @@ -2410,11 +2411,11 @@ int LAGr_SingleSourceShortestPath ) ; //------------------------------------------------------------------------------ -// LAGr_Betweenness: betweeness centrality metric +// LAGr_Betweenness: betweenness centrality metric //------------------------------------------------------------------------------ -/** LAGr_Betweenness: betweeness centrality metric. This methods computes an - * approximation of the betweeness-centrality metric of all nodes in the graph. +/** LAGr_Betweenness: betweenness centrality metric. This methods computes an + * approximation of the betweenness-centrality metric of all nodes in the graph. * Only a few given source nodes are used for the approximation. This is an * Advanced algorithm (G->AT is required). * @@ -2442,7 +2443,7 @@ LAGRAPH_PUBLIC int LAGr_Betweenness ( // output: - GrB_Vector *centrality, // centrality(i): betweeness centrality of i + GrB_Vector *centrality, // centrality(i): betweenness centrality of i // input: const LAGraph_Graph G, // input graph const GrB_Index *sources, // source vertices to compute shortest paths From 66f0601397af60ac249016d618b579cdd46da36e Mon Sep 17 00:00:00 2001 From: Tim Davis Date: Mon, 20 Jul 2026 16:01:41 -0500 Subject: [PATCH 15/25] revise SuiteSparsePolicy.cmake (from SuiteSparse 7.12.3) --- cmake_modules/SuiteSparsePolicy.cmake | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/cmake_modules/SuiteSparsePolicy.cmake b/cmake_modules/SuiteSparsePolicy.cmake index 80906521bf..27c43547f5 100644 --- a/cmake_modules/SuiteSparsePolicy.cmake +++ b/cmake_modules/SuiteSparsePolicy.cmake @@ -35,10 +35,10 @@ # GraphBLAS is false. # # SUITESPARSE_CUDA_ARCHITECTURES: a string, such as "all" or -# "35;50;75;80" that lists the CUDA architectures to use +# "75;80" that lists the CUDA architectures to use # when compiling CUDA kernels with nvcc. The "all" # option requires cmake 3.23 or later. -# Default: "52;75;80". +# Default: "all". # # BLA_VENDOR and BLA_SIZEOF_INTEGER: By default, SuiteSparse searches for # the BLAS library in a specific order. If you wish to @@ -310,24 +310,19 @@ if ( SUITESPARSE_USE_FORTRAN ) set ( SUITESPARSE_HAS_FORTRAN ON ) if ( NOT "${CMAKE_Fortran_COMPILER_ID}" STREQUAL "${CMAKE_C_COMPILER_ID}" OR NOT "${CMAKE_Fortran_COMPILER_ID}" STREQUAL "${CMAKE_CXX_COMPILER_ID}" ) - message ( STATUS " " ) - message ( STATUS "Incompatible Fortran/C/C++ compilers detected:" ) - message ( STATUS " Fortran: ${CMAKE_Fortran_COMPILER}" ) - message ( STATUS " Fortran id: ${CMAKE_Fortran_COMPILER_ID}" ) - message ( STATUS " C ${CMAKE_C_COMPILER}" ) - message ( STATUS " C id: ${CMAKE_C_COMPILER_ID}" ) - message ( STATUS " C++ ${CMAKE_CXX_COMPILER}" ) - message ( STATUS " C++ id: ${CMAKE_CXX_COMPILER_ID}" ) if ( "${CMAKE_C_COMPILER_ID}" STREQUAL "IntelLLVM" ) + message ( STATUS " " ) + message ( STATUS "Incompatible Fortran/C/C++ compilers detected:" ) + message ( STATUS " Fortran: ${CMAKE_Fortran_COMPILER}" ) + message ( STATUS " Fortran id: ${CMAKE_Fortran_COMPILER_ID}" ) + message ( STATUS " C ${CMAKE_C_COMPILER}" ) + message ( STATUS " C id: ${CMAKE_C_COMPILER_ID}" ) + message ( STATUS " C++ ${CMAKE_CXX_COMPILER}" ) + message ( STATUS " C++ id: ${CMAKE_CXX_COMPILER_ID}" ) # icx/icpx cannot be used with gfortran: this is a fatal error message ( FATAL_ERROR "ERROR: Using Fortran with SuiteSparse requires that " " it has the same compiler ID as the C/C++ compilers." " Use a compatible Fortran compiler, or set SUITESPARSE_USE_FORTRAN to OFF." ) - else ( ) - # other cases: just issue a warning and hope it works. - message ( WARNING "Warning: Using Fortran with SuiteSparse requires that " - " it has the same compiler ID as the C/C++ compilers." - " Use a compatible Fortran compiler, or set SUITESPARSE_USE_FORTRAN to OFF." ) endif ( ) endif ( ) message ( STATUS "Fortran: enabled" ) @@ -401,7 +396,7 @@ endif ( ) if ( SUITESPARSE_HAS_CUDA ) message ( STATUS "CUDA: enabled" ) - set ( SUITESPARSE_CUDA_ARCHITECTURES "52;75;80" CACHE STRING "CUDA architectures" ) + set ( SUITESPARSE_CUDA_ARCHITECTURES "all" CACHE STRING "CUDA architectures" ) set ( CMAKE_CUDA_ARCHITECTURES ${SUITESPARSE_CUDA_ARCHITECTURES} ) else ( ) message ( STATUS "CUDA: not enabled" ) From 81860d1f5318910761dbb3d3c6ddbd2a7d43c162 Mon Sep 17 00:00:00 2001 From: Tim Davis Date: Mon, 20 Jul 2026 19:14:59 -0500 Subject: [PATCH 16/25] LAGraph config --- CMakeLists.txt | 22 ++++++++++++++++++++-- Config/LAGraphConfig.cmake.in | 4 ++-- cmake_modules/FindGraphBLAS.cmake | 20 ++++++++++---------- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 91beb26b75..bc760d5587 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -163,8 +163,7 @@ else ( ) # message ( STATUS "GRAPHBLAS_ROOT: ${GRAPHBLAS_ROOT} $ENV{GRAPHBLAS_ROOT}" ) # No package version is explicitly stated here; an arbitrary GraphBLAS - # library can have any version number. For SuiteSparse:GraphBLAS, LAGraph - # requires v7.1.0 or later, which is checked in LAGraph.h. + # library can have any version number. message ( STATUS "Looking for GraphBLAS with FindGraphBLAS.cmake" ) find_package ( GraphBLAS MODULE REQUIRED ) @@ -386,6 +385,25 @@ else ( ) endif ( ) endif ( ) +if ( GRAPHBLAS_VERSION ) + if ( ${GRAPHBLAS_VERSION} MATCHES "([0-9]+).[0-9]+.[0-9]+" ) + set ( GraphBLAS_VER_MAJOR ${CMAKE_MATCH_1} ) + endif ( ) + if ( ${GRAPHBLAS_VERSION} MATCHES "[0-9]+.([0-9]+).[0-9]+" ) + set ( GraphBLAS_VER_MINOR ${CMAKE_MATCH_1} ) + endif ( ) + if ( ${GRAPHBLAS_VERSION} MATCHES "[0-9]+.[0-9]+.([0-9]+)" ) + set ( GraphBLAS_VER_PATCH ${CMAKE_MATCH_1} ) + endif ( ) + if ( LAGRAPH_DUMP ) + message ( STATUS "major: ${GraphBLAS_VER_MAJOR}" ) + message ( STATUS "minor: ${GraphBLAS_VER_MINOR}" ) + message ( STATUS "patch: ${GraphBLAS_VER_PATCH}" ) + endif ( ) +endif ( ) + +message (STATUS "Config with GraphBLAS version ${GraphBLAS_VER_MAJOR}.${GraphBLAS_VER_MINOR}.${GraphBLAS_VER_PATCH}" ) + # generate config file to be used in common build tree set ( SUITESPARSE_IN_BUILD_TREE ON ) configure_package_config_file ( diff --git a/Config/LAGraphConfig.cmake.in b/Config/LAGraphConfig.cmake.in index 6e801a9d86..666b93e03d 100644 --- a/Config/LAGraphConfig.cmake.in +++ b/Config/LAGraphConfig.cmake.in @@ -41,9 +41,9 @@ include ( CMakeFindDependencyMacro ) set ( _dependencies_found ON ) if ( NOT TARGET GraphBLAS::GraphBLAS ) - # Look GraphBLAS + # Look for GraphBLAS list ( PREPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR} ) - find_dependency ( GraphBLAS @GraphBLAS_VERSION_MAJOR@.@GraphBLAS_VERSION_MINOR@ ) + find_dependency ( GraphBLAS @GraphBLAS_VER_MAJOR@.@GraphBLAS_VER_MINOR@ ) endif ( ) if ( NOT GraphBLAS_FOUND ) diff --git a/cmake_modules/FindGraphBLAS.cmake b/cmake_modules/FindGraphBLAS.cmake index 64b8a9a3b3..3ebf3df2c9 100644 --- a/cmake_modules/FindGraphBLAS.cmake +++ b/cmake_modules/FindGraphBLAS.cmake @@ -271,18 +271,18 @@ string ( if ( GRAPHBLAS_VERSION ) if ( ${GRAPHBLAS_VERSION} MATCHES "([0-9]+).[0-9]+.[0-9]+" ) - set ( GraphBLAS_VERSION_MAJOR ${CMAKE_MATCH_1} ) + set ( GraphBLAS_VER_MAJOR ${CMAKE_MATCH_1} ) endif ( ) if ( ${GRAPHBLAS_VERSION} MATCHES "[0-9]+.([0-9]+).[0-9]+" ) - set ( GraphBLAS_VERSION_MINOR ${CMAKE_MATCH_1} ) + set ( GraphBLAS_VER_MINOR ${CMAKE_MATCH_1} ) endif ( ) if ( ${GRAPHBLAS_VERSION} MATCHES "[0-9]+.[0-9]+.([0-9]+)" ) - set ( GraphBLAS_VERSION_PATCH ${CMAKE_MATCH_1} ) + set ( GraphBLAS_VER_PATCH ${CMAKE_MATCH_1} ) endif ( ) if ( LAGRAPH_DUMP ) - message ( STATUS "major: ${GraphBLAS_VERSION_MAJOR}" ) - message ( STATUS "minor: ${GraphBLAS_VERSION_MINOR}" ) - message ( STATUS "patch: ${GraphBLAS_VERSION_PATCH}" ) + message ( STATUS "major: ${GraphBLAS_VER_MAJOR}" ) + message ( STATUS "minor: ${GraphBLAS_VER_MINOR}" ) + message ( STATUS "patch: ${GraphBLAS_VER_PATCH}" ) endif ( ) endif ( ) @@ -300,10 +300,10 @@ if ( EXISTS "${GRAPHBLAS_INCLUDE_DIR}" AND NOT GRAPHBLAS_VERSION ) message ( STATUS "minor: ${GRAPHBLAS_MINOR_STR}" ) message ( STATUS "patch: ${GRAPHBLAS_PATCH_STR}" ) endif ( ) - string ( REGEX MATCH "[0-9]+" GraphBLAS_VERSION_MAJOR ${GRAPHBLAS_MAJOR_STR} ) - string ( REGEX MATCH "[0-9]+" GraphBLAS_VERSION_MINOR ${GRAPHBLAS_MINOR_STR} ) - string ( REGEX MATCH "[0-9]+" GraphBLAS_VERSION_PATCH ${GRAPHBLAS_PATCH_STR} ) - set (GRAPHBLAS_VERSION "${GraphBLAS_VERSION_MAJOR}.${GraphBLAS_VERSION_MINOR}.${GraphBLAS_VERSION_PATCH}") + string ( REGEX MATCH "[0-9]+" GraphBLAS_VER_MAJOR ${GRAPHBLAS_MAJOR_STR} ) + string ( REGEX MATCH "[0-9]+" GraphBLAS_VER_MINOR ${GRAPHBLAS_MINOR_STR} ) + string ( REGEX MATCH "[0-9]+" GraphBLAS_VER_PATCH ${GRAPHBLAS_PATCH_STR} ) + set (GRAPHBLAS_VERSION "${GraphBLAS_VER_MAJOR}.${GraphBLAS_VER_MINOR}.${GraphBLAS_VER_PATCH}") endif ( ) set ( GRAPHBLAS_LIBRARIES ${GRAPHBLAS_LIBRARY} ) From cf39dbc5df4c2a8af471a7703eca73308be9becd Mon Sep 17 00:00:00 2001 From: Sterkachov Date: Tue, 20 Oct 2026 00:07:05 +0300 Subject: [PATCH 17/25] refactor(CFPQ_RSM): simplify reshape and s_mxm_chain APIs Replace the reshapeDup/free helper with in-place GxB_Matrix_reshape and pass GrB_Matrix handles directly instead of GrB_Matrix pointers. --- experimental/algorithm/LAGraph_CFPQ_RSM.c | 49 ++++++++++------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/experimental/algorithm/LAGraph_CFPQ_RSM.c b/experimental/algorithm/LAGraph_CFPQ_RSM.c index abe58b6855..ce6b457537 100644 --- a/experimental/algorithm/LAGraph_CFPQ_RSM.c +++ b/experimental/algorithm/LAGraph_CFPQ_RSM.c @@ -58,51 +58,46 @@ } static inline GrB_Info reshape( // input/output: - GrB_Matrix *C, // input/output matrix, reshaped in place + GrB_Matrix C, // input/output matrix, reshaped in place bool by_col, // true if reshape by column, false if by row GrB_Index nrows_new, // new number of rows of C GrB_Index ncols_new, // new number of columns of C const GrB_Descriptor desc // to control # of threads used ) { - if (C == NULL || *C == NULL) + if (C == NULL) return GrB_NULL_POINTER; - GrB_Matrix temp = GrB_NULL; - H_TRY(GxB_Matrix_reshapeDup(&temp, *C, by_col, nrows_new, ncols_new, desc)); - H_TRY(GrB_Matrix_free(C)); - - *C = temp; - return GrB_SUCCESS; + return GxB_Matrix_reshape(C, by_col, nrows_new, ncols_new, desc); } static GrB_Info s_mxm_chain(GrB_Matrix C, // output accumulator |Q|*|V*V| GrB_Matrix A, // RSM matrix |Q|*|Q| GrB_Matrix M, // frontier |Q|*|V*V| GrB_Matrix B, // graph matrix |V|*|V| - GrB_Matrix *temp1, // scratch |Q|*|V*V| - GrB_Matrix *temp2, // scratch |Q|*|V*V| + GrB_Matrix temp1, // scratch |Q|*|V*V| + GrB_Matrix temp2, // scratch |Q|*|V*V| GrB_Index Q, GrB_Index V) { - H_TRY(GrB_Matrix_clear(*temp1)); - H_TRY(GrB_Matrix_clear(*temp2)); + H_TRY(GrB_Matrix_clear(temp1)); + H_TRY(GrB_Matrix_clear(temp2)); // temp1 = A^T * M -> |Q|*|V*V| - H_TRY(GrB_mxm(*temp1, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, A, M, GrB_DESC_T0)); + H_TRY(GrB_mxm(temp1, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, A, M, GrB_DESC_T0)); // reshape: |Q| × |V*V| -> |Q*V|*|V| - H_TRY(reshape(temp1, false, Q * V, V, GrB_NULL)); - H_TRY(reshape(temp2, false, Q * V, V, GrB_NULL)); + H_TRY(GxB_Matrix_reshape(temp1, false, Q * V, V, GrB_NULL)); + H_TRY(GxB_Matrix_reshape(temp2, false, Q * V, V, GrB_NULL)); // temp2 = temp1 * B -> |Q*V|*|V| - H_TRY(GrB_mxm(*temp2, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, *temp1, B, GrB_NULL)); + H_TRY(GrB_mxm(temp2, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, temp1, B, GrB_NULL)); // reshape: |Q*V|*|V| -> |Q|*|V*V| - H_TRY(reshape(temp1, false, Q, V * V, GrB_NULL)); - H_TRY(reshape(temp2, false, Q, V * V, GrB_NULL)); + H_TRY(GxB_Matrix_reshape(temp1, false, Q, V * V, GrB_NULL)); + H_TRY(GxB_Matrix_reshape(temp2, false, Q, V * V, GrB_NULL)); // C |= temp2 - H_TRY(GrB_eWiseAdd(C, GrB_NULL, GrB_NULL, GrB_LOR, C, *temp2, GrB_NULL)); + H_TRY(GrB_eWiseAdd(C, GrB_NULL, GrB_NULL, GrB_LOR, C, temp2, GrB_NULL)); return GrB_SUCCESS; } @@ -273,7 +268,7 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr { if (rsm_term[i] == GrB_NULL || graph_term[i] == GrB_NULL) continue; - LG_TRY(s_mxm_chain(M_term, rsm_term[i], M, graph_term[i], &temp1, &temp2, Q, V)); + LG_TRY(s_mxm_chain(M_term, rsm_term[i], M, graph_term[i], temp1, temp2, Q, V)); } // PHASE 2: NON-TERMINAL TRANSITIONS @@ -288,7 +283,7 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr if (gnt_nvals == 0) continue; - LG_TRY(s_mxm_chain(M_nonterm, rsm_nonterm[i], M, graph_nt[i], &temp1, &temp2, Q, V)); + LG_TRY(s_mxm_chain(M_nonterm, rsm_nonterm[i], M, graph_nt[i], temp1, temp2, Q, V)); } // PHASE 3: CALL TRANSITIONS @@ -311,14 +306,14 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr // entry_vec = column-OR of mask_call reshaped to |Q*V|*|V| GRB_TRY(GrB_Matrix_clear(temp1)); GRB_TRY(GrB_eWiseAdd(temp1, GrB_NULL, GrB_NULL, GrB_LOR, temp1, mask_call, GrB_NULL)); - GRB_TRY(reshape(&temp1, false, Q * V, V, GrB_NULL)); + GRB_TRY(GxB_Matrix_reshape(temp1, false, Q * V, V, GrB_NULL)); GRB_TRY(GrB_Vector_clear(entry_vec)); // column OR = row OR of transpose GRB_TRY( GrB_reduce(entry_vec, GrB_NULL, GrB_NULL, GrB_LOR_MONOID_BOOL, temp1, GrB_DESC_T0)); - GRB_TRY(reshape(&temp1, false, Q, VV, GrB_NULL)); + GRB_TRY(GxB_Matrix_reshape(temp1, false, Q, VV, GrB_NULL)); GrB_Index ev_nvals = 0; GRB_TRY(GrB_Vector_nvals(&ev_nvals, entry_vec)); @@ -329,7 +324,7 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GRB_TRY(GrB_Matrix_free(&diag_entry)); diag_entry = GrB_NULL; GRB_TRY(GrB_Matrix_diag(&diag_entry, entry_vec, 0)); // |V|*|V| - GRB_TRY(reshape(&diag_entry, false, 1, VV, GrB_NULL)); + GRB_TRY(GxB_Matrix_reshape(diag_entry, false, 1, VV, GrB_NULL)); GRB_TRY(GrB_Matrix_clear(outer_result)); // |Q|*1 * 1*|V*V| -> |Q|*|V*V| @@ -358,13 +353,13 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr if (ne_vals == 0) continue; - GRB_TRY(reshape(&new_edges, false, V, V, GrB_NULL)); + GRB_TRY(GxB_Matrix_reshape(new_edges, false, V, V, GrB_NULL)); GRB_TRY(GrB_eWiseAdd(graph_nt[i], GrB_NULL, GrB_NULL, GrB_LOR, graph_nt[i], new_edges, GrB_NULL)); - LG_TRY(s_mxm_chain(M_return, rsm_nonterm[i], P, new_edges, &temp1, &temp2, Q, V)); + LG_TRY(s_mxm_chain(M_return, rsm_nonterm[i], P, new_edges, temp1, temp2, Q, V)); - GRB_TRY(reshape(&new_edges, false, 1, VV, GrB_NULL)); + GRB_TRY(GxB_Matrix_reshape(new_edges, false, 1, VV, GrB_NULL)); } // PHASE 5: MERGE & MARK VISITED From 751dce7f1a60480e45c89ee1e41583ca1b78356e Mon Sep 17 00:00:00 2001 From: Sterkachov Date: Tue, 20 Oct 2026 02:03:57 +0300 Subject: [PATCH 18/25] feat: add lacfpq_blocked algorithm with tests --- .../algorithm/LAGraph_CFPQ_RSM_blocked.c | 671 ++++++++++ experimental/test/test_CFPQ_RSM_blocked.c | 1157 +++++++++++++++++ include/LAGraphX.h | 3 + 3 files changed, 1831 insertions(+) create mode 100644 experimental/algorithm/LAGraph_CFPQ_RSM_blocked.c create mode 100644 experimental/test/test_CFPQ_RSM_blocked.c diff --git a/experimental/algorithm/LAGraph_CFPQ_RSM_blocked.c b/experimental/algorithm/LAGraph_CFPQ_RSM_blocked.c new file mode 100644 index 0000000000..04df6459ca --- /dev/null +++ b/experimental/algorithm/LAGraph_CFPQ_RSM_blocked.c @@ -0,0 +1,671 @@ +//------------------------------------------------------------------------------ +// LAGraph_CFPQ_RSM_blocked: block-matrix CFPQ via RSM +//------------------------------------------------------------------------------ + +// LAGraph, (c) 2019-2025 by The LAGraph Contributors, All Rights Reserved. +// SPDX-License-Identifier: BSD-2-Clause +// +// For additional details (including references to third party source code and +// other files) see the LICENSE file or contact permission@sei.cmu.edu. See +// Contributors.txt for a full list of contributors. Created, in part, with +// funding and support from the U.S. Government (see Acknowledgments.txt file). +// DM22-0790 + +//------------------------------------------------------------------------------ +// +// This is a C / GraphBLAS port of the python-graphblas "lacfpq_blocked" +// algorithm. Every Python loop over RSM states and transitions is replaced by +// a small number of sparse matrix multiplications operating on the whole state +// space at once. +// +// Block matrix stacking +// --------------------- +// Instead of keeping one |V|x|V| matrix per RSM state: +// +// P[q], M[q] in {0,1}^{V x V} (one pair per state q) +// +// all of them are stacked into a single QV x V matrix (QV = Q*V): +// +// P_block[q*V + u, v] = P[q][u, v] +// M_block[q*V + u, v] = M[q][u, v] +// +// where q is the 0-based RSM state index. Row q*V+u records the context +// "box(q) was entered at graph vertex u", while column v is the graph vertex +// the RSM has currently reached. +// +// Routing matrix K_l (precomputed once per RSM) +// ---------------------------------------------- +// K_l = (N_l)^T kron I_V in {0,1}^{QV x QV} +// +// where N_l is the QxQ RSM transition matrix for label l: +// +// K_l[q'*V + i, q*V + i] = 1 whenever q -l-> q', for all i in [0,V). +// +// Key identity (replaces the per-state transition loop): +// +// block-row q' of K_l @ (M_block @ G^l) +// = OR_{q : N_l[q,q']=1} ( M[q] @ G^l ) +// +// Selector matrices (precomputed once per RSM) +// -------------------------------------------- +// Final_sel[A] in {0,1}^{V x QV} -- I_V block at every final state of B_A. +// ( Final_sel[A] @ M_block )[u,v] = OR_{q_f in F_A} M[q_f][u,v] +// Replaces the per-final-state loop in Phase 1. +// +// Caller_sel[A] in {0,1}^{V x QV} -- same structure for caller states +// (states q_c with a q_c -A-> q_r transition). +// Caller_sel[A] @ M_block then reduced column-wise +// gives the call-mask for Phase 3 with no per-transition loop. +// +// Phase summary +// ------------- +// Phase | Original loop | Replacement +// ------+----------------------------------+--------------------------------- +// 1 | for q in finals(A) | Final_sel[A] @ M_block (VxV) +// 2 | for q in states x labels | K_l @ (M_block @ G^l), per label +// 3 | for t in nt_transitions | Caller_sel[A] @ M_block, col-reduce +// 4 | for t in nt_transitions | K_A @ (P_block @ dG[A]), per NT +// 5 | for q in states | single nvals check on New_block +// +// The result is the union, over all source vertices, of the corresponding rows +// of the start non-terminal summary matrix G_nt[start_nonterm]. +// +//------------------------------------------------------------------------------ + +#include "LAGraph.h" +#include "LAGraphX.h" +#include "LG_internal.h" +#include + +#include +#include + +//------------------------------------------------------------------------------ +// helper macros / functions +//------------------------------------------------------------------------------ + +#define H_TRY(expr) \ + { \ + GrB_Info _hi = (expr); \ + if (_hi != GrB_SUCCESS) \ + { \ + return _hi; \ + } \ + } + +// free an array of GrB_Matrix handles (allocated by LAGraph_Malloc) +static inline void free_matrix_array(GrB_Matrix **A, GrB_Index n) +{ + if (A == NULL || *A == NULL) + { + return; + } + for (GrB_Index i = 0; i < n; ++i) + { + GrB_free(&((*A)[i])); + } + LAGraph_Free((void **)A, NULL); +} + +// replace_with_mask: C = A restricted to the entries that are structurally +// absent from Mask (C and A may alias). +static inline GrB_Info replace_with_mask(GrB_Matrix C, GrB_Matrix A, GrB_Matrix Mask, + GrB_Index nrows, GrB_Index ncols) +{ + return GrB_Matrix_assign(C, Mask, GrB_NULL, A, GrB_ALL, nrows, GrB_ALL, ncols, GrB_DESC_RSC); +} + +// build_routing: K = (N)^T kron I_V, of size QV x QV, where N is the QxQ RSM +// transition matrix for a single label. +static inline GrB_Info build_routing(GrB_Matrix *K, GrB_Matrix N, GrB_Matrix I_V, GrB_Index QV) +{ + *K = GrB_NULL; + H_TRY(GrB_Matrix_new(K, GrB_BOOL, QV, QV)); + H_TRY(GrB_Matrix_kronecker_Semiring(*K, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, N, I_V, + GrB_DESC_T0)); + return GrB_SUCCESS; +} + +// build_selector: V x QV Boolean matrix with an identity block I_V at each +// listed state: sel[i, s*V + i] = true for each state s in states[0..nstates-1] +// and each vertex i in [0, V). +static GrB_Info build_selector(GrB_Matrix *sel, const GrB_Index *states, GrB_Index nstates, + GrB_Index V, GrB_Index QV, GrB_Scalar true_scalar) +{ + *sel = GrB_NULL; + H_TRY(GrB_Matrix_new(sel, GrB_BOOL, V, QV)); + if (nstates == 0) + { + return GrB_SUCCESS; + } + + GrB_Index n = nstates * V; + GrB_Index *rows = NULL; + GrB_Index *cols = NULL; + + int info = LAGraph_Malloc((void **)&rows, n, sizeof(GrB_Index), NULL); + if (info != GrB_SUCCESS) + { + return info; + } + info = LAGraph_Malloc((void **)&cols, n, sizeof(GrB_Index), NULL); + if (info != GrB_SUCCESS) + { + LAGraph_Free((void **)&rows, NULL); + return info; + } + + GrB_Index k = 0; + for (GrB_Index t = 0; t < nstates; ++t) + { + GrB_Index s = states[t]; + for (GrB_Index i = 0; i < V; ++i) + { + rows[k] = i; + cols[k] = s * V + i; + k++; + } + } + + GrB_Info bi = GxB_Matrix_build_Scalar(*sel, rows, cols, true_scalar, n); + LAGraph_Free((void **)&rows, NULL); + LAGraph_Free((void **)&cols, NULL); + return bi; +} + +//------------------------------------------------------------------------------ +// LAGraph_CFPQ_RSM_blocked +//------------------------------------------------------------------------------ + +#undef LG_FREE_WORK +#undef LG_FREE_ALL + +#define LG_FREE_WORK \ + { \ + GrB_free(&M_block); \ + GrB_free(&P_block); \ + GrB_free(&New_block); \ + GrB_free(&step); \ + GrB_free(&prod); \ + GrB_free(&fresh); \ + GrB_free(&extracted); \ + GrB_free(&inject); \ + GrB_free(&caller_OR); \ + GrB_free(&call_mask); \ + GrB_free(&caller_mask); \ + GrB_free(&source_mask); \ + GrB_free(&true_scalar); \ + GrB_free(&I_V); \ + free_matrix_array(&K_term, num_term); \ + free_matrix_array(&K_nt, num_nonterm); \ + free_matrix_array(&Final_sel, num_nonterm); \ + free_matrix_array(&Caller_sel, num_nonterm); \ + free_matrix_array(&dG, num_nonterm); \ + free_matrix_array(&G_nt, num_nonterm); \ + LAGraph_Free((void **)&buf_rows, msg); \ + LAGraph_Free((void **)&buf_cols, msg); \ + LAGraph_Free((void **)&buf_idx, msg); \ + LAGraph_Free((void **)&buf_vals, msg); \ + } + +#define LG_FREE_ALL \ + { \ + LG_FREE_WORK; \ + GrB_free(reachable); \ + } + +// LAGraph_CFPQ_RSM_blocked: single/multi-source context-free path querying by +// the block-matrix formulation of the RSM algorithm. +int LAGraph_CFPQ_RSM_blocked(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *graph_term, + const GrB_Index *sources, size_t num_sources, GrB_Index V, char *msg) +{ + LG_CLEAR_MSG; + + // working matrices / vectors + GrB_Matrix M_block = GrB_NULL; // frontier, QV x V + GrB_Matrix P_block = GrB_NULL; // visited configurations, QV x V + GrB_Matrix New_block = GrB_NULL; // next frontier, QV x V + GrB_Matrix step = GrB_NULL; // scratch, QV x V + GrB_Matrix prod = GrB_NULL; // scratch, QV x V + GrB_Matrix fresh = GrB_NULL; // scratch, QV x V + GrB_Matrix extracted = GrB_NULL; // summary extraction, V x V + GrB_Matrix inject = GrB_NULL; // call injection seeds, QV x V + GrB_Matrix caller_OR = GrB_NULL; // caller activity, V x V + GrB_Matrix I_V = GrB_NULL; // identity matrix, V x V + GrB_Scalar true_scalar = GrB_NULL; + + GrB_Vector call_mask = GrB_NULL; // active entry vertices, V + GrB_Vector caller_mask = GrB_NULL; // per-state caller flags, Q + GrB_Vector source_mask = GrB_NULL; // source vertices, V + + GrB_Matrix *K_term = NULL; // per terminal label: QV x QV + GrB_Matrix *K_nt = NULL; // per nonterminal label: QV x QV + GrB_Matrix *Final_sel = NULL; // per nonterminal: V x QV + GrB_Matrix *Caller_sel = NULL; // per nonterminal: V x QV + GrB_Matrix *dG = NULL; // per nonterminal: V x V + GrB_Matrix *G_nt = NULL; // per nonterminal: V x V (summary) + + GrB_Index *buf_rows = NULL; // scratch, size V + GrB_Index *buf_cols = NULL; // scratch, size V + GrB_Index *buf_idx = NULL; // scratch, size max(Q,V) + bool *buf_vals = NULL; // scratch, size max(Q,V) + + // shortcuts + GrB_Index Q = 0; + GrB_Index num_term = 0; + GrB_Index num_nonterm = 0; + GrB_Index QV = 0; + + LG_ASSERT(reachable != NULL, GrB_NULL_POINTER); + LG_ASSERT(rsm != NULL, GrB_NULL_POINTER); + LG_ASSERT(graph_term != NULL, GrB_NULL_POINTER); + LG_ASSERT(sources != NULL, GrB_NULL_POINTER); + + Q = rsm->state_count; + num_term = rsm->terminal_count; + num_nonterm = rsm->nonterminal_count; + QV = Q * V; + + GrB_Index start_nonterm = rsm->start_nonterminal; + GrB_Matrix *rsm_term = rsm->terminal_matrices; + GrB_Matrix *rsm_nonterm = rsm->nonterminal_matrices; + GrB_Index *start_states = rsm->start_states; + GrB_Vector *final_states = rsm->final_states; + + LG_ASSERT(rsm_term != NULL, GrB_NULL_POINTER); + LG_ASSERT(rsm_nonterm != NULL, GrB_NULL_POINTER); + LG_ASSERT(start_states != NULL, GrB_NULL_POINTER); + LG_ASSERT(final_states != NULL, GrB_NULL_POINTER); + LG_ASSERT(start_nonterm < num_nonterm, GrB_INVALID_VALUE); + LG_ASSERT(num_sources > 0, GrB_INVALID_VALUE); + LG_ASSERT(num_sources <= V, GrB_INVALID_VALUE); + + *reachable = GrB_NULL; + + //-------------------------------------------------------------------------- + // precompute: routing matrices K_l and selector matrices + //-------------------------------------------------------------------------- + + if (num_term > 0) + { + LG_TRY(LAGraph_Malloc((void **)&K_term, num_term, sizeof(GrB_Matrix), msg)); + for (GrB_Index t = 0; t < num_term; ++t) + K_term[t] = GrB_NULL; + } + if (num_nonterm > 0) + { + LG_TRY(LAGraph_Malloc((void **)&K_nt, num_nonterm, sizeof(GrB_Matrix), msg)); + LG_TRY(LAGraph_Malloc((void **)&Final_sel, num_nonterm, sizeof(GrB_Matrix), msg)); + LG_TRY(LAGraph_Malloc((void **)&Caller_sel, num_nonterm, sizeof(GrB_Matrix), msg)); + LG_TRY(LAGraph_Malloc((void **)&dG, num_nonterm, sizeof(GrB_Matrix), msg)); + for (GrB_Index i = 0; i < num_nonterm; ++i) + { + K_nt[i] = GrB_NULL; + Final_sel[i] = GrB_NULL; + Caller_sel[i] = GrB_NULL; + dG[i] = GrB_NULL; + } + } + + // scratch buffers + LG_TRY(LAGraph_Malloc((void **)&buf_rows, V, sizeof(GrB_Index), msg)); + LG_TRY(LAGraph_Malloc((void **)&buf_cols, V, sizeof(GrB_Index), msg)); + GrB_Index bmax = (Q > V) ? Q : V; + LG_TRY(LAGraph_Malloc((void **)&buf_idx, bmax, sizeof(GrB_Index), msg)); + LG_TRY(LAGraph_Malloc((void **)&buf_vals, bmax, sizeof(bool), msg)); + + // scalar "true", used to build all-true (I,J) tuples + GRB_TRY(GrB_Scalar_new(&true_scalar, GrB_BOOL)); + GRB_TRY(GrB_Scalar_setElement_BOOL(true_scalar, true)); + + // I_V, the V x V identity matrix + { + GrB_Vector ones = GrB_NULL; + GRB_TRY(GrB_Vector_new(&ones, GrB_BOOL, V)); + GRB_TRY(GrB_Vector_assign_BOOL(ones, GrB_NULL, GrB_NULL, true, GrB_ALL, V, GrB_NULL)); + GRB_TRY(GrB_Matrix_diag(&I_V, ones, 0)); + GrB_free(&ones); + } + + // routing matrix per terminal label + for (GrB_Index t = 0; t < num_term; ++t) + { + if (rsm_term[t] != GrB_NULL) + { + GRB_TRY(build_routing(&K_term[t], rsm_term[t], I_V, QV)); + } + else + { + GRB_TRY(GrB_Matrix_new(&K_term[t], GrB_BOOL, QV, QV)); + } + } + + // routing + selectors per nonterminal + GRB_TRY(GrB_Vector_new(&caller_mask, GrB_BOOL, Q)); + for (GrB_Index i = 0; i < num_nonterm; ++i) + { + if (rsm_nonterm[i] != GrB_NULL) + { + GRB_TRY(build_routing(&K_nt[i], rsm_nonterm[i], I_V, QV)); + } + else + { + GRB_TRY(GrB_Matrix_new(&K_nt[i], GrB_BOOL, QV, QV)); + } + + // final-state selector: states present in final_states[i] + if (final_states[i] != GrB_NULL) + { + GrB_Index nfinal = Q; + GRB_TRY(GrB_Vector_extractTuples_BOOL(buf_idx, buf_vals, &nfinal, final_states[i])); + GRB_TRY(build_selector(&Final_sel[i], buf_idx, nfinal, V, QV, true_scalar)); + } + else + { + GRB_TRY(GrB_Matrix_new(&Final_sel[i], GrB_BOOL, V, QV)); + } + + // caller-state selector: any row of rsm_nonterm[i] with an entry + if (rsm_nonterm[i] != GrB_NULL) + { + GRB_TRY(GrB_Vector_clear(caller_mask)); + GRB_TRY(GrB_Matrix_reduce_Monoid(caller_mask, GrB_NULL, GrB_NULL, GrB_LOR_MONOID_BOOL, + rsm_nonterm[i], GrB_NULL)); + GrB_Index ncaller = Q; + GRB_TRY(GrB_Vector_extractTuples_BOOL(buf_idx, buf_vals, &ncaller, caller_mask)); + GRB_TRY(build_selector(&Caller_sel[i], buf_idx, ncaller, V, QV, true_scalar)); + } + else + { + GRB_TRY(GrB_Matrix_new(&Caller_sel[i], GrB_BOOL, V, QV)); + } + + // summary edges discovered for this nonterminal + GRB_TRY(GrB_Matrix_new(&dG[i], GrB_BOOL, V, V)); + } + + //-------------------------------------------------------------------------- + // allocate working matrices + //-------------------------------------------------------------------------- + + GRB_TRY(GrB_Matrix_new(&M_block, GrB_BOOL, QV, V)); + GRB_TRY(GrB_Matrix_new(&P_block, GrB_BOOL, QV, V)); + GRB_TRY(GrB_Matrix_new(&New_block, GrB_BOOL, QV, V)); + GRB_TRY(GrB_Matrix_new(&step, GrB_BOOL, QV, V)); + GRB_TRY(GrB_Matrix_new(&prod, GrB_BOOL, QV, V)); + GRB_TRY(GrB_Matrix_new(&fresh, GrB_BOOL, QV, V)); + GRB_TRY(GrB_Matrix_new(&extracted, GrB_BOOL, V, V)); + GRB_TRY(GrB_Matrix_new(&inject, GrB_BOOL, QV, V)); + GRB_TRY(GrB_Matrix_new(&caller_OR, GrB_BOOL, V, V)); + GRB_TRY(GrB_Vector_new(&call_mask, GrB_BOOL, V)); + GRB_TRY(GrB_Vector_new(&source_mask, GrB_BOOL, V)); + GRB_TRY(GrB_Vector_new(reachable, GrB_BOOL, V)); + + // G_nt[i] = summary matrix for nonterminal i, initially empty + LG_TRY(LAGraph_Calloc((void **)&G_nt, num_nonterm, sizeof(GrB_Matrix), msg)); + for (GrB_Index i = 0; i < num_nonterm; ++i) + { + GRB_TRY(GrB_Matrix_new(&G_nt[i], GrB_BOOL, V, V)); + } + + //-------------------------------------------------------------------------- + // seed the initial frontier and visited set + //-------------------------------------------------------------------------- + + { + GrB_Index s_S = start_states[start_nonterm]; + for (size_t k = 0; k < num_sources; ++k) + { + GrB_Index u = sources[k]; + GrB_Index row = s_S * V + u; + GRB_TRY(GrB_Matrix_setElement_BOOL(M_block, true, row, u)); + GRB_TRY(GrB_Matrix_setElement_BOOL(P_block, true, row, u)); + } + } + + //-------------------------------------------------------------------------- + // main fixed-point loop + //-------------------------------------------------------------------------- + + bool keep_going = true; + while (keep_going) + { + // PHASE 1: summary extraction (independent of terminal labels) + // dG[A][u,w] = 1 iff box B_A was entered at u and just exited at w. + for (GrB_Index i = 0; i < num_nonterm; ++i) + { + GrB_Index sel_nvals = 0; + GRB_TRY(GrB_Matrix_nvals(&sel_nvals, Final_sel[i])); + if (sel_nvals == 0) + { + GRB_TRY(GrB_Matrix_clear(dG[i])); + continue; + } + + // extracted = Final_sel[i] * M_block -> V x V + GRB_TRY(GrB_mxm(extracted, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, Final_sel[i], + M_block, GrB_NULL)); + + // dG[i] = extracted & ~G_nt[i] + GRB_TRY(replace_with_mask(dG[i], extracted, G_nt[i], V, V)); + + GrB_Index dg_nvals = 0; + GRB_TRY(GrB_Matrix_nvals(&dg_nvals, dG[i])); + if (dg_nvals > 0) + { + // G_nt[i] |= dG[i] + GRB_TRY( + GrB_eWiseAdd(G_nt[i], GrB_NULL, GrB_NULL, GrB_LOR, G_nt[i], dG[i], GrB_NULL)); + } + } + + // PHASE 2: frontier propagation + // K_l @ (M_block @ G^l) routes each state-block through label-l edges + // and delivers results to the correct destination state blocks. + for (GrB_Index t = 0; t < num_term; ++t) + { + if (graph_term[t] == GrB_NULL) + { + continue; + } + GrB_Index k_nvals = 0; + GRB_TRY(GrB_Matrix_nvals(&k_nvals, K_term[t])); + if (k_nvals == 0) + { + continue; + } + GrB_Index g_nvals = 0; + GRB_TRY(GrB_Matrix_nvals(&g_nvals, graph_term[t])); + if (g_nvals == 0) + { + continue; + } + + // step = M_block * G^t -> QV x V + GRB_TRY(GrB_mxm(step, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, M_block, + graph_term[t], GrB_NULL)); + + // prod = K_t * step -> QV x V + GRB_TRY(GrB_mxm(prod, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, K_term[t], step, + GrB_NULL)); + + // fresh = prod & ~P_block + GRB_TRY(replace_with_mask(fresh, prod, P_block, QV, V)); + + GrB_Index f_nvals = 0; + GRB_TRY(GrB_Matrix_nvals(&f_nvals, fresh)); + if (f_nvals > 0) + { + GRB_TRY(GrB_eWiseAdd(New_block, GrB_NULL, GrB_NULL, GrB_LOR, New_block, fresh, + GrB_NULL)); + } + } + + for (GrB_Index i = 0; i < num_nonterm; ++i) + { + GrB_Index k_nvals = 0; + GRB_TRY(GrB_Matrix_nvals(&k_nvals, K_nt[i])); + if (k_nvals == 0) + { + continue; + } + GrB_Index g_nvals = 0; + GRB_TRY(GrB_Matrix_nvals(&g_nvals, G_nt[i])); + if (g_nvals == 0) + { + continue; + } + + GRB_TRY(GrB_mxm(step, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, M_block, G_nt[i], + GrB_NULL)); + GRB_TRY(GrB_mxm(prod, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, K_nt[i], step, + GrB_NULL)); + GRB_TRY(replace_with_mask(fresh, prod, P_block, QV, V)); + + GrB_Index f_nvals = 0; + GRB_TRY(GrB_Matrix_nvals(&f_nvals, fresh)); + if (f_nvals > 0) + { + GRB_TRY(GrB_eWiseAdd(New_block, GrB_NULL, GrB_NULL, GrB_LOR, New_block, fresh, + GrB_NULL)); + } + } + + // PHASE 3: call injection + // Any current-graph-vertex v active in a caller state spawns a fresh + // exploration of B_A seeded at context (s_A, v, v). + for (GrB_Index i = 0; i < num_nonterm; ++i) + { + GrB_Index cs_nvals = 0; + GRB_TRY(GrB_Matrix_nvals(&cs_nvals, Caller_sel[i])); + if (cs_nvals == 0) + { + continue; + } + + // caller_OR = Caller_sel[i] * M_block -> V x V + GRB_TRY(GrB_mxm(caller_OR, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, + Caller_sel[i], M_block, GrB_NULL)); + + GrB_Index co_nvals = 0; + GRB_TRY(GrB_Matrix_nvals(&co_nvals, caller_OR)); + if (co_nvals == 0) + { + continue; + } + + // call_mask = column-wise OR of caller_OR -> V + GRB_TRY(GrB_Vector_clear(call_mask)); + GRB_TRY(GrB_Matrix_reduce_Monoid(call_mask, GrB_NULL, GrB_NULL, GrB_LOR_MONOID_BOOL, + caller_OR, GrB_DESC_T0)); + + GrB_Index cm_nvals = 0; + GRB_TRY(GrB_Vector_nvals(&cm_nvals, call_mask)); + if (cm_nvals == 0) + { + continue; + } + + // inject[s_A*V + v, v] = true for every active vertex v + GrB_Index nv = V; + GRB_TRY(GrB_Vector_extractTuples_BOOL(buf_idx, buf_vals, &nv, call_mask)); + GrB_Index s_A = start_states[i]; + for (GrB_Index k = 0; k < nv; ++k) + { + buf_rows[k] = s_A * V + buf_idx[k]; + buf_cols[k] = buf_idx[k]; + } + GRB_TRY(GrB_Matrix_clear(inject)); + GRB_TRY(GxB_Matrix_build_Scalar(inject, buf_rows, buf_cols, true_scalar, nv)); + + // fresh = inject & ~P_block + GRB_TRY(replace_with_mask(fresh, inject, P_block, QV, V)); + + GrB_Index f_nvals = 0; + GRB_TRY(GrB_Matrix_nvals(&f_nvals, fresh)); + if (f_nvals > 0) + { + GRB_TRY(GrB_eWiseAdd(New_block, GrB_NULL, GrB_NULL, GrB_LOR, New_block, fresh, + GrB_NULL)); + } + } + + // PHASE 4: retroactive propagation + // Old call-site contexts x newly discovered summary edges. Structurally + // identical to Phase 2, using P_block and dG[A]. + for (GrB_Index i = 0; i < num_nonterm; ++i) + { + GrB_Index dg_nvals = 0; + GRB_TRY(GrB_Matrix_nvals(&dg_nvals, dG[i])); + if (dg_nvals == 0) + { + continue; + } + GrB_Index k_nvals = 0; + GRB_TRY(GrB_Matrix_nvals(&k_nvals, K_nt[i])); + if (k_nvals == 0) + { + continue; + } + + // step = P_block * dG[i] -> QV x V + GRB_TRY(GrB_mxm(step, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, P_block, dG[i], + GrB_NULL)); + + // prod = K_i * step -> QV x V + GRB_TRY(GrB_mxm(prod, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, K_nt[i], step, + GrB_NULL)); + + // fresh = prod & ~P_block + GRB_TRY(replace_with_mask(fresh, prod, P_block, QV, V)); + + GrB_Index f_nvals = 0; + GRB_TRY(GrB_Matrix_nvals(&f_nvals, fresh)); + if (f_nvals > 0) + { + GRB_TRY(GrB_eWiseAdd(New_block, GrB_NULL, GrB_NULL, GrB_LOR, New_block, fresh, + GrB_NULL)); + } + } + + // PHASE 5: commit + GrB_Index new_nvals = 0; + GRB_TRY(GrB_Matrix_nvals(&new_nvals, New_block)); + if (new_nvals == 0) + { + break; + } + + // P_block |= New_block + GRB_TRY(GrB_eWiseAdd(P_block, GrB_NULL, GrB_NULL, GrB_LOR, P_block, New_block, GrB_NULL)); + + // M_block = New_block; New_block is recycled + GrB_Matrix swap = M_block; + M_block = New_block; + New_block = swap; + GRB_TRY(GrB_Matrix_clear(New_block)); + } + + //-------------------------------------------------------------------------- + // extract the result: union of the source rows of G_nt[start_nonterm] + //-------------------------------------------------------------------------- + + { + bool *vals = NULL; + LG_TRY(LAGraph_Malloc((void **)&vals, num_sources, sizeof(bool), msg)); + for (size_t i = 0; i < num_sources; ++i) + { + vals[i] = true; + } + GrB_Info bi = + GrB_Vector_build_BOOL(source_mask, sources, vals, num_sources, GrB_SECOND_BOOL); + LAGraph_Free((void **)&vals, GrB_NULL); + GRB_TRY(bi); + } + + GRB_TRY(GrB_mxv(*reachable, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, G_nt[start_nonterm], + source_mask, GrB_DESC_T0)); + + LG_FREE_WORK; + return GrB_SUCCESS; +} diff --git a/experimental/test/test_CFPQ_RSM_blocked.c b/experimental/test/test_CFPQ_RSM_blocked.c new file mode 100644 index 0000000000..0eb301565d --- /dev/null +++ b/experimental/test/test_CFPQ_RSM_blocked.c @@ -0,0 +1,1157 @@ +//------------------------------------------------------------------------------ +// test_CFPQ_RSM_blocked.c: tests for LAGraph_CFPQ_RSM_blocked +//------------------------------------------------------------------------------ + +// LAGraph, (c) 2019-2025 by The LAGraph Contributors, All Rights Reserved. +// SPDX-License-Identifier: BSD-2-Clause +// +// For additional details (including references to third party source code and +// other files) see the LICENSE file or contact permission@sei.cmu.edu. See +// Contributors.txt for a full list of contributors. Created, in part, with +// funding and support from the U.S. Government (see Acknowledgments.txt file). +// DM22-0790 + +//------------------------------------------------------------------------------ +// +// This file ports the python-graphblas test suite of +// "lacfpq_blocked_with_tests.py" to C. Each Python grammar is expressed as a +// set of context-free productions and expanded to an RSM by the same +// construction used by the Python reference: +// +// each production A -> X1 X2 ... Xn +// +// becomes a chain of fresh states inside box B_A, starting at the (shared) +// start state of B_A. The last state of the chain is final. An empty +// right-hand side [] makes the start state itself final (epsilon production). +// +// Terminal and non-terminal symbols are encoded in a production as follows: +// +// TERM(t) = t terminal with label index t (t >= 0) +// NT(a) = -a - 1 non-terminal with index a (a >= 0) +// +// The graph is likewise built from a list of (src, label, dst) edges, exactly +// as in the Python tests. +// +//------------------------------------------------------------------------------ + +#include "LAGraph.h" +#include "LAGraphX.h" +#include "LAGraph_test.h" +#include +#include +#include + +char msg[LAGRAPH_MSG_LEN]; + +static RSM *rsm = NULL; +static size_t V = 0; +static size_t n_graph_term = 0; +static GrB_Matrix *graph_term = NULL; +static GrB_Vector result = GrB_NULL; + +//============================================================================== +// test setup / teardown +//============================================================================== + +static void setup(void) +{ + LAGraph_Init(msg); +} + +static void teardown(void) +{ + LAGraph_Finalize(msg); +} + +static RSM *alloc_rsm(GrB_Index state_count, GrB_Index term_count, GrB_Index nonterm_count, + GrB_Index start_nonterm) +{ + RSM *r = NULL; + LAGraph_Malloc((void **)&r, 1, sizeof(RSM), msg); + if (r == NULL) + return NULL; + + r->state_count = state_count; + r->terminal_count = term_count; + r->nonterminal_count = nonterm_count; + r->start_nonterminal = start_nonterm; + + LAGraph_Calloc((void **)&r->terminal_matrices, term_count, sizeof(GrB_Matrix), msg); + LAGraph_Calloc((void **)&r->nonterminal_matrices, nonterm_count, sizeof(GrB_Matrix), msg); + LAGraph_Calloc((void **)&r->start_states, nonterm_count, sizeof(GrB_Index), msg); + LAGraph_Calloc((void **)&r->final_states, nonterm_count, sizeof(GrB_Vector), msg); + + for (size_t i = 0; i < term_count; ++i) + GrB_Matrix_new(&r->terminal_matrices[i], GrB_BOOL, state_count, state_count); + + for (size_t i = 0; i < nonterm_count; ++i) + { + GrB_Matrix_new(&r->nonterminal_matrices[i], GrB_BOOL, state_count, state_count); + GrB_Vector_new(&r->final_states[i], GrB_BOOL, state_count); + } + + return r; +} + +static void free_rsm(void) +{ + if (rsm == NULL) + return; + + if (rsm->terminal_matrices != NULL) + { + for (size_t i = 0; i < rsm->terminal_count; ++i) + GrB_free(&rsm->terminal_matrices[i]); + LAGraph_Free((void **)&rsm->terminal_matrices, msg); + } + if (rsm->nonterminal_matrices != NULL) + { + for (size_t i = 0; i < rsm->nonterminal_count; ++i) + GrB_free(&rsm->nonterminal_matrices[i]); + LAGraph_Free((void **)&rsm->nonterminal_matrices, msg); + } + + if (rsm->final_states != NULL) + { + for (size_t i = 0; i < rsm->nonterminal_count; ++i) + { + GrB_free(&rsm->final_states[i]); + } + LAGraph_Free((void **)&rsm->final_states, msg); + } + + LAGraph_Free((void **)&rsm->start_states, msg); + LAGraph_Free((void **)&rsm, msg); +} + +static void free_graph(void) +{ + if (graph_term == NULL) + return; + for (size_t i = 0; i < n_graph_term; ++i) + GrB_free(&graph_term[i]); + LAGraph_Free((void **)&graph_term, msg); + n_graph_term = 0; + V = 0; +} + +static void free_workspace(void) +{ + GrB_free(&result); + free_rsm(); + free_graph(); +} + +static void check_reachable(GrB_Index *expected, GrB_Index n_expected, const char *test_name) +{ + GrB_Index nvals = 0; + OK(GrB_Vector_nvals(&nvals, result)); + + bool *got = (bool *)calloc(V, sizeof(bool)); + TEST_CHECK(got != NULL); + + GrB_Index *idx = GrB_NULL; + bool *val = GrB_NULL; + + if (nvals > 0) + { + idx = (GrB_Index *)malloc(nvals * sizeof(GrB_Index)); + val = (bool *)malloc(nvals * sizeof(bool)); + if (!TEST_CHECK(idx != NULL && val != NULL)) + { + free(got); + free(idx); + free(val); + return; + } + OK(GrB_Vector_extractTuples_BOOL(idx, val, &nvals, result)); + for (GrB_Index k = 0; k < nvals; ++k) + if (val[k]) + got[idx[k]] = true; + free(idx); + free(val); + } + + // every expected vertex must be reachable + for (GrB_Index k = 0; k < n_expected; ++k) + { + TEST_CHECK(got[expected[k]]); + TEST_MSG("%s: vertex %llu should be reachable", test_name, (unsigned long long)expected[k]); + } + + GrB_Index actual_count = 0; + for (GrB_Index v = 0; v < V; ++v) + if (got[v]) + actual_count++; + + TEST_CHECK(actual_count == n_expected); + TEST_MSG("%s: expected %llu reachable vertices, got %llu", test_name, + (unsigned long long)n_expected, (unsigned long long)actual_count); + + free(got); +} + +//============================================================================== +// RSM / graph builders +//============================================================================== + +#define TERM(sym) ((int)(sym)) +#define NT(sym) (-((int)(sym)) - 1) + +// a single production right-hand side +typedef struct +{ + const int *rhs; // symbols; terminal >= 0, non-terminal < 0 + size_t len; // number of symbols (0 for epsilon) +} Prod; + +// a single graph edge +typedef struct +{ + GrB_Index src; + int label; // terminal label index + GrB_Index dst; +} Edge; + +// a single RSM transition +typedef struct +{ + GrB_Index src; + int sym; // terminal >= 0, non-terminal < 0 + GrB_Index dst; +} Trans; + +// add one transition, encoded with TERM/NT, to the RSM under construction +static void add_transition(int sym, GrB_Index src, GrB_Index dst) +{ + if (sym >= 0) + { + OK(GrB_Matrix_setElement_BOOL(rsm->terminal_matrices[sym], true, src, dst)); + } + else + { + GrB_Index nt = (GrB_Index)(-sym - 1); + OK(GrB_Matrix_setElement_BOOL(rsm->nonterminal_matrices[nt], true, src, dst)); + } +} + +// build an RSM from a context-free grammar, using the same state layout as the +// Python build_rsm() function: each non-terminal gets one start state, and +// each production gets its own chain of fresh states starting at that state. +// prods[nt] is the list of productions of non-terminal nt. +static void init_rsm_from_grammar(size_t num_term, size_t num_nonterm, size_t start_nt, + const Prod *const *prods, const size_t *nprods) +{ + // count the states: one start state plus one state per RHS symbol + GrB_Index Q = 0; + for (size_t nt = 0; nt < num_nonterm; ++nt) + { + Q += 1; + for (size_t p = 0; p < nprods[nt]; ++p) + { + Q += (GrB_Index)prods[nt][p].len; + } + } + + rsm = alloc_rsm(Q, (GrB_Index)num_term, (GrB_Index)num_nonterm, (GrB_Index)start_nt); + if (rsm == NULL) + return; + + GrB_Index next_state = 0; + for (size_t nt = 0; nt < num_nonterm; ++nt) + { + GrB_Index start = next_state++; + rsm->start_states[nt] = start; + + for (size_t p = 0; p < nprods[nt]; ++p) + { + const Prod *prod = &prods[nt][p]; + GrB_Index cur = start; + for (size_t k = 0; k < prod->len; ++k) + { + GrB_Index nxt = next_state++; + add_transition(prod->rhs[k], cur, nxt); + cur = nxt; + } + OK(GrB_Vector_setElement_BOOL(rsm->final_states[nt], true, cur)); + } + } +} + +// build an RSM directly from transitions and final states +static void init_rsm_raw(size_t num_term, size_t num_nonterm, size_t start_nt, GrB_Index Q, + const GrB_Index *start_states, const Trans *trans, size_t ntrans, + const GrB_Index *const *finals, const size_t *nfinals) +{ + rsm = alloc_rsm(Q, (GrB_Index)num_term, (GrB_Index)num_nonterm, (GrB_Index)start_nt); + if (rsm == NULL) + return; + + for (size_t nt = 0; nt < num_nonterm; ++nt) + { + rsm->start_states[nt] = start_states[nt]; + for (size_t k = 0; k < nfinals[nt]; ++k) + { + OK(GrB_Vector_setElement_BOOL(rsm->final_states[nt], true, finals[nt][k])); + } + } + + for (size_t t = 0; t < ntrans; ++t) + { + add_transition(trans[t].sym, trans[t].src, trans[t].dst); + } +} + +// build the per-terminal graph matrices from an edge list +static void init_graph(GrB_Index n_vertices, size_t n_labels, const Edge *edges, size_t n_edges) +{ + V = (size_t)n_vertices; + n_graph_term = n_labels; + LAGraph_Calloc((void **)&graph_term, n_labels, sizeof(GrB_Matrix), msg); + for (size_t i = 0; i < n_labels; ++i) + OK(GrB_Matrix_new(&graph_term[i], GrB_BOOL, V, V)); + + for (size_t e = 0; e < n_edges; ++e) + { + TEST_CHECK(edges[e].label >= 0 && (size_t)edges[e].label < n_labels); + OK(GrB_Matrix_setElement_BOOL(graph_term[edges[e].label], true, edges[e].src, + edges[e].dst)); + } +} + +//============================================================================== +// grammars +//============================================================================== + +// S -> a S b | a b +static const int g_anbn_0[] = {TERM(0), NT(0), TERM(1)}; +static const int g_anbn_1[] = {TERM(0), TERM(1)}; +static const Prod g_anbn_S[] = {{g_anbn_0, 3}, {g_anbn_1, 2}}; + +static void init_rsm_anbn(void) +{ + static const Prod *const prods[] = {g_anbn_S}; + static const size_t nprods[] = {2}; + init_rsm_from_grammar(2, 1, 0, prods, nprods); +} + +// S -> a S | a +static const int g_right_0[] = {TERM(0), NT(0)}; +static const int g_right_1[] = {TERM(0)}; +static const Prod g_right_S[] = {{g_right_0, 2}, {g_right_1, 1}}; + +static void init_rsm_right_recursive(void) +{ + static const Prod *const prods[] = {g_right_S}; + static const size_t nprods[] = {2}; + init_rsm_from_grammar(1, 1, 0, prods, nprods); +} + +// S -> S a | epsilon +static const int g_left_0[] = {NT(0), TERM(0)}; +static const Prod g_left_S[] = {{g_left_0, 2}, {NULL, 0}}; + +static void init_rsm_left_recursive(void) +{ + static const Prod *const prods[] = {g_left_S}; + static const size_t nprods[] = {2}; + init_rsm_from_grammar(1, 1, 0, prods, nprods); +} + +// S -> a S b | epsilon +static const int g_dyck_0[] = {TERM(0), NT(0), TERM(1)}; +static const Prod g_dyck_S[] = {{g_dyck_0, 3}, {NULL, 0}}; + +static void init_rsm_dyck(void) +{ + static const Prod *const prods[] = {g_dyck_S}; + static const size_t nprods[] = {2}; + init_rsm_from_grammar(2, 1, 0, prods, nprods); +} + +// S -> a A ; A -> S b | b +static const int g_mutual_S_0[] = {TERM(0), NT(1)}; +static const Prod g_mutual_S[] = {{g_mutual_S_0, 2}}; +static const int g_mutual_A_0[] = {NT(0), TERM(1)}; +static const int g_mutual_A_1[] = {TERM(1)}; +static const Prod g_mutual_A[] = {{g_mutual_A_0, 2}, {g_mutual_A_1, 1}}; + +static void init_rsm_mutual_recursion(void) +{ + static const Prod *const prods[] = {g_mutual_S, g_mutual_A}; + static const size_t nprods[] = {1, 2}; + init_rsm_from_grammar(2, 2, 0, prods, nprods); +} + +// S -> epsilon +static const Prod g_eps_S[] = {{NULL, 0}}; + +static void init_rsm_epsilon(void) +{ + static const Prod *const prods[] = {g_eps_S}; + static const size_t nprods[] = {1}; + init_rsm_from_grammar(1, 1, 0, prods, nprods); +} + +// S -> a S a | epsilon +static const int g_even_0[] = {TERM(0), NT(0), TERM(0)}; +static const Prod g_even_S[] = {{g_even_0, 3}, {NULL, 0}}; + +static void init_rsm_even_palindrome(void) +{ + static const Prod *const prods[] = {g_even_S}; + static const size_t nprods[] = {2}; + init_rsm_from_grammar(1, 1, 0, prods, nprods); +} + +// S -> S S | a | epsilon +static const int g_ambig_0[] = {NT(0), NT(0)}; +static const int g_ambig_1[] = {TERM(0)}; +static const Prod g_ambig_S[] = {{g_ambig_0, 2}, {g_ambig_1, 1}, {NULL, 0}}; + +static void init_rsm_ambiguous(void) +{ + static const Prod *const prods[] = {g_ambig_S}; + static const size_t nprods[] = {3}; + init_rsm_from_grammar(1, 1, 0, prods, nprods); +} + +// S -> A B ; A -> a ; B -> b +static const int g_three_S_0[] = {NT(1), NT(2)}; +static const Prod g_three_S[] = {{g_three_S_0, 2}}; +static const int g_three_A_0[] = {TERM(0)}; +static const Prod g_three_A[] = {{g_three_A_0, 1}}; +static const int g_three_B_0[] = {TERM(1)}; +static const Prod g_three_B[] = {{g_three_B_0, 1}}; + +static void init_rsm_three_nonterminals(void) +{ + static const Prod *const prods[] = {g_three_S, g_three_A, g_three_B}; + static const size_t nprods[] = {1, 1, 1}; + init_rsm_from_grammar(2, 3, 0, prods, nprods); +} + +// S -> M b | a b ; M -> a S (CNF of S -> a S b | a b) +static const int g_cnf_S_0[] = {NT(1), TERM(1)}; +static const int g_cnf_S_1[] = {TERM(0), TERM(1)}; +static const Prod g_cnf_S[] = {{g_cnf_S_0, 2}, {g_cnf_S_1, 2}}; +static const int g_cnf_M_0[] = {TERM(0), NT(0)}; +static const Prod g_cnf_M[] = {{g_cnf_M_0, 2}}; + +static void init_rsm_cnf(void) +{ + static const Prod *const prods[] = {g_cnf_S, g_cnf_M}; + static const size_t nprods[] = {2, 1}; + init_rsm_from_grammar(2, 2, 0, prods, nprods); +} + +// S -> a S b | a b | c S d | c d +static const int g_abcd_0[] = {TERM(0), NT(0), TERM(1)}; +static const int g_abcd_1[] = {TERM(0), TERM(1)}; +static const int g_abcd_2[] = {TERM(2), NT(0), TERM(3)}; +static const int g_abcd_3[] = {TERM(2), TERM(3)}; +static const Prod g_abcd_S[] = { + {g_abcd_0, 3}, {g_abcd_1, 2}, {g_abcd_2, 3}, {g_abcd_3, 2}}; + +static void init_rsm_abcd(void) +{ + static const Prod *const prods[] = {g_abcd_S}; + static const size_t nprods[] = {4}; + init_rsm_from_grammar(4, 1, 0, prods, nprods); +} + +// S -> a S b | a b | c S d | c d | S S +static const int g_concat_4[] = {NT(0), NT(0)}; +static const Prod g_concat_S[] = { + {g_abcd_0, 3}, {g_abcd_1, 2}, {g_abcd_2, 3}, {g_abcd_3, 2}, {g_concat_4, 2}}; + +static void init_rsm_abcd_concat(void) +{ + static const Prod *const prods[] = {g_concat_S}; + static const size_t nprods[] = {5}; + init_rsm_from_grammar(4, 1, 0, prods, nprods); +} + +// raw RSM: S: 0 -a-> 1 -S-> 2 -b-> 0, start = 0, final = {0} +static const Trans g_raw_anbn_trans[] = { + {0, TERM(0), 1}, + {1, NT(0), 2}, + {2, TERM(1), 0}, +}; + +static void init_rsm_raw_anbn(void) +{ + static const GrB_Index starts[] = {0}; + static const GrB_Index finals_0[] = {0}; + static const GrB_Index *const finals[] = {finals_0}; + static const size_t nfinals[] = {1}; + init_rsm_raw(2, 1, 0, 3, starts, g_raw_anbn_trans, + sizeof(g_raw_anbn_trans) / sizeof(g_raw_anbn_trans[0]), finals, nfinals); +} + +// raw RSM: S: 0 -a-> 1 -S-> 2 -b-> 0, 0 -c-> 3 -S-> 4 -d-> 0, +// start = 0, final = {0} +static const Trans g_raw_abcd_trans[] = { + {0, TERM(0), 1}, + {1, NT(0), 2}, + {2, TERM(1), 0}, + {0, TERM(2), 3}, + {3, NT(0), 4}, + {4, TERM(3), 0}, +}; + +static void init_rsm_raw_abcd(void) +{ + static const GrB_Index starts[] = {0}; + static const GrB_Index finals_0[] = {0}; + static const GrB_Index *const finals[] = {finals_0}; + static const size_t nfinals[] = {1}; + init_rsm_raw(4, 1, 0, 5, starts, g_raw_abcd_trans, + sizeof(g_raw_abcd_trans) / sizeof(g_raw_abcd_trans[0]), finals, nfinals); +} + +//============================================================================== +// graphs +//============================================================================== + +// 0-a->1; 1-a->2; 2-a->0; 1-b->3; 3-b->1 (V=4, a=0, b=1) +static const Edge g_cyclic[] = { + {0, 0, 1}, + {1, 0, 2}, + {2, 0, 0}, + {1, 1, 3}, + {3, 1, 1}, +}; + +// 0-a->1; 1-a->2; 2-a->3 (V=4, a=0) +static const Edge g_chain_right[] = { + {0, 0, 1}, + {1, 0, 2}, + {2, 0, 3}, +}; + +// 0-a->1; 1-a->2 (V=3, a=0) +static const Edge g_chain_left[] = { + {0, 0, 1}, + {1, 0, 2}, +}; + +// 0-a->1; 1-a->2; 2-b->3; 3-b->4 (V=5, a=0, b=1) +static const Edge g_dyck_linear[] = { + {0, 0, 1}, + {1, 0, 2}, + {2, 1, 3}, + {3, 1, 4}, +}; + +// 0-a->0; 0-b->0 (V=1, a=0, b=1) +static const Edge g_dyck_self_loop[] = { + {0, 0, 0}, + {0, 1, 0}, +}; + +// 0-a->1 (V=2, a=0) +static const Edge g_epsilon[] = { + {0, 0, 1}, +}; + +// 0-a->1; 1-a->0; 0-a->2 (V=3, a=0) +static const Edge g_even[] = { + {0, 0, 1}, + {1, 0, 0}, + {0, 0, 2}, +}; + +// 0-a->1; 1-a->0; 0-b->2 (V=3, a=0, b=1) +static const Edge g_no_match[] = { + {0, 0, 1}, + {1, 0, 0}, + {0, 1, 2}, +}; + +// 0-a->1; 1-a->2; 2-a->3 (V=4, a=0) -- same shape as g_chain_right +static const Edge g_ambig[] = { + {0, 0, 1}, + {1, 0, 2}, + {2, 0, 3}, +}; + +// 0-a->1; 1-b->2; 0-b->3 (V=4, a=0, b=1) +static const Edge g_three[] = { + {0, 0, 1}, + {1, 1, 2}, + {0, 1, 3}, +}; + +// 0-a->1; 1-a->2; 2-a->0; +// 1-b->3; 3-b->4; 4-b->5; 5-b->6; 6-b->1 (V=7, a=0, b=1) +static const Edge g_cyclic_3a_5b[] = { + {0, 0, 1}, + {1, 0, 2}, + {2, 0, 0}, + {1, 1, 3}, + {3, 1, 4}, + {4, 1, 5}, + {5, 1, 6}, + {6, 1, 1}, +}; + +// 0-a->0; 0-b->1; 1-b->1 (V=2, a=0, b=1) +static const Edge g_reg_ab[] = { + {0, 0, 0}, + {0, 1, 1}, + {1, 1, 1}, +}; + +// 0-a->1; 1-a->2; 2-a->0 (V=3, a=0) +static const Edge g_left_cycle[] = { + {0, 0, 1}, + {1, 0, 2}, + {2, 0, 0}, +}; + +// 0-a->1; 1-a->2; 2-a->0; 1-d->3; 3-d->4; 4-d->0; +// 0-b->5; 5-b->0; 6-c->0; 0-c->6 (V=7, a=0, b=1, c=2, d=3) +static const Edge g_abcd_start1[] = { + {0, 0, 1}, + {1, 0, 2}, + {2, 0, 0}, + {1, 3, 3}, + {3, 3, 4}, + {4, 3, 0}, + {0, 1, 5}, + {5, 1, 0}, + {6, 2, 0}, + {0, 2, 6}, +}; + +// same as g_abcd_start1 but with 0-d->3 instead of 1-d->3 +static const Edge g_abcd_start6[] = { + {0, 0, 1}, + {1, 0, 2}, + {2, 0, 0}, + {0, 3, 3}, + {3, 3, 4}, + {4, 3, 0}, + {0, 1, 5}, + {5, 1, 0}, + {6, 2, 0}, + {0, 2, 6}, +}; + +#define N_EDGES(a) (sizeof(a) / sizeof((a)[0])) + +#define run_algorithm(sources, num_sources) \ + LAGraph_CFPQ_RSM_blocked(&result, rsm, graph_term, (sources), (num_sources), V, msg); + +//============================================================================== +// test cases +//============================================================================== + +//------------------------------------------------------------------------------ +// cyclic a^n b^n, start = 2 : expected {1, 3} +//------------------------------------------------------------------------------ +void test_cyclic_anbn_start2(void) +{ + setup(); + init_rsm_anbn(); + init_graph(4, 2, g_cyclic, N_EDGES(g_cyclic)); + + GrB_Index sources[] = {2}; + GrB_Info info = run_algorithm(sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {1, 3}; + check_reachable(expected, 2, "cyclic_anbn_start2"); + } + + free_workspace(); + OK(info); + teardown(); +} + +//------------------------------------------------------------------------------ +// cyclic a^n b^n, start = 0 : expected {1, 3} +//------------------------------------------------------------------------------ +void test_cyclic_anbn_start0(void) +{ + setup(); + init_rsm_anbn(); + init_graph(4, 2, g_cyclic, N_EDGES(g_cyclic)); + + GrB_Index sources[] = {0}; + GrB_Info info = run_algorithm(sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {1, 3}; + check_reachable(expected, 2, "cyclic_anbn_start0"); + } + + free_workspace(); + OK(info); + teardown(); +} + +//------------------------------------------------------------------------------ +// right-recursive S -> a S | a, start = 0 : expected {1, 2, 3} +//------------------------------------------------------------------------------ +void test_right_recursive(void) +{ + setup(); + init_rsm_right_recursive(); + init_graph(4, 1, g_chain_right, N_EDGES(g_chain_right)); + + GrB_Index sources[] = {0}; + GrB_Info info = run_algorithm(sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {1, 2, 3}; + check_reachable(expected, 3, "right_recursive"); + } + + free_workspace(); + OK(info); + teardown(); +} + +//------------------------------------------------------------------------------ +// left-recursive S -> S a | epsilon, start = 0 : expected {0, 1, 2} +//------------------------------------------------------------------------------ +void test_left_recursive(void) +{ + setup(); + init_rsm_left_recursive(); + init_graph(3, 1, g_chain_left, N_EDGES(g_chain_left)); + + GrB_Index sources[] = {0}; + GrB_Info info = run_algorithm(sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {0, 1, 2}; + check_reachable(expected, 3, "left_recursive"); + } + + free_workspace(); + OK(info); + teardown(); +} + +//------------------------------------------------------------------------------ +// Dyck S -> a S b | epsilon, start = 0 : expected {0, 4} +//------------------------------------------------------------------------------ +void test_dyck(void) +{ + setup(); + init_rsm_dyck(); + init_graph(5, 2, g_dyck_linear, N_EDGES(g_dyck_linear)); + + GrB_Index sources[] = {0}; + GrB_Info info = run_algorithm(sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {0, 4}; + check_reachable(expected, 2, "dyck"); + } + + free_workspace(); + OK(info); + teardown(); +} + +//------------------------------------------------------------------------------ +// Dyck self-loop, start = 0 : expected {0} +//------------------------------------------------------------------------------ +void test_dyck_self_loop(void) +{ + setup(); + init_rsm_dyck(); + init_graph(1, 2, g_dyck_self_loop, N_EDGES(g_dyck_self_loop)); + + GrB_Index sources[] = {0}; + GrB_Info info = run_algorithm(sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {0}; + check_reachable(expected, 1, "dyck_self_loop"); + } + + free_workspace(); + OK(info); + teardown(); +} + +//------------------------------------------------------------------------------ +// mutual recursion S -> a A, A -> S b | b, start = 2 : expected {1, 3} +//------------------------------------------------------------------------------ +void test_mutual_recursion(void) +{ + setup(); + init_rsm_mutual_recursion(); + init_graph(4, 2, g_cyclic, N_EDGES(g_cyclic)); + + GrB_Index sources[] = {2}; + GrB_Info info = run_algorithm(sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {1, 3}; + check_reachable(expected, 2, "mutual_recursion"); + } + + free_workspace(); + OK(info); + teardown(); +} + +//------------------------------------------------------------------------------ +// epsilon-only S -> epsilon, start = 0 : expected {0} +//------------------------------------------------------------------------------ +void test_epsilon_only(void) +{ + setup(); + init_rsm_epsilon(); + init_graph(2, 1, g_epsilon, N_EDGES(g_epsilon)); + + GrB_Index sources[] = {0}; + GrB_Info info = run_algorithm(sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {0}; + check_reachable(expected, 1, "epsilon_only"); + } + + free_workspace(); + OK(info); + teardown(); +} + +//------------------------------------------------------------------------------ +// even-palindrome S -> a S a | epsilon, start = 0 : expected {0} +//------------------------------------------------------------------------------ +void test_start_equals_final_with_cycle(void) +{ + setup(); + init_rsm_even_palindrome(); + init_graph(3, 1, g_even, N_EDGES(g_even)); + + GrB_Index sources[] = {0}; + GrB_Info info = run_algorithm(sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {0}; + check_reachable(expected, 1, "even_palindrome"); + } + + free_workspace(); + OK(info); + teardown(); +} + +//------------------------------------------------------------------------------ +// no match (dead-end b), start = 0 : expected {} +//------------------------------------------------------------------------------ +void test_no_match(void) +{ + setup(); + init_rsm_anbn(); + init_graph(3, 2, g_no_match, N_EDGES(g_no_match)); + + GrB_Index sources[] = {0}; + GrB_Info info = run_algorithm(sources, 1); + if (info == GrB_SUCCESS) + { + check_reachable(NULL, 0, "no_match"); + } + + free_workspace(); + OK(info); + teardown(); +} + +//------------------------------------------------------------------------------ +// ambiguous S -> S S | a | epsilon, start = 0 : expected {0, 1, 2, 3} +//------------------------------------------------------------------------------ +void test_ambiguous_grammar(void) +{ + setup(); + init_rsm_ambiguous(); + init_graph(4, 1, g_ambig, N_EDGES(g_ambig)); + + GrB_Index sources[] = {0}; + GrB_Info info = run_algorithm(sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {0, 1, 2, 3}; + check_reachable(expected, 4, "ambiguous_grammar"); + } + + free_workspace(); + OK(info); + teardown(); +} + +//------------------------------------------------------------------------------ +// three NTs S -> A B, A -> a, B -> b, start = 0 : expected {2} +//------------------------------------------------------------------------------ +void test_three_nonterminals(void) +{ + setup(); + init_rsm_three_nonterminals(); + init_graph(4, 2, g_three, N_EDGES(g_three)); + + GrB_Index sources[] = {0}; + GrB_Info info = run_algorithm(sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {2}; + check_reachable(expected, 1, "three_nonterminals"); + } + + free_workspace(); + OK(info); + teardown(); +} + +//------------------------------------------------------------------------------ +// cyclic Dyck S -> a S b | epsilon, start = 2 : expected {1, 2, 3} +//------------------------------------------------------------------------------ +void test_cyclic_with_epsilon_base(void) +{ + setup(); + init_rsm_dyck(); + init_graph(4, 2, g_cyclic, N_EDGES(g_cyclic)); + + GrB_Index sources[] = {2}; + GrB_Info info = run_algorithm(sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {1, 2, 3}; + check_reachable(expected, 3, "cyclic_with_epsilon_base"); + } + + free_workspace(); + OK(info); + teardown(); +} + +//------------------------------------------------------------------------------ +// cyclic a^n b^n, 3(a)-5(b) loops, start = 0 : expected {1, 3, 4, 5, 6} +//------------------------------------------------------------------------------ +void test_cyclic_3_5_anbn_start1(void) +{ + setup(); + init_rsm_anbn(); + init_graph(7, 2, g_cyclic_3a_5b, N_EDGES(g_cyclic_3a_5b)); + + GrB_Index sources[] = {0}; + GrB_Info info = run_algorithm(sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {1, 3, 4, 5, 6}; + check_reachable(expected, 5, "cyclic_3_5_anbn"); + } + + free_workspace(); + OK(info); + teardown(); +} + +//------------------------------------------------------------------------------ +// cyclic a^n b^n CNF, 3(a)-5(b) loops, start = 0 : expected {1, 3, 4, 5, 6} +//------------------------------------------------------------------------------ +void test_cyclic_3_5_anbn_CNF_start1(void) +{ + setup(); + init_rsm_cnf(); + init_graph(7, 2, g_cyclic_3a_5b, N_EDGES(g_cyclic_3a_5b)); + + GrB_Index sources[] = {0}; + GrB_Info info = run_algorithm(sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {1, 3, 4, 5, 6}; + check_reachable(expected, 5, "cyclic_3_5_anbn_CNF"); + } + + free_workspace(); + OK(info); + teardown(); +} + +//------------------------------------------------------------------------------ +// cyclic regular a^n b^n CNF, start = 0 : expected {1} +//------------------------------------------------------------------------------ +void test_cyclic_reg_anbn_CNF_start0(void) +{ + setup(); + init_rsm_cnf(); + init_graph(2, 2, g_reg_ab, N_EDGES(g_reg_ab)); + + GrB_Index sources[] = {0}; + GrB_Info info = run_algorithm(sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {1}; + check_reachable(expected, 1, "cyclic_reg_anbn_CNF"); + } + + free_workspace(); + OK(info); + teardown(); +} + +//------------------------------------------------------------------------------ +// left-recursive cycle S -> S a | epsilon, start = 0 : expected {0, 1, 2} +//------------------------------------------------------------------------------ +void test_left_recursive_cycle(void) +{ + setup(); + init_rsm_left_recursive(); + init_graph(3, 1, g_left_cycle, N_EDGES(g_left_cycle)); + + GrB_Index sources[] = {0}; + GrB_Info info = run_algorithm(sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {0, 1, 2}; + check_reachable(expected, 3, "left_recursive_cycle"); + } + + free_workspace(); + OK(info); + teardown(); +} + +//------------------------------------------------------------------------------ +// cyclic a^n b^n c^n d^n, start = 1 : expected {0, 5} +//------------------------------------------------------------------------------ +void test_cyclic_2_3_anbncndn_start1(void) +{ + setup(); + init_rsm_abcd(); + init_graph(7, 4, g_abcd_start1, N_EDGES(g_abcd_start1)); + + GrB_Index sources[] = {1}; + GrB_Info info = run_algorithm(sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {0, 5}; + check_reachable(expected, 2, "cyclic_anbncndn_start1"); + } + + free_workspace(); + OK(info); + teardown(); +} + +//------------------------------------------------------------------------------ +// cyclic a^n b^n c^n d^n, start = 6 : expected {0, 3, 4} +//------------------------------------------------------------------------------ +void test_cyclic_2_3_anbncndn_start6(void) +{ + setup(); + init_rsm_abcd(); + init_graph(7, 4, g_abcd_start6, N_EDGES(g_abcd_start6)); + + GrB_Index sources[] = {6}; + GrB_Info info = run_algorithm(sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {0, 3, 4}; + check_reachable(expected, 3, "cyclic_anbncndn_start6"); + } + + free_workspace(); + OK(info); + teardown(); +} + +//------------------------------------------------------------------------------ +// cyclic a^n b^n c^n d^n concat, start = 1 : expected {0, 3, 4, 5} +//------------------------------------------------------------------------------ +void test_cyclic_2_3_anbncndn_concat_start1(void) +{ + setup(); + init_rsm_abcd_concat(); + init_graph(7, 4, g_abcd_start6, N_EDGES(g_abcd_start6)); + + GrB_Index sources[] = {1}; + GrB_Info info = run_algorithm(sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {0, 3, 4, 5}; + check_reachable(expected, 4, "cyclic_anbncndn_concat_start1"); + } + + free_workspace(); + OK(info); + teardown(); +} + +//------------------------------------------------------------------------------ +// raw RSM a^n b^n, 3(a)-5(b) loops, start = 0 : expected {0, 1, 3, 4, 5, 6} +//------------------------------------------------------------------------------ +void test_cyclic_3_5_anbn_raw_RSM_start1(void) +{ + setup(); + init_rsm_raw_anbn(); + init_graph(7, 2, g_cyclic_3a_5b, N_EDGES(g_cyclic_3a_5b)); + + GrB_Index sources[] = {0}; + GrB_Info info = run_algorithm(sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {0, 1, 3, 4, 5, 6}; + check_reachable(expected, 6, "cyclic_3_5_anbn_raw_RSM"); + } + + free_workspace(); + OK(info); + teardown(); +} + +//------------------------------------------------------------------------------ +// raw RSM a^n b^n c^n d^n concat, start = 1 : expected {0, 1, 3, 4, 5} +//------------------------------------------------------------------------------ +void test_cyclic_2_3_anbncndn_concat_raw_rsm_start1(void) +{ + setup(); + init_rsm_raw_abcd(); + init_graph(7, 4, g_abcd_start6, N_EDGES(g_abcd_start6)); + + GrB_Index sources[] = {1}; + GrB_Info info = run_algorithm(sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {0, 1, 3, 4, 5}; + check_reachable(expected, 5, "cyclic_anbncndn_concat_raw_rsm"); + } + + free_workspace(); + OK(info); + teardown(); +} + +TEST_LIST = {{"cyclic_anbn_start2", test_cyclic_anbn_start2}, + {"cyclic_anbn_start0", test_cyclic_anbn_start0}, + {"right_recursive", test_right_recursive}, + {"left_recursive", test_left_recursive}, + {"dyck", test_dyck}, + {"dyck_self_loop", test_dyck_self_loop}, + {"mutual_recursion", test_mutual_recursion}, + {"epsilon_only", test_epsilon_only}, + {"start_equals_final_with_cycle", test_start_equals_final_with_cycle}, + {"no_match", test_no_match}, + {"ambiguous_grammar", test_ambiguous_grammar}, + {"three_nonterminals", test_three_nonterminals}, + {"cyclic_with_epsilon_base", test_cyclic_with_epsilon_base}, + {"cyclic_3_5_anbn_start1", test_cyclic_3_5_anbn_start1}, + {"cyclic_3_5_anbn_CNF_start1", test_cyclic_3_5_anbn_CNF_start1}, + {"cyclic_reg_anbn_CNF_start0", test_cyclic_reg_anbn_CNF_start0}, + {"left_recursive_cycle", test_left_recursive_cycle}, + {"cyclic_2_3_anbncndn_start1", test_cyclic_2_3_anbncndn_start1}, + {"cyclic_2_3_anbncndn_start6", test_cyclic_2_3_anbncndn_start6}, + {"cyclic_2_3_anbncndn_concat_start1", test_cyclic_2_3_anbncndn_concat_start1}, + {"cyclic_3_5_anbn_raw_RSM_start1", test_cyclic_3_5_anbn_raw_RSM_start1}, + {"cyclic_2_3_anbncndn_concat_raw_rsm_start1", + test_cyclic_2_3_anbncndn_concat_raw_rsm_start1}, + {NULL, NULL}}; diff --git a/include/LAGraphX.h b/include/LAGraphX.h index 18171ed555..c6ba3b9b7e 100644 --- a/include/LAGraphX.h +++ b/include/LAGraphX.h @@ -1112,6 +1112,9 @@ typedef struct int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *graph_term, const GrB_Index *sources, size_t num_sources, GrB_Index V, char *msg); +int LAGraph_CFPQ_RSM_blocked(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *graph_term, + const GrB_Index *sources, size_t num_sources, GrB_Index V, char *msg); + //------------------------------------------------------------------------------ // a simple example of an algorithm //------------------------------------------------------------------------------ From 2ab4122e795e2a46c9be2b43102d9a7a8624bfee Mon Sep 17 00:00:00 2001 From: Sterkachov Date: Tue, 20 Oct 2026 02:24:11 +0300 Subject: [PATCH 19/25] fix(CFPQ_RSM): delete obsolete reshape function --- experimental/algorithm/LAGraph_CFPQ_RSM.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/experimental/algorithm/LAGraph_CFPQ_RSM.c b/experimental/algorithm/LAGraph_CFPQ_RSM.c index ce6b457537..6dbddbf934 100644 --- a/experimental/algorithm/LAGraph_CFPQ_RSM.c +++ b/experimental/algorithm/LAGraph_CFPQ_RSM.c @@ -57,20 +57,6 @@ return _hi; \ } -static inline GrB_Info reshape( // input/output: - GrB_Matrix C, // input/output matrix, reshaped in place - bool by_col, // true if reshape by column, false if by row - GrB_Index nrows_new, // new number of rows of C - GrB_Index ncols_new, // new number of columns of C - const GrB_Descriptor desc // to control # of threads used -) -{ - if (C == NULL) - return GrB_NULL_POINTER; - - return GxB_Matrix_reshape(C, by_col, nrows_new, ncols_new, desc); -} - static GrB_Info s_mxm_chain(GrB_Matrix C, // output accumulator |Q|*|V*V| GrB_Matrix A, // RSM matrix |Q|*|Q| GrB_Matrix M, // frontier |Q|*|V*V| From 86765c239881672f7bac7059a1ccb6a81b9cb215 Mon Sep 17 00:00:00 2001 From: Sterkachov Date: Tue, 20 Oct 2026 02:40:37 +0300 Subject: [PATCH 20/25] refactor(lacfpq_blocked): rename struct due to conflicts in homka122 bench --- experimental/test/test_CFPQ_RSM_blocked.c | 34 +++++++++++------------ 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/experimental/test/test_CFPQ_RSM_blocked.c b/experimental/test/test_CFPQ_RSM_blocked.c index 0eb301565d..23e348698b 100644 --- a/experimental/test/test_CFPQ_RSM_blocked.c +++ b/experimental/test/test_CFPQ_RSM_blocked.c @@ -211,7 +211,7 @@ typedef struct GrB_Index src; int label; // terminal label index GrB_Index dst; -} Edge; +} GraphEdge; // a single RSM transition typedef struct @@ -303,7 +303,7 @@ static void init_rsm_raw(size_t num_term, size_t num_nonterm, size_t start_nt, G } // build the per-terminal graph matrices from an edge list -static void init_graph(GrB_Index n_vertices, size_t n_labels, const Edge *edges, size_t n_edges) +static void init_graph(GrB_Index n_vertices, size_t n_labels, const GraphEdge *edges, size_t n_edges) { V = (size_t)n_vertices; n_graph_term = n_labels; @@ -515,7 +515,7 @@ static void init_rsm_raw_abcd(void) //============================================================================== // 0-a->1; 1-a->2; 2-a->0; 1-b->3; 3-b->1 (V=4, a=0, b=1) -static const Edge g_cyclic[] = { +static const GraphEdge g_cyclic[] = { {0, 0, 1}, {1, 0, 2}, {2, 0, 0}, @@ -524,20 +524,20 @@ static const Edge g_cyclic[] = { }; // 0-a->1; 1-a->2; 2-a->3 (V=4, a=0) -static const Edge g_chain_right[] = { +static const GraphEdge g_chain_right[] = { {0, 0, 1}, {1, 0, 2}, {2, 0, 3}, }; // 0-a->1; 1-a->2 (V=3, a=0) -static const Edge g_chain_left[] = { +static const GraphEdge g_chain_left[] = { {0, 0, 1}, {1, 0, 2}, }; // 0-a->1; 1-a->2; 2-b->3; 3-b->4 (V=5, a=0, b=1) -static const Edge g_dyck_linear[] = { +static const GraphEdge g_dyck_linear[] = { {0, 0, 1}, {1, 0, 2}, {2, 1, 3}, @@ -545,39 +545,39 @@ static const Edge g_dyck_linear[] = { }; // 0-a->0; 0-b->0 (V=1, a=0, b=1) -static const Edge g_dyck_self_loop[] = { +static const GraphEdge g_dyck_self_loop[] = { {0, 0, 0}, {0, 1, 0}, }; // 0-a->1 (V=2, a=0) -static const Edge g_epsilon[] = { +static const GraphEdge g_epsilon[] = { {0, 0, 1}, }; // 0-a->1; 1-a->0; 0-a->2 (V=3, a=0) -static const Edge g_even[] = { +static const GraphEdge g_even[] = { {0, 0, 1}, {1, 0, 0}, {0, 0, 2}, }; // 0-a->1; 1-a->0; 0-b->2 (V=3, a=0, b=1) -static const Edge g_no_match[] = { +static const GraphEdge g_no_match[] = { {0, 0, 1}, {1, 0, 0}, {0, 1, 2}, }; // 0-a->1; 1-a->2; 2-a->3 (V=4, a=0) -- same shape as g_chain_right -static const Edge g_ambig[] = { +static const GraphEdge g_ambig[] = { {0, 0, 1}, {1, 0, 2}, {2, 0, 3}, }; // 0-a->1; 1-b->2; 0-b->3 (V=4, a=0, b=1) -static const Edge g_three[] = { +static const GraphEdge g_three[] = { {0, 0, 1}, {1, 1, 2}, {0, 1, 3}, @@ -585,7 +585,7 @@ static const Edge g_three[] = { // 0-a->1; 1-a->2; 2-a->0; // 1-b->3; 3-b->4; 4-b->5; 5-b->6; 6-b->1 (V=7, a=0, b=1) -static const Edge g_cyclic_3a_5b[] = { +static const GraphEdge g_cyclic_3a_5b[] = { {0, 0, 1}, {1, 0, 2}, {2, 0, 0}, @@ -597,14 +597,14 @@ static const Edge g_cyclic_3a_5b[] = { }; // 0-a->0; 0-b->1; 1-b->1 (V=2, a=0, b=1) -static const Edge g_reg_ab[] = { +static const GraphEdge g_reg_ab[] = { {0, 0, 0}, {0, 1, 1}, {1, 1, 1}, }; // 0-a->1; 1-a->2; 2-a->0 (V=3, a=0) -static const Edge g_left_cycle[] = { +static const GraphEdge g_left_cycle[] = { {0, 0, 1}, {1, 0, 2}, {2, 0, 0}, @@ -612,7 +612,7 @@ static const Edge g_left_cycle[] = { // 0-a->1; 1-a->2; 2-a->0; 1-d->3; 3-d->4; 4-d->0; // 0-b->5; 5-b->0; 6-c->0; 0-c->6 (V=7, a=0, b=1, c=2, d=3) -static const Edge g_abcd_start1[] = { +static const GraphEdge g_abcd_start1[] = { {0, 0, 1}, {1, 0, 2}, {2, 0, 0}, @@ -626,7 +626,7 @@ static const Edge g_abcd_start1[] = { }; // same as g_abcd_start1 but with 0-d->3 instead of 1-d->3 -static const Edge g_abcd_start6[] = { +static const GraphEdge g_abcd_start6[] = { {0, 0, 1}, {1, 0, 2}, {2, 0, 0}, From 79ab9a935c6ac8879b49a8901bf0bcb1691a574d Mon Sep 17 00:00:00 2001 From: Sterkachov Date: Tue, 20 Oct 2026 11:37:49 +0300 Subject: [PATCH 21/25] feat(CFPQ_RSM): mask new summary edges in PHASE 4 --- experimental/algorithm/LAGraph_CFPQ_RSM.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/experimental/algorithm/LAGraph_CFPQ_RSM.c b/experimental/algorithm/LAGraph_CFPQ_RSM.c index 6dbddbf934..aa91619093 100644 --- a/experimental/algorithm/LAGraph_CFPQ_RSM.c +++ b/experimental/algorithm/LAGraph_CFPQ_RSM.c @@ -340,6 +340,16 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr continue; GRB_TRY(GxB_Matrix_reshape(new_edges, false, V, V, GrB_NULL)); + + // dG = new_edges \ graph_nt[i] + GRB_TRY(GrB_apply(new_edges, graph_nt[i], GrB_NULL, GrB_IDENTITY_BOOL, new_edges, GrB_DESC_RSC)); + GRB_TRY(GrB_Matrix_nvals(&ne_vals, new_edges)); + if (ne_vals == 0) + { + GRB_TRY(GxB_Matrix_reshape(new_edges, false, 1, VV, GrB_NULL)); + continue; + } + GRB_TRY(GrB_eWiseAdd(graph_nt[i], GrB_NULL, GrB_NULL, GrB_LOR, graph_nt[i], new_edges, GrB_NULL)); From e34c516341780c42f4fd2c0ff3d3d1acf42efcbf Mon Sep 17 00:00:00 2001 From: Sterkachov Date: Tue, 20 Oct 2026 14:37:03 +0300 Subject: [PATCH 22/25] refactor(CFPQ_RSM): reuse shifted frontier for call transitions --- experimental/algorithm/LAGraph_CFPQ_RSM.c | 176 +++++++++------------- 1 file changed, 75 insertions(+), 101 deletions(-) diff --git a/experimental/algorithm/LAGraph_CFPQ_RSM.c b/experimental/algorithm/LAGraph_CFPQ_RSM.c index aa91619093..a07ee5ba9c 100644 --- a/experimental/algorithm/LAGraph_CFPQ_RSM.c +++ b/experimental/algorithm/LAGraph_CFPQ_RSM.c @@ -18,7 +18,6 @@ GrB_free(&M_new); \ GrB_free(&temp1); \ GrB_free(&temp2); \ - GrB_free(&mask_call); \ GrB_free(&frame); \ GrB_free(&new_edges); \ GrB_free(&diag_entry); \ @@ -37,12 +36,6 @@ GrB_free(&rsm_start[_i]); \ LAGraph_Free((void **)&rsm_start, msg); \ } \ - if (rsm_call != NULL) \ - { \ - for (size_t _i = 0; _i < num_nonterm; ++_i) \ - GrB_free(&rsm_call[_i]); \ - LAGraph_Free((void **)&rsm_call, msg); \ - } \ } #define LG_FREE_ALL \ { \ @@ -57,53 +50,60 @@ return _hi; \ } -static GrB_Info s_mxm_chain(GrB_Matrix C, // output accumulator |Q|*|V*V| - GrB_Matrix A, // RSM matrix |Q|*|Q| - GrB_Matrix M, // frontier |Q|*|V*V| - GrB_Matrix B, // graph matrix |V|*|V| - GrB_Matrix temp1, // scratch |Q|*|V*V| - GrB_Matrix temp2, // scratch |Q|*|V*V| - GrB_Index Q, GrB_Index V) + +// A is an RSM matrix |Q|x|Q|, M is |Q|x|VV| +// temp1 is |Q|x|VV| scratch on entry and holds (|QV|x|V|) on exit +static GrB_Info s_shift(GrB_Matrix temp1, GrB_Matrix A, GrB_Matrix M, GrB_Index Q, GrB_Index V) { H_TRY(GrB_Matrix_clear(temp1)); - H_TRY(GrB_Matrix_clear(temp2)); - - // temp1 = A^T * M -> |Q|*|V*V| + + // temp1 = A^T * M == |Q|x|VV| H_TRY(GrB_mxm(temp1, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, A, M, GrB_DESC_T0)); + + // reshape(scratch): |Q|x|VV| -> |QV|x|V| + H_TRY(GxB_Matrix_reshape(temp1, false, Q*V, V, GrB_NULL)); + return GrB_SUCCESS; +} + +// restore the scratch matrix to |Q|*|V*V|; its content is dead, so clear it first +static GrB_Info s_unshift(GrB_Matrix temp1, GrB_Index Q, GrB_Index V) +{ + H_TRY(GrB_Matrix_clear(temp1)); + H_TRY(GxB_Matrix_reshape(temp1, false, Q, V*V, GrB_NULL)); + return GrB_SUCCESS; +} - // reshape: |Q| × |V*V| -> |Q*V|*|V| - H_TRY(GxB_Matrix_reshape(temp1, false, Q * V, V, GrB_NULL)); - H_TRY(GxB_Matrix_reshape(temp2, false, Q * V, V, GrB_NULL)); +static GrB_Info s_mul_acc(GrB_Matrix C, // output accumulator |Q|*|VV| + GrB_Matrix temp1, // shifted frontier |QV|*|V| + GrB_Matrix B, // graph matrix |V|*|V| + GrB_Matrix temp2, // scratch |Q|*|VV| + GrB_Index Q, GrB_Index V) +{ + H_TRY(GrB_Matrix_clear(temp2)); + H_TRY(GxB_Matrix_reshape(temp2, false, Q*V, V, GrB_NULL)); - // temp2 = temp1 * B -> |Q*V|*|V| + // scratch = Xr * B -> |Q*V|*|V| H_TRY(GrB_mxm(temp2, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, temp1, B, GrB_NULL)); - // reshape: |Q*V|*|V| -> |Q|*|V*V| - H_TRY(GxB_Matrix_reshape(temp1, false, Q, V * V, GrB_NULL)); + // reshape(scratch): |Q*V|*|V| -> |Q|*|V*V| H_TRY(GxB_Matrix_reshape(temp2, false, Q, V * V, GrB_NULL)); // C |= temp2 H_TRY(GrB_eWiseAdd(C, GrB_NULL, GrB_NULL, GrB_LOR, C, temp2, GrB_NULL)); - return GrB_SUCCESS; } -static GrB_Info build_call_matrix(GrB_Matrix *call, GrB_Matrix rsm_nt, GrB_Vector rsm_start, - GrB_Index Q) +static GrB_Info s_mxm_chain(GrB_Matrix C, // output accumulator |Q|*|VV| + GrB_Matrix A, // RSM matrix |Q|*|Q| + GrB_Matrix M, // frontier |Q|*|VV| + GrB_Matrix B, // graph matrix |V|*|V| + GrB_Matrix temp1, // scratch |Q|*|VV| + GrB_Matrix temp2, // scratch |Q|*|VV| + GrB_Index Q, GrB_Index V) { - GrB_Vector call_mask = GrB_NULL; - - H_TRY(GrB_Matrix_new(call, GrB_BOOL, Q, Q)); - H_TRY(GrB_Vector_new(&call_mask, GrB_BOOL, Q)); - - // [q_call, q_ret] -> [q_call] - H_TRY(GrB_reduce(call_mask, GrB_NULL, GrB_NULL, GrB_LOR_MONOID_BOOL, rsm_nt, GrB_NULL)); - // |Q|*1 * 1*|Q| -> |Q|*|Q| - H_TRY(GrB_mxm(*call, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, (GrB_Matrix)call_mask, - (GrB_Matrix)rsm_start, GrB_DESC_T1)); - - H_TRY(GrB_Vector_free(&call_mask)); - + H_TRY(s_shift(temp1, A, M, Q, V)); + H_TRY(s_mul_acc(C, temp1, B, temp2, Q, V)); + H_TRY(s_unshift(temp1, Q, V)); return GrB_SUCCESS; } @@ -146,7 +146,6 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GrB_Matrix M_new = GrB_NULL; GrB_Matrix temp1 = GrB_NULL; GrB_Matrix temp2 = GrB_NULL; - GrB_Matrix mask_call = GrB_NULL; GrB_Matrix frame = GrB_NULL; GrB_Matrix new_edges = GrB_NULL; GrB_Matrix diag_entry = GrB_NULL; @@ -155,7 +154,6 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GrB_Vector source_mask = GrB_NULL; GrB_Matrix *graph_nt = NULL; - GrB_Matrix *rsm_call = NULL; GrB_Vector *rsm_start = NULL; // rsm_start[S][i] = true, iff start_states[S] = i LG_ASSERT(reachable != NULL, GrB_NULL_POINTER); @@ -171,13 +169,11 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr // allocate per-nonterminal arrays LG_TRY(LAGraph_Malloc((void **)&graph_nt, rsm->nonterminal_count, sizeof(GrB_Matrix), msg)); LG_TRY(LAGraph_Malloc((void **)&rsm_start, rsm->nonterminal_count, sizeof(GrB_Vector), msg)); - LG_TRY(LAGraph_Malloc((void **)&rsm_call, rsm->nonterminal_count, sizeof(GrB_Matrix), msg)); for (size_t i = 0; i < num_nonterm; ++i) { graph_nt[i] = GrB_NULL; rsm_start[i] = GrB_NULL; - rsm_call[i] = GrB_NULL; } for (size_t i = 0; i < num_nonterm; ++i) @@ -185,7 +181,6 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GRB_TRY(GrB_Matrix_new(&graph_nt[i], GrB_BOOL, V, V)); GRB_TRY(GrB_Vector_new(&rsm_start[i], GrB_BOOL, Q)); GrB_Vector_setElement_BOOL(rsm_start[i], true, start_states[i]); - GRB_TRY(build_call_matrix(&rsm_call[i], rsm_nonterm[i], rsm_start[i], Q)); } // allocate working matrices @@ -198,7 +193,6 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GRB_TRY(GrB_Matrix_new(&M_new, GrB_BOOL, Q, VV)); GRB_TRY(GrB_Matrix_new(&temp1, GrB_BOOL, Q, VV)); GRB_TRY(GrB_Matrix_new(&temp2, GrB_BOOL, Q, VV)); - GRB_TRY(GrB_Matrix_new(&mask_call, GrB_BOOL, Q, VV)); GRB_TRY(GrB_Matrix_new(&frame, GrB_BOOL, Q, VV)); GRB_TRY(GrB_Matrix_new(&new_edges, GrB_BOOL, 1, VV)); // there is no need to create matrix for diag_entry @@ -257,69 +251,49 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr LG_TRY(s_mxm_chain(M_term, rsm_term[i], M, graph_term[i], temp1, temp2, Q, V)); } - // PHASE 2: NON-TERMINAL TRANSITIONS + // PHASE 2+3: NONTERMINAL TRANSITIONS AND CALLS GRB_TRY(GrB_Matrix_clear(M_nonterm)); - for (size_t i = 0; i < num_nonterm; ++i) - { - if (rsm_nonterm[i] == GrB_NULL) - continue; - - GrB_Index gnt_nvals = 0; - GRB_TRY(GrB_Matrix_nvals(&gnt_nvals, graph_nt[i])); - if (gnt_nvals == 0) - continue; - - LG_TRY(s_mxm_chain(M_nonterm, rsm_nonterm[i], M, graph_nt[i], temp1, temp2, Q, V)); - } - - // PHASE 3: CALL TRANSITIONS GRB_TRY(GrB_Matrix_clear(M_call)); for (size_t i = 0; i < num_nonterm; ++i) { - if (rsm_call[i] == GrB_NULL || rsm_nonterm[i] == GrB_NULL) - continue; - - // mask_call = rsm_call[i]^T * M -> |Q|*|V*V| - GRB_TRY(GrB_Matrix_clear(mask_call)); - GRB_TRY(GrB_mxm(mask_call, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, rsm_call[i], - M, GrB_DESC_T0)); - - GrB_Index mc_nvals = 0; - GRB_TRY(GrB_Matrix_nvals(&mc_nvals, mask_call)); - if (mc_nvals == 0) - continue; - - // entry_vec = column-OR of mask_call reshaped to |Q*V|*|V| - GRB_TRY(GrB_Matrix_clear(temp1)); - GRB_TRY(GrB_eWiseAdd(temp1, GrB_NULL, GrB_NULL, GrB_LOR, temp1, mask_call, GrB_NULL)); - GRB_TRY(GxB_Matrix_reshape(temp1, false, Q * V, V, GrB_NULL)); - - GRB_TRY(GrB_Vector_clear(entry_vec)); - // column OR = row OR of transpose - GRB_TRY( - GrB_reduce(entry_vec, GrB_NULL, GrB_NULL, GrB_LOR_MONOID_BOOL, temp1, GrB_DESC_T0)); - - GRB_TRY(GxB_Matrix_reshape(temp1, false, Q, VV, GrB_NULL)); - - GrB_Index ev_nvals = 0; - GRB_TRY(GrB_Vector_nvals(&ev_nvals, entry_vec)); - if (ev_nvals == 0) + if (rsm_nonterm[i] == GrB_NULL) continue; - // Build diag(entry_vec) and flatten to 1*|V*V| - GRB_TRY(GrB_Matrix_free(&diag_entry)); - diag_entry = GrB_NULL; - GRB_TRY(GrB_Matrix_diag(&diag_entry, entry_vec, 0)); // |V|*|V| - GRB_TRY(GxB_Matrix_reshape(diag_entry, false, 1, VV, GrB_NULL)); - - GRB_TRY(GrB_Matrix_clear(outer_result)); - // |Q|*1 * 1*|V*V| -> |Q|*|V*V| - GRB_TRY(GrB_mxm(outer_result, GrB_NULL, GrB_NULL, GxB_LOR_LAND_BOOL, - (GrB_Matrix)rsm_start[i], diag_entry, GrB_NULL)); + // temp1 = reshape(R_A^T * M): |Q|*|VV| -> |QV|*|V| + LG_TRY(s_shift(temp1, rsm_nonterm[i], M, Q, V)); - // M_call |= outer_result - GRB_TRY( - GrB_eWiseAdd(M_call, GrB_NULL, GrB_NULL, GrB_LOR, M_call, outer_result, GrB_NULL)); + GrB_Index x_nvals = 0; + GRB_TRY(GrB_Matrix_nvals(&x_nvals, temp1)); + if (x_nvals > 0) + { + // CALL: entry_vec = column-OR of X_A + // (same set of vertices as the column-OR of R_call^T * M) + GRB_TRY(GrB_Vector_clear(entry_vec)); + GRB_TRY(GrB_reduce(entry_vec, GrB_NULL, GrB_NULL, GrB_LOR_MONOID_BOOL, temp1, + GrB_DESC_T0)); + + // Build diag(entry_vec) and flatten to 1*|V*V| + GRB_TRY(GrB_Matrix_free(&diag_entry)); + diag_entry = GrB_NULL; + GRB_TRY(GrB_Matrix_diag(&diag_entry, entry_vec, 0)); // |V|*|V| + GRB_TRY(GxB_Matrix_reshape(diag_entry, false, 1, VV, GrB_NULL)); + + GRB_TRY(GrB_Matrix_clear(outer_result)); + // |Q|*1 * 1*|V*V| -> |Q|*|V*V| + GRB_TRY(GrB_mxm(outer_result, GrB_NULL, GrB_NULL, GxB_LOR_LAND_BOOL, + (GrB_Matrix)rsm_start[i], diag_entry, GrB_NULL)); + + // M_call |= outer_result + GRB_TRY(GrB_eWiseAdd(M_call, GrB_NULL, GrB_NULL, GrB_LOR, M_call, outer_result, + GrB_NULL)); + + // NON-TERMINAL TRANSITIONS through already known summary edges + GrB_Index gnt_nvals = 0; + GRB_TRY(GrB_Matrix_nvals(&gnt_nvals, graph_nt[i])); + if (gnt_nvals > 0) + LG_TRY(s_mul_acc(M_nonterm, temp1, graph_nt[i], temp2, Q, V)); + } + LG_TRY(s_unshift(temp1, Q, V)); } // PHASE 4: RETURN TRANSITIONS From 63c4bd05d6f5a7705b51e6cc13f1ea3c85512206 Mon Sep 17 00:00:00 2001 From: Sterkachov Date: Tue, 20 Oct 2026 17:34:23 +0300 Subject: [PATCH 23/25] perf(CFPQ_RSM): maintain return-transition prefix incrementally Accumulate W = R_A^T * P per nonterminal instead of recomputing it in Phase 4. --- experimental/algorithm/LAGraph_CFPQ_RSM.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/experimental/algorithm/LAGraph_CFPQ_RSM.c b/experimental/algorithm/LAGraph_CFPQ_RSM.c index a07ee5ba9c..cc4ccb62d2 100644 --- a/experimental/algorithm/LAGraph_CFPQ_RSM.c +++ b/experimental/algorithm/LAGraph_CFPQ_RSM.c @@ -74,7 +74,7 @@ static GrB_Info s_unshift(GrB_Matrix temp1, GrB_Index Q, GrB_Index V) } static GrB_Info s_mul_acc(GrB_Matrix C, // output accumulator |Q|*|VV| - GrB_Matrix temp1, // shifted frontier |QV|*|V| + GrB_Matrix A, // shifted frontier |QV|*|V| GrB_Matrix B, // graph matrix |V|*|V| GrB_Matrix temp2, // scratch |Q|*|VV| GrB_Index Q, GrB_Index V) @@ -83,7 +83,7 @@ static GrB_Info s_mul_acc(GrB_Matrix C, // output accumulator |Q|*|VV| H_TRY(GxB_Matrix_reshape(temp2, false, Q*V, V, GrB_NULL)); // scratch = Xr * B -> |Q*V|*|V| - H_TRY(GrB_mxm(temp2, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, temp1, B, GrB_NULL)); + H_TRY(GrB_mxm(temp2, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, A, B, GrB_NULL)); // reshape(scratch): |Q*V|*|V| -> |Q|*|V*V| H_TRY(GxB_Matrix_reshape(temp2, false, Q, V * V, GrB_NULL)); @@ -154,6 +154,7 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GrB_Vector source_mask = GrB_NULL; GrB_Matrix *graph_nt = NULL; + GrB_Matrix *W = NULL; // W[i] = (rsm_nonterm[i])^T * P, |QV|*|V| size GrB_Vector *rsm_start = NULL; // rsm_start[S][i] = true, iff start_states[S] = i LG_ASSERT(reachable != NULL, GrB_NULL_POINTER); @@ -168,11 +169,13 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr // allocate per-nonterminal arrays LG_TRY(LAGraph_Malloc((void **)&graph_nt, rsm->nonterminal_count, sizeof(GrB_Matrix), msg)); + LG_TRY(LAGraph_Malloc((void **)&W, rsm->nonterminal_count, sizeof(GrB_Matrix), msg)); LG_TRY(LAGraph_Malloc((void **)&rsm_start, rsm->nonterminal_count, sizeof(GrB_Vector), msg)); for (size_t i = 0; i < num_nonterm; ++i) { graph_nt[i] = GrB_NULL; + W[i] = GrB_NULL; rsm_start[i] = GrB_NULL; } @@ -181,6 +184,8 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GRB_TRY(GrB_Matrix_new(&graph_nt[i], GrB_BOOL, V, V)); GRB_TRY(GrB_Vector_new(&rsm_start[i], GrB_BOOL, Q)); GrB_Vector_setElement_BOOL(rsm_start[i], true, start_states[i]); + if (rsm_nonterm[i] != GrB_NULL) + GRB_TRY(GrB_Matrix_new(&W[i], GrB_BOOL, Q*V, V)); } // allocate working matrices @@ -292,6 +297,9 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GRB_TRY(GrB_Matrix_nvals(&gnt_nvals, graph_nt[i])); if (gnt_nvals > 0) LG_TRY(s_mul_acc(M_nonterm, temp1, graph_nt[i], temp2, Q, V)); + + // RETURN bookkeeping: W |= X_A (P grows by exactly M, so W = R_A^T * P holds) + GRB_TRY(GrB_eWiseAdd(W[i], GrB_NULL, GrB_NULL, GrB_LOR, W[i], temp1, GrB_NULL)); } LG_TRY(s_unshift(temp1, Q, V)); } @@ -327,7 +335,11 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GRB_TRY(GrB_eWiseAdd(graph_nt[i], GrB_NULL, GrB_NULL, GrB_LOR, graph_nt[i], new_edges, GrB_NULL)); - LG_TRY(s_mxm_chain(M_return, rsm_nonterm[i], P, new_edges, temp1, temp2, Q, V)); + // M_return |= W * dG, where W = R_A^T * P + GrB_Index w_nvals = 0; + GRB_TRY(GrB_Matrix_nvals(&w_nvals, W[i])); + if (w_nvals > 0) + LG_TRY(s_mul_acc(M_return, W[i], new_edges, temp2, Q, V)); GRB_TRY(GxB_Matrix_reshape(new_edges, false, 1, VV, GrB_NULL)); } From 0cf18a8f46d7227a41b4a9e84d820e91b371d3e2 Mon Sep 17 00:00:00 2001 From: Sterkachov Date: Tue, 20 Oct 2026 19:00:26 +0300 Subject: [PATCH 24/25] perf(CFPQ_RSM): accumulate all per-iteration products in one shifted matrix Replace M_term/M_nonterm/M_return with a single |Q*V|*|V| accumulator, reshaped once per iteration instead of per product. --- experimental/algorithm/LAGraph_CFPQ_RSM.c | 85 +++++++++-------------- 1 file changed, 32 insertions(+), 53 deletions(-) diff --git a/experimental/algorithm/LAGraph_CFPQ_RSM.c b/experimental/algorithm/LAGraph_CFPQ_RSM.c index cc4ccb62d2..c803bd56c1 100644 --- a/experimental/algorithm/LAGraph_CFPQ_RSM.c +++ b/experimental/algorithm/LAGraph_CFPQ_RSM.c @@ -11,10 +11,8 @@ { \ GrB_free(&M); \ GrB_free(&P); \ - GrB_free(&M_term); \ - GrB_free(&M_nonterm); \ + GrB_free(&acc); \ GrB_free(&M_call); \ - GrB_free(&M_return); \ GrB_free(&M_new); \ GrB_free(&temp1); \ GrB_free(&temp2); \ @@ -53,57 +51,45 @@ // A is an RSM matrix |Q|x|Q|, M is |Q|x|VV| // temp1 is |Q|x|VV| scratch on entry and holds (|QV|x|V|) on exit -static GrB_Info s_shift(GrB_Matrix temp1, GrB_Matrix A, GrB_Matrix M, GrB_Index Q, GrB_Index V) +static GrB_Info s_shift(GrB_Matrix T, GrB_Matrix A, GrB_Matrix M, GrB_Index Q, GrB_Index V) { - H_TRY(GrB_Matrix_clear(temp1)); + H_TRY(GrB_Matrix_clear(T)); // temp1 = A^T * M == |Q|x|VV| - H_TRY(GrB_mxm(temp1, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, A, M, GrB_DESC_T0)); + H_TRY(GrB_mxm(T, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, A, M, GrB_DESC_T0)); // reshape(scratch): |Q|x|VV| -> |QV|x|V| - H_TRY(GxB_Matrix_reshape(temp1, false, Q*V, V, GrB_NULL)); + H_TRY(GxB_Matrix_reshape(T, false, Q*V, V, GrB_NULL)); return GrB_SUCCESS; } // restore the scratch matrix to |Q|*|V*V|; its content is dead, so clear it first -static GrB_Info s_unshift(GrB_Matrix temp1, GrB_Index Q, GrB_Index V) +static GrB_Info s_unshift(GrB_Matrix T, GrB_Index Q, GrB_Index V) { - H_TRY(GrB_Matrix_clear(temp1)); - H_TRY(GxB_Matrix_reshape(temp1, false, Q, V*V, GrB_NULL)); + H_TRY(GrB_Matrix_clear(T)); + H_TRY(GxB_Matrix_reshape(T, false, Q, V*V, GrB_NULL)); return GrB_SUCCESS; } -static GrB_Info s_mul_acc(GrB_Matrix C, // output accumulator |Q|*|VV| - GrB_Matrix A, // shifted frontier |QV|*|V| - GrB_Matrix B, // graph matrix |V|*|V| - GrB_Matrix temp2, // scratch |Q|*|VV| - GrB_Index Q, GrB_Index V) +static GrB_Info s_mul_acc(GrB_Matrix C, GrB_Matrix A, GrB_Matrix B, GrB_Matrix T) { - H_TRY(GrB_Matrix_clear(temp2)); - H_TRY(GxB_Matrix_reshape(temp2, false, Q*V, V, GrB_NULL)); - - // scratch = Xr * B -> |Q*V|*|V| - H_TRY(GrB_mxm(temp2, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, A, B, GrB_NULL)); - - // reshape(scratch): |Q*V|*|V| -> |Q|*|V*V| - H_TRY(GxB_Matrix_reshape(temp2, false, Q, V * V, GrB_NULL)); - - // C |= temp2 - H_TRY(GrB_eWiseAdd(C, GrB_NULL, GrB_NULL, GrB_LOR, C, temp2, GrB_NULL)); + H_TRY(GrB_Matrix_clear(T)); + H_TRY(GrB_mxm(T, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, A, B, GrB_NULL)); + H_TRY(GrB_eWiseAdd(C, GrB_NULL, GrB_NULL, GrB_LOR, C, T, GrB_NULL)); return GrB_SUCCESS; } -static GrB_Info s_mxm_chain(GrB_Matrix C, // output accumulator |Q|*|VV| - GrB_Matrix A, // RSM matrix |Q|*|Q| - GrB_Matrix M, // frontier |Q|*|VV| - GrB_Matrix B, // graph matrix |V|*|V| - GrB_Matrix temp1, // scratch |Q|*|VV| - GrB_Matrix temp2, // scratch |Q|*|VV| +static GrB_Info s_mxm_chain(GrB_Matrix C, // output accumulator |Q|*|VV| + GrB_Matrix A, // RSM matrix |Q|*|Q| + GrB_Matrix M, // frontier |Q|*|VV| + GrB_Matrix B, // graph matrix |V|*|V| + GrB_Matrix T1, // scratch |Q|*|VV| + GrB_Matrix T2, // scratch |QV|*|V| GrB_Index Q, GrB_Index V) { - H_TRY(s_shift(temp1, A, M, Q, V)); - H_TRY(s_mul_acc(C, temp1, B, temp2, Q, V)); - H_TRY(s_unshift(temp1, Q, V)); + H_TRY(s_shift(T1, A, M, Q, V)); + H_TRY(s_mul_acc(C, T1, B, T2)); + H_TRY(s_unshift(T1, Q, V)); return GrB_SUCCESS; } @@ -139,10 +125,8 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr // working matrices / vectors GrB_Matrix M = GrB_NULL; GrB_Matrix P = GrB_NULL; - GrB_Matrix M_term = GrB_NULL; - GrB_Matrix M_nonterm = GrB_NULL; + GrB_Matrix acc = GrB_NULL; GrB_Matrix M_call = GrB_NULL; - GrB_Matrix M_return = GrB_NULL; GrB_Matrix M_new = GrB_NULL; GrB_Matrix temp1 = GrB_NULL; GrB_Matrix temp2 = GrB_NULL; @@ -191,13 +175,11 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr // allocate working matrices GRB_TRY(GrB_Matrix_new(&M, GrB_BOOL, Q, VV)); GRB_TRY(GrB_Matrix_new(&P, GrB_BOOL, Q, VV)); - GRB_TRY(GrB_Matrix_new(&M_term, GrB_BOOL, Q, VV)); - GRB_TRY(GrB_Matrix_new(&M_nonterm, GrB_BOOL, Q, VV)); + GRB_TRY(GrB_Matrix_new(&acc, GrB_BOOL, Q*V, V)); GRB_TRY(GrB_Matrix_new(&M_call, GrB_BOOL, Q, VV)); - GRB_TRY(GrB_Matrix_new(&M_return, GrB_BOOL, Q, VV)); GRB_TRY(GrB_Matrix_new(&M_new, GrB_BOOL, Q, VV)); GRB_TRY(GrB_Matrix_new(&temp1, GrB_BOOL, Q, VV)); - GRB_TRY(GrB_Matrix_new(&temp2, GrB_BOOL, Q, VV)); + GRB_TRY(GrB_Matrix_new(&temp2, GrB_BOOL, Q*V, V)); GRB_TRY(GrB_Matrix_new(&frame, GrB_BOOL, Q, VV)); GRB_TRY(GrB_Matrix_new(&new_edges, GrB_BOOL, 1, VV)); // there is no need to create matrix for diag_entry @@ -248,16 +230,14 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr while (m_nvals > 0) { // PHASE 1: TERMINAL TRANSITIONS - GRB_TRY(GrB_Matrix_clear(M_term)); for (size_t i = 0; i < num_term; ++i) { if (rsm_term[i] == GrB_NULL || graph_term[i] == GrB_NULL) continue; - LG_TRY(s_mxm_chain(M_term, rsm_term[i], M, graph_term[i], temp1, temp2, Q, V)); + LG_TRY(s_mxm_chain(acc, rsm_term[i], M, graph_term[i], temp1, temp2, Q, V)); } // PHASE 2+3: NONTERMINAL TRANSITIONS AND CALLS - GRB_TRY(GrB_Matrix_clear(M_nonterm)); GRB_TRY(GrB_Matrix_clear(M_call)); for (size_t i = 0; i < num_nonterm; ++i) { @@ -296,7 +276,7 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GrB_Index gnt_nvals = 0; GRB_TRY(GrB_Matrix_nvals(&gnt_nvals, graph_nt[i])); if (gnt_nvals > 0) - LG_TRY(s_mul_acc(M_nonterm, temp1, graph_nt[i], temp2, Q, V)); + LG_TRY(s_mul_acc(acc, temp1, graph_nt[i], temp2)); // RETURN bookkeeping: W |= X_A (P grows by exactly M, so W = R_A^T * P holds) GRB_TRY(GrB_eWiseAdd(W[i], GrB_NULL, GrB_NULL, GrB_LOR, W[i], temp1, GrB_NULL)); @@ -305,7 +285,6 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr } // PHASE 4: RETURN TRANSITIONS - GRB_TRY(GrB_Matrix_clear(M_return)); for (size_t i = 0; i < num_nonterm; ++i) { if (final_states[i] == GrB_NULL || rsm_nonterm[i] == GrB_NULL) @@ -335,23 +314,23 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GRB_TRY(GrB_eWiseAdd(graph_nt[i], GrB_NULL, GrB_NULL, GrB_LOR, graph_nt[i], new_edges, GrB_NULL)); - // M_return |= W * dG, where W = R_A^T * P + // acc |= W * dG, where W = R_A^T * P GrB_Index w_nvals = 0; GRB_TRY(GrB_Matrix_nvals(&w_nvals, W[i])); if (w_nvals > 0) - LG_TRY(s_mul_acc(M_return, W[i], new_edges, temp2, Q, V)); + LG_TRY(s_mul_acc(acc, W[i], new_edges, temp2)); GRB_TRY(GxB_Matrix_reshape(new_edges, false, 1, VV, GrB_NULL)); } // PHASE 5: MERGE & MARK VISITED // GrB_assign has big overhead - // M_new = (M_term | M_nonterm | M_call | M_return) & ~P + // M_new = (reshape(acc) | M_call) & ~P GRB_TRY(GrB_Matrix_clear(M_new)); - GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_NULL, GrB_LOR, M_new, M_term, GrB_NULL)); - GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_NULL, GrB_LOR, M_new, M_nonterm, GrB_NULL)); + M_new = GrB_NULL; + GRB_TRY(GxB_Matrix_reshapeDup(&M_new, acc, false, Q, VV, GrB_NULL)); + GRB_TRY(GrB_Matrix_clear(acc)); GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_NULL, GrB_LOR, M_new, M_call, GrB_NULL)); - GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_NULL, GrB_LOR, M_new, M_return, GrB_NULL)); // M_new &= ~P GRB_TRY(GrB_assign(M_new, P, GrB_NULL, M_new, GrB_ALL, Q, GrB_ALL, VV, GrB_DESC_RSC)); From 4c5cd1a1062081b62b8962c225c344a3821db401 Mon Sep 17 00:00:00 2001 From: Sterkachov Date: Fri, 23 Oct 2026 02:26:20 +0300 Subject: [PATCH 25/25] refactor(CFPQ RSM): simplify implementation and reduce scratch matrices --- experimental/algorithm/LAGraph_CFPQ_RSM.c | 303 +++++++++------------- 1 file changed, 116 insertions(+), 187 deletions(-) diff --git a/experimental/algorithm/LAGraph_CFPQ_RSM.c b/experimental/algorithm/LAGraph_CFPQ_RSM.c index c803bd56c1..d11cbb4a09 100644 --- a/experimental/algorithm/LAGraph_CFPQ_RSM.c +++ b/experimental/algorithm/LAGraph_CFPQ_RSM.c @@ -1,11 +1,12 @@ -#include "LAGraph.h" -#include "LG_internal.h" -#include - -#include -#include -#include -#include +#define FREE_MATRIX_ARRAY(array) \ + { \ + if ((array) != NULL) \ + { \ + for (size_t _k = 0; _k < num_nonterm; ++_k) \ + GrB_free(&(array)[_k]); \ + LAGraph_Free((void **)&(array), NULL); \ + } \ + } #define LG_FREE_WORK \ { \ @@ -14,33 +15,30 @@ GrB_free(&acc); \ GrB_free(&M_call); \ GrB_free(&M_new); \ - GrB_free(&temp1); \ - GrB_free(&temp2); \ - GrB_free(&frame); \ + GrB_free(&scratch); \ GrB_free(&new_edges); \ GrB_free(&diag_entry); \ - GrB_free(&outer_result); \ GrB_free(&entry_vec); \ GrB_free(&source_mask); \ - if (graph_nt != NULL) \ - { \ - for (size_t _i = 0; _i < num_nonterm; ++_i) \ - GrB_free(&graph_nt[_i]); \ - LAGraph_Free((void **)&graph_nt, msg); \ - } \ - if (rsm_start != NULL) \ - { \ - for (size_t _i = 0; _i < num_nonterm; ++_i) \ - GrB_free(&rsm_start[_i]); \ - LAGraph_Free((void **)&rsm_start, msg); \ - } \ + FREE_MATRIX_ARRAY(graph_nt); \ + FREE_MATRIX_ARRAY(W); \ + FREE_MATRIX_ARRAY(rsm_start); \ } + #define LG_FREE_ALL \ { \ LG_FREE_WORK; \ GrB_free(reachable); \ } +#include "LAGraph.h" +#include "LG_internal.h" +#include + +#include +#include +#include +#include #define H_TRY(expr) \ { \ GrB_Info _hi = (expr); \ @@ -49,61 +47,56 @@ } -// A is an RSM matrix |Q|x|Q|, M is |Q|x|VV| -// temp1 is |Q|x|VV| scratch on entry and holds (|QV|x|V|) on exit +// T = reshape(A' * M), QV x V. A is Q x Q, M is Q x VV. +// T must be Q x VV on entry; its content is overwritten. static GrB_Info s_shift(GrB_Matrix T, GrB_Matrix A, GrB_Matrix M, GrB_Index Q, GrB_Index V) { - H_TRY(GrB_Matrix_clear(T)); - - // temp1 = A^T * M == |Q|x|VV| H_TRY(GrB_mxm(T, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, A, M, GrB_DESC_T0)); - - // reshape(scratch): |Q|x|VV| -> |QV|x|V| - H_TRY(GxB_Matrix_reshape(T, false, Q*V, V, GrB_NULL)); + H_TRY(GxB_Matrix_reshape(T, false, Q * V, V, GrB_NULL)); return GrB_SUCCESS; } -// restore the scratch matrix to |Q|*|V*V|; its content is dead, so clear it first +// Restore T to an empty Q x VV matrix; its content is dead, so clear it first static GrB_Info s_unshift(GrB_Matrix T, GrB_Index Q, GrB_Index V) { H_TRY(GrB_Matrix_clear(T)); - H_TRY(GxB_Matrix_reshape(T, false, Q, V*V, GrB_NULL)); + H_TRY(GxB_Matrix_reshape(T, false, Q, V * V, GrB_NULL)); return GrB_SUCCESS; } -static GrB_Info s_mul_acc(GrB_Matrix C, GrB_Matrix A, GrB_Matrix B, GrB_Matrix T) +// C |= A * B +static GrB_Info s_mul_acc(GrB_Matrix C, GrB_Matrix A, GrB_Matrix B) { - H_TRY(GrB_Matrix_clear(T)); - H_TRY(GrB_mxm(T, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, A, B, GrB_NULL)); - H_TRY(GrB_eWiseAdd(C, GrB_NULL, GrB_NULL, GrB_LOR, C, T, GrB_NULL)); + H_TRY(GrB_mxm(C, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, A, B, GrB_NULL)); return GrB_SUCCESS; } -static GrB_Info s_mxm_chain(GrB_Matrix C, // output accumulator |Q|*|VV| - GrB_Matrix A, // RSM matrix |Q|*|Q| - GrB_Matrix M, // frontier |Q|*|VV| - GrB_Matrix B, // graph matrix |V|*|V| - GrB_Matrix T1, // scratch |Q|*|VV| - GrB_Matrix T2, // scratch |QV|*|V| +// C |= reshape(A^T * M) * B +static GrB_Info s_mxm_chain(GrB_Matrix C, // output accumulator QV x V + GrB_Matrix A, // RSM matrix Q x Q + GrB_Matrix M, // frontier Q x VV + GrB_Matrix B, // graph matrix V x V + GrB_Matrix T, // scratch Q x VV GrB_Index Q, GrB_Index V) { - H_TRY(s_shift(T1, A, M, Q, V)); - H_TRY(s_mul_acc(C, T1, B, T2)); - H_TRY(s_unshift(T1, Q, V)); + H_TRY(s_shift(T, A, M, Q, V)); + H_TRY(s_mul_acc(C, T, B)); + H_TRY(s_unshift(T, Q, V)); return GrB_SUCCESS; } #undef H_TRY + typedef struct { GrB_Index state_count; // Q - GrB_Index terminal_count; // num_terminals - GrB_Index nonterminal_count; // num_nonterminals - GrB_Index start_nonterminal; // start_nonterm - GrB_Matrix *terminal_matrices; // rsm_term - GrB_Matrix *nonterminal_matrices; // rsm_nonterm - GrB_Index *start_states; // rsm_start - GrB_Vector *final_states; // rsm_final + GrB_Index terminal_count; + GrB_Index nonterminal_count; + GrB_Index start_nonterminal; + GrB_Matrix *terminal_matrices; // [t]: Q x Q transitions labeled t, or NULL if none + GrB_Matrix *nonterminal_matrices; // [i]: Q x Q transitions labeled i, or NULL if none + GrB_Index *start_states; // [i]: start state of the box of nonterminal i + GrB_Vector *final_states; // [i]: length-Q indicator of the final states of box i } RSM; int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *graph_term, @@ -111,51 +104,49 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr { LG_CLEAR_MSG; - // shortcuts - GrB_Index VV = V * V; - GrB_Index Q = rsm->state_count; - GrB_Index num_term = rsm->terminal_count; - GrB_Index num_nonterm = rsm->nonterminal_count; - GrB_Index start_nonterm = rsm->start_nonterminal; - GrB_Matrix *rsm_term = rsm->terminal_matrices; - GrB_Matrix *rsm_nonterm = rsm->nonterminal_matrices; - GrB_Index *start_states = rsm->start_states; - GrB_Vector *final_states = rsm->final_states; - // working matrices / vectors GrB_Matrix M = GrB_NULL; GrB_Matrix P = GrB_NULL; GrB_Matrix acc = GrB_NULL; GrB_Matrix M_call = GrB_NULL; GrB_Matrix M_new = GrB_NULL; - GrB_Matrix temp1 = GrB_NULL; - GrB_Matrix temp2 = GrB_NULL; - GrB_Matrix frame = GrB_NULL; + GrB_Matrix scratch = GrB_NULL; GrB_Matrix new_edges = GrB_NULL; GrB_Matrix diag_entry = GrB_NULL; - GrB_Matrix outer_result = GrB_NULL; GrB_Vector entry_vec = GrB_NULL; GrB_Vector source_mask = GrB_NULL; GrB_Matrix *graph_nt = NULL; - GrB_Matrix *W = NULL; // W[i] = (rsm_nonterm[i])^T * P, |QV|*|V| size - GrB_Vector *rsm_start = NULL; // rsm_start[S][i] = true, iff start_states[S] = i + GrB_Matrix *W = NULL; // QV x V, W[i] = (rsm_nonterm[i])^T * P + GrB_Matrix *rsm_start = NULL; // Q x 1, rsm_start[S][i] = true, iff start_states[S] = i + GrB_Index num_nonterm = 0; LG_ASSERT(reachable != NULL, GrB_NULL_POINTER); + *reachable = GrB_NULL; + LG_ASSERT(rsm != NULL, GrB_NULL_POINTER); + + // shortcuts + GrB_Index Q = rsm->state_count; + GrB_Index num_term = rsm->terminal_count; + num_nonterm = rsm->nonterminal_count; + GrB_Index start_nonterm = rsm->start_nonterminal; + GrB_Matrix *rsm_term = rsm->terminal_matrices; + GrB_Matrix *rsm_nonterm = rsm->nonterminal_matrices; + GrB_Index *start_states = rsm->start_states; + GrB_Vector *final_states = rsm->final_states; + LG_ASSERT(rsm_term != NULL, GrB_NULL_POINTER); LG_ASSERT(rsm_nonterm != NULL, GrB_NULL_POINTER); LG_ASSERT(start_states != NULL, GrB_NULL_POINTER); LG_ASSERT(final_states != NULL, GrB_NULL_POINTER); + LG_ASSERT(graph_term != NULL || num_term == 0, GrB_NULL_POINTER); + LG_ASSERT(sources != NULL || num_sources == 0, GrB_NULL_POINTER); LG_ASSERT(start_nonterm < num_nonterm, GrB_INVALID_VALUE); LG_ASSERT(num_sources <= V, GrB_INVALID_VALUE); - *reachable = GrB_NULL; - - // allocate per-nonterminal arrays - LG_TRY(LAGraph_Malloc((void **)&graph_nt, rsm->nonterminal_count, sizeof(GrB_Matrix), msg)); - LG_TRY(LAGraph_Malloc((void **)&W, rsm->nonterminal_count, sizeof(GrB_Matrix), msg)); - LG_TRY(LAGraph_Malloc((void **)&rsm_start, rsm->nonterminal_count, sizeof(GrB_Vector), msg)); - + LG_TRY(LAGraph_Malloc((void **)&graph_nt, num_nonterm, sizeof(GrB_Matrix), msg)); + LG_TRY(LAGraph_Malloc((void **)&W, num_nonterm, sizeof(GrB_Matrix), msg)); + LG_TRY(LAGraph_Malloc((void **)&rsm_start, num_nonterm, sizeof(GrB_Matrix), msg)); for (size_t i = 0; i < num_nonterm; ++i) { graph_nt[i] = GrB_NULL; @@ -166,132 +157,89 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr for (size_t i = 0; i < num_nonterm; ++i) { GRB_TRY(GrB_Matrix_new(&graph_nt[i], GrB_BOOL, V, V)); - GRB_TRY(GrB_Vector_new(&rsm_start[i], GrB_BOOL, Q)); - GrB_Vector_setElement_BOOL(rsm_start[i], true, start_states[i]); - if (rsm_nonterm[i] != GrB_NULL) - GRB_TRY(GrB_Matrix_new(&W[i], GrB_BOOL, Q*V, V)); + if (rsm_nonterm[i] == GrB_NULL) + continue; + + GRB_TRY(GrB_Matrix_new(&W[i], GrB_BOOL, Q * V, V)); + GRB_TRY(GrB_Matrix_new(&rsm_start[i], GrB_BOOL, Q, 1)); + GRB_TRY(GrB_Matrix_setElement_BOOL(rsm_start[i], true, start_states[i], 0)); } // allocate working matrices - GRB_TRY(GrB_Matrix_new(&M, GrB_BOOL, Q, VV)); - GRB_TRY(GrB_Matrix_new(&P, GrB_BOOL, Q, VV)); + GRB_TRY(GrB_Matrix_new(&M, GrB_BOOL, Q, V * V)); GRB_TRY(GrB_Matrix_new(&acc, GrB_BOOL, Q*V, V)); - GRB_TRY(GrB_Matrix_new(&M_call, GrB_BOOL, Q, VV)); - GRB_TRY(GrB_Matrix_new(&M_new, GrB_BOOL, Q, VV)); - GRB_TRY(GrB_Matrix_new(&temp1, GrB_BOOL, Q, VV)); - GRB_TRY(GrB_Matrix_new(&temp2, GrB_BOOL, Q*V, V)); - GRB_TRY(GrB_Matrix_new(&frame, GrB_BOOL, Q, VV)); - GRB_TRY(GrB_Matrix_new(&new_edges, GrB_BOOL, 1, VV)); + GRB_TRY(GrB_Matrix_new(&M_call, GrB_BOOL, Q, V * V)); + GRB_TRY(GrB_Matrix_new(&scratch, GrB_BOOL, Q, V * V)); + GRB_TRY(GrB_Matrix_new(&new_edges, GrB_BOOL, 1, V * V)); // there is no need to create matrix for diag_entry - GRB_TRY(GrB_Matrix_new(&outer_result, GrB_BOOL, Q, VV)); GRB_TRY(GrB_Vector_new(&entry_vec, GrB_BOOL, V)); GRB_TRY(GrB_Vector_new(&source_mask, GrB_BOOL, V)); - GRB_TRY(GrB_Vector_new(reachable, GrB_BOOL, V)); - // seed initial frontier M and visited P + // each source s starts a call to the start nonterminal at (s, s) + GrB_Index start_state = start_states[start_nonterm]; + for (size_t i = 0; i < num_sources; ++i) { - - GrB_Index nstart = 0; - GRB_TRY(GrB_Vector_nvals(&nstart, rsm_start[start_nonterm])); - - GrB_Index *seed_states = NULL; - bool *seed_vals = NULL; - LG_TRY(LAGraph_Malloc((void **)&seed_states, nstart, sizeof(GrB_Index), msg)); - LG_TRY(LAGraph_Malloc((void **)&seed_vals, nstart, sizeof(bool), msg)); - - GrB_Info info = GrB_Vector_extractTuples_BOOL(seed_states, seed_vals, &nstart, - rsm_start[start_nonterm]); - LAGraph_Free((void **)&seed_vals, GrB_NULL); - - if (info != GrB_SUCCESS) - { - LAGraph_Free((void **)&seed_states, GrB_NULL); - LG_FREE_ALL; - return info; - } - - for (size_t i = 0; i < num_sources; ++i) - { - GrB_Index seed_col = sources[i] * V + sources[i]; - for (GrB_Index k = 0; k < nstart; ++k) - { - GrB_Index q = seed_states[k]; - GRB_TRY(GrB_Matrix_setElement_BOOL(M, true, q, seed_col)); - GRB_TRY(GrB_Matrix_setElement_BOOL(P, true, q, seed_col)); - } - } - - LAGraph_Free((void **)&seed_states, GrB_NULL); + GrB_Index s = sources[i]; + GRB_TRY(GrB_Matrix_setElement_BOOL(M, true, start_state, s * V + s)); + GRB_TRY(GrB_Vector_setElement_BOOL(source_mask, true, s)); } + GRB_TRY(GrB_Matrix_dup(&P, M)); - // main fixed-point loop GrB_Index m_nvals = 0; GRB_TRY(GrB_Matrix_nvals(&m_nvals, M)); while (m_nvals > 0) { - // PHASE 1: TERMINAL TRANSITIONS + // Phase 1: terminal transitions for (size_t i = 0; i < num_term; ++i) { if (rsm_term[i] == GrB_NULL || graph_term[i] == GrB_NULL) continue; - LG_TRY(s_mxm_chain(acc, rsm_term[i], M, graph_term[i], temp1, temp2, Q, V)); + LG_TRY(s_mxm_chain(acc, rsm_term[i], M, graph_term[i], scratch, Q, V)); } - // PHASE 2+3: NONTERMINAL TRANSITIONS AND CALLS + // Phases 2+3: nonterminal transitions and calls GRB_TRY(GrB_Matrix_clear(M_call)); for (size_t i = 0; i < num_nonterm; ++i) { if (rsm_nonterm[i] == GrB_NULL) continue; - // temp1 = reshape(R_A^T * M): |Q|*|VV| -> |QV|*|V| - LG_TRY(s_shift(temp1, rsm_nonterm[i], M, Q, V)); + // scratch = reshape(R_A^T * M), QV x V + LG_TRY(s_shift(scratch, rsm_nonterm[i], M, Q, V)); GrB_Index x_nvals = 0; - GRB_TRY(GrB_Matrix_nvals(&x_nvals, temp1)); + GRB_TRY(GrB_Matrix_nvals(&x_nvals, scratch)); if (x_nvals > 0) { - // CALL: entry_vec = column-OR of X_A - // (same set of vertices as the column-OR of R_call^T * M) - GRB_TRY(GrB_Vector_clear(entry_vec)); - GRB_TRY(GrB_reduce(entry_vec, GrB_NULL, GrB_NULL, GrB_LOR_MONOID_BOOL, temp1, + // call: every vertex v where X is nonzero enters nonterminal i + // at (v, v), in its start state + GRB_TRY(GrB_reduce(entry_vec, GrB_NULL, GrB_NULL, GrB_LOR_MONOID_BOOL, scratch, GrB_DESC_T0)); + GRB_TRY(GrB_Matrix_diag(&diag_entry, entry_vec, 0)); // V x V + GRB_TRY(GxB_Matrix_reshape(diag_entry, false, 1, V * V, GrB_NULL)); + GRB_TRY(GrB_mxm(M_call, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, + rsm_start[i], diag_entry, GrB_NULL)); + GRB_TRY(GrB_free(&diag_entry)); - // Build diag(entry_vec) and flatten to 1*|V*V| - GRB_TRY(GrB_Matrix_free(&diag_entry)); - diag_entry = GrB_NULL; - GRB_TRY(GrB_Matrix_diag(&diag_entry, entry_vec, 0)); // |V|*|V| - GRB_TRY(GxB_Matrix_reshape(diag_entry, false, 1, VV, GrB_NULL)); - - GRB_TRY(GrB_Matrix_clear(outer_result)); - // |Q|*1 * 1*|V*V| -> |Q|*|V*V| - GRB_TRY(GrB_mxm(outer_result, GrB_NULL, GrB_NULL, GxB_LOR_LAND_BOOL, - (GrB_Matrix)rsm_start[i], diag_entry, GrB_NULL)); - - // M_call |= outer_result - GRB_TRY(GrB_eWiseAdd(M_call, GrB_NULL, GrB_NULL, GrB_LOR, M_call, outer_result, - GrB_NULL)); - - // NON-TERMINAL TRANSITIONS through already known summary edges + // skip over nonterminal i using the summary edges GrB_Index gnt_nvals = 0; GRB_TRY(GrB_Matrix_nvals(&gnt_nvals, graph_nt[i])); if (gnt_nvals > 0) - LG_TRY(s_mul_acc(acc, temp1, graph_nt[i], temp2)); + LG_TRY(s_mul_acc(acc, scratch, graph_nt[i])); - // RETURN bookkeeping: W |= X_A (P grows by exactly M, so W = R_A^T * P holds) - GRB_TRY(GrB_eWiseAdd(W[i], GrB_NULL, GrB_NULL, GrB_LOR, W[i], temp1, GrB_NULL)); + // RETURN bookkeeping: W[i] == reshape(R_i' * P) + GRB_TRY(GrB_eWiseAdd(W[i], GrB_NULL, GrB_NULL, GrB_LOR, W[i], scratch, GrB_NULL)); } - LG_TRY(s_unshift(temp1, Q, V)); + LG_TRY(s_unshift(scratch, Q, V)); } - // PHASE 4: RETURN TRANSITIONS + // Phase 4: return transitions for (size_t i = 0; i < num_nonterm; ++i) { if (final_states[i] == GrB_NULL || rsm_nonterm[i] == GrB_NULL) continue; - // 1*|Q| * |Q|*|V*V| -> 1*|V*V| - GRB_TRY(GrB_Matrix_clear(new_edges)); + // 1 x Q * Q x VV -> 1 x VV GRB_TRY(GrB_mxm(new_edges, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, (GrB_Matrix)final_states[i], M, GrB_DESC_T0)); @@ -307,7 +255,7 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GRB_TRY(GrB_Matrix_nvals(&ne_vals, new_edges)); if (ne_vals == 0) { - GRB_TRY(GxB_Matrix_reshape(new_edges, false, 1, VV, GrB_NULL)); + GRB_TRY(GxB_Matrix_reshape(new_edges, false, 1, V*V, GrB_NULL)); continue; } @@ -318,24 +266,16 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GrB_Index w_nvals = 0; GRB_TRY(GrB_Matrix_nvals(&w_nvals, W[i])); if (w_nvals > 0) - LG_TRY(s_mul_acc(acc, W[i], new_edges, temp2)); + LG_TRY(s_mul_acc(acc, W[i], new_edges)); - GRB_TRY(GxB_Matrix_reshape(new_edges, false, 1, VV, GrB_NULL)); + GRB_TRY(GxB_Matrix_reshape(new_edges, false, 1, V * V, GrB_NULL)); } - // PHASE 5: MERGE & MARK VISITED - // GrB_assign has big overhead - // M_new = (reshape(acc) | M_call) & ~P - GRB_TRY(GrB_Matrix_clear(M_new)); - M_new = GrB_NULL; - GRB_TRY(GxB_Matrix_reshapeDup(&M_new, acc, false, Q, VV, GrB_NULL)); + // Phase 5: M_new = (reshape(acc) | M_call) \ P + GRB_TRY(GrB_free(&M_new)); + GRB_TRY(GxB_Matrix_reshapeDup(&M_new, acc, false, Q, V * V, GrB_NULL)); GRB_TRY(GrB_Matrix_clear(acc)); - GRB_TRY(GrB_eWiseAdd(M_new, GrB_NULL, GrB_NULL, GrB_LOR, M_new, M_call, GrB_NULL)); - - // M_new &= ~P - GRB_TRY(GrB_assign(M_new, P, GrB_NULL, M_new, GrB_ALL, Q, GrB_ALL, VV, GrB_DESC_RSC)); - - // P |= M_new + GRB_TRY(GrB_eWiseAdd(M_new, P, GrB_NULL, GrB_LOR, M_new, M_call, GrB_DESC_RSC)); GRB_TRY(GrB_eWiseAdd(P, GrB_NULL, GrB_NULL, GrB_LOR, P, M_new, GrB_NULL)); GrB_Matrix swap = M; @@ -345,19 +285,8 @@ int LAGraph_CFPQ_RSM(GrB_Vector *reachable, const RSM *rsm, const GrB_Matrix *gr GRB_TRY(GrB_Matrix_nvals(&m_nvals, M)); } - // Extract all reachable vertices - { - bool *vals = NULL; - LG_TRY(LAGraph_Malloc((void **)&vals, num_sources, sizeof(bool), msg)); - for (size_t i = 0; i < num_sources; ++i) - vals[i] = true; - - GrB_Info _bi = - GrB_Vector_build_BOOL(source_mask, sources, vals, num_sources, GrB_SECOND_BOOL); - LAGraph_Free((void **)&vals, GrB_NULL); - GRB_TRY(_bi); - } - + // reachable(w) = OR_s source_mask(s) & graph_nt[start](s, w) + GRB_TRY(GrB_Vector_new(reachable, GrB_BOOL, V)); GRB_TRY(GrB_mxv(*reachable, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, graph_nt[start_nonterm], source_mask, GrB_DESC_T0));