1mod graph;
4
5use ontologos_core::{EntityId, Ontology, Taxonomy};
6use thiserror::Error;
7
8pub use graph::TaxonomyGraph;
9
10pub type Result<T> = std::result::Result<T, Error>;
12
13#[derive(Debug, Error)]
15pub enum Error {
16 #[error("unknown entity {0:?}")]
17 UnknownEntity(EntityId),
18 #[error(transparent)]
19 Core(#[from] ontologos_core::Error),
20}
21
22#[derive(Debug)]
24pub struct QueryEngine<'a> {
25 ontology: &'a Ontology,
26 taxonomy: &'a Taxonomy,
27 graph: TaxonomyGraph,
28}
29
30impl<'a> QueryEngine<'a> {
31 #[must_use]
32 pub fn new(ontology: &'a Ontology, taxonomy: &'a Taxonomy) -> Self {
33 Self {
34 ontology,
35 taxonomy,
36 graph: TaxonomyGraph::from_taxonomy(taxonomy),
37 }
38 }
39
40 pub fn direct_subclasses(&self, class: EntityId) -> Result<Vec<EntityId>> {
41 self.ensure_class(class)?;
42 Ok(self.graph.direct_subclasses(class))
43 }
44
45 pub fn direct_superclasses(&self, class: EntityId) -> Result<Vec<EntityId>> {
46 self.ensure_class(class)?;
47 Ok(self.graph.direct_superclasses(class))
48 }
49
50 pub fn is_subsumed(&self, sub: EntityId, sup: EntityId) -> Result<bool> {
51 self.ensure_class(sub)?;
52 self.ensure_class(sup)?;
53 Ok(self.graph.is_subsumed(sub, sup))
54 }
55
56 pub fn equivalent_classes(&self, class: EntityId) -> Result<Option<Vec<EntityId>>> {
57 self.ensure_class(class)?;
58 Ok(self
59 .taxonomy
60 .equivalent_classes(class)
61 .map(<[EntityId]>::to_vec))
62 }
63
64 pub fn unsatisfiable_classes(&self) -> Vec<EntityId> {
65 self.taxonomy.unsatisfiable.clone()
66 }
67
68 pub fn lookup(&self, iri: &str) -> Option<EntityId> {
69 self.ontology.lookup_entity(iri)
70 }
71
72 fn ensure_class(&self, class: EntityId) -> Result<()> {
73 let record = self.ontology.entity(class)?;
74 if record.kind != ontologos_core::EntityKind::Class {
75 return Err(Error::UnknownEntity(class));
76 }
77 Ok(())
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use ontologos_core::{Axiom, EntityKind, Ontology, Taxonomy};
84
85 use super::*;
86
87 #[test]
88 fn query_direct_subclasses_from_taxonomy() {
89 let mut ontology = Ontology::new();
90 let a = ontology
91 .entity_id("http://ex.org/A", EntityKind::Class)
92 .unwrap();
93 let b = ontology
94 .entity_id("http://ex.org/B", EntityKind::Class)
95 .unwrap();
96 ontology
97 .add_axiom(Axiom::SubClassOf {
98 subclass: a,
99 superclass: b,
100 })
101 .unwrap();
102
103 let taxonomy = Taxonomy {
104 subsumptions: vec![(a, b)],
105 ..Taxonomy::default()
106 };
107 let engine = QueryEngine::new(&ontology, &taxonomy);
108 let subs = engine.direct_subclasses(b).expect("subs");
109 assert!(subs.contains(&a));
110 }
111
112 #[test]
113 fn is_subsumed_transitive() {
114 let mut ontology = Ontology::new();
115 let a = ontology
116 .entity_id("http://ex.org/A", EntityKind::Class)
117 .unwrap();
118 let b = ontology
119 .entity_id("http://ex.org/B", EntityKind::Class)
120 .unwrap();
121 let c = ontology
122 .entity_id("http://ex.org/C", EntityKind::Class)
123 .unwrap();
124 ontology
125 .add_axiom(Axiom::SubClassOf {
126 subclass: a,
127 superclass: b,
128 })
129 .unwrap();
130 ontology
131 .add_axiom(Axiom::SubClassOf {
132 subclass: b,
133 superclass: c,
134 })
135 .unwrap();
136
137 let taxonomy = Taxonomy {
138 subsumptions: vec![(a, b), (b, c)],
139 ..Taxonomy::default()
140 };
141 let engine = QueryEngine::new(&ontology, &taxonomy);
142 assert!(engine.is_subsumed(a, c).expect("subsumed"));
143 }
144}