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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 1aad95a819..bc760d5587 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" @@ -152,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 ) @@ -375,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/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/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} ) 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" ) diff --git a/experimental/algorithm/LAGraph_CFPQ_RSM.c b/experimental/algorithm/LAGraph_CFPQ_RSM.c new file mode 100644 index 0000000000..d11cbb4a09 --- /dev/null +++ b/experimental/algorithm/LAGraph_CFPQ_RSM.c @@ -0,0 +1,295 @@ +#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 \ + { \ + GrB_free(&M); \ + GrB_free(&P); \ + GrB_free(&acc); \ + GrB_free(&M_call); \ + GrB_free(&M_new); \ + GrB_free(&scratch); \ + GrB_free(&new_edges); \ + GrB_free(&diag_entry); \ + GrB_free(&entry_vec); \ + GrB_free(&source_mask); \ + 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); \ + if (_hi != GrB_SUCCESS) \ + return _hi; \ + } + + +// 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_mxm(T, GrB_NULL, GrB_NULL, GrB_LOR_LAND_SEMIRING_BOOL, A, M, GrB_DESC_T0)); + H_TRY(GxB_Matrix_reshape(T, false, Q * V, V, GrB_NULL)); + return GrB_SUCCESS; +} + +// 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)); + return GrB_SUCCESS; +} + +// C |= A * B +static GrB_Info s_mul_acc(GrB_Matrix C, GrB_Matrix A, GrB_Matrix B) +{ + H_TRY(GrB_mxm(C, GrB_NULL, GrB_LOR, GrB_LOR_LAND_SEMIRING_BOOL, A, B, GrB_NULL)); + return GrB_SUCCESS; +} + +// 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(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; + 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, + const GrB_Index *sources, size_t num_sources, GrB_Index V, char *msg) +{ + LG_CLEAR_MSG; + + // 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 scratch = GrB_NULL; + GrB_Matrix new_edges = GrB_NULL; + GrB_Matrix diag_entry = GrB_NULL; + GrB_Vector entry_vec = GrB_NULL; + GrB_Vector source_mask = GrB_NULL; + + GrB_Matrix *graph_nt = NULL; + 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); + + 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; + W[i] = GrB_NULL; + rsm_start[i] = GrB_NULL; + } + + for (size_t i = 0; i < num_nonterm; ++i) + { + GRB_TRY(GrB_Matrix_new(&graph_nt[i], GrB_BOOL, 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, V * V)); + GRB_TRY(GrB_Matrix_new(&acc, GrB_BOOL, Q*V, V)); + 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_Vector_new(&entry_vec, GrB_BOOL, V)); + GRB_TRY(GrB_Vector_new(&source_mask, GrB_BOOL, V)); + + // 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 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)); + + GrB_Index m_nvals = 0; + GRB_TRY(GrB_Matrix_nvals(&m_nvals, M)); + while (m_nvals > 0) + { + // 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], scratch, Q, V)); + } + + // 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; + + // 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, scratch)); + if (x_nvals > 0) + { + // 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)); + + // 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, scratch, graph_nt[i])); + + // 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(scratch, Q, V)); + } + + // 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 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)); + + GrB_Index ne_vals = 0; + GRB_TRY(GrB_Matrix_nvals(&ne_vals, new_edges)); + if (ne_vals == 0) + 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, V*V, GrB_NULL)); + continue; + } + + GRB_TRY(GrB_eWiseAdd(graph_nt[i], GrB_NULL, GrB_NULL, GrB_LOR, graph_nt[i], new_edges, + GrB_NULL)); + + // 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(acc, W[i], new_edges)); + + GRB_TRY(GxB_Matrix_reshape(new_edges, false, 1, V * V, 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, 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; + M = M_new; + M_new = swap; + + GRB_TRY(GrB_Matrix_nvals(&m_nvals, M)); + } + + // 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)); + + LG_FREE_WORK; + return GrB_SUCCESS; +} 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/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/experimental/test/test_CFPQ_RSM.c b/experimental/test/test_CFPQ_RSM.c new file mode 100644 index 0000000000..7d741b3837 --- /dev/null +++ b/experimental/test/test_CFPQ_RSM.c @@ -0,0 +1,643 @@ +#include "LAGraph.h" +#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) +{ + 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 builders +//============================================================================== +// Grammar 1: S -> a S b | a b +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_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 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_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 init_rsm_complex(void) +{ + GrB_Index Q = 11; + rsm = alloc_rsm(Q, /*term_count=*/4, /*nonterm_count=*/3, /*start_nonterm=*/0); + + // --- S Component (States 0-2) --- + // S -> X | Y | S X | S Y + 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(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(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)); +} + +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 +//============================================================================== +// 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)); +} + +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)); +} + +#define run_algorithm(sources, num_sources) \ + LAGraph_CFPQ_RSM(&result, rsm, graph_term, (sources), (num_sources), V, msg); + +//============================================================================== +// 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(); + + init_rsm_aSb_ab(); + init_graph_double_cycle_ab(); + + GrB_Index sources = {1}; + GrB_Info info = run_algorithm(&sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {0, 3}; + check_reachable(expected, /*n_expected=*/2, "TC1"); + } + + free_workspace(); + 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(); + init_rsm_aSb_ab(); + init_graph_self_loops(); + + GrB_Index sources = {0}; + GrB_Info info = run_algorithm(&sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {1}; + check_reachable(expected, /*n_expected=*/1, "TC2"); + } + + free_workspace(); + 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(); + init_rsm_aSb_ab(); + init_graph_single_node(); + + GrB_Index sources = {0}; + GrB_Info info = run_algorithm(&sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {0}; + check_reachable(expected, /*n_expected=*/1, "TC3"); + } + + free_workspace(); + 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(); + init_rsm_aSb_c(); + init_graph_loops_abc(); + + GrB_Index sources = {0}; + GrB_Info info = run_algorithm(&sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {1}; + check_reachable(expected, 1, "TC4"); + } + + free_workspace(); + 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(); + init_rsm_aSb_c(); + init_graph_loops_abc_1(); + + GrB_Index sources = {1}; + GrB_Info info = run_algorithm(&sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {3}; + check_reachable(expected, 1, "TC5"); + } + + free_workspace(); + 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(); + init_rsm_aSb_c(); + + 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)); + + 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)); + + GrB_Index sources = {1}; + GrB_Info info = run_algorithm(&sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {2}; + check_reachable(expected, 1, "TC6"); + } + + free_workspace(); + OK(info); + teardown(); +} + +//------------------------------------------------------------------------------ +// TC7: S -> a S b | c +// 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_Index sources = {0}; + GrB_Info info = run_algorithm(&sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {2, 3, 4}; + check_reachable(expected, 3, "TC7"); + } + + free_workspace(); + 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(); + init_rsm_aSb_c(); + init_graph_loops_abc_2(); + + GrB_Index sources = {1}; + GrB_Info info = run_algorithm(&sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {2, 3, 4}; + check_reachable(expected, 3, "TC8"); + } + + free_workspace(); + OK(info); + teardown(); +} + +void test_TC9_mult_recursive_cycles(void) +{ + setup(); + + init_rsm_complex(); + init_graph_multi_cycle(); + + GrB_Index sources = {1}; + GrB_Info info = run_algorithm(&sources, 1); + if (info == GrB_SUCCESS) + { + GrB_Index expected[] = {2, 3, 5}; + check_reachable(expected, 3, "TC9"); + } + + free_workspace(); + OK(info); + 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}, + {"TC4_aSb_c_loops", test_TC4_aSb_c_loops}, + {"TC5_aSb_c_cycle1", test_TC5_aSb_c_cycle1}, + {"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}, + {"TC10_c_alias_overflow", test_TC10_c_alias_overflow}, + {NULL, NULL}}; diff --git a/experimental/test/test_CFPQ_RSM_blocked.c b/experimental/test/test_CFPQ_RSM_blocked.c new file mode 100644 index 0000000000..23e348698b --- /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; +} GraphEdge; + +// 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 GraphEdge *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 GraphEdge 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 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 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 GraphEdge 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 GraphEdge g_dyck_self_loop[] = { + {0, 0, 0}, + {0, 1, 0}, +}; + +// 0-a->1 (V=2, a=0) +static const GraphEdge g_epsilon[] = { + {0, 0, 1}, +}; + +// 0-a->1; 1-a->0; 0-a->2 (V=3, a=0) +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 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 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 GraphEdge 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 GraphEdge 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 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 GraphEdge 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 GraphEdge 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 GraphEdge 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/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 diff --git a/include/LAGraphX.h b/include/LAGraphX.h index 3355addb2e..c6ba3b9b7e 100644 --- a/include/LAGraphX.h +++ b/include/LAGraphX.h @@ -854,7 +854,8 @@ 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_VertexCentrality_Triangle // vertex triangle-centrality @@ -1096,6 +1097,24 @@ GrB_Info LAGraph_CFL_reachability char *msg // Message string for error reporting. ) ; +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, 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 //------------------------------------------------------------------------------