diff --git a/data/rpq_data/1_a.mtx b/data/rpq_data/1_a.mtx new file mode 100644 index 0000000000..8380171654 --- /dev/null +++ b/data/rpq_data/1_a.mtx @@ -0,0 +1,5 @@ +%%MatrixMarket matrix coordinate pattern general +%%GraphBLAS type bool +2 2 2 +1 2 +2 2 diff --git a/data/rpq_data/1_meta.txt b/data/rpq_data/1_meta.txt new file mode 100644 index 0000000000..1f25976c26 --- /dev/null +++ b/data/rpq_data/1_meta.txt @@ -0,0 +1,2 @@ +1 1 +1 2 diff --git a/data/rpq_data/1_sources.txt b/data/rpq_data/1_sources.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/data/rpq_data/1_sources.txt @@ -0,0 +1 @@ +1 diff --git a/data/rpq_data/2_a.mtx b/data/rpq_data/2_a.mtx new file mode 100644 index 0000000000..f7ac6c7e17 --- /dev/null +++ b/data/rpq_data/2_a.mtx @@ -0,0 +1,4 @@ +%%MatrixMarket matrix coordinate pattern general +%%GraphBLAS type bool +2 2 1 +2 1 diff --git a/data/rpq_data/2_b.mtx b/data/rpq_data/2_b.mtx new file mode 100644 index 0000000000..ed1f348ed5 --- /dev/null +++ b/data/rpq_data/2_b.mtx @@ -0,0 +1,4 @@ +%%MatrixMarket matrix coordinate pattern general +%%GraphBLAS type bool +2 2 1 +1 2 diff --git a/data/rpq_data/2_meta.txt b/data/rpq_data/2_meta.txt new file mode 100644 index 0000000000..8c2442fcc2 --- /dev/null +++ b/data/rpq_data/2_meta.txt @@ -0,0 +1,2 @@ +1 1 +1 1 diff --git a/data/rpq_data/2_sources.txt b/data/rpq_data/2_sources.txt new file mode 100644 index 0000000000..0cfbf08886 --- /dev/null +++ b/data/rpq_data/2_sources.txt @@ -0,0 +1 @@ +2 diff --git a/data/rpq_data/3_a.mtx b/data/rpq_data/3_a.mtx new file mode 100644 index 0000000000..1c0006d131 --- /dev/null +++ b/data/rpq_data/3_a.mtx @@ -0,0 +1,4 @@ +%%MatrixMarket matrix coordinate pattern general +%%GraphBLAS type bool +2 2 1 +1 1 diff --git a/data/rpq_data/3_b.mtx b/data/rpq_data/3_b.mtx new file mode 100644 index 0000000000..1c0006d131 --- /dev/null +++ b/data/rpq_data/3_b.mtx @@ -0,0 +1,4 @@ +%%MatrixMarket matrix coordinate pattern general +%%GraphBLAS type bool +2 2 1 +1 1 diff --git a/data/rpq_data/3_meta.txt b/data/rpq_data/3_meta.txt new file mode 100644 index 0000000000..8c2442fcc2 --- /dev/null +++ b/data/rpq_data/3_meta.txt @@ -0,0 +1,2 @@ +1 1 +1 1 diff --git a/data/rpq_data/3_sources.txt b/data/rpq_data/3_sources.txt new file mode 100644 index 0000000000..677154a8cd --- /dev/null +++ b/data/rpq_data/3_sources.txt @@ -0,0 +1 @@ +3 6 diff --git a/data/rpq_data/4_b.mtx b/data/rpq_data/4_b.mtx new file mode 100644 index 0000000000..cf03fa5edb --- /dev/null +++ b/data/rpq_data/4_b.mtx @@ -0,0 +1,8 @@ +%%MatrixMarket matrix coordinate pattern general +%%GraphBLAS type bool +6 6 5 +1 2 +2 3 +3 4 +4 5 +5 6 diff --git a/data/rpq_data/4_meta.txt b/data/rpq_data/4_meta.txt new file mode 100644 index 0000000000..8ba0cfea98 --- /dev/null +++ b/data/rpq_data/4_meta.txt @@ -0,0 +1,2 @@ +2 1 3 +1 6 diff --git a/data/rpq_data/4_sources.txt b/data/rpq_data/4_sources.txt new file mode 100644 index 0000000000..0efd67f950 --- /dev/null +++ b/data/rpq_data/4_sources.txt @@ -0,0 +1 @@ +2 4 diff --git a/data/rpq_data/a.mtx b/data/rpq_data/a.mtx new file mode 100644 index 0000000000..4aa1bbabc7 --- /dev/null +++ b/data/rpq_data/a.mtx @@ -0,0 +1,9 @@ +%%MatrixMarket matrix coordinate pattern general$ +%%GraphBLAS type bool$ +8 8 6 +1 2 +1 7 +2 4 +3 6 +5 8 +7 6 diff --git a/data/rpq_data/b.mtx b/data/rpq_data/b.mtx new file mode 100644 index 0000000000..d904eb2d79 --- /dev/null +++ b/data/rpq_data/b.mtx @@ -0,0 +1,10 @@ +%%MatrixMarket matrix coordinate pattern general$ +%%GraphBLAS type bool$ +8 8 7 +1 3 +2 5 +2 7 +4 4 +4 6 +5 1 +6 3 diff --git a/experimental/algorithm/LAGraph_RegularPathQuery.c b/experimental/algorithm/LAGraph_RegularPathQuery.c new file mode 100644 index 0000000000..8173daadb6 --- /dev/null +++ b/experimental/algorithm/LAGraph_RegularPathQuery.c @@ -0,0 +1,346 @@ +//------------------------------------------------------------------------------ +// LAGraph_RegularPathQuery.c: regular path query +//------------------------------------------------------------------------------ +// +// LAGraph, (c) 2019-2024 by The LAGraph Contributors, All Rights Reserved. +// SPDX-License-Identifier: BSD-2-Clause + +// Contributed by Georgiy Belyanin, Semyon Grigoriev, St. Petersburg State +// University. + +//------------------------------------------------------------------------------ + +// For an 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 regular language. +// +// The regular constraints are specified by a non-deterministic finite +// automaton (NFA) 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 NFA +// are defined by adjacency matrix decomposition. They are represented by arrays +// of graphs G and R both of length nl in which G[i]/R[i] represents the +// adjacency matrix of the i-th label for the graph/NFA correspondingly. +// +// Example of adjacency matrix decomposition: +// +// Graph: +// (0) --[a]-> (1) +// | ^ +// [b] [a]--/ +// | --/ +// v / +// (2) --[b]-> (3) +// +// Adjacency matrix decomposition of this graph consists of: +// * Adjacency matrix for the label a: +// 0 1 2 3 +// 0 | | t | | | +// 1 | | | | | +// 2 | | t | | | +// 3 | | | | | +// * Adjacency matrix for the label b: +// 0 1 2 3 +// 0 | | | t | | +// 1 | | | | | +// 2 | | | | t | +// 3 | | | | | +// +// The algorithm is based on the idea of considering the graph as +// non-deterministic finite automaton having the specified set of stating nodes +// as starting states and evaluating two modified breadth-first traversals of +// the graph and the input NFA at the same time considering the matching labels. +// +// The intuition behind this process is similar to building a direct product +// of the graph automaton and the input automaton. This construction results +// with an intersection of two regular languages. The first one is defined by +// the set of all words that can be obtained through edge label concatenation +// of paths starting in one of the source nodes. And the second one is the set +// of the paths accepted by the NFA thus matching the desired constraints. +// +// On algorithm step n the relation between the NFA edges and the graph nodes +// is built. These conditions are held for the pairs in this relation: +// * The node is reachable by a path of length n from one of the source nodes +// in the graph. +// * The state is reachable by a path of length n from one of the starting +// states in the NFA. +// * These paths have the same length and the same labels. +// The algorithm accumulates these relations. Then it extracts the nodes that +// are in relation with one of the final states. +// +// Full description is available at: +// https://arxiv.org/abs/2412.10287 +// +// Performance considerations: the best performance is shown when the algorithm +// receives a minimal deterministic finite automaton as an input. + +#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, NULL) ; \ + LAGraph_Free ((void **) &B, NULL) ; \ + LAGraph_Free ((void **) &BT, NULL) ; \ +} + +#define LG_FREE_ALL \ +{ \ + LG_FREE_WORK ; \ + GrB_free (reachable) ; \ +} + +#include "LG_internal.h" +#include "LAGraphX.h" + +int LAGraph_RegularPathQuery +( + // output: + GrB_Vector *reachable, // reachable(i) = true if node i is reachable + // from one of the starting nodes by a path + // satisfying regular constraints + // input: + LAGraph_Graph *R, // input non-deterministic finite automaton + // adjacency matrix decomposition + size_t nl, // total label count, # of matrices graph and + // NFA adjacency matrix decomposition + const GrB_Index *QS, // starting states in NFA + size_t nqs, // number of starting states in NFA + const GrB_Index *QF, // final states in NFA + size_t nqf, // number of final states in NFA + 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 = NULL ; // traversal frontier representing + // correspondence between NFA states + // and graph vertices + GrB_Matrix symbol_frontier = NULL ; // part of the new frontier for the + // specific label + GrB_Matrix next_frontier = NULL ; // frontier value on the next + // traversal step + GrB_Matrix visited = NULL ; // visited pairs (state, vertex) + GrB_Vector final_reducer = 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 NFA + GrB_Index states = ns ; // # pairs in the current + // correspondence between the graph and + // the NFA + + GrB_Index rows = 0 ; // utility matrix row count + GrB_Index cols = 0 ; // utility matrix column count + GrB_Index vals = 0 ; // utility matrix value count + + GrB_Matrix *A = NULL ; + GrB_Matrix *B = NULL ; + GrB_Matrix *BT = NULL ; + + LG_ASSERT (reachable != NULL, GrB_NULL_POINTER) ; + LG_ASSERT (G != NULL, GrB_NULL_POINTER) ; + LG_ASSERT (R != NULL, GrB_NULL_POINTER) ; + LG_ASSERT (S != NULL, GrB_NULL_POINTER) ; + + (*reachable) = NULL ; + + for (size_t i = 0 ; i < nl ; i++) + { + if (G[i] == NULL) continue ; + LG_TRY (LAGraph_CheckGraph (G[i], msg)) ; + } + + for (size_t i = 0 ; i < nl ; i++) + { + if (R[i] == 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] == NULL) + { + A[i] = 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] = NULL ; + + if (R[i] == NULL) + { + B[i] = 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 ; + } + } + + for (size_t i = 0 ; i < nl ; i++) + { + if (A[i] == NULL) continue ; + + GRB_TRY (GrB_Matrix_nrows (&ng, A[i])) ; + break ; + } + + for (size_t i = 0 ; i < nl ; i++) + { + if (B[i] == 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] == 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 NFA adjacency matrix decomposition are + // square and of the same dimensions + for (size_t i = 0 ; i < nl ; i++) + { + if (B[i] == NULL) continue ; + + GrB_Index rows = 0 ; + GrB_Index cols = 0 ; + + 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 NFA adjacency 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 = S [i] ; + LG_ASSERT_MSG (s < ng, GrB_INVALID_INDEX, "invalid graph source node") ; + } + + // Check starting states of the NFA + for (size_t i = 0 ; i < nqs ; i++) + { + GrB_Index qs = QS [i] ; + LG_ASSERT_MSG (qs < nr, GrB_INVALID_INDEX, + "invalid NFA starting state") ; + } + + // Check final states of the NFA + for (size_t i = 0 ; i < nqf ; i++) + { + GrB_Index qf = QF [i] ; + LG_ASSERT_MSG (qf < nr, GrB_INVALID_INDEX, "invalid NFA 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, NULL, NULL, true, QF, nqf, NULL) ; + + GRB_TRY (GrB_Matrix_new (&next_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, NULL, NULL, true, QS, nqs, S, ns, NULL) ; + GrB_assign (visited, NULL, NULL, true, QS, nqs, S, ns, NULL) ; + + // Initialize a few utility matrices + GRB_TRY (GrB_Matrix_new (&frontier, GrB_BOOL, nr, ng)) ; + GRB_TRY (GrB_Matrix_new (&symbol_frontier, GrB_BOOL, nr, ng)) ; + + // 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 NFA states and the graph nodes + for (size_t i = 0 ; i < nl ; i++) + { + if (A[i] == NULL || B[i] == NULL) continue ; + + // Traverse the NFA + // Try to use a provided transposed matrix or use the descriptor + if (BT[i] != NULL) + { + GRB_TRY (GrB_mxm (symbol_frontier, GrB_NULL, GrB_NULL, + GrB_LOR_LAND_SEMIRING_BOOL, BT[i], frontier, GrB_DESC_R)) ; + } + else + { + GRB_TRY (GrB_mxm (symbol_frontier, GrB_NULL, GrB_NULL, + GrB_LOR_LAND_SEMIRING_BOOL, B[i], frontier, GrB_DESC_RT0)) ; + } + + // 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 NFA 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/experimental/test/test_RegularPathQuery.c b/experimental/test/test_RegularPathQuery.c new file mode 100644 index 0000000000..a7262dfc2e --- /dev/null +++ b/experimental/test/test_RegularPathQuery.c @@ -0,0 +1,214 @@ +#include +#include +#include +#include +#include +#include + +#define LEN 512 +#define MAX_LABELS 3 +#define MAX_RESULTS 2000000 + +char msg [LAGRAPH_MSG_LEN] ; +LAGraph_Graph G[MAX_LABELS] ; +LAGraph_Graph R[MAX_LABELS] ; +GrB_Matrix A ; + +char testcase_name [LEN+1] ; +char filename [LEN+1] ; + +typedef struct +{ + const char* name ; + const char* graphs[MAX_LABELS] ; + const char* fas[MAX_LABELS] ; + const char* fa_meta ; + const char* sources ; + const GrB_Index expected[MAX_RESULTS] ; + const size_t expected_count ; +} +matrix_info ; + +const matrix_info files [ ] = +{ + {"simple 1 or more", + {"rpq_data/a.mtx", "rpq_data/b.mtx", NULL}, + {"rpq_data/1_a.mtx", NULL }, // Regex: a+ + "rpq_data/1_meta.txt", + "rpq_data/1_sources.txt", + {2, 4, 6, 7}, 4}, + {"simple kleene star", + {"rpq_data/a.mtx", "rpq_data/b.mtx", NULL}, + {"rpq_data/2_a.mtx", "rpq_data/2_b.mtx", NULL}, // Regex: (a b)* + "rpq_data/2_meta.txt", + "rpq_data/2_sources.txt", + {2, 6, 8}, 3}, + {"kleene star of the conjunction", + {"rpq_data/a.mtx", "rpq_data/b.mtx", NULL}, + {"rpq_data/3_a.mtx", "rpq_data/3_b.mtx", NULL}, // Regex: (a | b)* + "rpq_data/3_meta.txt", + "rpq_data/3_sources.txt", + {3, 6}, 2}, + {"simple repeat from n to m times", + {"rpq_data/a.mtx", "rpq_data/b.mtx", NULL}, + {"", "rpq_data/4_b.mtx", NULL}, // Regex: b b b (b b)? + "rpq_data/4_meta.txt", + "rpq_data/4_sources.txt", + {3, 4, 6}, 3}, + {NULL, NULL, NULL, NULL}, +} ; + +//**************************************************************************** +void test_RegularPathQueryBasic (void) +{ + LAGraph_Init (msg) ; + + for (int k = 0 ; ; k++) + { + if (files[k].sources == NULL) break ; + + snprintf (testcase_name, LEN, "basic regular path query %s", files[k].name) ; + TEST_CASE (testcase_name) ; + + // Load graph from MTX files representing 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 NFA from MTX files representing its adjacency matrix + // decomposition + for (int i = 0 ; ; i++) + { + const char *name = files[k].fas[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)) ; + OK (LAGraph_Cached_AT (R[i], msg)) ; + + TEST_CHECK (A == 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, "%ld", &s) != EOF) + S[ns++] = s - 1 ; + + OK (fclose(f)) ; + + // Load NFA starting states from the meta file + GrB_Index qs ; + GrB_Index QS[16] ; + size_t nqs = 0 ; + + name = files[k].fa_meta ; + snprintf (filename, LEN, LG_DATA_DIR "%s", name) ; + f = fopen (filename, "r") ; + TEST_CHECK (f != NULL) ; + + TEST_CHECK (fscanf(f, "%ld", &nqs) != EOF) ; + + for (uint64_t i = 0; i < nqs; i++) { + TEST_CHECK (fscanf(f, "%ld", &qs) != EOF) ; + QS[i] = qs - 1 ; + } + + // Load NFA final states from the same file + uint64_t qf ; + uint64_t QF[16] ; + size_t nqf = 0 ; + + TEST_CHECK (fscanf(f, "%ld", &nqf) != EOF) ; + + for (uint64_t i = 0; i < nqf; i++) { + TEST_CHECK (fscanf(f, "%ld", &qf) != EOF) ; + QF[i] = qf - 1 ; + } + + OK (fclose(f)) ; + + // Evaluate the algorithm + GrB_Vector r = NULL ; + + OK (LAGraph_RegularPathQuery (&r, R, MAX_LABELS, QS, nqs, + QF, nqf, 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 (GrB_Index), msg)) ; + + GrB_Vector_extractTuples (reachable, values, &nvals, r) ; + + // 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 = { + {"RegularPathQueryBasic", test_RegularPathQueryBasic}, + {NULL, NULL} +}; diff --git a/include/LAGraphX.h b/include/LAGraphX.h index 6f453690c7..ca4a886386 100644 --- a/include/LAGraphX.h +++ b/include/LAGraphX.h @@ -777,6 +777,29 @@ int LAGraph_scc ( char *msg ) ; +//**************************************************************************** +LAGRAPHX_PUBLIC +int LAGraph_RegularPathQuery // nodes reachable from the starting by the + // path satisfying regular expression +( + // output: + GrB_Vector *reachable, // reachable(i) = true if node i is reachable + // from one of the starting nodes by a path + // satisfying regular constraints + // input: + LAGraph_Graph *R, // input non-deterministic finite automaton + // adjacency matrix decomposition + size_t nl, // total label count, # of matrices graph and + // NFA adjacency matrix decomposition + const GrB_Index *QS, // starting states in NFA + size_t nqs, // number of starting states in NFA + const GrB_Index *QF, // final states in NFA + size_t nqf, // number of final states in NFA + 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