Skip to main content

ontologos_query/
lib.rs

1//! Query interface over classified ontologies (petgraph-backed hierarchy views).
2
3mod graph;
4
5use ontologos_core::{EntityId, Ontology, Taxonomy};
6use thiserror::Error;
7
8pub use graph::TaxonomyGraph;
9
10/// Result type for query operations.
11pub type Result<T> = std::result::Result<T, Error>;
12
13/// Query errors.
14#[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/// Query handle over a classified ontology taxonomy.
23#[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}