Skip to content

Commit dbc2586

Browse files
committed
feat: add rpq evalution abstraction, implement with LAGraph_RegularPathQuerry
1 parent d8f3b00 commit dbc2586

10 files changed

Lines changed: 953 additions & 34 deletions

File tree

AGENTS.md

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,24 @@ pathrex/
1818
│ ├── main.rs # Binary entry point (placeholder)
1919
│ ├── lagraph_sys.rs # FFI module — includes generated bindings
2020
│ ├── lagraph_sys_generated.rs# Bindgen output (checked in, regenerated in CI)
21-
│ ├── utils.rs # Internal helpers: CountingBuilder, CountOutput, VecSource,
22-
│ │ # grb_ok! and la_ok! macros
21+
│ ├── utils.rs # Public helpers: CountingBuilder, CountOutput, VecSource,
22+
│ │ # grb_ok! and la_ok! macros, build_graph
2323
│ ├── graph/
2424
│ │ ├── mod.rs # Core traits (GraphBuilder, GraphDecomposition, GraphSource,
2525
│ │ │ # Backend, Graph<B>), error types, RAII wrappers, GrB init
2626
│ │ └── inmemory.rs # InMemory marker, InMemoryBuilder, InMemoryGraph
27+
│ ├── rpq/
28+
│ │ ├── mod.rs # RPQ evaluation trait (RpqEvaluator), RpqResult, RpqError
29+
│ │ ├── nfarpq.rs # NFA-based RPQ evaluator using LAGraph_RegularPathQuery
30+
│ │ └── rpqmatrix.rs # Plan-based RPQ evaluator using LAGraph_RPQMatrix
2731
│ ├── sparql/
2832
│ │ └── mod.rs # SPARQL parsing (spargebra), PathTriple extraction, parse_rpq
2933
│ └── formats/
3034
│ ├── mod.rs # FormatError enum, re-exports
3135
│ └── csv.rs # Csv<R> — CSV → Edge iterator (CsvConfig, ColumnSpec)
3236
├── tests/
33-
│ └── inmemory_tests.rs # Integration tests for InMemoryBuilder / InMemoryGraph
37+
│ ├── inmemory_tests.rs # Integration tests for InMemoryBuilder / InMemoryGraph
38+
│ └── nfarpq_tests.rs # Integration tests for NfaRpqEvaluator
3439
├── deps/
3540
│ └── LAGraph/ # Git submodule (SparseLinearAlgebra/LAGraph)
3641
└── .github/workflows/ci.yml # CI: build GraphBLAS + LAGraph, cargo build & test
@@ -242,6 +247,43 @@ The module also handles spargebra's desugaring of sequence paths
242247
(`?x <a>/<b>/<c> ?y`) from a chain of BGP triples back into a single
243248
[`PropertyPathExpression::Sequence`].
244249

250+
### RPQ evaluation (`src/rpq/`)
251+
252+
The [`rpq`](src/rpq/mod.rs) module provides an abstraction for evaluating
253+
Regular Path Queries (RPQs) over edge-labeled graphs using GraphBLAS/LAGraph.
254+
255+
Key public items:
256+
257+
- [`RpqEvaluator`](src/rpq/mod.rs:47) — trait with a single method
258+
[`evaluate(subject, path, object, graph)`](src/rpq/mod.rs:48) that takes
259+
SPARQL [`TermPattern`] endpoints, a [`PropertyPathExpression`] path, and a
260+
[`GraphDecomposition`], returning an [`RpqResult`](src/rpq/mod.rs:42).
261+
- [`RpqResult`](src/rpq/mod.rs:42) — wraps a [`GraphblasVector`] of reachable
262+
vertices.
263+
- [`RpqError`](src/rpq/mod.rs:21) — error enum covering parse errors, extraction
264+
errors, unsupported paths, missing labels/vertices, and GraphBLAS failures.
265+
266+
#### `NfaRpqEvaluator` (`src/rpq/nfarpq.rs`)
267+
268+
[`NfaRpqEvaluator`](src/rpq/nfarpq.rs:265) implements [`RpqEvaluator`] by:
269+
270+
1. Converting a [`PropertyPathExpression`] into an [`Nfa`](src/rpq/nfarpq.rs:27)
271+
via Thompson's construction ([`Nfa::from_property_path()`](src/rpq/nfarpq.rs:35)).
272+
2. Eliminating ε-transitions via epsilon closure
273+
([`NfaBuilder::epsilon_closure()`](src/rpq/nfarpq.rs:198)).
274+
3. Building one `LAGraph_Graph` per NFA label transition
275+
([`Nfa::build_lagraph_matrices()`](src/rpq/nfarpq.rs:43)).
276+
4. Calling [`LAGraph_RegularPathQuery`] with the NFA matrices, data-graph
277+
matrices, start/final states, and source vertices.
278+
279+
Supported path operators: `NamedNode`, `Sequence`, `Alternative`,
280+
`ZeroOrMore`, `OneOrMore`, `ZeroOrOne`. `Reverse` and `NegatedPropertySet`
281+
return [`RpqError::UnsupportedPath`].
282+
283+
Subject/object resolution: a [`TermPattern::Variable`] means "all vertices";
284+
a [`TermPattern::NamedNode`] resolves to a single vertex via
285+
[`GraphDecomposition::get_node_id()`](src/graph/mod.rs:195).
286+
245287
### FFI layer
246288

247289
[`lagraph_sys`](src/lagraph_sys.rs) exposes raw C bindings for GraphBLAS and
@@ -250,10 +292,11 @@ LAGraph. Safe Rust wrappers live in [`graph::mod`](src/graph/mod.rs):
250292
- [`LagraphGraph`](src/graph/mod.rs:48) — RAII wrapper around `LAGraph_Graph` (calls
251293
`LAGraph_Delete` on drop). Also provides
252294
[`LagraphGraph::from_coo()`](src/graph/mod.rs:85) to build directly from COO arrays.
253-
- [`GraphblasVector`](src/graph/mod.rs:124) — RAII wrapper around `GrB_Vector`.
295+
- [`GraphblasVector`](src/graph/mod.rs:128) — RAII wrapper around `GrB_Vector`
296+
(derives `Debug`).
254297
- [`ensure_grb_init()`](src/graph/mod.rs:39) — one-time `LAGraph_Init` via `std::sync::Once`.
255298

256-
### Macros (`src/utils.rs`)
299+
### Macros & helpers (`src/utils.rs`)
257300

258301
Two `#[macro_export]` macros handle FFI error mapping:
259302

@@ -263,20 +306,28 @@ Two `#[macro_export]` macros handle FFI error mapping:
263306
appending the required `*mut i8` message buffer, and maps failure to
264307
`GraphError::LAGraph(info, msg)`.
265308

309+
A convenience function is also provided:
310+
311+
- [`build_graph(edges)`](src/utils.rs:184) — builds an `InMemoryGraph` from a
312+
slice of `(&str, &str, &str)` triples (source, target, label). Used by
313+
integration tests.
314+
266315
## Coding Conventions
267316

268317
- **Rust edition 2024**.
269-
- Error handling via `thiserror` derive macros; two main error enums:
270-
[`GraphError`](src/graph/mod.rs:15) and [`FormatError`](src/formats/mod.rs:24).
318+
- Error handling via `thiserror` derive macros; three main error enums:
319+
[`GraphError`](src/graph/mod.rs:15), [`FormatError`](src/formats/mod.rs:24),
320+
and [`RpqError`](src/rpq/mod.rs:21).
271321
- `FormatError` converts into `GraphError` via `#[from] FormatError` on the
272322
`GraphError::Format` variant.
273-
- Unsafe FFI calls are confined to `lagraph_sys`, `graph/mod.rs`, and
274-
`graph/inmemory.rs`. All raw pointers are wrapped in RAII types that free
275-
resources on drop.
323+
- Unsafe FFI calls are confined to `lagraph_sys`, `graph/mod.rs`,
324+
`graph/inmemory.rs`, and `rpq/nfarpq.rs`. All raw pointers are wrapped in
325+
RAII types that free resources on drop.
276326
- `unsafe impl Send + Sync` is provided for `LagraphGraph` and
277327
`GraphblasVector` because GraphBLAS handles are thread-safe after init.
278328
- Unit tests live in `#[cfg(test)] mod tests` blocks inside each module.
279-
Integration tests that need GraphBLAS live in [`tests/inmemory_tests.rs`](tests/inmemory_tests.rs).
329+
Integration tests that need GraphBLAS live in [`tests/inmemory_tests.rs`](tests/inmemory_tests.rs)
330+
and [`tests/nfarpq_tests.rs`](tests/nfarpq_tests.rs).
280331

281332
## Testing
282333

@@ -296,7 +347,11 @@ Tests in `src/formats/csv.rs` are pure Rust and need no native dependencies.
296347

297348
Tests in `src/sparql/mod.rs` are pure Rust and need no native dependencies.
298349

299-
Tests in `src/graph/inmemory.rs` and [`tests/inmemory_tests.rs`](tests/inmemory_tests.rs)
350+
Tests in `src/rpq/nfarpq.rs` (NFA construction unit tests) are pure Rust and need no
351+
native dependencies.
352+
353+
Tests in `src/graph/inmemory.rs`, [`tests/inmemory_tests.rs`](tests/inmemory_tests.rs),
354+
and [`tests/nfarpq_tests.rs`](tests/nfarpq_tests.rs)
300355
call real GraphBLAS/LAGraph and require the native libraries to be present.
301356

302357
## CI

build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ fn regenerate_bindings() {
8383
.allowlist_function("LAGraph_Delete")
8484
.allowlist_function("LAGraph_Cached_AT")
8585
.allowlist_function("LAGraph_MMRead")
86+
.allowlist_function("LAGraph_RegularPathQuery")
8687
.default_enum_style(bindgen::EnumVariation::Rust {
8788
non_exhaustive: false,
8889
})

src/graph/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ impl Drop for LagraphGraph {
125125
unsafe impl Send for LagraphGraph {}
126126
unsafe impl Sync for LagraphGraph {}
127127

128+
#[derive(Debug)]
128129
pub struct GraphblasVector {
129130
pub inner: GrB_Vector,
130131
}

src/lagraph_sys_generated.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,37 @@ unsafe extern "C" {
261261
msg: *mut ::std::os::raw::c_char,
262262
) -> ::std::os::raw::c_int;
263263
}
264+
unsafe extern "C" {
265+
pub fn LAGraph_RegularPathQuery(
266+
reachable: *mut GrB_Vector,
267+
R: *mut LAGraph_Graph,
268+
nl: usize,
269+
QS: *const GrB_Index,
270+
nqs: usize,
271+
QF: *const GrB_Index,
272+
nqf: usize,
273+
G: *mut LAGraph_Graph,
274+
S: *const GrB_Index,
275+
ns: usize,
276+
msg: *mut ::std::os::raw::c_char,
277+
) -> ::std::os::raw::c_int;
278+
}
279+
#[repr(u32)]
280+
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
281+
pub enum RPQMatrixOp {
282+
RPQ_MATRIX_OP_LABEL = 0,
283+
RPQ_MATRIX_OP_LOR = 1,
284+
RPQ_MATRIX_OP_CONCAT = 2,
285+
RPQ_MATRIX_OP_KLEENE = 3,
286+
RPQ_MATRIX_OP_KLEENE_L = 4,
287+
RPQ_MATRIX_OP_KLEENE_R = 5,
288+
}
289+
#[repr(C)]
290+
#[derive(Debug, Copy, Clone)]
291+
pub struct RPQMatrixPlan {
292+
pub op: RPQMatrixOp,
293+
pub lhs: *mut RPQMatrixPlan,
294+
pub rhs: *mut RPQMatrixPlan,
295+
pub mat: GrB_Matrix,
296+
pub res_mat: GrB_Matrix,
297+
}

src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
pub mod formats;
22
pub mod graph;
3+
pub mod rpq;
34
pub mod sparql;
45
#[allow(unused_unsafe, dead_code)]
5-
pub(crate) mod utils;
6+
pub mod utils;
67

78
pub mod lagraph_sys;

src/rpq/mod.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//! Regular Path Query (RPQ) evaluation over edge-labeled graphs.
2+
//! ```rust,ignore
3+
//! use pathrex::sparql::parse_rpq;
4+
//! use pathrex::rpq::{RpqEvaluator, nfarpq::NfaRpqEvaluator};
5+
//!
6+
//! let triple = parse_rpq("SELECT ?x ?y WHERE { ?x <knows>/<likes>* ?y . }")?;
7+
//! let result = NfaRpqEvaluator.evaluate(&triple.subject, &triple.path, &triple.object, &graph)?;
8+
//! ```
9+
10+
pub mod nfarpq;
11+
12+
use crate::graph::GraphDecomposition;
13+
use crate::graph::GraphblasVector;
14+
use crate::sparql::ExtractError;
15+
use spargebra::SparqlSyntaxError;
16+
use spargebra::algebra::PropertyPathExpression;
17+
use spargebra::term::TermPattern;
18+
use thiserror::Error;
19+
20+
#[derive(Debug, Error)]
21+
pub enum RpqError {
22+
#[error("SPARQL syntax error: {0}")]
23+
Parse(#[from] SparqlSyntaxError),
24+
25+
#[error("query extraction error: {0}")]
26+
Extract(#[from] ExtractError),
27+
28+
#[error("unsupported path expression: {0}")]
29+
UnsupportedPath(String),
30+
31+
#[error("label not found in graph: '{0}'")]
32+
LabelNotFound(String),
33+
34+
#[error("vertex not found in graph: '{0}'")]
35+
VertexNotFound(String),
36+
37+
#[error("GraphBLAS/LAGraph error: {0}")]
38+
GraphBlas(String),
39+
}
40+
41+
#[derive(Debug)]
42+
pub struct RpqResult {
43+
pub reachable: GraphblasVector,
44+
}
45+
46+
pub trait RpqEvaluator {
47+
fn evaluate<G: GraphDecomposition>(
48+
&self,
49+
subject: &TermPattern,
50+
path: &PropertyPathExpression,
51+
object: &TermPattern,
52+
graph: &G,
53+
) -> Result<RpqResult, RpqError>;
54+
}

0 commit comments

Comments
 (0)