use crate::ConceptPool;
use crate::ir::{ClassId, ConceptExpr, ConceptId};
use crate::ontology::{Axiom, InternalOntology};
#[derive(Debug, Default, Clone)]
pub struct Definitions {
defs: Vec<Option<ConceptId>>,
}
impl Definitions {
#[must_use]
pub fn body_of(&self, c: ClassId) -> Option<ConceptId> {
self.defs.get(c.index() as usize).copied().flatten()
}
#[must_use]
pub fn is_defined(&self, c: ClassId) -> bool {
self.body_of(c).is_some()
}
#[must_use]
pub fn num_classes(&self) -> usize {
self.defs.len()
}
#[must_use]
pub fn num_defined(&self) -> usize {
self.defs.iter().filter(|o| o.is_some()).count()
}
pub fn iter(&self) -> impl Iterator<Item = (ClassId, ConceptId)> + '_ {
(0u32..)
.zip(self.defs.iter())
.filter_map(|(i, body)| body.map(|b| (ClassId::new(i), b)))
}
}
#[must_use]
pub fn extract_definitions(ontology: &InternalOntology) -> Definitions {
let n = ontology.vocabulary.num_classes();
let mut defs: Vec<Option<ConceptId>> = vec![None; n];
for axiom in &ontology.axioms {
if let Axiom::EquivalentClasses(ids) = axiom
&& ids.len() == 2
&& let Some((name, body)) = pick_def_pair(ids[0], ids[1], &ontology.concepts)
{
let idx = name.index() as usize;
if defs[idx].is_none() {
defs[idx] = Some(body);
}
}
}
Definitions { defs }
}
fn pick_def_pair(a: ConceptId, b: ConceptId, pool: &ConceptPool) -> Option<(ClassId, ConceptId)> {
let a_atom = match pool.get(a) {
ConceptExpr::Atomic(c) => Some(*c),
_ => None,
};
let b_atom = match pool.get(b) {
ConceptExpr::Atomic(c) => Some(*c),
_ => None,
};
match (a_atom, b_atom) {
(Some(name), None) => Some((name, b)),
(None, Some(name)) => Some((name, a)),
_ => None,
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::many_single_char_names)]
use super::*;
use crate::ir::{Role, RoleId};
fn ontology_with(classes: &[&str]) -> InternalOntology {
let mut o = InternalOntology::new();
for c in classes {
o.vocabulary.intern_class(c);
}
o
}
fn cid(o: &InternalOntology, name: &str) -> ClassId {
o.vocabulary.class_id(name).expect("class missing")
}
fn atom(o: &mut InternalOntology, name: &str) -> ConceptId {
let c = cid(o, name);
o.concepts.atomic(c)
}
#[test]
fn empty_ontology_has_no_definitions() {
let o = ontology_with(&["A", "B"]);
let d = extract_definitions(&o);
assert_eq!(d.num_classes(), 2);
assert_eq!(d.num_defined(), 0);
assert!(!d.is_defined(cid(&o, "A")));
}
#[test]
fn equiv_atomic_to_complex_creates_definition() {
let mut o = ontology_with(&["A", "B"]);
let a = atom(&mut o, "A");
let b = atom(&mut o, "B");
let r = Role::named(RoleId::new(0));
let some_b = o.concepts.some(r, b);
o.axioms.push(Axiom::EquivalentClasses(vec![a, some_b]));
let d = extract_definitions(&o);
let a_id = cid(&o, "A");
assert!(d.is_defined(a_id));
assert_eq!(d.body_of(a_id), Some(some_b));
assert!(!d.is_defined(cid(&o, "B")));
}
#[test]
fn equiv_complex_to_atomic_creates_definition_with_swap() {
let mut o = ontology_with(&["A", "B"]);
let a = atom(&mut o, "A");
let b = atom(&mut o, "B");
let r = Role::named(RoleId::new(0));
let some_b = o.concepts.some(r, b);
o.axioms.push(Axiom::EquivalentClasses(vec![some_b, a]));
let d = extract_definitions(&o);
assert_eq!(d.body_of(cid(&o, "A")), Some(some_b));
}
#[test]
fn two_atomic_members_is_not_a_definition() {
let mut o = ontology_with(&["A", "B"]);
let a = atom(&mut o, "A");
let b = atom(&mut o, "B");
o.axioms.push(Axiom::EquivalentClasses(vec![a, b]));
let d = extract_definitions(&o);
assert_eq!(d.num_defined(), 0);
}
#[test]
fn three_or_more_members_skipped() {
let mut o = ontology_with(&["A", "B"]);
let a = atom(&mut o, "A");
let b = atom(&mut o, "B");
let r = Role::named(RoleId::new(0));
let some_b = o.concepts.some(r, b);
o.axioms.push(Axiom::EquivalentClasses(vec![a, b, some_b]));
let d = extract_definitions(&o);
assert_eq!(d.num_defined(), 0);
}
#[test]
fn two_complex_members_skipped() {
let mut o = ontology_with(&["A", "B"]);
let a = atom(&mut o, "A");
let b = atom(&mut o, "B");
let r = Role::named(RoleId::new(0));
let some_b = o.concepts.some(r, b);
let not_a = o.concepts.not(a);
o.axioms.push(Axiom::EquivalentClasses(vec![some_b, not_a]));
let d = extract_definitions(&o);
assert_eq!(d.num_defined(), 0);
}
#[test]
fn first_definition_wins_when_conflicts() {
let mut o = ontology_with(&["A", "B", "C"]);
let a = atom(&mut o, "A");
let b = atom(&mut o, "B");
let cc = atom(&mut o, "C");
let r = Role::named(RoleId::new(0));
let some_b = o.concepts.some(r, b);
let some_c = o.concepts.some(r, cc);
o.axioms.push(Axiom::EquivalentClasses(vec![a, some_b]));
o.axioms.push(Axiom::EquivalentClasses(vec![a, some_c]));
let d = extract_definitions(&o);
assert_eq!(d.body_of(cid(&o, "A")), Some(some_b));
}
#[test]
fn idempotent_when_same_def_appears_twice() {
let mut o = ontology_with(&["A", "B"]);
let a = atom(&mut o, "A");
let b = atom(&mut o, "B");
let r = Role::named(RoleId::new(0));
let some_b = o.concepts.some(r, b);
o.axioms.push(Axiom::EquivalentClasses(vec![a, some_b]));
o.axioms.push(Axiom::EquivalentClasses(vec![a, some_b]));
let d = extract_definitions(&o);
assert_eq!(d.num_defined(), 1);
}
#[test]
fn multiple_distinct_class_definitions() {
let mut o = ontology_with(&["A", "B", "X", "Y"]);
let a = atom(&mut o, "A");
let b = atom(&mut o, "B");
let x = atom(&mut o, "X");
let y = atom(&mut o, "Y");
let r = Role::named(RoleId::new(0));
let s = Role::named(RoleId::new(1));
let some_x = o.concepts.some(r, x);
let some_y = o.concepts.some(s, y);
o.axioms.push(Axiom::EquivalentClasses(vec![a, some_x]));
o.axioms.push(Axiom::EquivalentClasses(vec![b, some_y]));
let d = extract_definitions(&o);
assert_eq!(d.num_defined(), 2);
let pairs: Vec<(ClassId, ConceptId)> = d.iter().collect();
assert_eq!(pairs, vec![(cid(&o, "A"), some_x), (cid(&o, "B"), some_y)]);
}
#[test]
fn sub_class_only_is_not_a_definition() {
let mut o = ontology_with(&["A", "B"]);
let a = atom(&mut o, "A");
let b = atom(&mut o, "B");
let r = Role::named(RoleId::new(0));
let some_b = o.concepts.some(r, b);
o.axioms.push(Axiom::SubClassOf {
sub: a,
sup: some_b,
});
let d = extract_definitions(&o);
assert_eq!(d.num_defined(), 0);
}
}