Skip to content

Commit

Permalink
Fixed hierarchy queries (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
b-gehrke authored Oct 3, 2024
1 parent 4a69fff commit 0963944
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 40 deletions.
98 changes: 62 additions & 36 deletions src/ontology.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ use curie::Curie;
use horned_owl::io::rdf::reader::ConcreteRDFOntology;
use horned_owl::io::ResourceType;
use horned_owl::model::{
AnnotatedComponent, Annotation, AnnotationAssertion, AnnotationSubject, AnnotationValue,
ArcAnnotatedComponent, ArcStr, Build, Component, ComponentKind, HigherKinded, IRI,
Literal, MutableOntology, Ontology, OntologyID,
AnnotatedComponent, Annotation, AnnotationAssertion, AnnotationSubject, AnnotationValue, ArcAnnotatedComponent, ArcStr, Build, Class, ClassExpression, Component, ComponentKind, HigherKinded, Literal, MutableOntology, Ontology, OntologyID, SubClassOf, IRI
};
use horned_owl::ontology::component_mapped::{
ArcComponentMappedOntology, ComponentMappedIndex, ComponentMappedOntology,
Expand Down Expand Up @@ -325,43 +323,35 @@ impl PyIndexedOntology {
/// get_subclasses(self, iri: str, iri_is_absolute: Optional[bool] = None) -> Set[str]
///
/// Gets all subclasses of an entity.
#[pyo3[signature = (iri, iri_is_absolute = None)]]
pub fn get_subclasses(
#[pyo3[name="get_subclasses", signature = (iri, iri_is_absolute = None)]]
pub fn py_get_subclasses(
&mut self,
py: Python<'_>,
iri: String,
iri_is_absolute: Option<bool>,
) -> PyResult<HashSet<String>> {
let iri: IRI<ArcStr> = self.iri(py, iri, iri_is_absolute)?.into();

let subclasses = self.classes_to_subclasses.get(&iri);
if let Some(subclss) = subclasses {
let subclasses: HashSet<String> = subclss.iter().map(|sc| sc.to_string()).collect();
Ok(subclasses)
} else {
Ok(HashSet::new())
}
let classes: HashSet<String> = self.get_subclasses(&iri).into_iter().map(|i| i.to_string()).collect();

Ok(classes)
}

/// get_superclasses(self, iri: str, iri_is_absolute: Optional[bool] = None) -> Set[str]
///
/// Gets all superclasses of an entity.
#[pyo3[signature = (iri, iri_is_absolute = None)]]
pub fn get_superclasses(
#[pyo3[name="get_superclasses", signature = (iri, iri_is_absolute = None)]]
pub fn py_get_superclasses(
&mut self,
py: Python<'_>,
iri: String,
iri_is_absolute: Option<bool>,
) -> PyResult<HashSet<String>> {
let iri: IRI<ArcStr> = self.iri(py, iri, iri_is_absolute)?.into();

let superclasses = self.classes_to_superclasses.get(&iri);
if let Some(superclss) = superclasses {
let superclasses: HashSet<String> = superclss.iter().map(|sc| sc.to_string()).collect();
Ok(superclasses)
} else {
Ok(HashSet::new())
}
let classes: HashSet<String> = self.get_superclasses(&iri).into_iter().map(|i| i.to_string()).collect();

Ok(classes)
}

/// get_classes(self) -> Set[str]
Expand Down Expand Up @@ -882,10 +872,10 @@ impl PyIndexedOntology {

self.recurse_descendants(&parent_iri, &mut descendants);

Ok(descendants)
Ok(descendants.into_iter().map(|c| c.to_string()).collect())
}

/// get_ancestors(self, onto: PyIndexedOntology, child: str, iri_is_absolute: Optional[bool] = None) -> Set[str]
/// get_ancestors(self, child: str, iri_is_absolute: Optional[bool] = None) -> Set[str]
///
/// Gets all direct and indirect super classes of a class.
#[pyo3[signature = (child_iri, *, iri_is_absolute = None)]]
Expand All @@ -901,7 +891,7 @@ impl PyIndexedOntology {

self.recurse_ancestors(&child_iri, &mut ancestors);

Ok(ancestors)
Ok(ancestors.into_iter().map(|c| c.to_string()).collect())
}

/// build_iri_index(self) -> None
Expand Down Expand Up @@ -963,21 +953,57 @@ impl PyIndexedOntology {
}

impl PyIndexedOntology {
fn recurse_descendants(&self, superclass: &IRI<ArcStr>, descendants: &mut HashSet<String>) {
descendants.insert(superclass.into());
if self.classes_to_subclasses.contains_key(superclass) {
for cls2 in &mut self.classes_to_subclasses[superclass].iter() {
self.recurse_descendants(cls2, descendants);
}
pub fn get_subclasses(&self, iri: &IRI<ArcStr>) -> HashSet<IRI<ArcStr>> {
let subclass_axioms = if let Some(ref component_index) = &self.component_index {
Box::new(component_index.component_for_kind(ComponentKind::SubClassOf))
as Box<dyn Iterator<Item = &AnnotatedComponent<ArcStr>>>
} else {
Box::new((&self.set_index).into_iter())
};

subclass_axioms
.filter_map(|aax| match &aax.component {
Component::SubClassOf(SubClassOf{
sub: ClassExpression::Class(Class(sub)),
sup: ClassExpression::Class(Class(sup))}) if sup == iri => Some(sub.clone()),
_ => None,
})
.collect()
}

pub fn get_superclasses(&self, iri: &IRI<ArcStr>) -> HashSet<IRI<ArcStr>> {
let subclass_axioms = if let Some(ref component_index) = &self.component_index {
Box::new(component_index.component_for_kind(ComponentKind::SubClassOf))
as Box<dyn Iterator<Item = &AnnotatedComponent<ArcStr>>>
} else {
Box::new((&self.set_index).into_iter())
};

subclass_axioms
.filter_map(|aax| match &aax.component {
Component::SubClassOf(SubClassOf{
sub: ClassExpression::Class(Class(sub)),
sup: ClassExpression::Class(Class(sup))}) if sub == iri => Some(sup.clone()),
_ => None,
})
.collect()
}

fn recurse_descendants(&self, superclass: &IRI<ArcStr>, descendants: &mut HashSet<IRI<ArcStr>>) {
let subclasses = self.get_subclasses(superclass);

for cls in subclasses.into_iter() {
self.recurse_descendants(&cls, descendants);
descendants.insert(cls);
}
}

fn recurse_ancestors(&self, subclass: &IRI<ArcStr>, ancestors: &mut HashSet<String>) {
ancestors.insert(subclass.into());
if self.classes_to_superclasses.contains_key(subclass) {
for cls2 in &mut self.classes_to_superclasses[subclass].iter() {
self.recurse_ancestors(cls2, ancestors);
}
fn recurse_ancestors(&self, subclass: &IRI<ArcStr>, ancestors: &mut HashSet<IRI<ArcStr>>) {
let superclasses = self.get_superclasses(subclass);

for cls in superclasses.into_iter() {
self.recurse_ancestors(&cls, ancestors);
ancestors.insert(cls);
}
}

Expand Down
5 changes: 5 additions & 0 deletions test/resources/simple.ofn
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Ontology(
Declaration(Class(<https://example.com/A>))
Declaration(Class(<https://example.com/B>))
Declaration(Class(<https://example.com/C>))
Declaration(Class(<https://example.com/D>))


############################
Expand All @@ -25,5 +26,9 @@ AnnotationAssertion(rdfs:label <https://example.com/A> "ClassA")
AnnotationAssertion(rdfs:label <https://example.com/B> "ClassB")
SubClassOf(<https://example.com/B> <https://example.com/A>)

# Class: <https://example.com/D> (<https://example.com/D>)

SubClassOf(<https://example.com/D> <https://example.com/B>)


)
5 changes: 5 additions & 0 deletions test/resources/simple.ofn.raw
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Ontology(
Declaration(Class(<https://example.com/A>))
Declaration(Class(<https://example.com/B>))
Declaration(Class(<https://example.com/C>))
Declaration(Class(<https://example.com/D>))


############################
Expand All @@ -25,5 +26,9 @@ AnnotationAssertion(rdfs:label <https://example.com/A> "ClassA")
AnnotationAssertion(rdfs:label <https://example.com/B> "ClassB")
SubClassOf(<https://example.com/B> <https://example.com/A>)

# Class: <https://example.com/D> (<https://example.com/D>)

SubClassOf(<https://example.com/D> <https://example.com/B>)


)
2 changes: 2 additions & 0 deletions test/resources/simple.omn
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ Ontology:
Annotations: rdfs:label "ClassB"
SubClassOf: :A
Class: :C
Class: :D
SubClassOf: :B
2 changes: 2 additions & 0 deletions test/resources/simple.omn.raw
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ Ontology:
Annotations: rdfs:label "ClassB"
SubClassOf: :A
Class: :C
Class: :D
SubClassOf: :B
8 changes: 8 additions & 0 deletions test/resources/simple.owl
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@
<!-- https://example.com/C -->

<Class rdf:about="https://example.com/C"/>



<!-- https://example.com/D -->

<Class rdf:about="https://example.com/D">
<rdfs:subClassOf rdf:resource="https://example.com/B"/>
</Class>
</rdf:RDF>


Expand Down
8 changes: 8 additions & 0 deletions test/resources/simple.owl.raw
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@
<!-- https://example.com/C -->

<Class rdf:about="https://example.com/C"/>



<!-- https://example.com/D -->

<Class rdf:about="https://example.com/D">
<rdfs:subClassOf rdf:resource="https://example.com/B"/>
</Class>
</rdf:RDF>


Expand Down
7 changes: 7 additions & 0 deletions test/resources/simple.owx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,17 @@
<Declaration>
<Class IRI="https://example.com/C"/>
</Declaration>
<Declaration>
<Class IRI="https://example.com/D"/>
</Declaration>
<SubClassOf>
<Class IRI="https://example.com/B"/>
<Class IRI="https://example.com/A"/>
</SubClassOf>
<SubClassOf>
<Class IRI="https://example.com/D"/>
<Class IRI="https://example.com/B"/>
</SubClassOf>
<AnnotationAssertion>
<AnnotationProperty abbreviatedIRI="rdfs:label"/>
<IRI>https://example.com/A</IRI>
Expand Down
7 changes: 7 additions & 0 deletions test/resources/simple.owx.raw
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,17 @@
<Declaration>
<Class IRI="https://example.com/C"/>
</Declaration>
<Declaration>
<Class IRI="https://example.com/D"/>
</Declaration>
<SubClassOf>
<Class IRI="https://example.com/B"/>
<Class IRI="https://example.com/A"/>
</SubClassOf>
<SubClassOf>
<Class IRI="https://example.com/D"/>
<Class IRI="https://example.com/B"/>
</SubClassOf>
<AnnotationAssertion>
<AnnotationProperty abbreviatedIRI="rdfs:label"/>
<IRI>https://example.com/A</IRI>
Expand Down
10 changes: 6 additions & 4 deletions test/test_base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import os

from typing import List

import pyhornedowl
from pyhornedowl.model import *

Expand All @@ -22,9 +20,13 @@ def simple_ontology_comps() -> List[Component]:
DeclareClass(Class(IRI.parse("https://example.com/A"))),
DeclareClass(Class(IRI.parse("https://example.com/B"))),
DeclareClass(Class(IRI.parse("https://example.com/C"))),
DeclareClass(Class(IRI.parse("https://example.com/D"))),
SubClassOf(
sup=Class(IRI.parse("https://example.com/A")),
sub=Class(IRI.parse("https://example.com/B"))),
SubClassOf(
Class(IRI.parse("https://example.com/A")),
Class(IRI.parse("https://example.com/B"))),
sup=Class(IRI.parse("https://example.com/B")),
sub=Class(IRI.parse("https://example.com/D"))),
AnnotationAssertion(
IRI.parse("https://example.com/A"),
Annotation(AnnotationProperty(IRI.parse(RDFS_LABEL)),
Expand Down
89 changes: 89 additions & 0 deletions test/test_hierarchy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import unittest

from test_base import simple_ontology


class HierarchyTestCase(unittest.TestCase):
def test_no_subclass(self):
o = simple_ontology()

expected = set()
actual = o.get_subclasses(":C")

self.assertSetEqual(expected, actual)

def test_no_superclass(self):
o = simple_ontology()

expected = set()
actual = o.get_superclasses(":C")

self.assertSetEqual(expected, actual)

def test_direct_subclass(self):
o = simple_ontology()

expected = {"https://example.com/B"}
actual = o.get_subclasses(":A")

self.assertSetEqual(expected, actual)

def test_direct_superclass(self):
o = simple_ontology()

expected = {"https://example.com/A"}
actual = o.get_superclasses(":B")

self.assertSetEqual(expected, actual)

def test_no_ancestors(self):
o = simple_ontology()

expected = set()
actual = o.get_ancestors(":A")

self.assertSetEqual(expected, actual)

def test_no_descendants(self):
o = simple_ontology()

expected = set()
actual = o.get_descendants(":C")

self.assertSetEqual(expected, actual)

def test_single_ancestors(self):
o = simple_ontology()

expected = {"https://example.com/A"}
actual = o.get_ancestors(":B")

self.assertSetEqual(expected, actual)

def test_single_descendants(self):
o = simple_ontology()

expected = {"https://example.com/D"}
actual = o.get_descendants(":B")

self.assertSetEqual(expected, actual)

def test_multiple_ancestors(self):
o = simple_ontology()

expected = {"https://example.com/A", "https://example.com/B"}
actual = o.get_ancestors(":D")

self.assertSetEqual(expected, actual)

def test_multiple_descendants(self):
o = simple_ontology()

expected = {"https://example.com/B", "https://example.com/D"}
actual = o.get_descendants(":A")

self.assertSetEqual(expected, actual)


if __name__ == '__main__':
unittest.main()

0 comments on commit 0963944

Please sign in to comment.