Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
9c6a947
feat: initial commit
b08lsoai Dec 24, 2025
91070a5
test: add tests for the path extraction algorithm
b08lsoai Dec 25, 2025
3b65b2b
test: add tests for the single path search algorithm
b08lsoai Dec 25, 2025
23a6ca5
ref: make set_path_index callback type-safe
b08lsoai Dec 27, 2025
bac1db5
fix: fix output matrix corruption
b08lsoai Dec 27, 2025
b24bde1
ref: reduce code nesting
b08lsoai Dec 27, 2025
8cecb2c
fix: add path freeing on errors
b08lsoai Dec 27, 2025
2e10ed5
ref: reduce code nesting
b08lsoai Dec 27, 2025
7cc849a
test: add extract_single_path tests
b08lsoai Dec 27, 2025
ed1d853
ref: require pre-initialization of matrices
b08lsoai Jan 25, 2026
2fbdcb6
test: update tests
b08lsoai Jan 25, 2026
48964e2
fix: correct the msg recording
b08lsoai Jan 25, 2026
0db907c
docs: add comments to LAGraphX.h
b08lsoai Jan 26, 2026
6c134b7
ref: introduce CFPQ_core algorithm
b08lsoai Feb 3, 2026
85b9cf8
feat: change API LAGraph_CFL_extract_single_path
b08lsoai Feb 21, 2026
44df6a4
style: format code
b08lsoai Feb 21, 2026
d63d9b4
ref: improve naming across CFL modules
b08lsoai Mar 4, 2026
4262b85
docs: fix comments in path extraction
b08lsoai Mar 6, 2026
7a91d36
docs: add header for LAGraph_CFL_extract_single_path
b08lsoai Mar 6, 2026
e663a0f
ref: add input validation macros for CFL algorithms
b08lsoai Mar 7, 2026
be094e8
test: update tests for new API
b08lsoai Mar 8, 2026
d4e2cec
fix: free allocated memory when no paths found
b08lsoai Mar 8, 2026
7db3c27
docs: fix the header
b08lsoai Mar 8, 2026
4d71c96
docs: add header for LAGraph_CFL_single_pats
b08lsoai Mar 8, 2026
3092b0c
ref: extract rule classification into helper
b08lsoai Mar 8, 2026
91e01af
ref: replace malloc with callock
b08lsoai Mar 8, 2026
67dc5d6
ref: convert LG_CFL_CHECK_BASE_INPUTS macro to fun
b08lsoai Mar 8, 2026
9bfefc9
ref: relocate ADD_TO_MSG macro to LAGraphX.h
b08lsoai Mar 8, 2026
40e6cd0
ref: make ADD_TO_MSG macro parameters explicit
b08lsoai Mar 8, 2026
3a536cd
docs: improve docs for base input check functions
b08lsoai Mar 8, 2026
082d20b
feat: return type from LAGraph_CFL_single_path
b08lsoai Mar 13, 2026
9b3cfb2
docs: improve error message
b08lsoai Mar 14, 2026
3296ecd
build: add conditional compilation
b08lsoai Mar 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
227 changes: 227 additions & 0 deletions experimental/algorithm/LAGraph_CFL_extract_single_path.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
#define LG_FREE_ALL \
{ \
LAGraph_Free((void **)&output->paths, NULL); \
output->count = 0; \
output->capacity = 0; \
}

#include "LG_internal.h"
#include <LAGraphX.h>

#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
PathArray *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
GrB_Info info;
output->count = 0;
output->paths = NULL;
// Initial capacity 1, since most often looking for exactly 1 path with a fixed start and end
output->capacity = 1;

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;
}

// Check the 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 -> term]
if (is_rule_term)
{
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)
{
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;
}

GrB_Index n;
GRB_TRY(GrB_Matrix_nrows(&n, adj_matrices[0]));

GrB_Index start_begin = (start == NULL) ? 0 : *start;
GrB_Index start_end = (start == NULL) ? n - 1 : *start;
GrB_Index end_begin = (end == NULL) ? 0 : *end;
GrB_Index end_end = (end == NULL) ? n - 1 : *end;

LG_TRY(LAGraph_Malloc((void **)&output->paths, output->capacity, sizeof(Path), msg));

for (GrB_Index st = start_begin; st <= start_end; st++)
{
for (GrB_Index en = end_begin; en <= end_end; en++)
{
Path path;

// Function that extracts one path with fixed start and end
info = LAGraph_CFL_extract_single_path_internal(&path, st, en, nonterm, adj_matrices, T, terms_count, nonterms_count, rules, rules_count, msg);
if (info == GrB_SUCCESS)
{
if (output->count == output->capacity)
{
LG_TRY(LAGraph_Realloc((void **)&output->paths, output->capacity * 2, output->capacity,
sizeof(Path), msg));
output->capacity *= 2;
}
output->paths[output->count++] = path;
}
else if (info != GrB_NO_VALUE) // GrB_NO_VALUE is the absence of a path, not an error
{
LG_FREE_ALL;
return info;
}
}
}

LG_FREE_WORK;
return GrB_SUCCESS;
}
Loading
Loading