diff --git a/experimental/algorithm/LAGraph_CFL_AllPaths.c b/experimental/algorithm/LAGraph_CFL_AllPaths.c new file mode 100644 index 0000000000..5dad453604 --- /dev/null +++ b/experimental/algorithm/LAGraph_CFL_AllPaths.c @@ -0,0 +1,281 @@ +#define LG_FREE_WORK \ + { \ + GrB_free(&AllPaths_semiring); \ + GrB_free(&AllPaths_monoid); \ + GrB_free(&AllPaths_monoid_get_nvals); \ + GrB_free(&bottom_scalar); \ + GrB_free(&IAllPaths_mult); \ + GrB_free(&AllPaths_set); \ + GrB_free(&AllPaths_mult); \ + GrB_free(&AllPaths_add); \ + GrB_free(&AllPaths_add_get_nvals); \ + GrB_free(&Theta); \ + GrB_free(&AllPaths_type); \ + } + +#include "LG_internal.h" +#include + +//Merging two ordered arrays of internal vertices in the add function +static GrB_Index* merge_all_paths(GrB_Index* n, const void* left, const GrB_Index na, const void* right, const GrB_Index nb){ + GrB_Index* a = (GrB_Index*) left; + GrB_Index* b = (GrB_Index*) right; + + if (na == 0 && nb == 0) { + *n = 0; + return NULL; + } + + GrB_Index *tmp = malloc((na + nb) * sizeof(GrB_Index)); + + GrB_Index ia = 0, ib = 0, outn = 0; + while (ia < na && ib < nb) { + GrB_Index va = a[ia]; + GrB_Index vb = b[ib]; + if (va < vb) { + if (outn == 0 || tmp[outn-1] != va) tmp[outn++] = va; + ia++; + } else if (vb < va) { + if (outn == 0 || tmp[outn-1] != vb) tmp[outn++] = vb; + ib++; + } else { + if (outn == 0 || tmp[outn-1] != va) tmp[outn++] = va; + ia++; ib++; + } + } + while (ia < na) { + GrB_Index va = a[ia++]; + if (outn == 0 || tmp[outn-1] != va) tmp[outn++] = va; + } + while (ib < nb) { + GrB_Index vb = b[ib++]; + if (outn == 0 || tmp[outn-1] != vb) tmp[outn++] = vb; + } + + GrB_Index *sh = realloc(tmp, outn * sizeof(GrB_Index)); + if (sh) tmp = sh; + + *n = outn; + return tmp; +} + +void clear_elem_all_paths(AllPathsElem *z){ + if(z->middle){ + free(z->middle); + z->middle=NULL; + } + z->n = 0; +} + +void add_all_paths(AllPathsElem *z, AllPathsElem *x, AllPathsElem *y) +{ + //temp is needed to avoid freeing the memory of z in case z == x or z == y + AllPathsElem temp; + temp.middle = merge_all_paths(&temp.n, x->middle, x->n, y->middle, y->n); + clear_elem_all_paths(x); + clear_elem_all_paths(y); + z->middle = temp.middle; + z->n = temp.n; +} + +void mult_all_paths(AllPathsElem *z, + const AllPathsElem *x, GrB_Index ix, GrB_Index jx, + const AllPathsElem *y, GrB_Index iy, GrB_Index jy, + const void *theta) +{ + z->middle = malloc(sizeof(GrB_Index)); + z->middle[0] = jx; + z->n = 1; +} + +void set_all_paths(AllPathsElem *z, const AllPathsElem *x, const bool *edge_exist) +{ + AllPathsElem temp; + temp.middle = NULL; + temp.n = 0; + + if (edge_exist && *edge_exist){ + temp.middle = malloc(sizeof(GrB_Index)); + temp.n = 1; + temp.middle[0] = GrB_INDEX_MAX; //GrB_INDEX_MAX - marker of A->eps and A->t + } + + z->middle = merge_all_paths(&z->n, x->middle, x->n, temp.middle, temp.n); + clear_elem_all_paths(&temp); +} + +#define MULT_PATH_INDEX_DEFN \ +"void mult_all_paths(AllPathsElem *z, \n" \ +" const AllPathsElem *x, GrB_Index ix, GrB_Index jx, \n" \ +" const AllPathsElem *y, GrB_Index iy, GrB_Index jy, \n" \ +" const void *theta) \n" \ +"{ \n" \ +" z->middle = malloc(sizeof(GrB_Index)); \n" \ +" z->middle[0] = jx; \n" \ +" z->n = 1; \n" \ +"}" + +//Adding the count of all internal vertices in a reduction +void add_get_nvals_all_paths(AllPathsElem *z, const AllPathsElem *x, const AllPathsElem *y) +{ + z->n = x->n + y->n; + z->middle = NULL; +} + +//Made global so that the get_nvals_all_paths matches the GrB_Matrix_nvals signature +static GrB_Type* AllPaths_type_get_nvals = NULL; +static GrB_Monoid AllPaths_monoid_get_nvals = NULL; + +//A function that replaces GrB_Matrix_nvals in Reachability to check if new vertices have been added to the matrix. +GrB_Info get_nvals_all_paths(GrB_Index *nvals, const GrB_Matrix A){ + GrB_Scalar s = NULL; + GrB_Scalar_new(&s, *AllPaths_type_get_nvals); + GrB_Info info = GrB_reduce(s, NULL, AllPaths_monoid_get_nvals, A, NULL); + + if (info != GrB_SUCCESS) + { + GrB_free(&s); + return info; + } + + AllPathsElem result; + GrB_Scalar_extractElement_UDT(&result, s); + *nvals = result.n; + GrB_free(&s); + return GrB_SUCCESS; +} + +//To test the non-reduction approach in the future +GrB_Info get_nvals_all_paths2(GrB_Index *nvals, const GrB_Matrix A){ + GrB_Index accum = 0; + GxB_Iterator iterator; + GxB_Iterator_new(&iterator); + GrB_Info info = GxB_Matrix_Iterator_attach(iterator, A, NULL); + info = GxB_Matrix_Iterator_seek(iterator, 0); + AllPathsElem val; + + while (info != GxB_EXHAUSTED) + { + GxB_Iterator_get_UDT(iterator, (void*) &val); + accum+=val.n; + info = GxB_Matrix_Iterator_next(iterator); + } + + GrB_free(&iterator); + *nvals = accum; + + return GrB_SUCCESS; +} + +GrB_Info LAGraph_CFL_AllPaths( + // Output + GrB_Matrix *outputs, // Array of matrices containing results. + // The size of the array must be equal to nonterms_count. + // + // outputs[k]: (i, j) contains a AllPathsElem structure if and only if there is a path + // from node i to node j whose edge labels form a word + // derivable from the non-terminal 'k' of the specified CFG. + // Input + const GrB_Matrix *adj_matrices, // Array of adjacency matrices representing the graph. + // The length of this array is equal to the count of + // terminals (terms_count). + // + // adj_matrices[t]: (i, j) == 1 if and only if there + // is an edge between nodes i and j with the label of + // the terminal corresponding to index 't' (where t is + // in the range [0, terms_count - 1]). + int64_t terms_count, // The total number of terminal symbols in the CFG. + int64_t nonterms_count, // The total number of non-terminal symbols in the CFG. + const LAGraph_rule_WCNF *rules, // The rules of the CFG. + int64_t rules_count, // The total number of rules in the CFG. + char *msg // Message string for error reporting. +) +{ + // Semiring components + GrB_Type AllPaths_type = NULL; + GrB_BinaryOp AllPaths_add = NULL; + GrB_BinaryOp AllPaths_add_get_nvals = NULL; + GrB_Monoid AllPaths_monoid = NULL; + GxB_IndexBinaryOp IAllPaths_mult = NULL; + GrB_BinaryOp AllPaths_mult = NULL; + GrB_Semiring AllPaths_semiring = NULL; + GrB_BinaryOp AllPaths_set = NULL; + GrB_Scalar Theta = NULL; + GrB_Scalar bottom_scalar = NULL; + + GRB_TRY(GrB_Type_new(&AllPaths_type, sizeof(AllPathsElem))); + + AllPaths_type_get_nvals = &AllPaths_type; + + GRB_TRY(GrB_Scalar_new(&Theta, GrB_BOOL)); + GRB_TRY(GrB_Scalar_setElement_BOOL(Theta, false)); + + AllPathsElem bottom = {0, NULL}; + GRB_TRY(GrB_Scalar_new(&bottom_scalar, AllPaths_type)); + GRB_TRY(GrB_Scalar_setElement_UDT(bottom_scalar, (void *)(&bottom))); + + GRB_TRY(GrB_BinaryOp_new( + &AllPaths_add, + (void *)add_all_paths, + AllPaths_type, + AllPaths_type, + AllPaths_type)); + + GRB_TRY(GrB_Monoid_new( + &AllPaths_monoid, + AllPaths_add, + (void *)(&bottom))); + + GRB_TRY(GxB_IndexBinaryOp_new( + &IAllPaths_mult, + (void *)mult_all_paths, + AllPaths_type, + AllPaths_type, + AllPaths_type, + GrB_BOOL, + "mult_all_paths", + MULT_PATH_INDEX_DEFN)); + + GRB_TRY(GxB_BinaryOp_new_IndexOp( + &AllPaths_mult, + IAllPaths_mult, + Theta)); + + GRB_TRY(GrB_Semiring_new( + &AllPaths_semiring, + AllPaths_monoid, + AllPaths_mult)); + + GRB_TRY(GrB_BinaryOp_new( + &AllPaths_set, + (void *)set_all_paths, + AllPaths_type, + AllPaths_type, + GrB_BOOL)); + + GRB_TRY(GrB_BinaryOp_new( + &AllPaths_add_get_nvals, + (void *)add_get_nvals_all_paths, + AllPaths_type, + AllPaths_type, + AllPaths_type)); + + GRB_TRY(GrB_Monoid_new( + &AllPaths_monoid_get_nvals, + AllPaths_add_get_nvals, + (void *)(&bottom))); + + CFL_Semiring semiring = {.type = AllPaths_type, + .semiring = AllPaths_semiring, + .add = AllPaths_add, + .mult = AllPaths_mult, + .init_path = AllPaths_set, + .bottom_scalar = bottom_scalar, + .get_nvals = get_nvals_all_paths}; + + LG_TRY(LAGraph_CFPQ_core(outputs, adj_matrices, terms_count, nonterms_count, rules, rules_count, &semiring, msg)); + + AllPaths_type_get_nvals = NULL; + LG_FREE_WORK; + return GrB_SUCCESS; +} diff --git a/experimental/algorithm/LAGraph_CFL_reachability.c b/experimental/algorithm/LAGraph_CFL_reachability.c index c4c5d25ea5..b09881924c 100644 --- a/experimental/algorithm/LAGraph_CFL_reachability.c +++ b/experimental/algorithm/LAGraph_CFL_reachability.c @@ -15,62 +15,14 @@ // Querying Using Linear Algebra", URL: // https://disser.spbu.ru/files/2022/disser_azimov.pdf -#define LG_FREE_WORK \ - { \ - LAGraph_Free ((void **) &nnzs, msg) ; \ - GrB_free(&true_scalar); \ - GrB_free(&identity_matrix); \ - LAGraph_Free ((void **) &T, msg); \ - LAGraph_Free ((void **) &indexes, msg); \ - LAGraph_Free((void**) &t_empty_flags, NULL); \ - LAGraph_Free((void**) &eps_rules, NULL); \ - LAGraph_Free((void**) &term_rules, NULL); \ - LAGraph_Free((void**) &bin_rules, NULL); \ - } - -#define LG_FREE_ALL \ - { \ - for (int64_t i = 0; i < nonterms_count; i++) { \ - GrB_free(&T[i]); \ - } \ - \ - LG_FREE_WORK; \ +#define LG_FREE_WORK \ + { \ + GrB_free(&false_scalar); \ } #include "LG_internal.h" #include -#define ERROR_RULE(msg,i) \ - { \ - LG_ASSERT_MSGF(false, GrB_INVALID_VALUE, \ - "Rule with index %" PRId64 " is invalid. ", msg, i); \ - } - -#define ADD_TO_MSG(...) \ - { \ - if (msg_len == 0) { \ - msg_len += \ - snprintf(msg, LAGRAPH_MSG_LEN, \ - "LAGraph failure (file %s, line %d): ", \ - __FILE__, __LINE__); \ - } \ - if (msg_len < LAGRAPH_MSG_LEN) { \ - msg_len += snprintf(msg + msg_len, LAGRAPH_MSG_LEN - msg_len, \ - __VA_ARGS__); \ - } \ - } - -#define ADD_INDEX_TO_ERROR_RULE(rule, i) \ - { \ - rule.len_indexes_str += snprintf( \ - rule.indexes_str + rule.len_indexes_str, \ - LAGRAPH_MSG_LEN - rule.len_indexes_str, \ - rule.count == 0 ? "%" PRId64 : ", %" PRId64, i); \ - rule.count++; \ - } - - - // LAGraph_CFL_reachability: Context-Free Language Reachability Matrix-Based Algorithm // // This function determines the set of vertex pairs (u, v) in a graph (represented by @@ -113,8 +65,7 @@ // (1, 2) - because there exists a path (1-5-2) that forms the word "ab" // (0, 3) - because there exists a path (0-1-5-2-3) that forms the word "aabb" -GrB_Info LAGraph_CFL_reachability -( +GrB_Info LAGraph_CFL_reachability( // Output GrB_Matrix *outputs, // Array of matrices containing results. // The size of the array must be equal to nonterms_count. @@ -138,247 +89,17 @@ GrB_Info LAGraph_CFL_reachability char *msg // Message string for error reporting. ) { - -#if LAGRAPH_SUITESPARSE - // Declare workspace and clear the msg string, if not NULL - GrB_Matrix *T; - bool *t_empty_flags = NULL ; // t_empty_flags[i] == true <=> T[i] is empty - GrB_Matrix identity_matrix = NULL; - uint64_t *nnzs = NULL; - LG_CLEAR_MSG; - size_t msg_len = 0; // For error formatting - bool iso_flag = false; - GrB_Index *indexes = NULL; - - // Arrays for processing rules - size_t *eps_rules = NULL, eps_rules_count = 0; // [Variable -> eps] - size_t *term_rules = NULL, term_rules_count = 0; // [Variable -> term] - size_t *bin_rules = NULL, bin_rules_count = 0; // [Variable -> AB] - - GrB_Scalar true_scalar; - GrB_Scalar_new(&true_scalar, GrB_BOOL); - GrB_Scalar_setElement_BOOL(true_scalar, true); - - LG_TRY(LAGraph_Calloc((void **) &T, nonterms_count, sizeof(GrB_Matrix), msg)); - LG_TRY(LAGraph_Calloc((void **) &t_empty_flags, nonterms_count, sizeof(bool), msg)) ; - - LG_ASSERT_MSG(terms_count > 0, GrB_INVALID_VALUE, - "The number of terminals must be greater than zero."); - LG_ASSERT_MSG(nonterms_count > 0, GrB_INVALID_VALUE, - "The number of non-terminals must be greater than zero."); - LG_ASSERT_MSG(rules_count > 0, GrB_INVALID_VALUE, - "The number of rules must be greater than zero."); - LG_ASSERT_MSG(outputs != NULL, GrB_NULL_POINTER, "The outputs array cannot be null."); - LG_ASSERT_MSG(rules != NULL, GrB_NULL_POINTER, "The rules array cannot be null."); - LG_ASSERT_MSG(adj_matrices != NULL, GrB_NULL_POINTER, - "The adjacency matrices array cannot be null."); - - // Find null adjacency matrices - bool found_null = false; - for (int64_t i = 0; i < terms_count; i++) { - if (adj_matrices[i] != NULL) - continue; - - if (!found_null) { - ADD_TO_MSG("Adjacency matrices with these indexes are null: "); - ADD_TO_MSG("%" PRId64, i); - } else { - ADD_TO_MSG("%" PRId64, i); - } - - found_null = true; - } - - if (found_null) { - LG_FREE_ALL; - return GrB_NULL_POINTER; - } - - GrB_Index n; - GRB_TRY(GrB_Matrix_ncols(&n, adj_matrices[0])); - - // Create nonterms matrices - for (int64_t i = 0; i < nonterms_count; i++) { - GRB_TRY(GrB_Matrix_new(&T[i], GrB_BOOL, n, n)); - t_empty_flags[i] = true; - } - - LG_TRY(LAGraph_Calloc((void **) &eps_rules, rules_count, sizeof(size_t), msg)) ; - LG_TRY(LAGraph_Calloc((void **) &term_rules, rules_count, sizeof(size_t), msg)) ; - LG_TRY(LAGraph_Calloc((void **) &bin_rules, rules_count, sizeof(size_t), msg)) ; - - // Process rules - typedef struct { - size_t count; - size_t len_indexes_str; - char indexes_str[LAGRAPH_MSG_LEN]; - } rule_error_s; - rule_error_s term_err = {0}; - rule_error_s nonterm_err = {0}; - rule_error_s invalid_err = {0}; - for (int64_t i = 0; i < rules_count; i++) { - LAGraph_rule_WCNF rule = rules[i]; - - bool is_rule_eps = rule.prod_A == -1 && rule.prod_B == -1; - bool is_rule_term = rule.prod_A != -1 && rule.prod_B == -1; - bool is_rule_bin = rule.prod_A != -1 && rule.prod_B != -1; - - // Check that all rules are well-formed - if (rule.nonterm < 0 || rule.nonterm >= nonterms_count) { - ADD_INDEX_TO_ERROR_RULE(nonterm_err, i); - } - - // [Variable -> eps] - if (is_rule_eps) { - eps_rules[eps_rules_count++] = i; - - continue; - } - - // [Variable -> term] - if (is_rule_term) { - term_rules[term_rules_count++] = i; - - if (rule.prod_A < -1 || rule.prod_A >= terms_count) { - ADD_INDEX_TO_ERROR_RULE(term_err, i); - } - - continue; - } - - // [Variable -> A B] - if (is_rule_bin) { - bin_rules[bin_rules_count++] = i; - - if (rule.prod_A < -1 || rule.prod_A >= nonterms_count || rule.prod_B < -1 || - rule.prod_B >= nonterms_count) { - ADD_INDEX_TO_ERROR_RULE(nonterm_err, i); - } - - continue; - } - - // [Variable -> _ B] - ADD_INDEX_TO_ERROR_RULE(invalid_err, i); - } - - if (term_err.count + nonterm_err.count + invalid_err.count > 0) { - ADD_TO_MSG("Count of invalid rules: %" PRId64 ".\n", - (int64_t) (term_err.count + nonterm_err.count + invalid_err.count)); - - if (nonterm_err.count > 0) { - ADD_TO_MSG("Non-terminals must be in range [0, nonterms_count). "); - ADD_TO_MSG("Indexes of invalid rules: %s\n", nonterm_err.indexes_str) - } - if (term_err.count > 0) { - ADD_TO_MSG("Terminals must be in range [-1, nonterms_count). "); - ADD_TO_MSG("Indexes of invalid rules: %s\n", term_err.indexes_str) - } - if (invalid_err.count > 0) { - ADD_TO_MSG("[Variable -> _ B] type of rule is not acceptable. "); - ADD_TO_MSG("Indexes of invalid rules: %.120s\n", invalid_err.indexes_str) - } - - LG_FREE_ALL; - return GrB_INVALID_VALUE; - } - - // Rule [Variable -> term] - for (int64_t i = 0; i < term_rules_count; i++) { - LAGraph_rule_WCNF term_rule = rules[term_rules[i]]; - GrB_Index adj_matrix_nnz = 0; - GRB_TRY(GrB_Matrix_nvals(&adj_matrix_nnz, adj_matrices[term_rule.prod_A])); - - if (adj_matrix_nnz == 0) { - continue; - } - - GxB_eWiseUnion( - T[term_rule.nonterm], GrB_NULL, GrB_NULL, GxB_PAIR_BOOL, - T[term_rule.nonterm], true_scalar, adj_matrices[term_rule.prod_A], true_scalar, GrB_NULL - ); - - t_empty_flags[term_rule.nonterm] = false; - - #ifdef DEBUG_CFL_REACHBILITY - GxB_Matrix_iso(&iso_flag, T[term_rule.nonterm]); - printf("[TERM] eWiseUnion: NONTERM: %d (ISO: %d)\n", term_rule.nonterm, iso_flag); - #endif - } - - GrB_Vector v_diag; - GRB_TRY(GrB_Vector_new(&v_diag, GrB_BOOL, n)); - GRB_TRY(GrB_Vector_assign_BOOL(v_diag, GrB_NULL, GrB_NULL, true, GrB_ALL, n, NULL)); - GRB_TRY(GrB_Matrix_diag(&identity_matrix, v_diag, 0)); - GRB_TRY(GrB_free(&v_diag)); - - // Rule [Variable -> eps] - for (int64_t i = 0; i < eps_rules_count; i++) { - LAGraph_rule_WCNF eps_rule = rules[eps_rules[i]]; - - GxB_eWiseUnion ( - T[eps_rule.nonterm],GrB_NULL,GxB_PAIR_BOOL,GxB_PAIR_BOOL, - T[eps_rule.nonterm],true_scalar,identity_matrix,true_scalar,GrB_NULL - ); - - t_empty_flags[eps_rule.nonterm] = false; - - #ifdef DEBUG_CFL_REACHBILITY - GxB_Matrix_iso(&iso_flag, T[eps_rule.nonterm]); - printf("[EPS] eWiseUnion: NONTERM: %d (ISO: %d)\n", - eps_rule.nonterm, iso_flag); - #endif - } - - // Rule [Variable -> Variable1 Variable2] - LG_TRY(LAGraph_Calloc((void **) &nnzs, nonterms_count, sizeof(uint64_t), msg)); - bool changed = true; - while (changed) { - changed = false; - for (int64_t i = 0; i < bin_rules_count; i++) { - LAGraph_rule_WCNF bin_rule = rules[bin_rules[i]]; - - // If one of matrices is empty then their product will be empty - if (t_empty_flags[bin_rule.prod_A] || t_empty_flags[bin_rule.prod_B]) { - continue; - } - - GrB_BinaryOp acc_op = t_empty_flags[bin_rule.nonterm] ? GrB_NULL : GxB_ANY_BOOL; - GRB_TRY(GrB_mxm(T[bin_rule.nonterm], GrB_NULL, acc_op, - GxB_ANY_PAIR_BOOL, T[bin_rule.prod_A], T[bin_rule.prod_B], - GrB_NULL)) - - GrB_Index new_nnz; - GRB_TRY(GrB_Matrix_nvals(&new_nnz, T[bin_rule.nonterm])); - if (new_nnz != 0) t_empty_flags[bin_rule.nonterm] = false; - - changed = changed || (nnzs[bin_rule.nonterm] != new_nnz); - nnzs[bin_rule.nonterm] = new_nnz; - - #ifdef DEBUG_CFL_REACHBILITY - GxB_Matrix_iso(&iso_flag, T[bin_rule.nonterm]); - printf("[TERM1 TERM2] MULTIPLY, S: %d, A: %d, B: %d, " - "I: %" PRId64 " (ISO: %d)\n", - bin_rule.nonterm, bin_rule.prod_A, bin_rule.prod_B, i, iso_flag); - #endif - - } - } - - #ifdef DEBUG_CFL_REACHBILITY - for (int64_t i = 0; i < nonterms_count; i++) { - printf("MATRIX WITH INDEX %" PRId64 ":\n", i); - GxB_print(T[i], GxB_SUMMARY); - } - #endif - - for (int64_t i = 0; i < nonterms_count; i++) { - outputs[i] = T[i]; - } - + GrB_Scalar false_scalar; + GRB_TRY(GrB_Scalar_new(&false_scalar, GrB_BOOL)); + GRB_TRY(GrB_Scalar_setElement_BOOL(false_scalar, false)); + CFL_Semiring semiring = {.type = GrB_BOOL, + .semiring = GxB_ANY_PAIR_BOOL, + .add = GxB_ANY_BOOL, + .mult = GxB_PAIR_BOOL, + .init_path = GxB_PAIR_BOOL, + .bottom_scalar = false_scalar, + .get_nvals = GrB_Matrix_nvals}; + LG_TRY(LAGraph_CFPQ_core(outputs, adj_matrices, terms_count, nonterms_count, rules, rules_count, &semiring, msg)); LG_FREE_WORK; return GrB_SUCCESS; -#else - return (GrB_NOT_IMPLEMENTED) ; -#endif } diff --git a/experimental/algorithm/LAGraph_CFPQ_core.c b/experimental/algorithm/LAGraph_CFPQ_core.c new file mode 100644 index 0000000000..c4aecae1cf --- /dev/null +++ b/experimental/algorithm/LAGraph_CFPQ_core.c @@ -0,0 +1,325 @@ +#define LG_FREE_WORK \ + { \ + LAGraph_Free((void **)&nnzs, NULL); \ + GrB_free(&false_scalar); \ + GrB_free(&identity_matrix); \ + LAGraph_Free((void **)&T, NULL); \ + LAGraph_Free((void **)&indexes, NULL); \ + LAGraph_Free((void **)&t_empty_flags, NULL); \ + LAGraph_Free((void **)&eps_rules, NULL); \ + LAGraph_Free((void **)&term_rules, NULL); \ + LAGraph_Free((void **)&bin_rules, NULL); \ + } + +#define LG_FREE_ALL \ + { \ + for (int64_t i = 0; i < nonterms_count; i++) \ + { \ + GrB_free(&T[i]); \ + } \ + \ + LG_FREE_WORK; \ + } + +#include "LG_internal.h" +#include + +#define ERROR_RULE(msg, i) \ + { \ + LG_ASSERT_MSGF(false, GrB_INVALID_VALUE, \ + "Rule with index %" PRId64 " is invalid. ", msg, i); \ + } + +#define ADD_TO_MSG(...) \ + { \ + if (msg_len == 0) \ + { \ + msg_len += \ + snprintf(msg, LAGRAPH_MSG_LEN, \ + "LAGraph failure (file %s, line %d): ", \ + __FILE__, __LINE__); \ + } \ + if (msg_len < LAGRAPH_MSG_LEN) \ + { \ + msg_len += snprintf(msg + msg_len, LAGRAPH_MSG_LEN - msg_len, \ + __VA_ARGS__); \ + } \ + } + +#define ADD_INDEX_TO_ERROR_RULE(rule, i) \ + { \ + rule.len_indexes_str += snprintf( \ + rule.indexes_str + rule.len_indexes_str, \ + LAGRAPH_MSG_LEN - rule.len_indexes_str, \ + rule.count == 0 ? "%" PRId64 : ", %" PRId64, i); \ + rule.count++; \ + } + +GrB_Info LAGraph_CFPQ_core( + // Output + GrB_Matrix *outputs, // Array of matrices containing results. + // The size of the array must be equal to nonterms_count. + // + // outputs[k]: (i, j) contains a corresponding semiring value if and only if there is a path + // from node i to node j whose edge labels form a word + // derivable from the non-terminal 'k' of the specified CFG. + // Input + const GrB_Matrix *adj_matrices, // Array of adjacency matrices representing the graph. + // The length of this array is equal to the count of + // terminals (terms_count). + // + // adj_matrices[t]: (i, j) == 1 if and only if there + // is an edge between nodes i and j with the label of + // the terminal corresponding to index 't' (where t is + // in the range [0, terms_count - 1]). + int64_t terms_count, // The total number of terminal symbols in the CFG. + int64_t nonterms_count, // The total number of non-terminal symbols in the CFG. + const LAGraph_rule_WCNF *rules, // The rules of the CFG. + int64_t rules_count, // The total number of rules in the CFG. + const CFL_Semiring *semiring, // The algebraic structure that defines operations on matrices for a specific problem + char *msg // Message string for error reporting. +) +{ + +#if LAGRAPH_SUITESPARSE + // Declare workspace and clear the msg string, if not NULL + GrB_Matrix *T; + bool *t_empty_flags = NULL; // t_empty_flags[i] == true <=> T[i] is empty + GrB_Matrix identity_matrix = NULL; + uint64_t *nnzs = NULL; + LG_CLEAR_MSG; + size_t msg_len = 0; // For error formatting + bool iso_flag = false; + GrB_Index *indexes = NULL; + + // Arrays for processing rules + size_t *eps_rules = NULL, eps_rules_count = 0; // [Variable -> eps] + size_t *term_rules = NULL, term_rules_count = 0; // [Variable -> term] + size_t *bin_rules = NULL, bin_rules_count = 0; // [Variable -> AB] + + GrB_Scalar false_scalar; + GRB_TRY(GrB_Scalar_new(&false_scalar, GrB_BOOL)); + GRB_TRY(GrB_Scalar_setElement_BOOL(false_scalar, false)); + + LG_TRY(LAGraph_Calloc((void **)&T, nonterms_count, sizeof(GrB_Matrix), msg)); + LG_TRY(LAGraph_Calloc((void **)&t_empty_flags, nonterms_count, sizeof(bool), msg)); + + LG_ASSERT_MSG(terms_count > 0, GrB_INVALID_VALUE, + "The number of terminals must be greater than zero."); + LG_ASSERT_MSG(nonterms_count > 0, GrB_INVALID_VALUE, + "The number of non-terminals must be greater than zero."); + LG_ASSERT_MSG(rules_count > 0, GrB_INVALID_VALUE, + "The number of rules must be greater than zero."); + LG_ASSERT_MSG(outputs != NULL, GrB_NULL_POINTER, "The outputs array cannot be null."); + LG_ASSERT_MSG(rules != NULL, GrB_NULL_POINTER, "The rules array cannot be null."); + LG_ASSERT_MSG(adj_matrices != NULL, GrB_NULL_POINTER, + "The adjacency matrices array cannot be null."); + LG_ASSERT_MSG(semiring != NULL, GrB_NULL_POINTER, + "The semiring cannot be null."); + + // Find null adjacency matrices + bool found_null = false; + for (int64_t i = 0; i < terms_count; i++) + { + if (adj_matrices[i] != NULL) + continue; + + if (!found_null) + { + ADD_TO_MSG("Adjacency matrices with these indexes are null: "); + ADD_TO_MSG("%" PRId64, i); + } + else + { + ADD_TO_MSG(" %" PRId64, i); + } + + found_null = true; + } + + if (found_null) + { + LG_FREE_ALL; + return GrB_NULL_POINTER; + } + + GrB_Index n; + GRB_TRY(GrB_Matrix_ncols(&n, adj_matrices[0])); + + // Create nonterms matrices + for (int64_t i = 0; i < nonterms_count; i++) + { + GRB_TRY(GrB_Matrix_new(&T[i], semiring->type, n, n)); + t_empty_flags[i] = true; + } + + LG_TRY(LAGraph_Calloc((void **)&eps_rules, rules_count, sizeof(size_t), msg)); + LG_TRY(LAGraph_Calloc((void **)&term_rules, rules_count, sizeof(size_t), msg)); + LG_TRY(LAGraph_Calloc((void **)&bin_rules, rules_count, sizeof(size_t), msg)); + + // Process rules + typedef struct + { + size_t count; + size_t len_indexes_str; + char indexes_str[LAGRAPH_MSG_LEN]; + } rule_error_s; + rule_error_s term_err = {0}; + rule_error_s nonterm_err = {0}; + rule_error_s invalid_err = {0}; + for (int64_t i = 0; i < rules_count; i++) + { + LAGraph_rule_WCNF rule = rules[i]; + + bool is_rule_eps = rule.prod_A == -1 && rule.prod_B == -1; + bool is_rule_term = rule.prod_A != -1 && rule.prod_B == -1; + bool is_rule_bin = rule.prod_A != -1 && rule.prod_B != -1; + + // Check that all rules are well-formed + if (rule.nonterm < 0 || rule.nonterm >= nonterms_count) + { + ADD_INDEX_TO_ERROR_RULE(nonterm_err, i); + } + + // [Variable -> eps] + if (is_rule_eps) + { + eps_rules[eps_rules_count++] = i; + + continue; + } + + // [Variable -> term] + if (is_rule_term) + { + term_rules[term_rules_count++] = i; + + if (rule.prod_A < -1 || rule.prod_A >= terms_count) + { + ADD_INDEX_TO_ERROR_RULE(term_err, i); + } + + continue; + } + + // [Variable -> A B] + if (is_rule_bin) + { + bin_rules[bin_rules_count++] = i; + + if (rule.prod_A < -1 || rule.prod_A >= nonterms_count || rule.prod_B < -1 || + rule.prod_B >= nonterms_count) + { + ADD_INDEX_TO_ERROR_RULE(nonterm_err, i); + } + + continue; + } + + // [Variable -> _ B] + ADD_INDEX_TO_ERROR_RULE(invalid_err, i); + } + + if (term_err.count + nonterm_err.count + invalid_err.count > 0) + { + ADD_TO_MSG("Count of invalid rules: %" PRId64 ".\n", + (int64_t)(term_err.count + nonterm_err.count + invalid_err.count)); + + if (nonterm_err.count > 0) + { + ADD_TO_MSG("Non-terminals must be in range [0, nonterms_count). "); + ADD_TO_MSG("Indexes of invalid rules: %s\n", nonterm_err.indexes_str) + } + if (term_err.count > 0) + { + ADD_TO_MSG("Terminals must be in range [-1, nonterms_count). "); + ADD_TO_MSG("Indexes of invalid rules: %s\n", term_err.indexes_str) + } + if (invalid_err.count > 0) + { + ADD_TO_MSG("[Variable -> _ B] type of rule is not acceptable. "); + ADD_TO_MSG("Indexes of invalid rules: %.120s\n", invalid_err.indexes_str) + } + + LG_FREE_ALL; + return GrB_INVALID_VALUE; + } + + // Rule [Variable -> term] + for (int64_t i = 0; i < term_rules_count; i++) + { + LAGraph_rule_WCNF term_rule = rules[term_rules[i]]; + GrB_Index adj_matrix_nnz = 0; + GRB_TRY(GrB_Matrix_nvals(&adj_matrix_nnz, adj_matrices[term_rule.prod_A])); + + if (adj_matrix_nnz == 0) + { + continue; + } + GxB_eWiseUnion( + T[term_rule.nonterm], GrB_NULL, GrB_NULL, semiring->init_path, + T[term_rule.nonterm], semiring->bottom_scalar, adj_matrices[term_rule.prod_A], false_scalar, GrB_NULL); + + t_empty_flags[term_rule.nonterm] = false; + } + + GrB_Vector v_diag; + GRB_TRY(GrB_Vector_new(&v_diag, GrB_BOOL, n)); + GRB_TRY(GrB_Vector_assign_BOOL(v_diag, GrB_NULL, GrB_NULL, true, GrB_ALL, n, NULL)); + GRB_TRY(GrB_Matrix_diag(&identity_matrix, v_diag, 0)); + GRB_TRY(GrB_free(&v_diag)); + + // Rule [Variable -> eps] + for (int64_t i = 0; i < eps_rules_count; i++) + { + LAGraph_rule_WCNF eps_rule = rules[eps_rules[i]]; + GrB_BinaryOp acc_op = t_empty_flags[eps_rule.nonterm] ? GrB_NULL : semiring->add; + GxB_eWiseUnion( + T[eps_rule.nonterm], GrB_NULL, acc_op, semiring->init_path, + T[eps_rule.nonterm], semiring->bottom_scalar, identity_matrix, false_scalar, GrB_NULL); + + t_empty_flags[eps_rule.nonterm] = false; + } + + // Rule [Variable -> Variable1 Variable2] + LG_TRY(LAGraph_Calloc((void **)&nnzs, nonterms_count, sizeof(uint64_t), msg)); + bool changed = true; + while (changed) + { + changed = false; + for (int64_t i = 0; i < bin_rules_count; i++) + { + LAGraph_rule_WCNF bin_rule = rules[bin_rules[i]]; + + // If one of matrices is empty then their product will be empty + if (t_empty_flags[bin_rule.prod_A] || t_empty_flags[bin_rule.prod_B]) + { + continue; + } + + GrB_BinaryOp acc_op = t_empty_flags[bin_rule.nonterm] ? GrB_NULL : semiring->add; + GRB_TRY(GrB_mxm(T[bin_rule.nonterm], GrB_NULL, acc_op, + semiring->semiring, T[bin_rule.prod_A], T[bin_rule.prod_B], + GrB_NULL)) + + GrB_Index new_nnz; + GRB_TRY(semiring->get_nvals(&new_nnz, T[bin_rule.nonterm])); + if (new_nnz != 0) + t_empty_flags[bin_rule.nonterm] = false; + + changed = changed || (nnzs[bin_rule.nonterm] != new_nnz); + nnzs[bin_rule.nonterm] = new_nnz; + } + } + + for (int64_t i = 0; i < nonterms_count; i++) + { + outputs[i] = T[i]; + } + + LG_FREE_WORK; + return GrB_SUCCESS; +#else + return (GrB_NOT_IMPLEMENTED); +#endif +} diff --git a/experimental/test/test_CFL_AllPaths.c b/experimental/test/test_CFL_AllPaths.c new file mode 100644 index 0000000000..f7be983edf --- /dev/null +++ b/experimental/test/test_CFL_AllPaths.c @@ -0,0 +1,778 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define run_algorithm() \ + LAGraph_CFL_AllPaths(outputs, adj_matrices, grammar.terms_count, \ + grammar.nonterms_count, grammar.rules, grammar.rules_count, \ + msg) + +#define check_error(error) \ + { \ + retval = run_algorithm(); \ + TEST_CHECK(retval == error); \ + TEST_MSG("retval = %d (%s)", retval, msg); \ + } + +#define check_result(nonterm, result) \ + { \ + char *expected = output_to_str(nonterm); \ + TEST_CHECK(strcmp(result, expected) == 0); \ + TEST_MSG("Wrong result. Actual: %s", expected); \ + LAGraph_Free ((void **) &expected, msg); \ + } + +typedef struct { + size_t nonterms_count; + size_t terms_count; + size_t rules_count; + LAGraph_rule_WCNF *rules; +} grammar_t; + +GrB_Matrix *adj_matrices = NULL; +int n_adj_matrices = 0 ; +GrB_Matrix *outputs = NULL; +grammar_t grammar = {0, 0, 0, NULL}; +char msg[LAGRAPH_MSG_LEN]; + +void setup() { LAGraph_Init(msg); } + +void teardown(void) { LAGraph_Finalize(msg); } + +void init_outputs() +{ + LAGraph_Calloc ((void **) &outputs, + grammar.nonterms_count, sizeof(GrB_Matrix), msg) ; +} + +char *output_to_str(size_t nonterm) +{ + GrB_Index nnz = 0; + OK(GrB_Matrix_nvals(&nnz, outputs[nonterm])); + + if (nnz == 0) + { + char *empty = NULL; + LAGraph_Malloc ((void **) &empty, 1, sizeof(char), msg); + empty[0] = '\0'; + return empty; + } + + GrB_Index *row = NULL; + GrB_Index *col = NULL; + void *val_void = NULL; + + LAGraph_Malloc ((void **) &row, nnz, sizeof(GrB_Index), msg); + LAGraph_Malloc ((void **) &col, nnz, sizeof(GrB_Index), msg); + LAGraph_Malloc ((void **) &val_void, nnz, sizeof(AllPathsElem), msg); + + OK(GrB_Matrix_extractTuples(row, col, val_void, &nnz, outputs[nonterm])); + + AllPathsElem *val = (AllPathsElem *) val_void; + + size_t cap = 256; + size_t len = 0; + char *result_str = NULL; + LAGraph_Malloc ((void **) &result_str, cap, sizeof(char), msg); + result_str[0] = '\0'; + + for (size_t i = 0; i < nnz; i++) + { + size_t needed = 64 + (val[i].n) * 20; + if (len + needed + 1 > cap) + { + while (len + needed + 1 > cap) cap *= 2; + result_str = (char *) realloc(result_str, cap); + if (result_str == NULL) + { + LAGraph_Free ((void **) &row, msg); + LAGraph_Free ((void **) &col, msg); + LAGraph_Free ((void **) &val, msg); + LAGraph_Free ((void **) &result_str, msg); + OK(GrB_OUT_OF_MEMORY); + } + } + + int wrote = 0; + if (val[i].n == 0 || val[i].middle == NULL) + { + wrote = sprintf(result_str + len, + (i == 0 ? "(%" PRIu64 ", %" PRIu64 "):[]" : " (%" PRIu64 ", %" PRIu64 "):[]"), + (uint64_t) row[i], (uint64_t) col[i]); + len += (size_t) wrote; + } + else + { + wrote = sprintf(result_str + len, + (i == 0 ? "(%" PRIu64 ", %" PRIu64 "):[" : " (%" PRIu64 ", %" PRIu64 "):["), + (uint64_t) row[i], (uint64_t) col[i]); + len += (size_t) wrote; + + for (GrB_Index k = 0; k < val[i].n; k++) + { + if (k == 0) + { + if (val[i].middle[k] == GrB_INDEX_MAX) { + wrote = sprintf(result_str + len, "INDEX_MAX"); + } else { + wrote = sprintf(result_str + len, "%" PRIu64, (uint64_t)val[i].middle[k]); + } + } + else + { + if (val[i].middle[k] == GrB_INDEX_MAX) { + wrote = sprintf(result_str + len, ",INDEX_MAX"); + } else { + wrote = sprintf(result_str + len, ",%" PRIu64, (uint64_t)val[i].middle[k]); + } + } + len += (size_t) wrote; + } + + result_str[len++] = ']'; + result_str[len] = '\0'; + } + } + + LAGraph_Free ((void **) &row, msg); + LAGraph_Free ((void **) &col, msg); + LAGraph_Free ((void **) &val, msg); + + return result_str; +} + +void print_outputs(void) +{ + for (size_t i = 0; i < grammar.nonterms_count; i++) + { + char *s = output_to_str(i); + printf("Output[%zu]: %s\n", i, s[0] ? s : "(empty)"); + LAGraph_Free ((void **) &s, msg); + } +} + +//Cleaning of internal elements before free matrix +void free_AllPaths_matrix(GrB_Matrix* ptr_output){ + GrB_Matrix output = *ptr_output; + GxB_Iterator iterator; + GxB_Iterator_new(&iterator); + GrB_Info info = GxB_Matrix_Iterator_attach(iterator, output, NULL); + info = GxB_Matrix_Iterator_seek(iterator, 0); + AllPathsElem val; + while (info != GxB_EXHAUSTED) + { + GxB_Iterator_get_UDT(iterator, (void*) &val); + if (val.middle) free(val.middle); + info = GxB_Matrix_Iterator_next(iterator); + } + + GrB_free(&iterator); + GrB_free(ptr_output); +} + +void free_workspace() { + + if (adj_matrices != NULL) + { + for (size_t i = 0; i < n_adj_matrices ; i++) + { + GrB_free(&adj_matrices[i]); + } + } + LAGraph_Free ((void **) &adj_matrices, msg); + + if (outputs != NULL) + { + for (size_t i = 0; i < grammar.nonterms_count; i++) + { + free_AllPaths_matrix(&outputs[i]); + } + } + LAGraph_Free ((void **) &outputs, msg); + + LAGraph_Free ((void **) &grammar.rules, msg); + grammar = (grammar_t){0, 0, 0, NULL}; +} + +//==================== +// Grammars +//==================== + +// S -> aSb | ab in WCNF +// +// Terms: [0 a] [1 b] +// Nonterms: [0 S] [1 A] [2 B] [3 C] +// S -> AB [0 1 2 0] +// S -> AC [0 1 3 0] +// C -> SB [3 0 2 0] +// A -> a [1 0 -1 0] +// B -> b [2 1 -1 0] +void init_grammar_aSb() { + LAGraph_rule_WCNF *rules = NULL ; + LAGraph_Calloc ((void **) &rules, 5, sizeof(LAGraph_rule_WCNF), msg); + + rules[0] = (LAGraph_rule_WCNF){0, 1, 2, 0}; + rules[1] = (LAGraph_rule_WCNF){0, 1, 3, 0}; + rules[2] = (LAGraph_rule_WCNF){3, 0, 2, 0}; + rules[3] = (LAGraph_rule_WCNF){1, 0, -1, 0}; + rules[4] = (LAGraph_rule_WCNF){2, 1, -1, 0}; + + grammar = (grammar_t){ + .nonterms_count = 4, .terms_count = 2, .rules_count = 5, .rules = rules}; +} + +// S -> aS | a | eps in WCNF +// +// Terms: [0 a] +// Nonterms: [0 S] +// S -> SS [0 0 0 0] +// S -> a [0 0 -1 0] +// S -> eps [0 -1 -1 0] +void init_grammar_aS() { + LAGraph_rule_WCNF *rules = NULL ; + LAGraph_Calloc ((void **) &rules, 3, sizeof(LAGraph_rule_WCNF), msg); + + rules[0] = (LAGraph_rule_WCNF){0, 0, 0, 0}; + rules[1] = (LAGraph_rule_WCNF){0, 0, -1, 0}; + rules[2] = (LAGraph_rule_WCNF){0, -1, -1, 0}; + + grammar = (grammar_t){ + .nonterms_count = 1, .terms_count = 1, .rules_count = 3, .rules = rules}; +} + +// Complex grammar +// aaaabbbb or aaabbb +// +// Terms: [0 a] [1 b] +// Nonterms: [0 S] [n Sn] +// S -> S1 S2 [0 1 2 0] +// S -> S15 S16 [0 15 16 0] +// S1 -> S3 S4 [1 3 4 0] +// S2 -> S5 S6 [2 5 6 0] +// S3 -> S7 S8 [3 7 8 0] +// S4 -> S9 S10 [4 9 10 0] +// S5 -> S11 S12 [5 11 12 0] +// S6 -> S13 S14 [6 13 14 0] +// S16 -> S17 S18 [16 17 18 0] +// S17 -> S19 S20 [17 19 20 0] +// S18 -> S21 S22 [18 21 22 0] +// S22 -> S23 S24 [22 23 24 0] +// S7 -> a [7 0 -1 0] +// S8 -> a [8 0 -1 0] +// S9 -> a [9 0 -1 0] +// S10 -> a [10 0 -1 0] +// S11 -> b [11 1 -1 0] +// S12 -> b [12 1 -1 0] +// S13 -> b [13 1 -1 0] +// S14 -> b [14 1 -1 0] +// S15 -> a [15 0 -1 0] +// S19 -> a [19 0 -1 0] +// S20 -> a [20 0 -1 0] +// S21 -> b [21 1 -1 0] +// S23 -> b [23 1 -1 0] +// S24 -> b [24 1 -1 0] +void init_grammar_complex() { + LAGraph_rule_WCNF *rules = NULL ; + LAGraph_Calloc ((void **) &rules, 26, sizeof(LAGraph_rule_WCNF), msg); + + rules[0] = (LAGraph_rule_WCNF){0, 1, 2, 0}; + rules[1] = (LAGraph_rule_WCNF){0, 15, 16, 0}; + rules[2] = (LAGraph_rule_WCNF){1, 3, 4, 0}; + rules[3] = (LAGraph_rule_WCNF){2, 5, 6, 0}; + rules[4] = (LAGraph_rule_WCNF){3, 7, 8, 0}; + rules[5] = (LAGraph_rule_WCNF){4, 9, 10, 0}; + rules[6] = (LAGraph_rule_WCNF){5, 11, 12, 0}; + rules[7] = (LAGraph_rule_WCNF){6, 13, 14, 0}; + rules[8] = (LAGraph_rule_WCNF){16, 17, 18, 0}; + rules[9] = (LAGraph_rule_WCNF){17, 19, 20, 0}; + rules[10] = (LAGraph_rule_WCNF){18, 21, 22, 0}; + rules[11] = (LAGraph_rule_WCNF){22, 23, 24, 0}; + rules[12] = (LAGraph_rule_WCNF){7, 0, -1, 0}; + rules[13] = (LAGraph_rule_WCNF){8, 0, -1, 0}; + rules[14] = (LAGraph_rule_WCNF){9, 0, -1, 0}; + rules[15] = (LAGraph_rule_WCNF){10, 0, -1, 0}; + rules[16] = (LAGraph_rule_WCNF){11, 1, -1, 0}; + rules[17] = (LAGraph_rule_WCNF){12, 1, -1, 0}; + rules[18] = (LAGraph_rule_WCNF){13, 1, -1, 0}; + rules[19] = (LAGraph_rule_WCNF){14, 1, -1, 0}; + rules[20] = (LAGraph_rule_WCNF){15, 0, -1, 0}; + rules[21] = (LAGraph_rule_WCNF){19, 0, -1, 0}; + rules[22] = (LAGraph_rule_WCNF){20, 0, -1, 0}; + rules[23] = (LAGraph_rule_WCNF){21, 1, -1, 0}; + rules[24] = (LAGraph_rule_WCNF){23, 1, -1, 0}; + rules[25] = (LAGraph_rule_WCNF){24, 1, -1, 0}; + + grammar = (grammar_t){ + .nonterms_count = 25, .terms_count = 2, .rules_count = 26, .rules = rules}; +} + +//==================== +// Graphs +//==================== + +// Graph: +// +// 0 -a-> 1 +// 1 -a-> 2 +// 2 -a-> 0 +// 0 -b-> 3 +// 3 -b-> 0 +void init_graph_double_cycle() { + LAGraph_Calloc ((void **) &adj_matrices, 2, sizeof (GrB_Matrix), msg) ; + n_adj_matrices = 2 ; + + GrB_Matrix adj_matrix_a, adj_matrix_b; + OK(GrB_Matrix_new(&adj_matrix_a, GrB_BOOL, 4, 4)); + OK(GrB_Matrix_new(&adj_matrix_b, GrB_BOOL, 4, 4)); + + OK(GrB_Matrix_setElement(adj_matrix_a, true, 0, 1)); + OK(GrB_Matrix_setElement(adj_matrix_a, true, 1, 2)); + OK(GrB_Matrix_setElement(adj_matrix_a, true, 2, 0)); + + OK(GrB_Matrix_setElement(adj_matrix_b, true, 0, 3)); + OK(GrB_Matrix_setElement(adj_matrix_b, true, 3, 0)); + + adj_matrices[0] = adj_matrix_a; + adj_matrices[1] = adj_matrix_b; +} + +// Graph: +// +// 0 -a-> 1 +// 1 -a-> 2 +// 2 -a-> 3 +// 3 -a-> 4 +// 3 -b-> 5 +// 4 -b-> 3 +// 5 -b-> 6 +// 6 -b-> 7 +void init_graph_1() { + LAGraph_Calloc ((void **) &adj_matrices, 2, sizeof (GrB_Matrix), msg) ; + n_adj_matrices = 2 ; + + GrB_Matrix adj_matrix_a, adj_matrix_b; + OK(GrB_Matrix_new(&adj_matrix_a, GrB_BOOL, 8, 8)); + OK(GrB_Matrix_new(&adj_matrix_b, GrB_BOOL, 8, 8)); + + OK(GrB_Matrix_setElement(adj_matrix_a, true, 0, 1)); + OK(GrB_Matrix_setElement(adj_matrix_a, true, 1, 2)); + OK(GrB_Matrix_setElement(adj_matrix_a, true, 2, 3)); + OK(GrB_Matrix_setElement(adj_matrix_a, true, 3, 4)); + + OK(GrB_Matrix_setElement(adj_matrix_b, true, 3, 5)); + OK(GrB_Matrix_setElement(adj_matrix_b, true, 4, 3)); + OK(GrB_Matrix_setElement(adj_matrix_b, true, 5, 6)); + OK(GrB_Matrix_setElement(adj_matrix_b, true, 6, 7)); + + adj_matrices[0] = adj_matrix_a; + adj_matrices[1] = adj_matrix_b; +} + +// Graph: +// +// 0 -a-> 2 +// 1 -a-> 2 +// 3 -a-> 5 +// 4 -a-> 5 +// 2 -a-> 6 +// 5 -a-> 6 +// 2 -b-> 0 +// 2 -b-> 1 +// 5 -b-> 3 +// 5 -b-> 4 +// 6 -b-> 2 +// 6 -b-> 5 +void init_graph_tree() { + LAGraph_Calloc ((void **) &adj_matrices, 2, sizeof (GrB_Matrix), msg) ; + n_adj_matrices = 2 ; + + GrB_Matrix adj_matrix_a, adj_matrix_b; + OK(GrB_Matrix_new(&adj_matrix_a, GrB_BOOL, 7, 7)); + OK(GrB_Matrix_new(&adj_matrix_b, GrB_BOOL, 7, 7)); + + OK(GrB_Matrix_setElement(adj_matrix_a, true, 0, 2)); + OK(GrB_Matrix_setElement(adj_matrix_a, true, 1, 2)); + OK(GrB_Matrix_setElement(adj_matrix_a, true, 3, 5)); + OK(GrB_Matrix_setElement(adj_matrix_a, true, 4, 5)); + OK(GrB_Matrix_setElement(adj_matrix_a, true, 2, 6)); + OK(GrB_Matrix_setElement(adj_matrix_a, true, 5, 6)); + + OK(GrB_Matrix_setElement(adj_matrix_b, true, 2, 0)); + OK(GrB_Matrix_setElement(adj_matrix_b, true, 2, 1)); + OK(GrB_Matrix_setElement(adj_matrix_b, true, 5, 3)); + OK(GrB_Matrix_setElement(adj_matrix_b, true, 5, 4)); + OK(GrB_Matrix_setElement(adj_matrix_b, true, 6, 2)); + OK(GrB_Matrix_setElement(adj_matrix_b, true, 6, 5)); + + adj_matrices[0] = adj_matrix_a; + adj_matrices[1] = adj_matrix_b; +} + +// Graph: +// +// 0 -a-> 1 +// 1 -a-> 2 +// 2 -a-> 0 +void init_graph_one_cycle() { + LAGraph_Calloc ((void **) &adj_matrices, 1, sizeof (GrB_Matrix), msg) ; + n_adj_matrices = 1 ; + + GrB_Matrix adj_matrix_a; + GrB_Matrix_new(&adj_matrix_a, GrB_BOOL, 3, 3); + + OK(GrB_Matrix_setElement(adj_matrix_a, true, 0, 1)); + OK(GrB_Matrix_setElement(adj_matrix_a, true, 1, 2)); + OK(GrB_Matrix_setElement(adj_matrix_a, true, 2, 0)); + + adj_matrices[0] = adj_matrix_a; +} + +// Graph: + +// 0 -a-> 1 +// 1 -a-> 2 +// 2 -b-> 3 +// 3 -b-> 4 +void init_graph_line() { + LAGraph_Calloc ((void **) &adj_matrices, 2, sizeof (GrB_Matrix), msg) ; + n_adj_matrices = 2 ; + + GrB_Matrix adj_matrix_a, adj_matrix_b; + GrB_Matrix_new(&adj_matrix_a, GrB_BOOL, 5, 5); + GrB_Matrix_new(&adj_matrix_b, GrB_BOOL, 5, 5); + + OK(GrB_Matrix_setElement(adj_matrix_a, true, 0, 1)); + OK(GrB_Matrix_setElement(adj_matrix_a, true, 1, 2)); + + OK(GrB_Matrix_setElement(adj_matrix_b, true, 2, 3)); + OK(GrB_Matrix_setElement(adj_matrix_b, true, 3, 4)); + + adj_matrices[0] = adj_matrix_a; + adj_matrices[1] = adj_matrix_b; +} + +// Graph: + +// 0 -a-> 0 +// 0 -b-> 1 +// 1 -c-> 2 +void init_graph_2() { + LAGraph_Calloc ((void **) &adj_matrices, 3, sizeof (GrB_Matrix), msg) ; + n_adj_matrices = 3 ; + + GrB_Matrix adj_matrix_a, adj_matrix_b, adj_matrix_c; + GrB_Matrix_new(&adj_matrix_a, GrB_BOOL, 3, 3); + GrB_Matrix_new(&adj_matrix_b, GrB_BOOL, 3, 3); + GrB_Matrix_new(&adj_matrix_c, GrB_BOOL, 3, 3); + + OK(GrB_Matrix_setElement(adj_matrix_a, true, 0, 0)); + OK(GrB_Matrix_setElement(adj_matrix_b, true, 0, 1)); + OK(GrB_Matrix_setElement(adj_matrix_c, true, 1, 2)); + + adj_matrices[0] = adj_matrix_a; + adj_matrices[1] = adj_matrix_b; + adj_matrices[2] = adj_matrix_c; +} + +// Graph: + +// 0 -a-> 1 +// 1 -a-> 0 +// 0 -b-> 0 +void init_graph_3() { + LAGraph_Calloc ((void **) &adj_matrices, 2, sizeof (GrB_Matrix), msg) ; + n_adj_matrices = 2 ; + + GrB_Matrix adj_matrix_a, adj_matrix_b; + GrB_Matrix_new(&adj_matrix_a, GrB_BOOL, 2, 2); + GrB_Matrix_new(&adj_matrix_b, GrB_BOOL, 2, 2); + + OK(GrB_Matrix_setElement(adj_matrix_a, true, 0, 1)); + OK(GrB_Matrix_setElement(adj_matrix_a, true, 1, 0)); + OK(GrB_Matrix_setElement(adj_matrix_b, true, 0, 0)); + + adj_matrices[0] = adj_matrix_a; + adj_matrices[1] = adj_matrix_b; +} + +// Graph: + +// 0 -b-> 1 +// 1 -b-> 0 +void init_graph_4() { + LAGraph_Calloc ((void **) &adj_matrices, 2, sizeof (GrB_Matrix), msg) ; + n_adj_matrices = 2 ; + + GrB_Matrix adj_matrix_a, adj_matrix_b; + GrB_Matrix_new(&adj_matrix_a, GrB_BOOL, 2, 2); + GrB_Matrix_new(&adj_matrix_b, GrB_BOOL, 2, 2); + + OK(GrB_Matrix_setElement(adj_matrix_b, true, 0, 1)); + OK(GrB_Matrix_setElement(adj_matrix_b, true, 1, 0)); + + adj_matrices[0] = adj_matrix_a; + adj_matrices[1] = adj_matrix_b; +} + +//==================== +// Tests with valid result +//==================== + +void test_CFL_AllPaths_cycle(void) { +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aS(); + init_graph_one_cycle(); + init_outputs() ; + + OK(run_algorithm()); + check_result(0, "(0, 0):[0,1,2,INDEX_MAX] (0, 1):[0,1,2,INDEX_MAX] (0, 2):[0,1,2] (1, 0):[0,1,2] (1, 1):[0,1,2,INDEX_MAX] (1, 2):[0,1,2,INDEX_MAX] (2, 0):[0,1,2,INDEX_MAX] (2, 1):[0,1,2] (2, 2):[0,1,2,INDEX_MAX]"); + + free_workspace(); + teardown(); +#endif +} + +void test_CFL_AllPaths_two_cycle(void) { +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_graph_double_cycle(); + init_outputs() ; + + OK(run_algorithm()); + check_result(0, "(0, 0):[1] (0, 3):[1] (1, 0):[2] (1, 3):[2] (2, 0):[0] (2, 3):[0]"); + check_result(1,"(0, 1):[INDEX_MAX] (1, 2):[INDEX_MAX] (2, 0):[INDEX_MAX]"); + + free_workspace(); + teardown(); +#endif +} + +void test_CFL_AllPaths_labels_more_than_nonterms(void) { +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_graph_2(); + init_outputs() ; + + OK(run_algorithm()); + check_result(0, "(0, 1):[0]"); + check_result(1,"(0, 0):[INDEX_MAX]"); + check_result(2,"(0, 1):[INDEX_MAX]"); + check_result(3,""); + + free_workspace(); + teardown(); +#endif +} + +void test_CFL_AllPaths_complex_grammar(void) { +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_complex(); + init_graph_1(); + init_outputs() ; + + OK(run_algorithm()); + check_result(0, "(0, 7):[1,4] (1, 6):[2]"); + check_result(1, "(0, 4):[2]"); + check_result(6,"(3, 6):[5] (4, 5):[3] (5, 7):[6]"); + check_result(7,"(0, 1):[INDEX_MAX] (1, 2):[INDEX_MAX] (2, 3):[INDEX_MAX] (3, 4):[INDEX_MAX]"); + + + free_workspace(); + teardown(); +#endif +} + +void test_CFL_AllPaths_tree(void) { +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_graph_tree(); + init_outputs() ; + + OK(run_algorithm()); + check_result(0,"(0, 0):[2] (0, 1):[2] (0, 3):[2] (0, 4):[2] (1, 0):[2] (1, 1):[2] (1, 3):[2] (1, 4):[2] (2, 2):[6] (2, 5):[6] (3, 0):[5] (3, 1):[5] (3, 3):[5] (3, 4):[5] (4, 0):[5] (4, 1):[5] (4, 3):[5] (4, 4):[5] (5, 2):[6] (5, 5):[6]"); + check_result(1, "(0, 2):[INDEX_MAX] (1, 2):[INDEX_MAX] (2, 6):[INDEX_MAX] (3, 5):[INDEX_MAX] (4, 5):[INDEX_MAX] (5, 6):[INDEX_MAX]"); + + free_workspace(); + teardown(); +#endif +} + +void test_CFL_AllPaths_line(void) { +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_graph_line(); + init_outputs() ; + + OK(run_algorithm()); + check_result(0,"(0, 4):[1] (1, 3):[2]"); + check_result(1,"(0, 1):[INDEX_MAX] (1, 2):[INDEX_MAX]"); + check_result(2,"(2, 3):[INDEX_MAX] (3, 4):[INDEX_MAX]"); + check_result(3,"(1, 4):[3]"); + + free_workspace(); + teardown(); +#endif +} + +void test_CFL_AllPaths_two_nodes_cycle(void) { +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_graph_3(); + init_outputs() ; + + OK(run_algorithm()); + check_result(0,"(0, 0):[1] (1, 0):[0]"); + check_result(1,"(0, 1):[INDEX_MAX] (1, 0):[INDEX_MAX]"); + check_result(2, "(0, 0):[INDEX_MAX]"); + check_result(3, "(0, 0):[0] (1, 0):[0]"); + + free_workspace(); + teardown(); +#endif +} + +void test_CFL_AllPaths_with_empty_adj_matrix(void) { +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aS(); + init_graph_4(); + init_outputs() ; + + OK(run_algorithm()); + check_result(0, "(0, 0):[0,INDEX_MAX] (1, 1):[1,INDEX_MAX]"); + + free_workspace(); + teardown(); +#endif +} + +//==================== +// Tests with invalid result +//==================== + +void test_CFL_AllPaths_invalid_rules(void) { +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_graph_double_cycle(); + init_outputs() ; + + // Rule [Variable -> _ B] + grammar.rules[0] = + (LAGraph_rule_WCNF){.nonterm = 0, .prod_A = -1, .prod_B = 1, .index = 0}; + check_error(GrB_INVALID_VALUE); + + // Rule [_ -> A B] + grammar.rules[0] = + (LAGraph_rule_WCNF){.nonterm = -1, .prod_A = 1, .prod_B = 2, .index = 0}; + check_error(GrB_INVALID_VALUE); + + // Rule [C -> A B], where C >= nonterms_count + grammar.rules[0] = + (LAGraph_rule_WCNF){.nonterm = 10, .prod_A = 1, .prod_B = 2, .index = 0}; + check_error(GrB_INVALID_VALUE); + + // Rule [S -> A B], where A >= nonterms_count + grammar.rules[0] = + (LAGraph_rule_WCNF){.nonterm = 0, .prod_A = 10, .prod_B = 2, .index = 0}; + check_error(GrB_INVALID_VALUE); + + // Rule [C -> t], where t >= terms_count + grammar.rules[0] = + (LAGraph_rule_WCNF){.nonterm = 0, .prod_A = 10, .prod_B = -1, .index = 0}; + check_error(GrB_INVALID_VALUE); + + free_workspace(); + teardown(); +#endif +} + +void test_CFL_AllPaths_null_pointers(void) { +#if LAGRAPH_SUITESPARSE + + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_graph_double_cycle(); + init_outputs() ; + +// adj_matrices[0] = NULL; +// adj_matrices[1] = NULL; + GrB_free(&adj_matrices[0]); + GrB_free(&adj_matrices[1]); + + check_error(GrB_NULL_POINTER); + +// adj_matrices = NULL; + LAGraph_Free ((void **) &adj_matrices, msg); + check_error(GrB_NULL_POINTER); + + free_workspace(); + init_grammar_aSb(); + init_graph_double_cycle(); + init_outputs() ; + +// outputs = NULL; + LAGraph_Free ((void **) &outputs, msg); + check_error(GrB_NULL_POINTER); + + free_workspace(); + init_grammar_aSb(); + init_graph_double_cycle(); + init_outputs() ; + +// grammar.rules = NULL; + LAGraph_Free ((void **) &grammar.rules, msg); + check_error(GrB_NULL_POINTER); + + free_workspace(); + teardown(); +#endif +} + +TEST_LIST = {{"CFL_AllPaths_complex_grammar", test_CFL_AllPaths_complex_grammar}, + {"CFL_AllPaths_cycle", test_CFL_AllPaths_cycle}, + {"CFL_AllPaths_two_cycle", test_CFL_AllPaths_two_cycle}, + {"CFL_AllPaths_labels_more_than_nonterms", + test_CFL_AllPaths_labels_more_than_nonterms}, + {"CFL_AllPaths_tree", test_CFL_AllPaths_tree}, + {"CFL_AllPaths_line", test_CFL_AllPaths_line}, + {"CFL_AllPaths_two_nodes_cycle", test_CFL_AllPaths_two_nodes_cycle}, + {"CFL_AllPaths_basic_invalid_rules", test_CFL_AllPaths_invalid_rules}, + {"CFL_AllPaths_with_empty_adj_matrix", test_CFL_AllPaths_with_empty_adj_matrix}, + #if !defined ( GRAPHBLAS_HAS_CUDA ) + {"CFL_AllPaths_null_pointers", test_CFL_AllPaths_null_pointers}, + #endif + {NULL, NULL}}; + diff --git a/include/LAGraphX.h b/include/LAGraphX.h index 3355addb2e..8b340d7dca 100644 --- a/include/LAGraphX.h +++ b/include/LAGraphX.h @@ -1096,6 +1096,61 @@ GrB_Info LAGraph_CFL_reachability char *msg // Message string for error reporting. ) ; +typedef GrB_Info (*CFPQ_get_nvals)(GrB_Index *nvals, const GrB_Matrix A); + +typedef struct +{ + GrB_Type type; + GrB_Semiring semiring; + GrB_BinaryOp add; + GrB_BinaryOp mult; + GrB_BinaryOp init_path; // Function for defining elements used to describe information about paths of length 0 and 1. + // Depends on the specific task, therefore it is included in this structure. + CFPQ_get_nvals get_nvals; + GrB_Scalar bottom_scalar; +} CFL_Semiring; + +typedef struct +{ + GrB_Index n; + GrB_Index* middle; +} AllPathsElem; + +GrB_Info LAGraph_CFPQ_core +( + // Output + GrB_Matrix *outputs, // Array of matrices containing results. + // The size of the array must be equal to nonterms_count. + // + // outputs[k]: (i, j) contains a corresponding semiring value if and only if there is a path + // from node i to node j whose edge labels form a word + // derivable from the non-terminal 'k' of the specified CFG. + // Input + const GrB_Matrix *adj_matrices, // Array of adjacency matrices representing the graph. + // The length of this array is equal to the count of + // terminals (terms_count). + // + // adj_matrices[t]: (i, j) == 1 if and only if there + // is an edge between nodes i and j with the label of + // the terminal corresponding to index 't' (where t is + // in the range [0, terms_count - 1]). + int64_t terms_count, // The total number of terminal symbols in the CFG. + int64_t nonterms_count, // The total number of non-terminal symbols in the CFG. + const LAGraph_rule_WCNF *rules, // The rules of the CFG. + int64_t rules_count, // The total number of rules in the CFG. + const CFL_Semiring *semiring, // The algebraic structure that defines operations on matrices for a specific problem + char *msg // Message string for error reporting. +); + +GrB_Info LAGraph_CFL_AllPaths( + GrB_Matrix *outputs, + const GrB_Matrix *adj_matrices, + int64_t terms_count, + int64_t nonterms_count, + const LAGraph_rule_WCNF *rules, + int64_t rules_count, + char *msg); + //------------------------------------------------------------------------------ // a simple example of an algorithm //------------------------------------------------------------------------------