1use anyhow::Result;
2use oxigraph::model::{GraphName, NamedNode, Quad, Subject, Term};
3use oxigraph::store::Store;
4
5#[derive(Debug, PartialEq, Eq, Hash, Clone)]
6pub enum ReasoningStrategy {
7 None,
8 RDFS,
9 OWLRL,
10}
11
12pub struct SynapseReasoner {
13 pub strategy: ReasoningStrategy,
14}
15
16impl SynapseReasoner {
17 pub fn new(strategy: ReasoningStrategy) -> Self {
18 Self { strategy }
19 }
20
21 pub fn apply(&self, store: &Store) -> Result<Vec<(String, String, String)>> {
23 let mut inferred = Vec::new();
24
25 match self.strategy {
26 ReasoningStrategy::None => {}
27 ReasoningStrategy::RDFS => {
28 let subclass_prop =
31 NamedNode::new("http://www.w3.org/2000/01/rdf-schema#subClassOf")?;
32
33 for q1 in store
34 .quads_for_pattern(None, Some(subclass_prop.as_ref()), None, None)
35 .flatten()
36 {
37 if let Subject::NamedNode(a) = q1.subject {
38 if let Term::NamedNode(b) = q1.object {
39 for q2 in store
40 .quads_for_pattern(
41 Some(b.as_ref().into()),
42 Some(subclass_prop.as_ref()),
43 None,
44 None,
45 )
46 .flatten()
47 {
48 if let Term::NamedNode(c) = q2.object {
49 inferred.push((
50 a.as_str().to_string(),
51 subclass_prop.as_str().to_string(),
52 c.as_str().to_string(),
53 ));
54 }
55 }
56 }
57 }
58 }
59 }
60 ReasoningStrategy::OWLRL => {
61 let type_prop = NamedNode::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?;
64 let transitive_class =
65 NamedNode::new("http://www.w3.org/2002/07/owl#TransitiveProperty")?;
66
67 for q in store
69 .quads_for_pattern(
70 None,
71 Some(type_prop.as_ref()),
72 Some(transitive_class.as_ref().into()),
73 None,
74 )
75 .flatten()
76 {
77 if let Subject::NamedNode(p_node) = q.subject {
78 let p_ref = p_node.as_ref();
79
80 for xy_quad in store
82 .quads_for_pattern(None, Some(p_ref), None, None)
83 .flatten()
84 {
85 if let Subject::NamedNode(x) = xy_quad.subject {
86 if let Term::NamedNode(y) = xy_quad.object {
87 for yz_quad in store
89 .quads_for_pattern(
90 Some(y.as_ref().into()),
91 Some(p_ref),
92 None,
93 None,
94 )
95 .flatten()
96 {
97 if let Term::NamedNode(z) = yz_quad.object {
98 inferred.push((
99 x.as_str().to_string(),
100 p_node.as_str().to_string(),
101 z.as_str().to_string(),
102 ));
103 }
104 }
105 }
106 }
107 }
108 }
109 }
110
111 let symmetric_class =
114 NamedNode::new("http://www.w3.org/2002/07/owl#SymmetricProperty")?;
115
116 for q in store
117 .quads_for_pattern(
118 None,
119 Some(type_prop.as_ref()),
120 Some(symmetric_class.as_ref().into()),
121 None,
122 )
123 .flatten()
124 {
125 if let Subject::NamedNode(p_node) = q.subject {
126 let p_ref = p_node.as_ref();
127
128 for e in store
129 .quads_for_pattern(None, Some(p_ref), None, None)
130 .flatten()
131 {
132 if let Subject::NamedNode(s_node) = e.subject {
134 if let Term::NamedNode(obj_node) = e.object {
135 inferred.push((
136 obj_node.as_str().to_string(),
137 p_node.as_str().to_string(),
138 s_node.as_str().to_string(),
139 ));
140 }
141 }
142 }
143 }
144 }
145
146 let inverse_prop = NamedNode::new("http://www.w3.org/2002/07/owl#inverseOf")?;
149
150 for q in store
151 .quads_for_pattern(None, Some(inverse_prop.as_ref()), None, None)
152 .flatten()
153 {
154 if let Subject::NamedNode(p1_node) = q.subject {
155 let p1_ref = p1_node.as_ref();
156 if let Term::NamedNode(p2_node) = q.object {
157 for e in store
159 .quads_for_pattern(None, Some(p1_ref), None, None)
160 .flatten()
161 {
162 if let Subject::NamedNode(x) = e.subject {
163 if let Term::NamedNode(y) = e.object {
164 inferred.push((
165 y.as_str().to_string(),
166 p2_node.as_str().to_string(),
167 x.as_str().to_string(),
168 ));
169 }
170 }
171 }
172 }
173 }
174 }
175 }
176 }
177
178 Ok(inferred)
179 }
180
181 pub fn materialize(&self, store: &Store) -> Result<usize> {
183 let mut total_inferred = 0;
184
185 loop {
187 let inferred = self.apply(store)?;
188 if inferred.is_empty() {
189 break;
190 }
191
192 let mut new_triples = 0;
193 for (s, p, o) in inferred {
194 let s_node = NamedNode::new(s)?;
195 let p_node = NamedNode::new(p)?;
196 let o_node = NamedNode::new(o)?;
197
198 let quad = Quad::new(s_node, p_node, o_node, GraphName::DefaultGraph);
199
200 if !store.contains(&quad)? {
204 store.insert(&quad)?;
205 new_triples += 1;
206 }
207 }
208
209 if new_triples == 0 {
210 break;
211 }
212 total_inferred += new_triples;
213 }
214
215 Ok(total_inferred)
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn test_rdfs_transitivity() -> Result<()> {
225 let store = Store::new()?;
226 let reasoner = SynapseReasoner::new(ReasoningStrategy::RDFS);
227
228 let a = NamedNode::new("http://example.org/A")?;
229 let b = NamedNode::new("http://example.org/B")?;
230 let c = NamedNode::new("http://example.org/C")?;
231 let sub_class_of = NamedNode::new("http://www.w3.org/2000/01/rdf-schema#subClassOf")?;
232
233 store.insert(&Quad::new(
234 a.clone(),
235 sub_class_of.clone(),
236 b.clone(),
237 GraphName::DefaultGraph,
238 ))?;
239 store.insert(&Quad::new(
240 b.clone(),
241 sub_class_of.clone(),
242 c.clone(),
243 GraphName::DefaultGraph,
244 ))?;
245
246 let inferred = reasoner.apply(&store)?;
247 assert!(!inferred.is_empty());
248 assert!(inferred.contains(&(
249 a.as_str().to_string(),
250 sub_class_of.as_str().to_string(),
251 c.as_str().to_string()
252 )));
253
254 Ok(())
255 }
256
257 #[test]
258 fn test_owl_transitive_property() -> Result<()> {
259 let store = Store::new()?;
260 let reasoner = SynapseReasoner::new(ReasoningStrategy::OWLRL);
261
262 let p = NamedNode::new("http://example.org/ancestorOf")?;
263 let type_prop = NamedNode::new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")?;
264 let trans_class = NamedNode::new("http://www.w3.org/2002/07/owl#TransitiveProperty")?;
265
266 store.insert(&Quad::new(
268 p.clone(),
269 type_prop,
270 trans_class,
271 GraphName::DefaultGraph,
272 ))?;
273
274 let x = NamedNode::new("http://example.org/grandparent")?;
275 let y = NamedNode::new("http://example.org/parent")?;
276 let z = NamedNode::new("http://example.org/child")?;
277
278 store.insert(&Quad::new(
280 x.clone(),
281 p.clone(),
282 y.clone(),
283 GraphName::DefaultGraph,
284 ))?;
285 store.insert(&Quad::new(
287 y.clone(),
288 p.clone(),
289 z.clone(),
290 GraphName::DefaultGraph,
291 ))?;
292
293 let inferred = reasoner.apply(&store)?;
294 assert!(inferred.contains(&(
295 x.as_str().to_string(),
296 p.as_str().to_string(),
297 z.as_str().to_string()
298 )));
299
300 Ok(())
301 }
302}