diff --git a/experimental/algorithm/LAGraph_CFL_extract_single_path.c b/experimental/algorithm/LAGraph_CFL_extract_single_path.c new file mode 100644 index 0000000000..7f54884346 --- /dev/null +++ b/experimental/algorithm/LAGraph_CFL_extract_single_path.c @@ -0,0 +1,342 @@ +#define LG_FREE_WORK \ + { \ + LAGraph_Free((void **)&eps_rules, NULL); \ + LAGraph_Free((void **)&term_rules, NULL); \ + LAGraph_Free((void **)&bin_rules, NULL); \ + } + +#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_CFL_extract_single_path( + // Output + Path *output, + // Input + GrB_Index start, + GrB_Index end, + int32_t nonterm, + const GrB_Matrix *adj_matrices, + const GrB_Matrix *T, + 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. +) +{ + LG_CLEAR_MSG; + size_t msg_len = 0; // For error formatting + output->len = 0; + output->path = 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] + 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(nonterm < nonterms_count, GrB_INVALID_VALUE, + "The start non-terminal must be no greater than the number of non-terminals."); + LG_ASSERT_MSG(T != NULL, GrB_NULL_POINTER, "The T 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); + found_null = true; + } + + if (found_null) + { + LG_FREE_ALL; + return GrB_NULL_POINTER; + } + + // Find null T matrices + found_null = false; + for (int64_t i = 0; i < nonterms_count; i++) + { + if (T[i] != NULL) + continue; + + if (!found_null) + { + ADD_TO_MSG("T matrices with these indexes are null:"); + } + ADD_TO_MSG(" %" PRId64, i); + + found_null = true; + } + if (found_null) + { + LG_FREE_ALL; + return GrB_NULL_POINTER; + } + + 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; + } + PathIndex index; + GrB_Info info = GrB_Matrix_extractElement_UDT(&index, T[nonterm], start, end); + if (info == GrB_SUCCESS) // Such a path exists + { + if (index.height == 1) + { + if (start == end) // Height = 1 and start = end is an empty eps-path + { + for (size_t i = 0; i < eps_rules_count; i++) + { + LAGraph_rule_WCNF term_rule = rules[eps_rules[i]]; + if (term_rule.nonterm == nonterm) + { + LG_FREE_WORK; + return GrB_SUCCESS; + } + } + } + // Height = 1 and different vertices is a term-path + for (int64_t i = 0; i < terms_count; i++) + { + bool edge; + if (GrB_Matrix_extractElement_BOOL(&edge, adj_matrices[i], start, end) == GrB_SUCCESS) + { + for (size_t j = 0; j < term_rules_count; j++) + { + LAGraph_rule_WCNF term_rule = rules[term_rules[j]]; + if (term_rule.nonterm == nonterm && term_rule.prod_A == i) + { + LG_TRY(LAGraph_Calloc((void **)&output->path, 1, sizeof(Edge), msg)); + output->len = 1; + output->path[0] = (Edge){start, i, end}; + LG_FREE_WORK; + return GrB_SUCCESS; + } + } + } + } + // If couldn't find rules for outputting an empty or terminal path, + // then the path were looking for doesn't match the rules + LG_FREE_WORK; + ADD_TO_MSG("The extracted path does not match the input grammar."); + return GrB_NO_VALUE; + } + // Rules of the form Nonterm -> Nonterm * Nonterm are traversed recursively and merged + for (size_t i = 0; i < bin_rules_count; i++) + { + LAGraph_rule_WCNF term_rule = rules[bin_rules[i]]; + if (term_rule.nonterm != nonterm) + { + continue; + } + PathIndex indexB, indexC; + if ((info = GrB_Matrix_extractElement_UDT(&indexB, T[term_rule.prod_A], start, index.middle)) != GrB_SUCCESS) + { + // If haven't found such a piece of the path, then continue. + if (info != GrB_NO_VALUE) + { + LG_FREE_WORK; + return info; + } + + continue; + } + if ((info = GrB_Matrix_extractElement_UDT(&indexC, T[term_rule.prod_B], index.middle, end)) != GrB_SUCCESS) + { + if (info != GrB_NO_VALUE) + { + LG_FREE_WORK; + return info; + } + continue; + } + + // Height compliance check + int32_t maxH = (indexB.height > indexC.height ? indexB.height : indexC.height); + if (index.height != maxH + 1) + { + continue; + } + + Path left, right; + // If didn't find the path, try the other rules. + if ((info = LAGraph_CFL_extract_single_path(&left, start, index.middle, term_rule.prod_A, adj_matrices, T, terms_count, nonterms_count, rules, rules_count, msg)) != GrB_SUCCESS) + { + if (info == GrB_NO_VALUE) + { + continue; + } + LG_FREE_WORK; + return info; + } + if ((info = LAGraph_CFL_extract_single_path(&right, index.middle, end, term_rule.prod_B, adj_matrices, T, terms_count, nonterms_count, rules, rules_count, msg)) != GrB_SUCCESS) + { + if (info == GrB_NO_VALUE) + { + LG_TRY(LAGraph_Free((void **)&left.path, msg)); + continue; + } + LG_TRY(LAGraph_Free((void **)&left.path, msg)); + LG_FREE_WORK; + return info; + } + + output->len = left.len + right.len; + + LG_TRY(LAGraph_Calloc((void **)&output->path, output->len, sizeof(Edge), msg)); + + memcpy(output->path, left.path, left.len * sizeof(Edge)); + memcpy(output->path + left.len, right.path, right.len * sizeof(Edge)); + LG_TRY(LAGraph_Free((void **)&left.path, msg)); + LG_TRY(LAGraph_Free((void **)&right.path, msg)); + LG_FREE_WORK; + return GrB_SUCCESS; + } + + // If couldn't find rules for outputting an path, + // then the path were looking for doesn't match the rules + LG_FREE_WORK; + ADD_TO_MSG("The extracted path does not match the input grammar."); + return GrB_NO_VALUE; + } + // Such a path doesn't exists - return an empty path and GrB_NO_VALUE + else if (info == GrB_NO_VALUE) + { + LG_FREE_WORK; + return GrB_NO_VALUE; + } + // Return some other error + LG_FREE_WORK; + return info; +} diff --git a/experimental/algorithm/LAGraph_CFL_single_path.c b/experimental/algorithm/LAGraph_CFL_single_path.c new file mode 100644 index 0000000000..decbe217a2 --- /dev/null +++ b/experimental/algorithm/LAGraph_CFL_single_path.c @@ -0,0 +1,484 @@ +#define LG_FREE_WORK \ + { \ + GrB_free(&true_scalar); \ + GrB_free(&bottom_scalar); \ + GrB_free(&IPI_set); \ + GrB_free(&IPI_mult); \ + GrB_free(&PI_set); \ + GrB_free(&PI_mult); \ + GrB_free(&PI_add); \ + GrB_free(&PI_semiring); \ + GrB_free(&Theta); \ + GrB_free(&PI_monoid); \ + GrB_free(&identity_matrix); \ + LAGraph_Free((void **)&t_empty_flags, NULL); \ + LAGraph_Free((void **)&nnzs, NULL); \ + LAGraph_Free((void **)&eps_rules, NULL); \ + LAGraph_Free((void **)&term_rules, NULL); \ + LAGraph_Free((void **)&bin_rules, NULL); \ + } + +#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++; \ + } + +void add_path_index(PathIndex *z, const PathIndex *x, const PathIndex *y) +{ + // If one is ⊥, then we take another. + if (x->height == 0 && x->middle == 0) + { + *z = *y; + } + else if (y->height == 0 && y->middle == 0) + { + *z = *x; + } + // Take the path with the minimum height + else if (x->height < y->height || (x->height == y->height && x->middle <= y->middle)) + { + *z = *x; + } + else + { + *z = *y; + } +} + +void mult_path_index(PathIndex *z, + const PathIndex *x, GrB_Index ix, GrB_Index jx, + const PathIndex + *y, + GrB_Index iy, GrB_Index jy, + const void *theta) +{ + // Сoncatenation with ⊥ gives ⊥ + if ((x->height == 0 && x->middle == 0) || (y->height == 0 && y->middle == 0)) + { + z->height = 0; + z->middle = 0; + } + else + { + z->middle = jx; + z->height = (x->height > y->height ? x->height : y->height) + 1; + } +} + +void set_path_index(PathIndex *z, + const PathIndex *x, GrB_Index ix, GrB_Index jx, + const bool *edge_exist, GrB_Index i_edge, GrB_Index j_edge, + const void *theta) +{ + if (*edge_exist) + { + z->middle = ix; + z->height = 1; + } + else + { + z->middle = 0; + z->height = 0; + } +} + +#define SET_PATH_INDEX_DEFN \ + "void set_path_index( \n" \ + " PathIndex *z, \n" \ + " const PathIndex *x, GrB_Index ix, GrB_Index jx, \n" \ + " const bool *edge_exist, GrB_Index i_edge, GrB_Index j_edge, \n" \ + " const void *theta) \n" \ + "{ \n" \ + " if (*edge_exist) \n" \ + " { \n" \ + " z->middle = ix; \n" \ + " z->height = 1; \n" \ + " } \n" \ + " else \n" \ + " { \n" \ + " z->middle = 0; \n" \ + " z->height = 0; \n" \ + " } \n" \ + "}" + +#define MULT_PATH_INDEX_DEFN \ + "void mult_path_index( \n" \ + " PathIndex *z, \n" \ + " const PathIndex *x, GrB_Index ix, GrB_Index jx, \n" \ + " const PathIndex *y, GrB_Index iy, GrB_Index jy, \n" \ + " const void *theta) \n" \ + "{ \n" \ + " if ((x->height == 0 && x->middle == 0) || \n" \ + " (y->height == 0 && y->middle == 0)) \n" \ + " { \n" \ + " z->height = 0; \n" \ + " z->middle = 0; \n" \ + " } \n" \ + " else \n" \ + " { \n" \ + " z->middle = jx; \n" \ + " z->height = (x->height > y->height ? \n" \ + " x->height : y->height) + 1; \n" \ + " } \n" \ + "}" + +GrB_Info LAGraph_CFL_single_path( + // 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 PathIndex 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. +) +{ + // Declare workspace + bool *t_empty_flags = NULL; // t_empty_flags[i] == true <=> outputs[i] is empty + uint64_t *nnzs = NULL; + LG_CLEAR_MSG; + size_t msg_len = 0; // For error formatting + GrB_Matrix identity_matrix = NULL; + GrB_Type PI_type = NULL; // Type PathIndex + + // Semiring components + GrB_BinaryOp PI_add = NULL; + GrB_Monoid PI_monoid = NULL; + GxB_IndexBinaryOp IPI_mult = NULL; + GrB_BinaryOp PI_mult = NULL; + GrB_Semiring PI_semiring = NULL; + GxB_IndexBinaryOp IPI_set = NULL; + GrB_BinaryOp PI_set = NULL; + + GrB_Scalar Theta = NULL; + GrB_Scalar true_scalar = NULL; + GrB_Scalar bottom_scalar = 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] + + 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); + found_null = true; + } + + if (found_null) + { + LG_FREE_ALL; + return GrB_NULL_POINTER; + } + + // Check that outputs are initialized correctly + found_null = false; + for (int64_t i = 0; i < nonterms_count; i++) + { + if (outputs[i] != NULL) + continue; + + if (!found_null) + { + ADD_TO_MSG("Outputs matrices with these indexes are null:"); + } + ADD_TO_MSG(" %" PRId64, i); + + found_null = true; + } + + if (found_null) + { + LG_FREE_ALL; + return GrB_NULL_POINTER; + } + + GRB_TRY(GxB_Matrix_type(&PI_type, outputs[0])); + + // Theta cannot be NULL + GRB_TRY(GrB_Scalar_new(&Theta, GrB_BOOL)); + GRB_TRY(GrB_Scalar_setElement_BOOL(Theta, false)); + + PathIndex bottom = {0, 0}; + GRB_TRY(GrB_Scalar_new(&bottom_scalar, PI_type)); + GRB_TRY(GrB_Scalar_setElement_UDT(bottom_scalar, (void *)(&bottom))); + + GRB_TRY(GrB_Scalar_new(&true_scalar, GrB_BOOL)); + GRB_TRY(GrB_Scalar_setElement_BOOL(true_scalar, true)); + + // Create semiring + GRB_TRY(GrB_BinaryOp_new( + &PI_add, + (void *)add_path_index, + PI_type, + PI_type, + PI_type)); + + GRB_TRY(GrB_Monoid_new( + &PI_monoid, + PI_add, + (void *)(&bottom))); // ⊥ - neutral element for the addition operation + + GRB_TRY(GxB_IndexBinaryOp_new( + &IPI_mult, + (void *)mult_path_index, + PI_type, + PI_type, + PI_type, + GrB_BOOL, + "mult_path_index", + MULT_PATH_INDEX_DEFN)); + + GRB_TRY(GxB_BinaryOp_new_IndexOp( + &PI_mult, + IPI_mult, + Theta)); + + GRB_TRY(GrB_Semiring_new( + &PI_semiring, + PI_monoid, + PI_mult)); + + GRB_TRY(GxB_IndexBinaryOp_new( + &IPI_set, + (void *)set_path_index, + PI_type, + PI_type, + GrB_BOOL, + GrB_BOOL, + "set_path_index", + SET_PATH_INDEX_DEFN)); + + GRB_TRY(GxB_BinaryOp_new_IndexOp( + &PI_set, + IPI_set, + Theta)); + + 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; + } + GrB_BinaryOp acc_op = t_empty_flags[term_rule.nonterm] ? GrB_NULL : PI_add; + GxB_eWiseUnion( + outputs[term_rule.nonterm], GrB_NULL, acc_op, PI_set, + outputs[term_rule.nonterm], bottom_scalar, adj_matrices[term_rule.prod_A], true_scalar, GrB_NULL); + + t_empty_flags[term_rule.nonterm] = false; + } + + // Rule [Variable -> eps] + GrB_Index n; + GRB_TRY(GrB_Matrix_ncols(&n, adj_matrices[0])); + 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)); + + 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 : PI_add; + GxB_eWiseUnion( + outputs[eps_rule.nonterm], GrB_NULL, acc_op, PI_set, + outputs[eps_rule.nonterm], bottom_scalar, identity_matrix, true_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 : PI_add; + GRB_TRY(GrB_mxm(outputs[bin_rule.nonterm], GrB_NULL, acc_op, + PI_semiring, outputs[bin_rule.prod_A], outputs[bin_rule.prod_B], + GrB_NULL)) + + GrB_Index new_nnz; + GRB_TRY(GrB_Matrix_nvals(&new_nnz, outputs[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; + } + } + + LG_FREE_WORK; + return GrB_SUCCESS; +} diff --git a/experimental/test/test_CFL_extract_single_path.c b/experimental/test/test_CFL_extract_single_path.c new file mode 100644 index 0000000000..a8b36e21c4 --- /dev/null +++ b/experimental/test/test_CFL_extract_single_path.c @@ -0,0 +1,997 @@ +#include +#include +#include +#include +#include +#include + +#define run_aux_algorithm() \ + LAGraph_CFL_single_path(outputs, adj_matrices, grammar.terms_count, \ + grammar.nonterms_count, grammar.rules, grammar.rules_count, \ + msg) + +#define run_algorithm() \ + LAGraph_CFL_extract_single_path(&path, start, end, 0, adj_matrices, outputs, 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); \ + } + +// Check the path through the string +#define check_result1(expected_ret, result) \ + { \ + retval = run_algorithm(); \ + TEST_CHECK(retval == expected_ret); \ + TEST_MSG("retval = %d (%s)", retval, msg); \ + char *expected = path_to_str(); \ + TEST_CHECK(strcmp(result, expected) == 0); \ + TEST_MSG("Wrong result. Actual: %s", expected); \ + LAGraph_Free((void **)&expected, msg); \ + } + +// Check the path according to its type +#define check_result2(expected_path) \ + { \ + retval = run_algorithm(); \ + if (expected_path == non_exist) \ + { \ + TEST_CHECK(retval == GrB_NO_VALUE); \ + TEST_MSG("retval = %d (%s)", retval, msg); \ + TEST_CHECK(check_empty_path()); \ + TEST_MSG("Wrong result"); \ + } \ + else if (expected_path == empty) \ + { \ + TEST_CHECK(retval == GrB_SUCCESS); \ + TEST_MSG("retval = %d (%s)", retval, msg); \ + TEST_CHECK(check_empty_path()); \ + TEST_MSG("Wrong result"); \ + } \ + else \ + { \ + TEST_CHECK(retval == GrB_SUCCESS); \ + TEST_MSG("retval = %d (%s)", retval, msg); \ + TEST_CHECK(check_non_empty_path()); \ + TEST_MSG("Wrong result"); \ + } \ + } + +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}; +Path path; +char msg[LAGRAPH_MSG_LEN]; +GrB_Type PI_type = NULL; + +typedef enum // Path type +{ + empty = 0, // Empty path - through a rule with eps + non_empty = 1, // Path with non-zero length + non_exist = 2, // Path does not exist +} Type_of_path; + +void setup() +{ + LAGraph_Init(msg); + GrB_Type_new(&PI_type, sizeof(PathIndex)); +} + +void teardown(void) { LAGraph_Finalize(msg); } + +void init_outputs() +{ + LAGraph_Calloc((void **)&outputs, + grammar.nonterms_count, sizeof(GrB_Matrix), msg); +} + +bool check_empty_path() +{ + return path.len == 0 && path.path == NULL; +} + +bool check_non_empty_path() +{ + if (path.len == 0) + { + return false; + } + for (size_t i = 0; i < path.len; i++) + { + Edge cur_edge = path.path[i]; + bool edge_exist; + if (GrB_Matrix_extractElement_BOOL(&edge_exist, adj_matrices[cur_edge.label], cur_edge.start, cur_edge.end) != GrB_SUCCESS) + { + return false; + } + } + return true; +} + +char *path_to_str() +{ + char *result_str = NULL; + // 15 - size of "%ld->(%d)->%ld " + // 15 - size of "len: %zu path: " + // we need 16 + len * 15 + LAGraph_Malloc((void **)&result_str, 15 + path.len * 15, sizeof(char), msg); + result_str[0] = '\0'; + + sprintf(result_str + strlen(result_str), "len: %zu path: ", path.len); + + if (path.len > 0) + { + for (size_t i = 0; i < path.len; i++) + { + sprintf(result_str + strlen(result_str), "%" PRIu64 "->(%" PRId32 ")->%" PRIu64 " ", path.path[i].start, path.path[i].label, path.path[i].end); + } + } + + return result_str; +} + +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++) + { + GrB_free(&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; + + for (int64_t i = 0; i < grammar.nonterms_count; i++) + { + GrB_Matrix_new(&outputs[i], PI_type, 4, 4); + } +} + +// 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; + + for (int64_t i = 0; i < grammar.nonterms_count; i++) + { + GrB_Matrix_new(&outputs[i], PI_type, 3, 3); + } +} + +// 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; + + for (int64_t i = 0; i < grammar.nonterms_count; i++) + { + GrB_Matrix_new(&outputs[i], PI_type, 8, 8); + } +} + +// 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; + + for (int64_t i = 0; i < grammar.nonterms_count; i++) + { + GrB_Matrix_new(&outputs[i], PI_type, 7, 7); + } +} + +// 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; + + for (int64_t i = 0; i < grammar.nonterms_count; i++) + { + GrB_Matrix_new(&outputs[i], PI_type, 5, 5); + } +} + +// 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; + + for (int64_t i = 0; i < grammar.nonterms_count; i++) + { + GrB_Matrix_new(&outputs[i], PI_type, 3, 3); + } +} + +// 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; + + for (int64_t i = 0; i < grammar.nonterms_count; i++) + { + GrB_Matrix_new(&outputs[i], PI_type, 2, 2); + } +} + +// 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; + + for (int64_t i = 0; i < grammar.nonterms_count; i++) + { + GrB_Matrix_new(&outputs[i], PI_type, 2, 2); + } +} + +//===================== +// Tests full result +//===================== + +void test_CFL_extract_single_path_two_cycle(void) +{ +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_outputs(); + init_graph_double_cycle(); + int expected_ret[16] = {GrB_SUCCESS, GrB_NO_VALUE, GrB_NO_VALUE, GrB_SUCCESS, GrB_SUCCESS, GrB_NO_VALUE, GrB_NO_VALUE, GrB_SUCCESS, GrB_SUCCESS, GrB_NO_VALUE, GrB_NO_VALUE, GrB_SUCCESS, GrB_NO_VALUE, GrB_NO_VALUE, GrB_NO_VALUE, GrB_NO_VALUE}; + char *expected_path[16] = {"len: 12 path: 0->(0)->1 1->(0)->2 2->(0)->0 0->(0)->1 1->(0)->2 2->(0)->0 0->(1)->3 3->(1)->0 0->(1)->3 3->(1)->0 0->(1)->3 3->(1)->0 ", + "len: 0 path: ", + "len: 0 path: ", + "len: 6 path: 0->(0)->1 1->(0)->2 2->(0)->0 0->(1)->3 3->(1)->0 0->(1)->3 ", + "len: 4 path: 1->(0)->2 2->(0)->0 0->(1)->3 3->(1)->0 ", + "len: 0 path: ", + "len: 0 path: ", + "len: 10 path: 1->(0)->2 2->(0)->0 0->(0)->1 1->(0)->2 2->(0)->0 0->(1)->3 3->(1)->0 0->(1)->3 3->(1)->0 0->(1)->3 ", + "len: 8 path: 2->(0)->0 0->(0)->1 1->(0)->2 2->(0)->0 0->(1)->3 3->(1)->0 0->(1)->3 3->(1)->0 ", + "len: 0 path: ", + "len: 0 path: ", + "len: 2 path: 2->(0)->0 0->(1)->3 ", + "len: 0 path: ", + "len: 0 path: ", + "len: 0 path: ", + "len: 0 path: "}; + OK(run_aux_algorithm()); + for (GrB_Index start = 0; start < 4; start++) + { + for (GrB_Index end = 0; end < 4; end++) + { + check_result1(expected_ret[start * 4 + end], expected_path[start * 4 + end]); + if (path.len > 0) + { + LAGraph_Free((void **)&path.path, msg); + } + } + } + free_workspace(); + teardown(); +#endif +} + +//========================================== +// Tests that the path exists in the graph +//========================================== + +void test_CFL_extract_single_path_cycle(void) +{ +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aS(); + init_outputs(); + init_graph_one_cycle(); + int expected[9] = {empty, non_empty, non_empty, + non_empty, empty, non_empty, + non_empty, non_empty, empty}; + OK(run_aux_algorithm()); + for (GrB_Index start = 0; start < 3; start++) + { + for (GrB_Index end = 0; end < 3; end++) + { + check_result2(expected[start * 3 + end]); + if (path.len > 0) + { + LAGraph_Free((void **)&path.path, msg); + } + } + } + free_workspace(); + teardown(); +#endif +} + +void test_CFL_extract_single_path_labels_more_than_nonterms(void) +{ +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_outputs(); + init_graph_2(); + + int expected[9] = {non_exist, non_empty, non_exist, + non_exist, non_exist, non_exist, + non_exist, non_exist, non_exist}; + OK(run_aux_algorithm()); + for (GrB_Index start = 0; start < 3; start++) + { + for (GrB_Index end = 0; end < 3; end++) + { + check_result2(expected[start * 3 + end]); + if (path.len > 0) + { + LAGraph_Free((void **)&path.path, msg); + } + } + } + free_workspace(); + teardown(); +#endif +} + +void test_CFL_extract_single_path_complex_grammar(void) +{ +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_complex(); + init_outputs(); + init_graph_1(); + int expected[64] = {non_exist, non_exist, non_exist, non_exist, non_exist, non_exist, non_exist, non_empty, + non_exist, non_exist, non_exist, non_exist, non_exist, non_exist, non_empty, non_exist, + non_exist, non_exist, non_exist, non_exist, non_exist, non_exist, non_exist, non_exist, + non_exist, non_exist, non_exist, non_exist, non_exist, non_exist, non_exist, non_exist, + non_exist, non_exist, non_exist, non_exist, non_exist, non_exist, non_exist, non_exist, + non_exist, non_exist, non_exist, non_exist, non_exist, non_exist, non_exist, non_exist, + non_exist, non_exist, non_exist, non_exist, non_exist, non_exist, non_exist, non_exist, + non_exist, non_exist, non_exist, non_exist, non_exist, non_exist, non_exist, non_exist}; + OK(run_aux_algorithm()); + for (GrB_Index start = 0; start < 8; start++) + { + for (GrB_Index end = 0; end < 8; end++) + { + check_result2(expected[start * 8 + end]); + if (path.len > 0) + { + LAGraph_Free((void **)&path.path, msg); + } + } + } + free_workspace(); + teardown(); +#endif +} + +void test_CFL_extract_single_path_tree(void) +{ +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_outputs(); + init_graph_tree(); + + int expected[49] = {non_empty, non_empty, non_exist, non_empty, non_empty, non_exist, non_exist, + non_empty, non_empty, non_exist, non_empty, non_empty, non_exist, non_exist, + non_exist, non_exist, non_empty, non_exist, non_exist, non_empty, non_exist, + non_empty, non_empty, non_exist, non_empty, non_empty, non_exist, non_exist, + non_empty, non_empty, non_exist, non_empty, non_empty, non_exist, non_exist, + non_exist, non_exist, non_empty, non_exist, non_exist, non_empty, non_exist, + non_exist, non_exist, non_exist, non_exist, non_exist, non_exist, non_exist}; + OK(run_aux_algorithm()); + for (GrB_Index start = 0; start < 7; start++) + { + for (GrB_Index end = 0; end < 7; end++) + { + check_result2(expected[start * 7 + end]); + if (path.len > 0) + { + LAGraph_Free((void **)&path.path, msg); + } + } + } + free_workspace(); + teardown(); +#endif +} + +void test_CFL_extract_single_path_line(void) +{ +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_outputs(); + init_graph_line(); + int expected[25] = {non_exist, non_exist, non_exist, non_exist, non_empty, + non_exist, non_exist, non_exist, non_empty, non_exist, + non_exist, non_exist, non_exist, non_exist, non_exist, + non_exist, non_exist, non_exist, non_exist, non_exist, + non_exist, non_exist, non_exist, non_exist, non_exist}; + OK(run_aux_algorithm()); + for (GrB_Index start = 0; start < 5; start++) + { + for (GrB_Index end = 0; end < 5; end++) + { + check_result2(expected[start * 5 + end]); + if (path.len > 0) + { + LAGraph_Free((void **)&path.path, msg); + } + } + } + free_workspace(); + teardown(); +#endif +} + +void test_CFL_extract_single_path_two_nodes_cycle(void) +{ +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_outputs(); + init_graph_3(); + int expected[4] = {non_empty, non_exist, + non_empty, non_exist}; + OK(run_aux_algorithm()); + for (GrB_Index start = 0; start < 2; start++) + { + for (GrB_Index end = 0; end < 2; end++) + { + check_result2(expected[start * 2 + end]); + if (path.len > 0) + { + LAGraph_Free((void **)&path.path, msg); + } + } + } + free_workspace(); + teardown(); +#endif +} + +void test_CFL_extract_single_path_with_empty_adj_matrix(void) +{ +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aS(); + init_outputs(); + init_graph_4(); + + int expected[4] = {empty, non_exist, + non_exist, empty}; + OK(run_aux_algorithm()); + for (GrB_Index start = 0; start < 2; start++) + { + for (GrB_Index end = 0; end < 2; end++) + { + check_result2(expected[start * 2 + end]); + LAGraph_Free((void **)&path.path, msg); + } + } + free_workspace(); + teardown(); +#endif +} + +void test_CFL_extract_single_path_inappropriate_grammar(void) +{ +#if LAGRAPH_SUITESPARSE + + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_outputs(); + init_graph_double_cycle(); + OK(run_aux_algorithm()); + // Random path + GrB_Index start = 0; + GrB_Index end = 0; + + LAGraph_Free((void **)&grammar.rules, msg); + grammar = (grammar_t){0, 0, 0, NULL}; + init_grammar_aS(); + check_result2(non_exist); + + free_workspace(); + teardown(); +#endif +} + +void test_CFL_extract_single_path_inappropriate_graph(void) +{ +#if LAGRAPH_SUITESPARSE + + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_outputs(); + init_graph_double_cycle(); + OK(run_aux_algorithm()); + // Random path + GrB_Index start = 0; + GrB_Index end = 0; + + GrB_free(&adj_matrices[0]); + GrB_free(&adj_matrices[1]); + LAGraph_Free((void **)&adj_matrices, msg); + + init_graph_4(); + check_result2(non_exist); + + free_workspace(); + teardown(); +#endif +} + +//=========================== +// Tests with invalid result +//=========================== + +void test_CFL_extract_single_path_invalid_rules(void) +{ +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_outputs(); + init_graph_double_cycle(); + OK(run_aux_algorithm()); + // Random path + GrB_Index start = 1; + GrB_Index end = 2; + + // 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_extract_single_path_null_pointers(void) +{ +#if LAGRAPH_SUITESPARSE + + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_outputs(); + init_graph_double_cycle(); + OK(run_aux_algorithm()); + // Random path + GrB_Index start = 1; + GrB_Index end = 2; + + // 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_outputs(); + init_graph_double_cycle(); + + // outputs = NULL; + if (outputs != NULL) + { + for (size_t i = 0; i < grammar.nonterms_count; i++) + { + GrB_free(&outputs[i]); + } + } + LAGraph_Free((void **)&outputs, msg); + check_error(GrB_NULL_POINTER); + + free_workspace(); + init_grammar_aSb(); + init_outputs(); + init_graph_double_cycle(); + + // grammar.rules = NULL; + LAGraph_Free((void **)&grammar.rules, msg); + check_error(GrB_NULL_POINTER); + + free_workspace(); + teardown(); +#endif +} + +void test_CFL_extract_single_path_vertex_out_the_graph(void) +{ +#if LAGRAPH_SUITESPARSE + + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_outputs(); + init_graph_double_cycle(); // 4 * 4 + OK(run_aux_algorithm()); + + GrB_Index start = 4; // Vertex outside the graph + GrB_Index end = 2; + + check_error(GrB_INVALID_INDEX); + + free_workspace(); + teardown(); +#endif +} + +TEST_LIST = { + {"CFL_extract_single_path_two_cycle", test_CFL_extract_single_path_two_cycle}, + {"CFL_extract_single_path_cycle", test_CFL_extract_single_path_cycle}, + {"CFL_extract_single_path_labels_more_than_nonterms", test_CFL_extract_single_path_labels_more_than_nonterms}, + {"CFL_extract_single_path_complex_grammar", test_CFL_extract_single_path_complex_grammar}, + {"CFL_extract_single_path_tree", test_CFL_extract_single_path_tree}, + {"CFL_extract_single_path_line", test_CFL_extract_single_path_line}, + {"CFL_extract_single_path_two_nodes_cycle", test_CFL_extract_single_path_two_nodes_cycle}, + {"CFL_extract_single_path_with_empty_adj_matrix", test_CFL_extract_single_path_with_empty_adj_matrix}, + {"CFL_extract_single_path_inappropriate_grammar", test_CFL_extract_single_path_inappropriate_grammar}, + {"CFL_extract_single_path_inappropriate_graph", test_CFL_extract_single_path_inappropriate_graph}, + {"CFL_extract_single_path_invalid_rules", test_CFL_extract_single_path_invalid_rules}, + {"CFL_extract_single_path_null_pointers", test_CFL_extract_single_path_null_pointers}, + {"CFL_extract_single_path_vertex_out_the_graph", test_CFL_extract_single_path_vertex_out_the_graph}, + {NULL, NULL}}; diff --git a/experimental/test/test_CFL_single_path.c b/experimental/test/test_CFL_single_path.c new file mode 100644 index 0000000000..152ad23cb7 --- /dev/null +++ b/experimental/test/test_CFL_single_path.c @@ -0,0 +1,778 @@ +#include +#include +#include +#include +#include +#include + +#define run_algorithm() \ + LAGraph_CFL_single_path(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(result) \ + { \ + char *expected = output_to_str(0); \ + 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]; +GrB_Type PI_type = NULL; + +void setup() +{ + LAGraph_Init(msg); + GrB_Type_new(&PI_type, sizeof(PathIndex)); +} + +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])); + GrB_Index *row = NULL; + GrB_Index *col = NULL; + PathIndex *val = NULL; + LAGraph_Malloc((void **)&row, nnz, sizeof(GrB_Index), msg); + LAGraph_Malloc((void **)&col, nnz, sizeof(GrB_Index), msg); + LAGraph_Malloc((void **)&val, nnz, sizeof(PathIndex), msg); + + OK(GrB_Matrix_extractTuples_UDT(row, col, val, &nnz, outputs[nonterm])); + + char *result_str = NULL; + // 33 - size of "(%ld, %ld): middle=%ld height-%d " + LAGraph_Malloc((void **)&result_str, nnz * 33, sizeof(char), msg); + result_str[0] = '\0'; + + for (size_t i = 0; i < nnz; i++) + { + sprintf(result_str + strlen(result_str), "(%" PRIu64 ", %" PRIu64 "): middle=%" PRIu64 " height=%" PRId32 " ", + row[i], col[i], + val[i].middle, + val[i].height); + } + + LAGraph_Free((void **)&row, msg); + LAGraph_Free((void **)&col, msg); + LAGraph_Free((void **)&val, msg); + + return result_str; +} + +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++) + { + GrB_free(&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; + + for (int64_t i = 0; i < grammar.nonterms_count; i++) + { + GrB_Matrix_new(&outputs[i], PI_type, 4, 4); + } +} + +// 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; + + for (int64_t i = 0; i < grammar.nonterms_count; i++) + { + GrB_Matrix_new(&outputs[i], PI_type, 3, 3); + } +} + +// 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; + + for (int64_t i = 0; i < grammar.nonterms_count; i++) + { + GrB_Matrix_new(&outputs[i], PI_type, 8, 8); + } +} + +// 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; + + for (int64_t i = 0; i < grammar.nonterms_count; i++) + { + GrB_Matrix_new(&outputs[i], PI_type, 7, 7); + } +} + +// 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; + + for (int64_t i = 0; i < grammar.nonterms_count; i++) + { + GrB_Matrix_new(&outputs[i], PI_type, 5, 5); + } +} + +// 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; + + for (int64_t i = 0; i < grammar.nonterms_count; i++) + { + GrB_Matrix_new(&outputs[i], PI_type, 3, 3); + } +} + +// 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; + + for (int64_t i = 0; i < grammar.nonterms_count; i++) + { + GrB_Matrix_new(&outputs[i], PI_type, 2, 2); + } +} + +// 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; + + for (int64_t i = 0; i < grammar.nonterms_count; i++) + { + GrB_Matrix_new(&outputs[i], PI_type, 2, 2); + } +} + +//==================== +// Tests with valid result +//==================== + +void test_CFL_single_path_cycle(void) +{ +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aS(); + init_outputs(); + init_graph_one_cycle(); + + OK(run_algorithm()); + check_result("(0, 0): middle=0 height=1 " + "(0, 1): middle=0 height=1 " + "(0, 2): middle=1 height=2 " + "(1, 0): middle=2 height=2 " + "(1, 1): middle=1 height=1 " + "(1, 2): middle=1 height=1 " + "(2, 0): middle=2 height=1 " + "(2, 1): middle=0 height=2 " + "(2, 2): middle=2 height=1 "); + + free_workspace(); + teardown(); +#endif +} + +void test_CFL_single_path_two_cycle(void) +{ +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_outputs(); + init_graph_double_cycle(); + + OK(run_algorithm()); + check_result("(0, 0): middle=1 height=12 " + "(0, 3): middle=1 height=6 " + "(1, 0): middle=2 height=4 " + "(1, 3): middle=2 height=10 " + "(2, 0): middle=0 height=8 " + "(2, 3): middle=0 height=2 "); + + free_workspace(); + teardown(); +#endif +} + +void test_CFL_single_path_labels_more_than_nonterms(void) +{ +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_outputs(); + init_graph_2(); + + OK(run_algorithm()); + check_result("(0, 1): middle=0 height=2 "); + + free_workspace(); + teardown(); +#endif +} + +void test_CFL_single_path_complex_grammar(void) +{ +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_complex(); + init_outputs(); + init_graph_1(); + + OK(run_algorithm()); + check_result("(0, 7): middle=4 height=4 " + "(1, 6): middle=2 height=5 "); + + free_workspace(); + teardown(); +#endif +} + +void test_CFL_single_path_tree(void) +{ +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_outputs(); + init_graph_tree(); + + OK(run_algorithm()); + check_result("(0, 0): middle=2 height=2 " + "(0, 1): middle=2 height=2 " + "(0, 3): middle=2 height=4 " + "(0, 4): middle=2 height=4 " + "(1, 0): middle=2 height=2 " + "(1, 1): middle=2 height=2 " + "(1, 3): middle=2 height=4 " + "(1, 4): middle=2 height=4 " + "(2, 2): middle=6 height=2 " + "(2, 5): middle=6 height=2 " + "(3, 0): middle=5 height=4 " + "(3, 1): middle=5 height=4 " + "(3, 3): middle=5 height=2 " + "(3, 4): middle=5 height=2 " + "(4, 0): middle=5 height=4 " + "(4, 1): middle=5 height=4 " + "(4, 3): middle=5 height=2 " + "(4, 4): middle=5 height=2 " + "(5, 2): middle=6 height=2 " + "(5, 5): middle=6 height=2 "); + free_workspace(); + teardown(); +#endif +} + +void test_CFL_single_path_line(void) +{ +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_outputs(); + init_graph_line(); + + OK(run_algorithm()); + check_result("(0, 4): middle=1 height=4 " + "(1, 3): middle=2 height=2 "); + + free_workspace(); + teardown(); +#endif +} + +void test_CFL_single_path_two_nodes_cycle(void) +{ +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_outputs(); + init_graph_3(); + + OK(run_algorithm()); + check_result("(0, 0): middle=1 height=4 " + "(1, 0): middle=0 height=2 "); + + free_workspace(); + teardown(); +#endif +} + +void test_CFL_single_path_with_empty_adj_matrix(void) +{ +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aS(); + init_outputs(); + init_graph_4(); + + OK(run_algorithm()); + check_result("(0, 0): middle=0 height=1 " + "(1, 1): middle=1 height=1 "); + + free_workspace(); + teardown(); +#endif +} + +//==================== +// Tests with invalid result +//==================== + +void test_CFL_single_path_invalid_rules(void) +{ +#if LAGRAPH_SUITESPARSE + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_outputs(); + init_graph_double_cycle(); + + // 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_single_path_null_pointers(void) +{ +#if LAGRAPH_SUITESPARSE + + setup(); + GrB_Info retval; + + init_grammar_aSb(); + init_outputs(); + init_graph_double_cycle(); + + // 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_outputs(); + init_graph_double_cycle(); + + // outputs = NULL; + if (outputs != NULL) + { + for (size_t i = 0; i < grammar.nonterms_count; i++) + { + GrB_free(&outputs[i]); + } + } + check_error(GrB_NULL_POINTER); + + LAGraph_Free((void **)&outputs, msg); + check_error(GrB_NULL_POINTER); + + free_workspace(); + init_grammar_aSb(); + init_outputs(); + init_graph_double_cycle(); + + // grammar.rules = NULL; + LAGraph_Free((void **)&grammar.rules, msg); + check_error(GrB_NULL_POINTER); + + free_workspace(); + teardown(); +#endif +} + +TEST_LIST = { + {"CFL_reachability_cycle", test_CFL_single_path_cycle}, + {"CFL_path_two_cycle", test_CFL_single_path_two_cycle}, + {"CFL_single_path_labels_more_than_nonterms", test_CFL_single_path_labels_more_than_nonterms}, + {"CFL_single_path_complex_grammar", test_CFL_single_path_complex_grammar}, + {"CFL_single_path_tree", test_CFL_single_path_tree}, + {"CFL_single_path_line", test_CFL_single_path_line}, + {"CFL_single_path_two_nodes_cycle", test_CFL_single_path_two_nodes_cycle}, + {"CFL_single_path_with_empty_adj_matrix", test_CFL_single_path_with_empty_adj_matrix}, + {"CFL_single_path_invalid_rules", test_CFL_single_path_invalid_rules}, + {"CFL_single_path_null_pointers", test_CFL_single_path_null_pointers}, + {NULL, NULL}}; diff --git a/include/LAGraphX.h b/include/LAGraphX.h index 3355addb2e..52cce8dce0 100644 --- a/include/LAGraphX.h +++ b/include/LAGraphX.h @@ -1096,6 +1096,78 @@ GrB_Info LAGraph_CFL_reachability char *msg // Message string for error reporting. ) ; +// Edge of the graph from vertex start to vertex end with terminal label +typedef struct { + GrB_Index start; + int32_t label; + GrB_Index end; +} Edge; + +typedef struct { + Edge *path; + size_t len; +} Path; + +// Structure for storing single path information +typedef struct { + GrB_Index middle; // Auxiliary vertex for merging paths + int32_t height; // Minimum height of the derivation tree of the string formed by this path from a nonterminal +} PathIndex; + +GrB_Info LAGraph_CFL_single_path( + // 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 PathIndex 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. +); + +GrB_Info LAGraph_CFL_extract_single_path( + // Output + Path *output, // A path extracted from a graph that forms a word produced by the rules of the grammar. + // + // If the path is not found, it is equal to the empty path: len = 0, path = NULL and GrB_NO_VALUE is returned. + // If the path is empty, i.e., formed by the rule: nonterminal -> eps, then the empty path: len = 0, path = NULL and GrB_SUCCESS is returned. + // Input + GrB_Index start, // Source vertex of a graph path + GrB_Index end, // Destination vertex of a graph path + int32_t nonterm, + 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]). + const GrB_Matrix *T, // Matrices containing information about existing paths for each non-terminal + // + // outputs[k]: (i, j) contains a PathIndex 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. + 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. +); + //------------------------------------------------------------------------------ // a simple example of an algorithm //------------------------------------------------------------------------------