use std::collections::HashMap;
use ontologos_core::EntityId;
use ontologos_core::Taxonomy;
use petgraph::graph::{DiGraph, NodeIndex};
use petgraph::visit::EdgeRef;
use petgraph::Direction;
#[derive(Debug, Default)]
pub struct TaxonomyGraph {
nodes: HashMap<EntityId, NodeIndex>,
graph: DiGraph<EntityId, ()>,
}
impl TaxonomyGraph {
#[must_use]
pub fn from_taxonomy(taxonomy: &Taxonomy) -> Self {
let mut tg = Self::default();
for &(sub, sup) in &taxonomy.subsumptions {
tg.add_edge(sub, sup);
}
tg
}
fn add_edge(&mut self, sub: EntityId, sup: EntityId) {
let sub_idx = *self
.nodes
.entry(sub)
.or_insert_with(|| self.graph.add_node(sub));
let sup_idx = *self
.nodes
.entry(sup)
.or_insert_with(|| self.graph.add_node(sup));
self.graph.add_edge(sub_idx, sup_idx, ());
}
#[must_use]
pub fn direct_superclasses(&self, class: EntityId) -> Vec<EntityId> {
let Some(&idx) = self.nodes.get(&class) else {
return Vec::new();
};
self.graph
.edges_directed(idx, Direction::Outgoing)
.map(|e| self.graph[e.target()])
.collect()
}
#[must_use]
pub fn direct_subclasses(&self, class: EntityId) -> Vec<EntityId> {
let Some(&idx) = self.nodes.get(&class) else {
return Vec::new();
};
self.graph
.edges_directed(idx, Direction::Incoming)
.map(|e| self.graph[e.source()])
.collect()
}
#[must_use]
pub fn is_subsumed(&self, sub: EntityId, sup: EntityId) -> bool {
if sub == sup {
return true;
}
let Some(&sub_idx) = self.nodes.get(&sub) else {
return false;
};
let Some(&sup_idx) = self.nodes.get(&sup) else {
return false;
};
petgraph::algo::has_path_connecting(&self.graph, sub_idx, sup_idx, None)
}
}
#[cfg(test)]
mod tests {
use ontologos_core::{EntityId, Taxonomy};
use super::*;
#[test]
fn graph_finds_transitive_path() {
let a = EntityId(1);
let b = EntityId(2);
let c = EntityId(3);
let taxonomy = Taxonomy {
subsumptions: vec![(a, b), (b, c)],
equivalences: vec![],
unsatisfiable: vec![],
};
let g = TaxonomyGraph::from_taxonomy(&taxonomy);
assert!(g.is_subsumed(a, c));
}
}