Skip to content

Commit 900d096

Browse files
committed
feat: add RPQMatrix evalution
1 parent 19235fe commit 900d096

9 files changed

Lines changed: 651 additions & 1 deletion

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ edition = "2024"
55

66
[dependencies]
77
csv = "1.4.0"
8+
egg = "0.10.0"
89
libc = "0.2"
910
oxrdf = "0.3.3"
1011
oxttl = "0.2.3"

build.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ fn regenerate_bindings() {
7474
.allowlist_function("GrB_Vector_extractTuples_BOOL")
7575
.allowlist_function("GrB_vxm")
7676
.allowlist_item("LAGRAPH_MSG_LEN")
77+
.allowlist_item("RPQMatrixOp")
78+
.allowlist_type("RPQMatrixPlan")
7779
.allowlist_type("LAGraph_Graph")
7880
.allowlist_type("LAGraph_Kind")
7981
.allowlist_function("LAGraph_CheckGraph")
@@ -83,6 +85,8 @@ fn regenerate_bindings() {
8385
.allowlist_function("LAGraph_Delete")
8486
.allowlist_function("LAGraph_Cached_AT")
8587
.allowlist_function("LAGraph_MMRead")
88+
.allowlist_function("LAGraph_RPQMatrix")
89+
.allowlist_function("LAGraph_DestroyRpqMatrixPlan")
8690
.default_enum_style(bindgen::EnumVariation::Rust {
8791
non_exhaustive: false,
8892
})

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: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,32 @@ unsafe extern "C" {
261261
msg: *mut ::std::os::raw::c_char,
262262
) -> ::std::os::raw::c_int;
263263
}
264+
#[repr(u32)]
265+
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
266+
pub enum RPQMatrixOp {
267+
RPQ_MATRIX_OP_LABEL = 0,
268+
RPQ_MATRIX_OP_LOR = 1,
269+
RPQ_MATRIX_OP_CONCAT = 2,
270+
RPQ_MATRIX_OP_KLEENE = 3,
271+
RPQ_MATRIX_OP_KLEENE_L = 4,
272+
RPQ_MATRIX_OP_KLEENE_R = 5,
273+
}
274+
#[repr(C)]
275+
#[derive(Debug, Copy, Clone)]
276+
pub struct RPQMatrixPlan {
277+
pub op: RPQMatrixOp,
278+
pub lhs: *mut RPQMatrixPlan,
279+
pub rhs: *mut RPQMatrixPlan,
280+
pub mat: GrB_Matrix,
281+
pub res_mat: GrB_Matrix,
282+
}
283+
unsafe extern "C" {
284+
pub fn LAGraph_RPQMatrix(
285+
nnz: *mut GrB_Index,
286+
plan: *mut RPQMatrixPlan,
287+
msg: *mut ::std::os::raw::c_char,
288+
) -> GrB_Info;
289+
}
290+
unsafe extern "C" {
291+
pub fn LAGraph_DestroyRpqMatrixPlan(plan: *mut RPQMatrixPlan) -> GrB_Info;
292+
}

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

src/rpq/rpqmatrix.rs

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
//! Plan-based RPQ evaluation using `LAGraph_RPQMatrix`.
2+
3+
use std::ptr::null_mut;
4+
5+
use egg::{Id, RecExpr, define_language};
6+
use spargebra::algebra::PropertyPathExpression;
7+
use spargebra::term::TermPattern;
8+
9+
use crate::graph::{GraphDecomposition, GraphblasVector, ensure_grb_init};
10+
use crate::lagraph_sys::*;
11+
use crate::rpq::{RpqError, RpqEvaluator, RpqResult};
12+
use crate::{grb_ok, la_ok};
13+
14+
unsafe impl Send for RPQMatrixPlan {}
15+
16+
define_language! {
17+
pub enum RpqPlan {
18+
Label(String),
19+
"/" = Seq([Id; 2]),
20+
"|" = Alt([Id; 2]),
21+
"*" = Star([Id; 1]),
22+
}
23+
}
24+
25+
/// Compile a [`PropertyPathExpression`] into [`RecExpr<RpqPlan>`].
26+
pub fn to_expr(path: &PropertyPathExpression) -> Result<RecExpr<RpqPlan>, RpqError> {
27+
let mut expr = RecExpr::default();
28+
to_expr_aux(path, &mut expr)?;
29+
Ok(expr)
30+
}
31+
32+
fn to_expr_aux(
33+
path: &PropertyPathExpression,
34+
expr: &mut RecExpr<RpqPlan>,
35+
) -> Result<Id, RpqError> {
36+
match path {
37+
PropertyPathExpression::NamedNode(nn) => {
38+
Ok(expr.add(RpqPlan::Label(nn.as_str().to_owned())))
39+
}
40+
41+
PropertyPathExpression::Sequence(lhs, rhs) => {
42+
let l = to_expr_aux(lhs, expr)?;
43+
let r = to_expr_aux(rhs, expr)?;
44+
Ok(expr.add(RpqPlan::Seq([l, r])))
45+
}
46+
47+
PropertyPathExpression::Alternative(lhs, rhs) => {
48+
let l = to_expr_aux(lhs, expr)?;
49+
let r = to_expr_aux(rhs, expr)?;
50+
Ok(expr.add(RpqPlan::Alt([l, r])))
51+
}
52+
53+
PropertyPathExpression::ZeroOrMore(inner) => {
54+
let i = to_expr_aux(inner, expr)?;
55+
Ok(expr.add(RpqPlan::Star([i])))
56+
}
57+
58+
PropertyPathExpression::OneOrMore(inner) => {
59+
let e = to_expr_aux(inner, expr)?;
60+
let s = expr.add(RpqPlan::Star([e]));
61+
Ok(expr.add(RpqPlan::Seq([e, s])))
62+
}
63+
64+
PropertyPathExpression::ZeroOrOne(_) => Err(RpqError::UnsupportedPath(
65+
"ZeroOrOne (?) is not supported by RPQMatrix".into(),
66+
)),
67+
68+
PropertyPathExpression::Reverse(_) => Err(RpqError::UnsupportedPath(
69+
"Reverse paths are not supported".into(),
70+
)),
71+
72+
PropertyPathExpression::NegatedPropertySet(_) => Err(RpqError::UnsupportedPath(
73+
"NegatedPropertySet paths are not supported".into(),
74+
)),
75+
}
76+
}
77+
78+
/// Convert a [`RecExpr<RpqPlan>`] into the flat [`RPQMatrixPlan`] array that
79+
/// `LAGraph_RPQMatrix` expects.
80+
pub fn materialize<G: GraphDecomposition>(
81+
expr: &RecExpr<RpqPlan>,
82+
graph: &G,
83+
) -> Result<Vec<RPQMatrixPlan>, RpqError> {
84+
let null_plan = RPQMatrixPlan {
85+
op: RPQMatrixOp::RPQ_MATRIX_OP_LABEL,
86+
lhs: null_mut(),
87+
rhs: null_mut(),
88+
mat: null_mut(),
89+
res_mat: null_mut(),
90+
};
91+
let mut plans = vec![null_plan; expr.len()];
92+
93+
for (id, node) in expr.as_ref().iter().enumerate() {
94+
plans[id] = match node {
95+
RpqPlan::Label(label) => {
96+
let lg = graph
97+
.get_graph(label)
98+
.map_err(|_| RpqError::LabelNotFound(label.clone()))?;
99+
let mat = unsafe { (*lg.inner).A };
100+
RPQMatrixPlan {
101+
op: RPQMatrixOp::RPQ_MATRIX_OP_LABEL,
102+
lhs: null_mut(),
103+
rhs: null_mut(),
104+
mat,
105+
res_mat: null_mut(),
106+
}
107+
}
108+
109+
RpqPlan::Seq([l, r]) => RPQMatrixPlan {
110+
op: RPQMatrixOp::RPQ_MATRIX_OP_CONCAT,
111+
lhs: unsafe { plans.as_mut_ptr().add(usize::from(*l)) },
112+
rhs: unsafe { plans.as_mut_ptr().add(usize::from(*r)) },
113+
mat: null_mut(),
114+
res_mat: null_mut(),
115+
},
116+
117+
RpqPlan::Alt([l, r]) => RPQMatrixPlan {
118+
op: RPQMatrixOp::RPQ_MATRIX_OP_LOR,
119+
lhs: unsafe { plans.as_mut_ptr().add(usize::from(*l)) },
120+
rhs: unsafe { plans.as_mut_ptr().add(usize::from(*r)) },
121+
mat: null_mut(),
122+
res_mat: null_mut(),
123+
},
124+
125+
RpqPlan::Star([i]) => RPQMatrixPlan {
126+
op: RPQMatrixOp::RPQ_MATRIX_OP_KLEENE,
127+
lhs: null_mut(),
128+
rhs: unsafe { plans.as_mut_ptr().add(usize::from(*i)) },
129+
mat: null_mut(),
130+
res_mat: null_mut(),
131+
},
132+
};
133+
}
134+
135+
Ok(plans)
136+
}
137+
138+
/// RPQ evaluator backed by `LAGraph_RPQMatrix`.
139+
pub struct RpqMatrixEvaluator;
140+
141+
impl RpqEvaluator for RpqMatrixEvaluator {
142+
fn evaluate<G: GraphDecomposition>(
143+
&self,
144+
subject: &TermPattern,
145+
path: &PropertyPathExpression,
146+
object: &TermPattern,
147+
graph: &G,
148+
) -> Result<RpqResult, RpqError> {
149+
if !matches!(object, TermPattern::Variable(_)) {
150+
return Err(RpqError::UnsupportedPath(
151+
"bound object term is not yet supported by RpqMatrixEvaluator".into(),
152+
));
153+
}
154+
155+
ensure_grb_init().map_err(|e| RpqError::GraphBlas(e.to_string()))?;
156+
157+
let n = graph.num_nodes() as GrB_Index;
158+
159+
let expr = to_expr(path)?;
160+
161+
let mut plans = materialize(&expr, graph)?;
162+
let root_ptr = unsafe { plans.as_mut_ptr().add(plans.len() - 1) };
163+
164+
let mut nnz: GrB_Index = 0;
165+
la_ok!(LAGraph_RPQMatrix(&mut nnz, root_ptr))
166+
.map_err(|e| RpqError::GraphBlas(e.to_string()))?;
167+
168+
let res_mat = unsafe { (*root_ptr).res_mat };
169+
170+
let src = unsafe {
171+
GraphblasVector::new_bool(n).map_err(|e| RpqError::GraphBlas(e.to_string()))?
172+
};
173+
match subject {
174+
TermPattern::NamedNode(nn) => {
175+
let id = graph
176+
.get_node_id(nn.as_str())
177+
.ok_or_else(|| RpqError::VertexNotFound(nn.as_str().to_owned()))?
178+
as GrB_Index;
179+
grb_ok!(GrB_Vector_setElement_BOOL(src.inner, true, id))
180+
.map_err(|e| RpqError::GraphBlas(e.to_string()))?;
181+
}
182+
TermPattern::Variable(_) => {
183+
for i in 0..n {
184+
grb_ok!(GrB_Vector_setElement_BOOL(src.inner, true, i))
185+
.map_err(|e| RpqError::GraphBlas(e.to_string()))?;
186+
}
187+
}
188+
_ => {
189+
return Err(RpqError::UnsupportedPath(
190+
"subject must be a variable or named node".into(),
191+
));
192+
}
193+
}
194+
195+
let result = unsafe {
196+
GraphblasVector::new_bool(n).map_err(|e| RpqError::GraphBlas(e.to_string()))?
197+
};
198+
grb_ok!(GrB_vxm(
199+
result.inner,
200+
null_mut(),
201+
null_mut(),
202+
GrB_LOR_LAND_SEMIRING_BOOL,
203+
src.inner,
204+
res_mat,
205+
null_mut(),
206+
))
207+
.map_err(|e| RpqError::GraphBlas(e.to_string()))?;
208+
209+
grb_ok!(LAGraph_DestroyRpqMatrixPlan(root_ptr))
210+
.map_err(|e| RpqError::GraphBlas(e.to_string()))?;
211+
212+
Ok(RpqResult { reachable: result })
213+
}
214+
}

src/utils.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,26 @@
11
use crate::{graph::*, lagraph_sys::*};
22
use std::{fmt::Display, sync::Arc};
33

4+
pub fn build_graph(edges: &[(&str, &str, &str)]) -> <InMemory as Backend>::Graph {
5+
let builder = InMemoryBuilder::new();
6+
let edges = edges
7+
.iter()
8+
.cloned()
9+
.map(|(s, t, l)| {
10+
Ok(Edge {
11+
source: s.to_string(),
12+
label: l.to_string(),
13+
target: t.to_string(),
14+
})
15+
})
16+
.collect::<Vec<Result<Edge, GraphError>>>();
17+
builder
18+
.with_stream(edges.into_iter())
19+
.expect("Should insert edges stream")
20+
.build()
21+
.expect("build must succeed")
22+
}
23+
424
pub struct CountOutput<E: std::error::Error>(pub usize, std::marker::PhantomData<E>);
525

626
impl<E: std::error::Error> CountOutput<E> {

0 commit comments

Comments
 (0)