1use crate::NQuin;
30
31pub const RDF_TYPE: u64 = crate::q_hash("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
34pub const RDFS_SUBCLASS_OF: u64 = crate::q_hash("http://www.w3.org/2000/01/rdf-schema#subClassOf");
35pub const RDFS_SUBPROPERTY_OF: u64 =
36 crate::q_hash("http://www.w3.org/2000/01/rdf-schema#subPropertyOf");
37pub const RDFS_DOMAIN: u64 = crate::q_hash("http://www.w3.org/2000/01/rdf-schema#domain");
38pub const RDFS_RANGE: u64 = crate::q_hash("http://www.w3.org/2000/01/rdf-schema#range");
39pub const OWL_SAME_AS: u64 = crate::q_hash("http://www.w3.org/2002/07/owl#sameAs");
40pub const OWL_INVERSE_OF: u64 = crate::q_hash("http://www.w3.org/2002/07/owl#inverseOf");
41pub const OWL_EQUIVALENT_CLASS: u64 =
42 crate::q_hash("http://www.w3.org/2002/07/owl#equivalentClass");
43pub const OWL_EQUIVALENT_PROPERTY: u64 =
44 crate::q_hash("http://www.w3.org/2002/07/owl#equivalentProperty");
45pub const OWL_DISJOINT_WITH: u64 = crate::q_hash("http://www.w3.org/2002/07/owl#disjointWith");
46pub const OWL_SYMMETRIC_PROPERTY: u64 =
47 crate::q_hash("http://www.w3.org/2002/07/owl#SymmetricProperty");
48pub const OWL_TRANSITIVE_PROPERTY: u64 =
49 crate::q_hash("http://www.w3.org/2002/07/owl#TransitiveProperty");
50pub const OWL_FUNCTIONAL_PROPERTY: u64 =
51 crate::q_hash("http://www.w3.org/2002/07/owl#FunctionalProperty");
52pub const OWL_INVERSE_FUNCTIONAL_PROPERTY: u64 =
53 crate::q_hash("http://www.w3.org/2002/07/owl#InverseFunctionalProperty");
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub struct RdfTriple {
61 pub s: u64,
62 pub p: u64,
63 pub o: u64,
64}
65
66impl RdfTriple {
67 pub const fn new(s: u64, p: u64, o: u64) -> Self {
68 Self { s, p, o }
69 }
70
71 pub const fn from_nquin(q: &NQuin) -> Self {
73 Self {
74 s: q.subject,
75 p: q.predicate,
76 o: q.object,
77 }
78 }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub struct ChainAxiom {
85 pub composed: u64,
86 pub first: u64,
87 pub second: u64,
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub struct DisjointnessViolation {
94 pub individual: u64,
95 pub class_a: u64,
96 pub class_b: u64,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum MaterializeError {
101 WorkingSetFull,
103 ContradictionBufferFull,
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub struct MaterializeSummary {
109 pub triple_count: usize,
111 pub inferred_count: usize,
113 pub contradiction_count: usize,
115 pub iterations: u32,
117 pub saturated: bool,
119}
120
121#[inline]
124fn contains(triples: &[RdfTriple], len: usize, t: RdfTriple) -> bool {
125 triples[..len].iter().any(|&x| x == t)
126}
127
128#[inline]
131fn try_push(
132 triples: &mut [RdfTriple],
133 len: &mut usize,
134 t: RdfTriple,
135) -> Result<bool, MaterializeError> {
136 if contains(triples, *len, t) {
137 return Ok(false);
138 }
139 if *len >= triples.len() {
140 return Err(MaterializeError::WorkingSetFull);
141 }
142 triples[*len] = t;
143 *len += 1;
144 Ok(true)
145}
146
147pub fn materialize_owl_rl(
157 triples: &mut [RdfTriple],
158 initial_len: usize,
159 chains: &[ChainAxiom],
160 max_iters: u32,
161 contradictions_out: &mut [DisjointnessViolation],
162) -> Result<MaterializeSummary, MaterializeError> {
163 let mut len = initial_len.min(triples.len());
164 let mut iterations = 0u32;
165 let mut saturated = false;
166
167 while iterations < max_iters {
168 iterations += 1;
169 let mut changed = false;
170 let n = len; for i in 0..n {
173 let t = triples[i];
174
175 if t.p == OWL_EQUIVALENT_CLASS {
177 changed |= try_push(
178 triples,
179 &mut len,
180 RdfTriple::new(t.s, RDFS_SUBCLASS_OF, t.o),
181 )?;
182 changed |= try_push(
183 triples,
184 &mut len,
185 RdfTriple::new(t.o, RDFS_SUBCLASS_OF, t.s),
186 )?;
187 } else if t.p == OWL_EQUIVALENT_PROPERTY {
188 changed |= try_push(
189 triples,
190 &mut len,
191 RdfTriple::new(t.s, RDFS_SUBPROPERTY_OF, t.o),
192 )?;
193 changed |= try_push(
194 triples,
195 &mut len,
196 RdfTriple::new(t.o, RDFS_SUBPROPERTY_OF, t.s),
197 )?;
198 }
199 else if t.p == OWL_SAME_AS && t.s != t.o {
201 changed |= try_push(triples, &mut len, RdfTriple::new(t.o, OWL_SAME_AS, t.s))?;
202 }
203 else if t.p == OWL_INVERSE_OF {
205 let (p1, p2) = (t.s, t.o);
206 for j in 0..n {
207 let u = triples[j];
208 if u.p == p1 {
209 changed |= try_push(triples, &mut len, RdfTriple::new(u.o, p2, u.s))?;
210 }
211 if u.p == p2 {
212 changed |= try_push(triples, &mut len, RdfTriple::new(u.o, p1, u.s))?;
213 }
214 }
215 }
216 }
217
218 for i in 0..n {
220 let a = triples[i];
221 if a.p != RDFS_SUBCLASS_OF && a.p != RDFS_SUBPROPERTY_OF {
222 continue;
223 }
224 for j in 0..n {
225 let b = triples[j];
226 if b.p == a.p && b.s == a.o && a.s != b.o {
227 changed |= try_push(triples, &mut len, RdfTriple::new(a.s, a.p, b.o))?;
228 }
229 }
230 }
231
232 for i in 0..n {
235 let inst = triples[i];
236 for j in 0..n {
237 let ax = triples[j];
238 if ax.p == RDFS_SUBCLASS_OF
239 && inst.p == RDF_TYPE
240 && inst.o == ax.s
241 && inst.o != ax.o
242 {
243 changed |= try_push(triples, &mut len, RdfTriple::new(inst.s, RDF_TYPE, ax.o))?;
244 } else if ax.p == RDFS_SUBPROPERTY_OF && inst.p == ax.s && ax.s != ax.o {
245 changed |= try_push(triples, &mut len, RdfTriple::new(inst.s, ax.o, inst.o))?;
246 }
247 }
248 }
249
250 for i in 0..n {
252 let ax = triples[i];
253 if ax.p != RDFS_DOMAIN && ax.p != RDFS_RANGE {
254 continue;
255 }
256 for j in 0..n {
257 let inst = triples[j];
258 if inst.p == ax.s {
259 let subj = if ax.p == RDFS_DOMAIN { inst.s } else { inst.o };
260 changed |= try_push(triples, &mut len, RdfTriple::new(subj, RDF_TYPE, ax.o))?;
261 }
262 }
263 }
264
265 for i in 0..n {
268 let inst = triples[i];
269 if inst.p == RDF_TYPE {
270 continue;
271 }
272 let is_symmetric = contains(
273 triples,
274 len,
275 RdfTriple::new(inst.p, RDF_TYPE, OWL_SYMMETRIC_PROPERTY),
276 );
277 if is_symmetric && inst.s != inst.o {
278 changed |= try_push(triples, &mut len, RdfTriple::new(inst.o, inst.p, inst.s))?;
279 }
280 let is_transitive = contains(
281 triples,
282 len,
283 RdfTriple::new(inst.p, RDF_TYPE, OWL_TRANSITIVE_PROPERTY),
284 );
285 if is_transitive {
286 for j in 0..n {
287 let next = triples[j];
288 if next.p == inst.p && next.s == inst.o && inst.s != next.o {
289 changed |=
290 try_push(triples, &mut len, RdfTriple::new(inst.s, inst.p, next.o))?;
291 }
292 }
293 }
294 }
295
296 for i in 0..n {
299 let a = triples[i];
300 if a.p == RDF_TYPE {
301 continue;
302 }
303 let functional = contains(
304 triples,
305 len,
306 RdfTriple::new(a.p, RDF_TYPE, OWL_FUNCTIONAL_PROPERTY),
307 );
308 let inv_functional = contains(
309 triples,
310 len,
311 RdfTriple::new(a.p, RDF_TYPE, OWL_INVERSE_FUNCTIONAL_PROPERTY),
312 );
313 if !functional && !inv_functional {
314 continue;
315 }
316 for j in 0..n {
317 let b = triples[j];
318 if b.p != a.p {
319 continue;
320 }
321 if functional && b.s == a.s && a.o != b.o {
322 changed |= try_push(triples, &mut len, RdfTriple::new(a.o, OWL_SAME_AS, b.o))?;
323 }
324 if inv_functional && b.o == a.o && a.s != b.s {
325 changed |= try_push(triples, &mut len, RdfTriple::new(a.s, OWL_SAME_AS, b.s))?;
326 }
327 }
328 }
329
330 for i in 0..n {
332 let a = triples[i];
333 if a.p != OWL_SAME_AS {
334 continue;
335 }
336 for j in 0..n {
337 let b = triples[j];
338 if b.p == OWL_SAME_AS && b.s == a.o && a.s != b.o {
339 changed |= try_push(triples, &mut len, RdfTriple::new(a.s, OWL_SAME_AS, b.o))?;
340 }
341 }
342 }
343
344 for chain in chains {
346 for i in 0..n {
347 let lhs = triples[i];
348 if lhs.p != chain.first {
349 continue;
350 }
351 for j in 0..n {
352 let rhs = triples[j];
353 if rhs.p == chain.second && rhs.s == lhs.o {
354 changed |= try_push(
355 triples,
356 &mut len,
357 RdfTriple::new(lhs.s, chain.composed, rhs.o),
358 )?;
359 }
360 }
361 }
362 }
363
364 if !changed {
365 saturated = true;
366 break;
367 }
368 }
369
370 let mut contradiction_count = 0usize;
372 for i in 0..len {
373 let dj = triples[i];
374 if dj.p != OWL_DISJOINT_WITH {
375 continue;
376 }
377 let (c1, c2) = (dj.s, dj.o);
378 for j in 0..len {
379 let tj = triples[j];
380 if tj.p != RDF_TYPE || tj.o != c1 {
381 continue;
382 }
383 let x = tj.s;
384 if !contains(triples, len, RdfTriple::new(x, RDF_TYPE, c2)) {
385 continue;
386 }
387 let dup = contradictions_out[..contradiction_count].iter().any(|e| {
388 e.individual == x
389 && ((e.class_a == c1 && e.class_b == c2)
390 || (e.class_a == c2 && e.class_b == c1))
391 });
392 if dup {
393 continue;
394 }
395 if contradiction_count >= contradictions_out.len() {
396 return Err(MaterializeError::ContradictionBufferFull);
397 }
398 contradictions_out[contradiction_count] = DisjointnessViolation {
399 individual: x,
400 class_a: c1,
401 class_b: c2,
402 };
403 contradiction_count += 1;
404 }
405 }
406
407 Ok(MaterializeSummary {
408 triple_count: len,
409 inferred_count: len - initial_len.min(triples.len()),
410 contradiction_count,
411 iterations,
412 saturated,
413 })
414}
415
416#[cfg(test)]
417mod tests {
418 use super::*;
419
420 fn h(s: &str) -> u64 {
422 crate::q_hash(s)
423 }
424
425 #[test]
426 fn subclass_transitivity_and_type_propagation() {
427 let (alice, student, person, agent) = (h("alice"), h("Student"), h("Person"), h("Agent"));
428 let mut triples = [RdfTriple::new(0, 0, 0); 64];
429 triples[0] = RdfTriple::new(alice, RDF_TYPE, student);
430 triples[1] = RdfTriple::new(student, RDFS_SUBCLASS_OF, person);
431 triples[2] = RdfTriple::new(person, RDFS_SUBCLASS_OF, agent);
432 let mut contra = [DisjointnessViolation {
433 individual: 0,
434 class_a: 0,
435 class_b: 0,
436 }; 4];
437
438 let s = materialize_owl_rl(&mut triples, 3, &[], 16, &mut contra).unwrap();
439 assert!(s.saturated);
440 let out = &triples[..s.triple_count];
441 assert!(out.contains(&RdfTriple::new(student, RDFS_SUBCLASS_OF, agent)));
443 assert!(out.contains(&RdfTriple::new(alice, RDF_TYPE, person)));
445 assert!(out.contains(&RdfTriple::new(alice, RDF_TYPE, agent)));
446 assert_eq!(s.contradiction_count, 0);
447 }
448
449 #[test]
450 fn domain_and_range_typing() {
451 let (knows, person, x, y) = (h("knows"), h("Person"), h("x"), h("y"));
452 let mut triples = [RdfTriple::new(0, 0, 0); 32];
453 triples[0] = RdfTriple::new(knows, RDFS_DOMAIN, person);
454 triples[1] = RdfTriple::new(knows, RDFS_RANGE, person);
455 triples[2] = RdfTriple::new(x, knows, y);
456 let mut contra = [DisjointnessViolation {
457 individual: 0,
458 class_a: 0,
459 class_b: 0,
460 }; 2];
461
462 let s = materialize_owl_rl(&mut triples, 3, &[], 16, &mut contra).unwrap();
463 let out = &triples[..s.triple_count];
464 assert!(out.contains(&RdfTriple::new(x, RDF_TYPE, person))); assert!(out.contains(&RdfTriple::new(y, RDF_TYPE, person))); }
467
468 #[test]
469 fn transitive_and_inverse_properties() {
470 let (anc, has_child, a, b, c) = (h("ancestorOf"), h("hasChild"), h("a"), h("b"), h("c"));
471 let mut triples = [RdfTriple::new(0, 0, 0); 32];
472 triples[0] = RdfTriple::new(anc, RDF_TYPE, OWL_TRANSITIVE_PROPERTY);
473 triples[1] = RdfTriple::new(a, anc, b);
474 triples[2] = RdfTriple::new(b, anc, c);
475 triples[3] = RdfTriple::new(anc, OWL_INVERSE_OF, has_child);
476 let mut contra = [DisjointnessViolation {
477 individual: 0,
478 class_a: 0,
479 class_b: 0,
480 }; 2];
481
482 let s = materialize_owl_rl(&mut triples, 4, &[], 16, &mut contra).unwrap();
483 let out = &triples[..s.triple_count];
484 assert!(out.contains(&RdfTriple::new(a, anc, c))); assert!(out.contains(&RdfTriple::new(b, has_child, a))); assert!(out.contains(&RdfTriple::new(c, has_child, b)));
487 }
488
489 #[test]
490 fn property_chain_unrolling() {
491 let (parent, brother, uncle, x, p, u) = (
493 h("parentOf"),
494 h("brotherOf"),
495 h("uncleOf"),
496 h("x"),
497 h("p"),
498 h("u"),
499 );
500 let mut triples = [RdfTriple::new(0, 0, 0); 16];
501 triples[0] = RdfTriple::new(x, parent, p);
502 triples[1] = RdfTriple::new(p, brother, u);
503 let chains = [ChainAxiom {
504 composed: uncle,
505 first: parent,
506 second: brother,
507 }];
508 let mut contra = [DisjointnessViolation {
509 individual: 0,
510 class_a: 0,
511 class_b: 0,
512 }; 2];
513
514 let s = materialize_owl_rl(&mut triples, 2, &chains, 16, &mut contra).unwrap();
515 assert!(triples[..s.triple_count].contains(&RdfTriple::new(x, uncle, u)));
516 }
517
518 #[test]
519 fn disjointness_isolation_does_not_halt() {
520 let (cat, dog, animal, fido, rex, pet, breed) = (
523 h("Cat"),
524 h("Dog"),
525 h("Animal"),
526 h("fido"),
527 h("rex"),
528 h("Pet"),
529 h("Breed"),
530 );
531 let mut triples = [RdfTriple::new(0, 0, 0); 64];
532 triples[0] = RdfTriple::new(cat, OWL_DISJOINT_WITH, dog);
533 triples[1] = RdfTriple::new(fido, RDF_TYPE, cat);
534 triples[2] = RdfTriple::new(fido, RDF_TYPE, dog);
535 triples[3] = RdfTriple::new(breed, RDFS_SUBCLASS_OF, pet);
536 triples[4] = RdfTriple::new(rex, RDF_TYPE, breed);
537 let _ = animal;
538 let mut contra = [DisjointnessViolation {
539 individual: 0,
540 class_a: 0,
541 class_b: 0,
542 }; 4];
543
544 let s = materialize_owl_rl(&mut triples, 5, &[], 16, &mut contra).unwrap();
545 assert_eq!(s.contradiction_count, 1);
547 assert_eq!(contra[0].individual, fido);
548 assert!(triples[..s.triple_count].contains(&RdfTriple::new(rex, RDF_TYPE, pet)));
550 }
551
552 #[test]
553 fn functional_property_implies_sameas() {
554 let (has_ssn, x, a, b) = (h("hasSSN"), h("x"), h("idA"), h("idB"));
556 let mut triples = [RdfTriple::new(0, 0, 0); 16];
557 triples[0] = RdfTriple::new(has_ssn, RDF_TYPE, OWL_FUNCTIONAL_PROPERTY);
558 triples[1] = RdfTriple::new(x, has_ssn, a);
559 triples[2] = RdfTriple::new(x, has_ssn, b);
560 let mut contra = [DisjointnessViolation {
561 individual: 0,
562 class_a: 0,
563 class_b: 0,
564 }; 2];
565
566 let s = materialize_owl_rl(&mut triples, 3, &[], 16, &mut contra).unwrap();
567 let out = &triples[..s.triple_count];
568 assert!(
569 out.contains(&RdfTriple::new(a, OWL_SAME_AS, b))
570 || out.contains(&RdfTriple::new(b, OWL_SAME_AS, a))
571 );
572 assert!(out.contains(&RdfTriple::new(a, OWL_SAME_AS, b)));
574 assert!(out.contains(&RdfTriple::new(b, OWL_SAME_AS, a)));
575 }
576
577 #[test]
578 fn working_set_full_is_reported() {
579 let (a, sub) = (h("a"), RDFS_SUBCLASS_OF);
580 let mut triples = [RdfTriple::new(0, 0, 0); 6];
582 triples[0] = RdfTriple::new(a, RDF_TYPE, h("C0"));
583 triples[1] = RdfTriple::new(h("C0"), sub, h("C1"));
584 triples[2] = RdfTriple::new(h("C1"), sub, h("C2"));
585 triples[3] = RdfTriple::new(h("C2"), sub, h("C3"));
586 let mut contra = [DisjointnessViolation {
587 individual: 0,
588 class_a: 0,
589 class_b: 0,
590 }; 2];
591 let r = materialize_owl_rl(&mut triples, 4, &[], 16, &mut contra);
592 assert_eq!(r, Err(MaterializeError::WorkingSetFull));
593 }
594}