Skip to content

Commit 790df02

Browse files
Add basic LAGraph expression evaluation
This patch introduces basic RPQ evaluation logic. It basically interfaces the LAGraph (experimental) library that offers a collection of the sparse linear algebra-based algorithms. It uses GraphBLAS [^1] as a backend library for evaluating sparse matrix algebra. Rust <-> C interfacing is done via FFI. Currently, the project requires our fork [^2] since the required algorithm is not upstreamed yet. If you are reading this and notice we occasionally have removed this from the fork we are asking for an excuse and hope that you will be able to find the suitable algorithm by yourself from the interfaces described on the Rust side. [^1] https://github.com/DrTimothyAldenDavis/GraphBLAS [^2] https://github.com/SparseLinearAlgebra/LAGraph
1 parent 0dca9e7 commit 790df02

File tree

7 files changed

+289
-43
lines changed

7 files changed

+289
-43
lines changed

.github/workflows/ci.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,24 @@ jobs:
1515
runs-on: ubuntu-latest
1616
steps:
1717
- uses: actions/checkout@v4
18+
with:
19+
submodules: recurse
1820
- uses: crate-ci/typos@master
1921
- run: rustup update stable && rustup default stable
2022
- run: cargo fmt --check
2123
- run: cargo clippy
24+
- run: |
25+
git clone https://github.com/DrTimothyAldenDavis/GraphBLAS.git
26+
cd GraphBLAS
27+
git checkout tags/v${{ matrix.config.grb_version }}
28+
make compact
29+
sudo make install
30+
cd ..
31+
- run: |
32+
cd vendor/LAGraph
33+
git checkout rpq-matrix
34+
make
35+
sudo make install
36+
cd ../..
2237
- run: cargo build --verbose
2338
- run: cargo test --verbose

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "vendor/LAGraph"]
2+
path = vendor/LAGraph
3+
url = https://github.com/SparseLinearAlgebra/LAGraph.git

build.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
fn main() {
2+
println!("cargo:rustc-link-lib=dylib=lagraphx");
3+
println!("cargo:rustc-link-search=native=../rpq-matrix-lagraph/LAGraph/build/experimental");
4+
5+
println!("cargo:rustc-link-lib=dylib=lagraph");
6+
println!("cargo:rustc-link-search=native=../rpq-matrix-lagraph/LAGraph/build/src");
7+
}

src/eval.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use std::ptr::null_mut;
2+
3+
use crate::graph::Graph;
4+
use crate::plan::Plan;
5+
use std::collections::HashMap;
6+
7+
#[repr(C)]
8+
pub enum RpqMatrixOp {
9+
Label,
10+
Lor,
11+
Concat,
12+
Kleene,
13+
KleeneL,
14+
KleeneR,
15+
}
16+
17+
#[repr(C)]
18+
pub struct RpqMatrixPlan {
19+
pub op: RpqMatrixOp,
20+
pub lhs: *mut RpqMatrixPlan,
21+
pub rhs: *mut RpqMatrixPlan,
22+
pub mat: *mut libc::c_void,
23+
pub res_mat: *mut libc::c_void,
24+
}
25+
26+
#[link(name = "lagraphx")]
27+
extern "C" {
28+
pub fn LAGraph_RpqMatrix_initialize() -> libc::c_longlong;
29+
pub fn LAGraph_RPQMatrix(plan: *mut RpqMatrixPlan, msg: *mut libc::c_char) -> libc::c_longlong;
30+
pub fn LAGraph_RPQMatrix_label(
31+
mat: *mut *mut libc::c_void,
32+
x: usize,
33+
i: usize,
34+
j: usize,
35+
) -> libc::c_longlong;
36+
}
37+
38+
#[link(name = "lagraph")]
39+
extern "C" {
40+
pub fn LAGraph_MMRead(
41+
mat: *mut *mut libc::c_void,
42+
f: *mut libc::FILE,
43+
msg: *mut libc::c_char,
44+
) -> libc::c_int;
45+
}
46+
47+
pub fn eval(graph: &Graph, expr: egg::RecExpr<Plan>) {
48+
let mut plans: HashMap<egg::Id, RpqMatrixPlan> = HashMap::with_capacity(expr.len());
49+
expr.items().for_each(|(id, plan)| {
50+
let eval_plan = match plan {
51+
&Plan::Seq([lhs, rhs]) => RpqMatrixPlan {
52+
op: RpqMatrixOp::Concat,
53+
lhs: plans.get_mut(&lhs).unwrap() as *mut RpqMatrixPlan,
54+
rhs: plans.get_mut(&rhs).unwrap() as *mut RpqMatrixPlan,
55+
res_mat: null_mut(),
56+
mat: null_mut(),
57+
},
58+
&Plan::Alt([lhs, rhs]) => RpqMatrixPlan {
59+
op: RpqMatrixOp::Lor,
60+
lhs: plans.get_mut(&lhs).unwrap() as *mut RpqMatrixPlan,
61+
rhs: plans.get_mut(&rhs).unwrap() as *mut RpqMatrixPlan,
62+
res_mat: null_mut(),
63+
mat: null_mut(),
64+
},
65+
&Plan::Star([lhs]) => RpqMatrixPlan {
66+
op: RpqMatrixOp::Kleene,
67+
lhs: null_mut(),
68+
rhs: plans.get_mut(&lhs).unwrap() as *mut RpqMatrixPlan,
69+
res_mat: null_mut(),
70+
mat: null_mut(),
71+
},
72+
&Plan::LStar([lhs, rhs]) => RpqMatrixPlan {
73+
op: RpqMatrixOp::KleeneL,
74+
lhs: plans.get_mut(&lhs).unwrap() as *mut RpqMatrixPlan,
75+
rhs: plans.get_mut(&rhs).unwrap() as *mut RpqMatrixPlan,
76+
res_mat: null_mut(),
77+
mat: null_mut(),
78+
},
79+
&Plan::RStar([lhs, rhs]) => RpqMatrixPlan {
80+
op: RpqMatrixOp::KleeneR,
81+
lhs: plans.get_mut(&lhs).unwrap() as *mut RpqMatrixPlan,
82+
rhs: plans.get_mut(&rhs).unwrap() as *mut RpqMatrixPlan,
83+
res_mat: null_mut(),
84+
mat: null_mut(),
85+
},
86+
Plan::Label(meta) => {
87+
let mut mat: *mut libc::c_void = std::ptr::null_mut();
88+
let mat = *graph
89+
.mats
90+
.get(&meta.name)
91+
.or({
92+
graph.verts.get(&meta.name).map(|vert_idx| {
93+
unsafe {
94+
LAGraph_RPQMatrix_label(
95+
&mut mat as *mut *mut libc::c_void,
96+
*vert_idx,
97+
graph.verts.len(),
98+
graph.verts.len(),
99+
);
100+
}
101+
&mat
102+
})
103+
})
104+
.unwrap();
105+
RpqMatrixPlan {
106+
op: RpqMatrixOp::Label,
107+
lhs: null_mut(),
108+
rhs: null_mut(),
109+
res_mat: null_mut(),
110+
mat,
111+
}
112+
}
113+
};
114+
plans.insert(id, eval_plan);
115+
});
116+
let (_id, plan) = plans.iter_mut().last().unwrap();
117+
unsafe {
118+
LAGraph_RPQMatrix(plan as *mut RpqMatrixPlan, null_mut());
119+
}
120+
}

src/graph.rs

Lines changed: 94 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
1-
use std::{collections::HashMap, io, path::Path};
1+
use std::{
2+
collections::HashMap,
3+
ffi::CString,
4+
io,
5+
path::{Path, PathBuf},
6+
};
27

38
use egg::{Id, RecExpr};
49

510
use crate::{
6-
plan::Plan,
11+
eval::LAGraph_MMRead,
12+
plan::{LabelMeta, Plan},
713
query::{Pattern, Query, Vertex},
814
};
915

1016
pub struct Graph {
1117
nvals: HashMap<String, usize>,
18+
pub mats: HashMap<String, *mut libc::c_void>,
19+
pub verts: HashMap<String, usize>,
1220
}
1321

1422
impl Graph {
15-
fn plan_aux(&self, expr: &mut RecExpr<Plan>, pattern: Pattern) -> Id {
23+
fn plan_aux(&self, expr: &mut RecExpr<Plan>, pattern: Pattern) -> Result<Id, String> {
1624
match pattern {
1725
Pattern::Uri(uri) => Ok(expr.add(Plan::Label(LabelMeta {
1826
nvals: *self
@@ -22,50 +30,65 @@ impl Graph {
2230
name: uri,
2331
}))),
2432
Pattern::Seq(lhs, rhs) => {
25-
let lhs = self.plan_aux(expr, *lhs);
26-
let rhs = self.plan_aux(expr, *rhs);
27-
expr.add(Plan::Seq([lhs, rhs]))
33+
let lhs = self.plan_aux(expr, *lhs)?;
34+
let rhs = self.plan_aux(expr, *rhs)?;
35+
Ok(expr.add(Plan::Seq([lhs, rhs])))
2836
}
2937
Pattern::Alt(lhs, rhs) => {
30-
let lhs = self.plan_aux(expr, *lhs);
31-
let rhs = self.plan_aux(expr, *rhs);
32-
expr.add(Plan::Alt([lhs, rhs]))
38+
let lhs = self.plan_aux(expr, *lhs)?;
39+
let rhs = self.plan_aux(expr, *rhs)?;
40+
Ok(expr.add(Plan::Alt([lhs, rhs])))
3341
}
3442
Pattern::Star(lhs) => {
35-
let lhs = self.plan_aux(expr, *lhs);
36-
expr.add(Plan::Star([lhs]))
43+
let lhs = self.plan_aux(expr, *lhs)?;
44+
Ok(expr.add(Plan::Star([lhs])))
3745
}
3846
Pattern::Plus(lhs) => {
39-
let lhs = self.plan_aux(expr, *lhs);
47+
let lhs = self.plan_aux(expr, *lhs)?;
4048
let aux = expr.add(Plan::Star([lhs]));
41-
expr.add(Plan::Seq([lhs, aux]))
49+
Ok(expr.add(Plan::Seq([lhs, aux])))
50+
}
51+
Pattern::Opt(_lhs) => {
52+
todo!("opt (?) queries are not supported yet")
4253
}
43-
_ => todo!("TBD"),
4454
}
4555
}
4656

47-
pub fn run(&self, query: Query) -> RecExpr<Plan> {
57+
pub fn run(&self, query: Query) -> Result<RecExpr<Plan>, String> {
4858
let mut expr: RecExpr<Plan> = RecExpr::default();
4959
match query {
5060
Query {
51-
src: Vertex::Con(_v),
61+
src: Vertex::Any,
62+
pattern,
63+
dest: Vertex::Any,
64+
} => self.plan_aux(&mut expr, pattern)?,
65+
Query {
66+
src: Vertex::Con(name),
5267
pattern,
5368
dest: Vertex::Any,
5469
} => {
55-
// TODO: add src vertex knowledge.
56-
self.plan_aux(&mut expr, pattern)
70+
let lhs = expr.add(Plan::Label(LabelMeta { name, nvals: 1 }));
71+
let rhs = self.plan_aux(&mut expr, pattern)?;
72+
expr.add(Plan::Seq([lhs, rhs]))
5773
}
5874
Query {
5975
src: Vertex::Any,
6076
pattern,
61-
dest: Vertex::Con(_v),
77+
dest: Vertex::Con(name),
78+
} => {
79+
let lhs = self.plan_aux(&mut expr, pattern)?;
80+
let rhs = expr.add(Plan::Label(LabelMeta { name, nvals: 1 }));
81+
expr.add(Plan::Seq([lhs, rhs]))
82+
}
83+
Query {
84+
src: Vertex::Con(_v1),
85+
pattern: _,
86+
dest: Vertex::Con(_v2),
6287
} => {
63-
// TODO: add dest vertex knowledge.
64-
self.plan_aux(&mut expr, pattern)
88+
todo!("con to con queries are not supported yet")
6589
}
66-
_ => todo!("TBD"),
6790
};
68-
expr
91+
Ok(expr)
6992
}
7093
}
7194

@@ -84,16 +107,36 @@ pub fn load_dir(path: &Path) -> io::Result<Graph> {
84107
})
85108
.collect();
86109

87-
let nvals: HashMap<String, usize> = dirs
110+
let verts_file = path.join("vertices.txt");
111+
let verts: HashMap<String, usize> = std::fs::read_to_string(verts_file)?
112+
.lines()
113+
.filter_map(|line| {
114+
let mut splits = line.split_whitespace();
115+
let vert = splits.next()?;
116+
let vert = vert[1..vert.len() - 1].to_string();
117+
let num = splits.next()?.parse::<usize>().ok()?;
118+
Some((vert, num))
119+
})
120+
.collect();
121+
122+
let mat_files: Vec<(String, PathBuf)> = dirs
88123
.flatten()
89124
.map(|entry| entry.path())
90125
.filter_map(|entry| Some(entry.file_stem()?.to_str()?.to_string()))
91126
.filter_map(|entry| entry.parse::<usize>().ok())
92127
.filter_map(|entry| {
93-
let edge = edges.get(&entry)?;
94-
let edge_file = path.join(format!("{}.txt", entry));
128+
Some((
129+
edges.get(&entry)?.clone(),
130+
path.join(format!("{}.txt", entry)),
131+
))
132+
})
133+
.collect();
134+
135+
let nvals: HashMap<String, usize> = mat_files
136+
.iter()
137+
.filter_map(|(edge, file)| {
95138
// TODO: read only first 3 lines :/.
96-
let edge_nvals = std::fs::read_to_string(edge_file)
139+
let edge_nvals = std::fs::read_to_string(file)
97140
.ok()?
98141
.lines()
99142
.nth(2)?
@@ -106,5 +149,28 @@ pub fn load_dir(path: &Path) -> io::Result<Graph> {
106149
})
107150
.collect();
108151

109-
Ok(Graph { nvals })
152+
let mats: HashMap<String, *mut libc::c_void> = mat_files
153+
.iter()
154+
.map(|(edge, file)| {
155+
let mut mat: *mut libc::c_void = std::ptr::null_mut();
156+
unsafe {
157+
let c_file = CString::new(file.to_str().unwrap()).unwrap();
158+
let mode = CString::new("r").unwrap();
159+
let f = libc::fopen(c_file.as_ptr(), mode.as_ptr());
160+
let code =
161+
LAGraph_MMRead(&mut mat as *mut *mut libc::c_void, f, std::ptr::null_mut());
162+
assert_eq!(
163+
code,
164+
0,
165+
"unable to load matrix for {} in {} (error {})",
166+
edge,
167+
file.display(),
168+
code
169+
);
170+
};
171+
(edge.clone(), mat)
172+
})
173+
.collect();
174+
175+
Ok(Graph { nvals, mats, verts })
110176
}

0 commit comments

Comments
 (0)