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