diff --git a/Cargo.toml b/Cargo.toml index ef1d56b..5f31fbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "py-horned-owl" -version = "1.0.0" +version = "1.0.0-dev.1" authors = ["Janna Hastings "] edition = "2018" @@ -15,8 +15,8 @@ version = "0.21" features = ["abi3-py37", "extension-module", "experimental-inspect", "multiple-pymethods"] [dependencies] -horned-owl = "1.0.0" -horned-bin = "1.0.0" +horned-owl = {git = "https://github.com/b-gehrke/horned-owl.git", branch = "py-horned-owl-performance"} +horned-bin = {git = "https://github.com/b-gehrke/horned-owl.git", branch = "py-horned-owl-performance"} curie = "0.1.2" failure = "0.1.8" quote = "1.0" diff --git a/docs/source/index.rst b/docs/source/index.rst index 26a19f3..0ca71f9 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -17,6 +17,7 @@ Contents quickstart installation usage + performance details api diff --git a/docs/source/performance.rst b/docs/source/performance.rst new file mode 100644 index 0000000..80a3839 --- /dev/null +++ b/docs/source/performance.rst @@ -0,0 +1,15 @@ +Performance +=========== + +The underlying Rust library Horned-OWL offers an `index system `_ to allow fast access to different kinds of parts of an ontology. + +By default, Py-Horned-OWL loads an ontology in a simple hash set (using a `set index `_). While this allows for quick loading and iterating of all components in an ontology, queries like "give me all classes" or "give me all axioms for this IRI" are slower or even not supported at all. + +Especially queries by IRI are not supported without having an `iri mapped index `_. By default, it is implicitly created when a function requiring it is called. + +The `component mapped index `_ improves the performance of entity or specific axioms lookups. For example, the functions :func:`~pyhornedowl.PyIndexedOntology.get_classes`, :func:`~pyhornedowl.PyIndexedOntology.get_object_properties`, etc., :func:`~pyhornedowl.PyIndexedOntology.get_iri`, or :func:`~pyhornedowl.PyIndexedOntology.get_iri_for_label` benefit from a component index. + +The indexes can be build manually using the :func:`~pyhornedowl.PyIndexedOntology.build_iri_index`, :func:`~pyhornedowl.PyIndexedOntology.build_component_index`, or to build both indexes together :func:`~pyhornedowl.PyIndexedOntology.build_indexes` methods. + +You change the behaviour for index creating using the `index_creation_strategy` parameters in the :func:`~pyhornedowl.open_ontology` functions or the constructor of :func:`~pyhornedowl.PyIndexedOntology`. + diff --git a/gen_pyi.py b/gen_pyi.py index 5529180..fc78702 100644 --- a/gen_pyi.py +++ b/gen_pyi.py @@ -28,39 +28,76 @@ f.write("]\n") with open("pyhornedowl/__init__.pyi", "w") as f: - f.write("import typing\nfrom typing import *\n\n") + f.write("import typing\n") + f.write("from typing import *\n") + f.write("from typing_extensions import deprecated\n\n") f.write("import model\n") f.write("\n") for name, entry in pho.__dict__.items(): if isinstance(entry, type): f.write(f"class {name}:\n") + # There appears to be a bug with pyo3. Documentation on enum + # variants is not attached to their mapped python types. Hence we + # use a workarround of adding their documentation to the enum in + # the style: ": ". + member_docs = {} + if hasattr(entry, "__doc__"): + entry_doc = entry.__doc__ + if entry_doc is not None: + f.write(" \"\"\"\n") + for line in entry_doc.splitlines(): + member_doc_m = re.match(r"^(\w+): (.*)$", line) + if member_doc_m: + member_docs[member_doc_m.group(1)]=member_doc_m.group(2) + else: + f.write(f" {line}\n") + + f.write(" \"\"\"\n") for member_name, member in entry.__dict__.items(): if member_name.startswith("_"): continue - - if hasattr(member, "__doc__"): + + # E.g. for enums + if isinstance(member, entry): + f.write(f" {member_name}: typing.Self\n") + if member_name in member_docs or hasattr(member, "__doc__") and member.__doc__ is not None: + doc = member_docs.get(member_name, getattr(member, "__doc__")) + f.write(" \"\"\"\n") + for line in doc.splitlines(): + f.write(f" {line}\n") + f.write(" \"\"\"\n") + elif hasattr(member, "__doc__"): doc = member.__doc__ if doc is not None: lines = doc.splitlines() if len(lines) > 2: - sign = lines[0] + annotations_end = lines.index(next(x for x in lines if not x.startswith("@")), 0) + annotations = lines[:annotations_end] + sign = lines[annotations_end] + + for ann in annotations: + f.write(f" {ann}\n") f.write(f" def {sign}:\n") - doc = "\n".join([f" {l}" for l in lines[2:]]) + doc = "\n".join([f" {l}" for l in lines[annotations_end+2:]]) f.write(f' """\n{doc}\n """\n ...\n\n') - - if callable(entry): + f.write("\n") + elif callable(entry): if hasattr(entry, "__doc__"): doc = entry.__doc__ if doc is not None: lines = doc.splitlines() if len(lines) > 2: - sign = lines[0] + annotations_end = lines.index(next(x for x in lines if not x.startswith("@")), 0) + annotations = lines[:annotations_end] + sign = lines[annotations_end] + for ann in annotations: + f.write(f"{ann}\n") f.write(f"def {sign}:\n") - doc = "\n".join([f" {l}" for l in lines[2:]]) + doc = "\n".join([f" {l}" for l in lines[annotations_end+2:]]) f.write(f' """\n{doc}\n """\n ...\n\n') f.write("\n") @@ -90,7 +127,8 @@ def handle_module(module: str): with open(f"pyhornedowl/{module}/__init__.pyi", "w") as f: f.write("import typing\n") - f.write("from typing import *\n\n") + f.write("from typing import *\n") + f.write("from typing_extensions import deprecated\n\n") for name, entry in getattr(pho, module).__dict__.items(): if isinstance(entry, type): diff --git a/pyhornedowl/__init__.py b/pyhornedowl/__init__.py index fb2f841..e73db86 100644 --- a/pyhornedowl/__init__.py +++ b/pyhornedowl/__init__.py @@ -1,4 +1,4 @@ from __future__ import annotations -from .pyhornedowl import PyIndexedOntology, open_ontology, open_ontology_from_file, open_ontology_from_string, get_descendants, get_ancestors +from .pyhornedowl import PyIndexedOntology, IndexCreationStrategy, open_ontology, open_ontology_from_file, open_ontology_from_string, get_descendants, get_ancestors -__all__ = ["PyIndexedOntology", "open_ontology", "open_ontology_from_file", "open_ontology_from_string", "get_descendants", "get_ancestors"] +__all__ = ["PyIndexedOntology", "IndexCreationStrategy", "open_ontology", "open_ontology_from_file", "open_ontology_from_string", "get_descendants", "get_ancestors"] diff --git a/pyhornedowl/__init__.pyi b/pyhornedowl/__init__.pyi index d202ac2..c0d0093 100644 --- a/pyhornedowl/__init__.pyi +++ b/pyhornedowl/__init__.pyi @@ -1,9 +1,13 @@ import typing from typing import * +from typing_extensions import deprecated import model class PyIndexedOntology: + """ + Represents a loaded ontology. + """ def add_default_prefix_names(self) -> None: """ Adds the prefix for rdf, rdfs, xsd, and owl @@ -218,7 +222,7 @@ class PyIndexedOntology: """ ... - def annotation_property(self, iri: str, *, absolute: Optional[bool]=None) -> model.annotationProperty: + def annotation_property(self, iri: str, *, absolute: Optional[bool]=None) -> model.AnnotationProperty: """ Convenience method to create an annotationProperty from an IRI. @@ -258,14 +262,50 @@ class PyIndexedOntology: """ ... - def get_ancestors(onto: PyIndexedOntology, child: str, iri_is_absolute: Optional[bool] = None) -> Set[str]: + def get_ancestors(self, onto: PyIndexedOntology, child: str, iri_is_absolute: Optional[bool] = None) -> Set[str]: """ Gets all direct and indirect super classes of a class. """ ... + def build_iri_index(self) -> None: + """ + Builds an index by iri (IRIMappedIndex). + """ + ... + + def component_index(self) -> None: + """ + Builds an index by component kind (ComponentMappedIndex). + """ + ... + + def build_indexes(self) -> None: + """ + Builds indexes to allow (a quicker) access to axioms and entities. + """ + ... + + +class IndexCreationStrategy: + """ + Values to indicate when to build the additional indexes. + + """ + OnLoad: typing.Self + """ + Create the additional indexes when the ontology is loaded + """ + OnQuery: typing.Self + """ + Create the additional indexes only when they are needed + """ + Explicit: typing.Self + """ + Only create the additional indexes when explicity requested + """ -def open_ontology(ontology: str, serialization: Optional[typing.Literal['owl', 'rdf','ofn', 'owx']]=None) -> PyIndexedOntology: +def open_ontology(ontology: str, serialization: Optional[typing.Literal['owl', 'rdf','ofn', 'owx']]=None, index_strategy = IndexCreationStrategy.OnQuery) -> PyIndexedOntology: """ Opens an ontology from a path or plain text. @@ -277,7 +317,7 @@ def open_ontology(ontology: str, serialization: Optional[typing.Literal['owl', ' ... -def open_ontology_from_file(path: str, serialization: Optional[typing.Literal['owl', 'rdf','ofn', 'owx']]=None) -> PyIndexedOntology: +def open_ontology_from_file(path: str, serialization: Optional[typing.Literal['owl', 'rdf','ofn', 'owx']]=None, index_strategy = IndexCreationStrategy.OnQuery) -> PyIndexedOntology: """ Opens an ontology from a file @@ -286,7 +326,7 @@ def open_ontology_from_file(path: str, serialization: Optional[typing.Literal['o ... -def open_ontology_from_string(ontology: str, serialization: Optional[typing.Literal['owl', 'rdf','ofn', 'owx']]=None) -> PyIndexedOntology: +def open_ontology_from_string(ontology: str, serialization: Optional[typing.Literal['owl', 'rdf','ofn', 'owx']]=None, index_strategy = IndexCreationStrategy.OnQuery) -> PyIndexedOntology: """ Opens an ontology from plain text. @@ -295,17 +335,17 @@ def open_ontology_from_string(ontology: str, serialization: Optional[typing.Lite ... +@deprecated("please use `PyIndexedOntology.get_descendants` instead") def get_descendants(onto: PyIndexedOntology, parent: str) -> Set[str]: """ - DEPRECATED: please use `PyIndexedOntology::get_descendants` instead Gets all direct and indirect subclasses of a class. """ ... +@deprecated(please use `PyIndexedOntology.get_ancestors` instead) def get_ancestors(onto: PyIndexedOntology, child: str) -> Set[str]: """ - DEPRECATED: please use `PyIndexedOntology::get_ancestors` instead Gets all direct and indirect super classes of a class. """ ... diff --git a/pyhornedowl/model/__init__.pyi b/pyhornedowl/model/__init__.pyi index 3b3a0e4..3fc16ac 100644 --- a/pyhornedowl/model/__init__.pyi +++ b/pyhornedowl/model/__init__.pyi @@ -1,5 +1,6 @@ import typing from typing import * +from typing_extensions import deprecated class Class: first: IRI diff --git a/src/lib.rs b/src/lib.rs index e1a202d..479d1d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,6 @@ use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::Path; -//use failure::Error; use std::sync::Arc; use curie::PrefixMapping; @@ -9,13 +8,11 @@ use horned_bin::path_type; use horned_owl::error::HornedError; use horned_owl::io::{ParserConfiguration, RDFParserConfiguration, ResourceType}; use horned_owl::model::*; -use horned_owl::ontology::iri_mapped::IRIMappedOntology; -use horned_owl::ontology::set::SetOntology; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use pyo3::wrap_pyfunction; -use crate::ontology::{get_ancestors, get_descendants, PyIndexedOntology}; +use crate::ontology::{get_ancestors, get_descendants, PyIndexedOntology, IndexCreationStrategy}; #[macro_use] mod doc; @@ -53,38 +50,43 @@ fn guess_serialization(path: &String, serialization: Option<&str>) -> PyResult( content: &mut R, b: &Build>, -) -> Result<(SetOntology, PrefixMapping), HornedError> { - horned_owl::io::owx::reader::read_with_build(content, &b) + index_strategy: IndexCreationStrategy +) -> Result<(PyIndexedOntology, PrefixMapping), HornedError> { + let (o, m) = horned_owl::io::owx::reader::read_with_build(content, &b)?; + Ok((PyIndexedOntology::from_set_ontology(o, index_strategy), m)) } fn open_ontology_ofn( content: &mut R, b: &Build>, -) -> Result<(SetOntology, PrefixMapping), HornedError> { - horned_owl::io::ofn::reader::read_with_build(content, &b) + index_strategy: IndexCreationStrategy +) -> Result<(PyIndexedOntology, PrefixMapping), HornedError> { + let (o, m) = horned_owl::io::ofn::reader::read_with_build(content, &b)?; + Ok((PyIndexedOntology::from_set_ontology(o, index_strategy), m)) } fn open_ontology_rdf( content: &mut R, b: &Build, -) -> Result<(SetOntology, PrefixMapping), HornedError> { + index_strategy: IndexCreationStrategy +) -> Result<(PyIndexedOntology, PrefixMapping), HornedError> { horned_owl::io::rdf::reader::read_with_build::( content, - &b, + &b, ParserConfiguration { rdf: RDFParserConfiguration { lax: true }, ..Default::default() }, - ).map(|(o, _)| (SetOntology::from(o), Default::default())) + ).map(|(o, _)| (PyIndexedOntology::from_rdf_ontology(o, index_strategy), PrefixMapping::default())) } -/// open_ontology_from_file(path: str, serialization: Optional[typing.Literal['owl', 'rdf','ofn', 'owx']]=None) -> PyIndexedOntology +/// open_ontology_from_file(path: str, serialization: Optional[typing.Literal['owl', 'rdf','ofn', 'owx']]=None, index_strategy = IndexCreationStrategy.OnQuery) -> PyIndexedOntology /// /// Opens an ontology from a file /// /// If the serialization is not specified it is guessed from the file extension. Defaults to OWL/XML. -#[pyfunction(signature = (path, serialization = None))] -fn open_ontology_from_file(path: String, serialization: Option<&str>) -> PyResult { +#[pyfunction(signature = (path, serialization = None, index_strategy = IndexCreationStrategy::OnQuery))] +fn open_ontology_from_file(path: String, serialization: Option<&str>, index_strategy: IndexCreationStrategy) -> PyResult { let serialization = guess_serialization(&path, serialization)?; let file = File::open(path)?; @@ -92,46 +94,43 @@ fn open_ontology_from_file(path: String, serialization: Option<&str>) -> PyResul let b = Build::new_arc(); - let (onto, mapping) = match serialization { - ResourceType::OFN => open_ontology_ofn(&mut f, &b), - ResourceType::OWX => open_ontology_owx(&mut f, &b), - ResourceType::RDF => open_ontology_rdf(&mut f, &b) + let (mut pio, mapping) = match serialization { + ResourceType::OFN => open_ontology_ofn(&mut f, &b, index_strategy), + ResourceType::OWX => open_ontology_owx(&mut f, &b, index_strategy), + ResourceType::RDF => open_ontology_rdf(&mut f, &b, index_strategy) }.map_err(to_py_err!("Failed to open ontology"))?; - let iro = IRIMappedOntology::from(onto); - let mut lo = PyIndexedOntology::from(iro); - lo.mapping = mapping; //Needed when saving - Ok(lo) + pio.mapping = mapping; + Ok(pio) } -/// open_ontology_from_string(ontology: str, serialization: Optional[typing.Literal['owl', 'rdf','ofn', 'owx']]=None) -> PyIndexedOntology +/// open_ontology_from_string(ontology: str, serialization: Optional[typing.Literal['owl', 'rdf','ofn', 'owx']]=None, index_strategy = IndexCreationStrategy.OnQuery) -> PyIndexedOntology /// /// Opens an ontology from plain text. /// /// If no serialization is specified, all parsers are tried until one succeeds -#[pyfunction(signature = (ontology, serialization = None))] -fn open_ontology_from_string(ontology: String, serialization: Option<&str>) -> PyResult { +#[pyfunction(signature = (ontology, serialization = None, index_strategy = IndexCreationStrategy::OnQuery))] +fn open_ontology_from_string(ontology: String, serialization: Option<&str>, index_strategy: IndexCreationStrategy) -> PyResult { let serialization = parse_serialization(serialization); let mut f = BufReader::new(ontology.as_bytes()); let b = Build::new_arc(); - let (onto, mapping) = match serialization { - Some(ResourceType::OFN) => open_ontology_ofn(&mut f, &b), - Some(ResourceType::OWX) => open_ontology_owx(&mut f, &b), - Some(ResourceType::RDF) => open_ontology_rdf(&mut f, &b), - None => open_ontology_ofn(&mut f, &b) - .or_else(|_| open_ontology_rdf(&mut f, &b)) - .or_else(|_| open_ontology_owx(&mut f, &b)) + let (imo, mapping) = match serialization { + Some(ResourceType::OFN) => open_ontology_ofn(&mut f, &b, index_strategy), + Some(ResourceType::OWX) => open_ontology_owx(&mut f, &b, index_strategy), + Some(ResourceType::RDF) => open_ontology_rdf(&mut f, &b, index_strategy), + None => open_ontology_ofn(&mut f, &b, index_strategy) + .or_else(|_| open_ontology_rdf(&mut f, &b, index_strategy)) + .or_else(|_| open_ontology_owx(&mut f, &b, index_strategy)) }.map_err(to_py_err!("Failed to open ontology"))?; - let iro = IRIMappedOntology::from(onto); - let mut lo = PyIndexedOntology::from(iro); + let mut lo = PyIndexedOntology::from(imo); lo.mapping = mapping; //Needed when saving Ok(lo) } -/// open_ontology(ontology: str, serialization: Optional[typing.Literal['owl', 'rdf','ofn', 'owx']]=None) -> PyIndexedOntology +/// open_ontology(ontology: str, serialization: Optional[typing.Literal['owl', 'rdf','ofn', 'owx']]=None, index_strategy = IndexCreationStrategy.OnQuery) -> PyIndexedOntology /// /// Opens an ontology from a path or plain text. /// @@ -139,18 +138,19 @@ fn open_ontology_from_string(ontology: String, serialization: Option<&str>) -> P /// in plain text. /// If no serialization is specified the serialization is guessed by the file extension or all parsers are tried /// until one succeeds. -#[pyfunction(signature = (ontology, serialization = None))] -fn open_ontology(ontology: String, serialization: Option<&str>) -> PyResult { +#[pyfunction(signature = (ontology, serialization = None, index_strategy = IndexCreationStrategy::OnQuery))] +fn open_ontology(ontology: String, serialization: Option<&str>, index_strategy: IndexCreationStrategy) -> PyResult { if Path::exists(ontology.as_ref()) { - open_ontology_from_file(ontology, serialization) + open_ontology_from_file(ontology, serialization, index_strategy) } else { - open_ontology_from_string(ontology, serialization) + open_ontology_from_string(ontology, serialization, index_strategy) } } #[pymodule] fn pyhornedowl(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; + m.add_class::()?; m.add_function(wrap_pyfunction!(open_ontology, m)?)?; m.add_function(wrap_pyfunction!(open_ontology_from_file, m)?)?; diff --git a/src/ontology.rs b/src/ontology.rs index aceeeb3..cf6ebcc 100644 --- a/src/ontology.rs +++ b/src/ontology.rs @@ -1,19 +1,50 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use std::fs::File; -use std::ops::Deref; use std::sync::Arc; use curie::{Curie, PrefixMapping}; +use horned_owl::io::rdf::reader::RDFOntology; use horned_owl::io::ResourceType; -use horned_owl::model::{AnnotatedComponent, Annotation, AnnotationAssertion, AnnotationValue, ArcStr, Build, ClassExpression, Component, ComponentKind, HigherKinded, IRI, Kinded, Literal, MutableOntology, OntologyID, SubClassOf}; -use horned_owl::ontology::component_mapped::{ArcComponentMappedOntology, ComponentMappedOntology}; -use horned_owl::ontology::iri_mapped::{ArcIRIMappedOntology, IRIMappedOntology}; +use horned_owl::model::{ + AnnotatedComponent, Annotation, AnnotationAssertion, AnnotationSubject, AnnotationValue, + ArcAnnotatedComponent, ArcStr, Build, Component, ComponentKind, HigherKinded, Literal, + MutableOntology, Ontology, OntologyID, IRI, +}; +use horned_owl::ontology::component_mapped::{ + ArcComponentMappedOntology, ComponentMappedIndex, ComponentMappedOntology, +}; +use horned_owl::ontology::indexed::OntologyIndex; +use horned_owl::ontology::iri_mapped::IRIMappedIndex; +use horned_owl::ontology::set::{SetIndex, SetOntology}; use horned_owl::vocab::AnnotationBuiltIn; -use pyo3::{pyclass, pyfunction, pymethods, PyObject, PyResult, Python, ToPyObject}; use pyo3::exceptions::PyValueError; +use pyo3::{pyclass, pyfunction, pymethods, PyObject, PyResult, Python, ToPyObject}; use crate::{guess_serialization, model, to_py_err}; -use crate::model::AnonymousIndividual; + +#[pyclass] +#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Clone, Copy)] +/// Values to indicate when to build the additional indexes. +/// +/// OnLoad: Create the additional indexes when the ontology is loaded +/// OnQuery: Create the additional indexes only when they are needed +/// Explicit: Only create the additional indexes when explicity requested +pub enum IndexCreationStrategy { + /// Create the additional indexes when the ontology is loaded + OnLoad, + + /// Create the additional indexes only when they are needed + OnQuery, + + /// Only create the additional indexes when explicity requested + Explicit, +} + +impl Default for IndexCreationStrategy { + fn default() -> Self { + IndexCreationStrategy::OnQuery + } +} /// Represents a loaded ontology. #[pyclass] @@ -26,10 +57,14 @@ pub struct PyIndexedOntology { pub classes_to_superclasses: HashMap, HashSet>>, //The primary store of the axioms is a Horned OWL indexed ontology - pub ontology: ArcIRIMappedOntology, + pub iri_index: Option>, + pub component_index: Option>, + pub set_index: SetIndex, //Need this for converting IRIs to IDs and for saving again afterwards pub mapping: PrefixMapping, pub build: Build, + + pub index_strategy: IndexCreationStrategy, } impl Default for PyIndexedOntology { @@ -38,31 +73,80 @@ impl Default for PyIndexedOntology { labels_to_iris: Default::default(), classes_to_subclasses: Default::default(), classes_to_superclasses: Default::default(), - ontology: ArcIRIMappedOntology::new_arc(), + iri_index: None, + component_index: None, + set_index: Default::default(), mapping: Default::default(), build: Build::new_arc(), + index_strategy: IndexCreationStrategy::OnQuery, } } } +impl Ontology for PyIndexedOntology {} +impl MutableOntology for PyIndexedOntology { + fn insert(&mut self, ax: AA) -> bool + where + AA: Into>, + { + self.insert(ax.into().into()) + } + + fn take(&mut self, ax: &AnnotatedComponent) -> Option> { + if let Some(ref mut iri_index) = &mut self.iri_index { + iri_index.index_take(ax); + } + if let Some(ref mut component_index) = &mut self.component_index { + component_index.index_take(ax); + } + self.set_index.index_take(ax) + } +} + +impl From> for PyIndexedOntology { + fn from(value: RDFOntology) -> Self { + Self::from_rdf_ontology(value, Default::default()) + } +} + +impl From> for PyIndexedOntology { + fn from(value: SetOntology) -> Self { + Self::from_set_ontology(value, Default::default()) + } +} + #[pymethods] impl PyIndexedOntology { #[new] - pub fn new() -> Self { - Default::default() + #[pyo3(signature = (index_strategy = IndexCreationStrategy::OnQuery))] + pub fn new(index_strategy: IndexCreationStrategy) -> Self { + let mut s = Self::default(); + + if index_strategy == IndexCreationStrategy::OnLoad { + s.iri_index = Default::default(); + s.component_index = Default::default(); + } + + s.index_strategy = index_strategy; + + s } /// add_default_prefix_names(self) -> None /// /// Adds the prefix for rdf, rdfs, xsd, and owl pub fn add_default_prefix_names(&mut self) -> PyResult<()> { - self.mapping.add_prefix("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#") + self.mapping + .add_prefix("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#") .map_err(to_py_err!("Error while adding predefined prefix 'rdf'"))?; - self.mapping.add_prefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#") + self.mapping + .add_prefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#") .map_err(to_py_err!("Error while adding predefined prefix 'rdfs'"))?; - self.mapping.add_prefix("xsd", "http://www.w3.org/2001/XMLSchema#") + self.mapping + .add_prefix("xsd", "http://www.w3.org/2001/XMLSchema#") .map_err(to_py_err!("Error while adding predefined prefix 'xsd'"))?; - self.mapping.add_prefix("owl", "http://www.w3.org/2002/07/owl#") + self.mapping + .add_prefix("owl", "http://www.w3.org/2002/07/owl#") .map_err(to_py_err!("Error while adding predefined prefix 'owl'"))?; Ok(()) @@ -74,7 +158,11 @@ impl PyIndexedOntology { /// /// If the term does not have an ID, `None` is returned. #[pyo3[signature = (iri, *, iri_is_absolute = None)]] - pub fn get_id_for_iri(&mut self, iri: String, iri_is_absolute: Option) -> PyResult> { + pub fn get_id_for_iri( + &mut self, + iri: String, + iri_is_absolute: Option, + ) -> PyResult> { let iri: String = self.iri(iri, iri_is_absolute)?.into(); let res = self.mapping.shrink_iri(iri.as_str()); @@ -131,31 +219,39 @@ impl PyIndexedOntology { /// /// Adds an or updates the `AnnotationAssertion` axiom for `rdfs:label`. #[pyo3[signature = (iri, label, *, absolute = None)]] - pub fn set_label(&mut self, iri: String, label: String, absolute: Option) -> PyResult<()> { + pub fn set_label( + &mut self, + iri: String, + label: String, + absolute: Option, + ) -> PyResult<()> { let iri: IRI = self.iri(iri, absolute)?.into(); let ax1: AnnotatedComponent = Component::AnnotationAssertion(AnnotationAssertion { subject: iri.clone().into(), ann: Annotation { - ap: self - .build - .annotation_property(AnnotationBuiltIn::Label), + ap: self.build.annotation_property(AnnotationBuiltIn::Label), av: AnnotationValue::Literal(Literal::Simple { literal: label.clone(), }), }, }) - .into(); + .into(); + + let components = if let Some(ref mut iri_index) = &mut self.iri_index { + Box::new(iri_index.component_for_iri(&iri)) + as Box>> + } else { + Box::new((&self.set_index).into_iter()) + }; //If we already have a label, update it: - let old_ax = &self - .ontology - .components_for_iri(&iri) + let old_ax = &components .filter_map(|aax: &AnnotatedComponent| match &aax.component { Component::AnnotationAssertion(AnnotationAssertion { - subject: _subj, - ann, - }) => match ann { + subject: AnnotationSubject::IRI(subj), + ann, + }) if subj == &iri => match ann { Annotation { ap, av: AnnotationValue::Literal(Literal::Simple { literal: _old }), @@ -173,11 +269,10 @@ impl PyIndexedOntology { .next(); if let Some(old_ax) = old_ax { - self.ontology.update_axiom(old_ax, ax1); - } else { - //If no label already, just add one - self.ontology.insert(ax1); + self.take(old_ax); } + + self.insert(Arc::new(ax1)); Ok(()) } @@ -187,28 +282,60 @@ impl PyIndexedOntology { /// /// If the term does not have a label, `None` is returned. pub fn get_iri_for_label(&mut self, label: String) -> PyResult> { - Ok(self.labels_to_iris.get(&label).map(String::from)) + let components = if let Some(ref mut component_index) = &mut self.component_index { + Box::new(component_index.annotation_assertion()) + as Box>> + } else { + Box::new((&self.set_index).into_iter().filter_map(|c| match c { + AnnotatedComponent {component: Component::AnnotationAssertion(a), ..} => Some(a), + _ => None + })) + }; + + let mut labels = components.filter_map(|ax| match ax { + AnnotationAssertion { + subject: AnnotationSubject::IRI(subj), + ann: + Annotation { + ap, + av: AnnotationValue::Literal(Literal::Simple { literal }), + }, + } if *literal == label && AnnotationBuiltIn::Label.underlying().eq(&ap.0.to_string()) => Some(subj), + _ => None, + }); + + Ok(labels.next().map(|i| i.to_string())) } /// get_iri(self) -> Optional[str] /// /// Returns the ontology iri, if it exists. pub fn get_iri(&mut self) -> PyResult> { - Ok(self.get_id().and_then(|x| x.iri.as_ref()).map(model::IRI::from)) + Ok(self + .get_id() + .and_then(|x| x.iri.as_ref()) + .map(model::IRI::from)) } /// get_version_iri(self) -> Optional[str] /// /// Returns the ontologys version iri, if it exists. pub fn get_version_iri(&mut self) -> PyResult> { - Ok(self.get_id().and_then(|x| x.viri.as_ref()).map(model::IRI::from)) + Ok(self + .get_id() + .and_then(|x| x.viri.as_ref()) + .map(model::IRI::from)) } /// 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(&mut self, iri: String, iri_is_absolute: Option) -> PyResult> { + pub fn get_subclasses( + &mut self, + iri: String, + iri_is_absolute: Option, + ) -> PyResult> { let iri: IRI = self.iri(iri, iri_is_absolute)?.into(); let subclasses = self.classes_to_subclasses.get(&iri); @@ -224,7 +351,11 @@ impl PyIndexedOntology { /// /// Gets all superclasses of an entity. #[pyo3[signature = (iri, iri_is_absolute = None)]] - pub fn get_superclasses(&mut self, iri: String, iri_is_absolute: Option) -> PyResult> { + pub fn get_superclasses( + &mut self, + iri: String, + iri_is_absolute: Option, + ) -> PyResult> { let iri: IRI = self.iri(iri, iri_is_absolute)?.into(); let superclasses = self.classes_to_superclasses.get(&iri); @@ -241,14 +372,20 @@ impl PyIndexedOntology { /// Returns the IRIs of all declared classes in the ontology. pub fn get_classes(&mut self) -> PyResult> { //Get the DeclareClass axioms - let classes = self.ontology.component_for_kind(ComponentKind::DeclareClass); + let classes = if let Some(ref mut component_index) = &mut self.component_index { + Box::new(component_index.component_for_kind(ComponentKind::DeclareClass)) + as Box>> + } else { + Box::new((&self.set_index).into_iter()) + }; - let classes: HashSet = classes - .filter_map(|aax| match aax.clone().component { - Component::DeclareClass(dc) => Some(dc.0.0.to_string()), + let classes = classes + .filter_map(|aax| match &aax.component { + Component::DeclareClass(dc) => Some(dc.0 .0.to_string()), _ => None, }) .collect(); + Ok(classes) } @@ -257,13 +394,16 @@ impl PyIndexedOntology { /// Returns the IRIs of all declared object properties in the ontology. pub fn get_object_properties(&mut self) -> PyResult> { //Get the DeclareObjectProperty axioms - let object_properties = self - .ontology - .component_for_kind(ComponentKind::DeclareObjectProperty); + let object_properties = if let Some(ref mut component_index) = &mut self.component_index { + Box::new(component_index.component_for_kind(ComponentKind::DeclareObjectProperty)) + as Box>> + } else { + Box::new((&self.set_index).into_iter()) + }; let object_properties: HashSet = object_properties .filter_map(|aax| match aax.clone().component { - Component::DeclareObjectProperty(dop) => Some(dop.0.0.to_string()), + Component::DeclareObjectProperty(dop) => Some(dop.0 .0.to_string()), _ => None, }) .collect(); @@ -285,8 +425,13 @@ impl PyIndexedOntology { class_iri_is_absolute: Option, ann_iri_is_absolute: Option, ) -> PyResult> { - self.get_annotations(class_iri, ann_iri, class_iri_is_absolute, ann_iri_is_absolute) - .map(|x| x.first().map(Into::into)) + self.get_annotations( + class_iri, + ann_iri, + class_iri_is_absolute, + ann_iri_is_absolute, + ) + .map(|x| x.first().map(Into::into)) } /// get_annotations(self, class_iri: str, ann_iri: str, *, class_iri_is_absolute: Optional[bool] = None, ann_iri_is_absolute: Optional[bool]=None) -> List[str] @@ -296,13 +441,27 @@ impl PyIndexedOntology { /// Note: The order is neither necessarily the same as in the ontology neither is it stable. /// Get all annotation values with `PyIndexedOntology.get_annotations`. #[pyo3[signature = (class_iri, ann_iri, *, class_iri_is_absolute = None, ann_iri_is_absolute = None)]] - pub fn get_annotations(&mut self, class_iri: String, ann_iri: String, class_iri_is_absolute: Option, ann_iri_is_absolute: Option) -> PyResult> { + pub fn get_annotations( + &mut self, + class_iri: String, + ann_iri: String, + class_iri_is_absolute: Option, + ann_iri_is_absolute: Option, + ) -> PyResult> { let class_iri: IRI = self.iri(class_iri, class_iri_is_absolute)?.into(); let ann_iri: IRI = self.iri(ann_iri, ann_iri_is_absolute)?.into(); - let literal_values: Vec = self.ontology.components_for_iri(&class_iri) + + let components = if let Some(iri_index) = &self.iri_index { + Box::new(iri_index.component_for_iri(&class_iri)) + as Box>> + } else { + Box::new((&self.set_index).into_iter()) + }; + + let literal_values = components .filter_map(|aax: &AnnotatedComponent| { match &aax.component { - Component::AnnotationAssertion(AnnotationAssertion { subject: _, ann }) => { + Component::AnnotationAssertion(AnnotationAssertion { subject: AnnotationSubject::IRI(s), ann }) if &class_iri == s => { match ann { Annotation { ap, av: AnnotationValue::Literal(Literal::Simple { literal }) } => { if ann_iri.eq(&ap.0) { Some(literal.clone()) } else { None } @@ -337,14 +496,18 @@ impl PyIndexedOntology { let mut amo: ArcComponentMappedOntology = ComponentMappedOntology::new_arc(); //Copy the components into an ComponentMappedOntology as that is what horned owl writes - for component in self.ontology.iter() { + for component in (&self.set_index).into_iter() { amo.insert(component.clone()); } let result = match serialization { - ResourceType::OFN => horned_owl::io::ofn::writer::write(&mut file, &amo, Some(&self.mapping)), - ResourceType::OWX => horned_owl::io::owx::writer::write(&mut file, &amo, Some(&self.mapping)), - ResourceType::RDF => horned_owl::io::rdf::writer::write(&mut file, &amo) + ResourceType::OFN => { + horned_owl::io::ofn::writer::write(&mut file, &amo, Some(&self.mapping)) + } + ResourceType::OWX => { + horned_owl::io::owx::writer::write(&mut file, &amo, Some(&self.mapping)) + } + ResourceType::RDF => horned_owl::io::rdf::writer::write(&mut file, &amo), }; result.map_err(to_py_err!("Problem saving the ontology to a file")) @@ -354,66 +517,104 @@ impl PyIndexedOntology { /// /// Gets all axioms for an entity. #[pyo3[signature = (iri, *, iri_is_absolute = None)]] - pub fn get_axioms_for_iri(&mut self, iri: String, iri_is_absolute: Option) -> PyResult> { + pub fn get_axioms_for_iri( + &mut self, + iri: String, + iri_is_absolute: Option, + ) -> PyResult> { let iri: IRI = self.iri(iri, iri_is_absolute)?.into(); - let axioms = self - .ontology - .components_for_iri(&iri) - .filter_map(|a| - if a.is_axiom() { - Some(model::AnnotatedComponent::from(a)) - } else { None }) - .collect(); + let iri_index = if let Some(index) = &self.iri_index { + Some(index) + } else { + if self.index_strategy == IndexCreationStrategy::OnQuery { + self.build_iri_index(); + } + if let Some(index) = &self.iri_index { + Some(index) + } else { + None + } + }; - Ok(axioms) + if let Some(iri_index) = iri_index { + let axioms = iri_index + .component_for_iri(&iri) + .filter_map(|a| { + if a.is_axiom() { + Some(model::AnnotatedComponent::from(a)) + } else { + None + } + }) + .collect(); + + Ok(axioms) + } else { + return Err(PyValueError::new_err("IRI index not yet build!")); + } } /// get_components_for_iri(self, iri: str, iri_is_absolute: Optional[bool] = None) -> List[model.AnnotatedComponent] /// /// Gets all components (axiom, swrl, and meta component) for an entity. #[pyo3[signature = (iri, *, iri_is_absolute = None)]] - pub fn get_components_for_iri(&mut self, iri: String, iri_is_absolute: Option) -> PyResult> { + pub fn get_components_for_iri( + &mut self, + iri: String, + iri_is_absolute: Option, + ) -> PyResult> { let iri: IRI = self.iri(iri, iri_is_absolute)?.into(); - let components = self - .ontology - .components_for_iri(&iri) - .map(model::AnnotatedComponent::from) - .collect(); + let iri_index = if let Some(index) = &self.iri_index { + Some(index) + } else { + if self.index_strategy == IndexCreationStrategy::OnQuery { + self.build_iri_index(); + } + if let Some(index) = &self.iri_index { + Some(index) + } else { + None + } + }; - Ok(components) + if let Some(iri_index) = iri_index { + let components = iri_index + .component_for_iri(&iri) + .map(model::AnnotatedComponent::from) + .collect(); + + Ok(components) + } else { + Err(PyValueError::new_err("IRI index not yet build!")) + } } /// get_axioms(self) -> List[model.AnnotatedComponent] /// /// Returns all axioms of the ontology. pub fn get_axioms(&mut self) -> PyResult> { - let r = self - .ontology - .iter() - - .filter_map(|a| + let r = (&self.set_index) + .into_iter() + .filter_map(|a| { if a.is_axiom() { Some(model::AnnotatedComponent::from(a.clone())) } else { None } - ) + }) .collect(); Ok(r) } - /// get_components(self) -> List[model.AnnotatedComponent] /// /// Returns all axioms of the ontology. pub fn get_components(&mut self) -> PyResult> { - let r = self - .ontology - .iter() - + let r = (&self.set_index) + .into_iter() .map(model::AnnotatedComponent::from) .collect(); @@ -429,13 +630,11 @@ impl PyIndexedOntology { component: model::Component, annotations: Option>, ) -> PyResult<()> { - let ann: model::BTreeSetWrap = annotations.unwrap_or(BTreeSet::new()).into(); - let annotated_component: AnnotatedComponent = model::AnnotatedComponent { - component, - ann, - }.into(); - self.insert(&annotated_component); - self.ontology.insert(annotated_component); + let ann: model::BTreeSetWrap = + annotations.unwrap_or(BTreeSet::new()).into(); + let annotated_component: AnnotatedComponent = + model::AnnotatedComponent { component, ann }.into(); + self.insert(Arc::new(annotated_component)); Ok(()) } @@ -457,18 +656,16 @@ impl PyIndexedOntology { /// Removes a component from the ontology. pub fn remove_component(&mut self, component: model::Component) -> PyResult<()> { let ax: Component> = component.into(); - let annotated = self - .ontology - .iter() + let annotated = (&self.set_index) + .into_iter() .find(|a| a.component == ax) .ok_or(PyValueError::new_err("args"))? .to_owned(); - self.ontology.remove(&annotated); + self.take(&annotated); Ok(()) } - /// remove_axiom(self, ax: model.Component) -> None /// /// Synonym for `remove_component` @@ -494,14 +691,16 @@ impl PyIndexedOntology { Ok(r) } - /// curie(self, iri: str) -> model.IRI /// /// Creates a new IRI from CURIE string. /// /// Use this method instead of `model.IRI.parse` if possible as it is more optimized using caches. pub fn curie(&self, curie: String) -> PyResult { - let iri = self.mapping.expand_curie_string(&curie).map_err(to_py_err!("Invalid curie"))?; + let iri = self + .mapping + .expand_curie_string(&curie) + .map_err(to_py_err!("Invalid curie"))?; Ok(model::IRI::new(iri, &self.build)) } @@ -520,7 +719,7 @@ impl PyIndexedOntology { /// Convenience method to add a Declare(Class(iri)) axiom. #[pyo3[signature = (iri, *, absolute = None)]] pub fn declare_class(&mut self, iri: String, absolute: Option) -> PyResult { - Ok(self.ontology.declare::>(self.clazz(iri, absolute)?.into())) + Ok(self.declare::>(self.clazz(iri, absolute)?.into())) } /// object_property(self, iri: str, *, absolute: Optional[bool]=None) -> model.ObjectProperty @@ -529,7 +728,11 @@ impl PyIndexedOntology { /// /// Uses the `iri` method to cache native IRI instances. #[pyo3[signature = (iri, *, absolute = None)]] - pub fn object_property(&self, iri: String, absolute: Option) -> PyResult { + pub fn object_property( + &self, + iri: String, + absolute: Option, + ) -> PyResult { Ok(model::ObjectProperty(self.iri(iri, absolute)?)) } @@ -537,8 +740,14 @@ impl PyIndexedOntology { /// /// Convenience method to add a Declare(ObjectProperty(iri)) axiom. #[pyo3[signature = (iri, *, absolute = None)]] - pub fn declare_object_property(&mut self, iri: String, absolute: Option) -> PyResult { - Ok(self.ontology.declare::>(self.object_property(iri, absolute)?.into())) + pub fn declare_object_property( + &mut self, + iri: String, + absolute: Option, + ) -> PyResult { + Ok(self.declare::>( + self.object_property(iri, absolute)?.into(), + )) } /// data_property(self, iri: str, *, absolute: Optional[bool]=None) -> model.DataProperty @@ -547,7 +756,11 @@ impl PyIndexedOntology { /// /// Uses the `iri` method to cache native IRI instances. #[pyo3[signature = (iri, *, absolute = None)]] - pub fn data_property(&self, iri: String, absolute: Option) -> PyResult { + pub fn data_property( + &self, + iri: String, + absolute: Option, + ) -> PyResult { Ok(model::DataProperty(self.iri(iri, absolute)?)) } @@ -556,16 +769,22 @@ impl PyIndexedOntology { /// Convenience method to add a Declare(DataProperty(iri)) axiom. #[pyo3[signature = (iri, *, absolute = None)]] pub fn declare_data_property(&mut self, iri: String, absolute: Option) -> PyResult { - Ok(self.ontology.declare::>(self.data_property(iri, absolute)?.into())) + Ok(self.declare::>( + self.data_property(iri, absolute)?.into(), + )) } - /// annotation_property(self, iri: str, *, absolute: Optional[bool]=None) -> model.annotationProperty + /// annotation_property(self, iri: str, *, absolute: Optional[bool]=None) -> model.AnnotationProperty /// /// Convenience method to create an annotationProperty from an IRI. /// /// Uses the `iri` method to cache native IRI instances. #[pyo3[signature = (iri, *, absolute = None)]] - pub fn annotation_property(&self, iri: String, absolute: Option) -> PyResult { + pub fn annotation_property( + &self, + iri: String, + absolute: Option, + ) -> PyResult { Ok(model::AnnotationProperty(self.iri(iri, absolute)?)) } @@ -573,8 +792,16 @@ impl PyIndexedOntology { /// /// Convenience method to add a Declare(annotationProperty(iri)) axiom. #[pyo3[signature = (iri, *, absolute = None)]] - pub fn declare_annotation_property(&mut self, iri: String, absolute: Option) -> PyResult { - Ok(self.ontology.declare::>(self.annotation_property(iri, absolute)?.into())) + pub fn declare_annotation_property( + &mut self, + iri: String, + absolute: Option, + ) -> PyResult { + Ok( + self.declare::>( + self.annotation_property(iri, absolute)?.into(), + ), + ) } /// named_individual(self, iri: str, *, absolute: Optional[bool]=None) -> model.NamedIndividual @@ -583,7 +810,11 @@ impl PyIndexedOntology { /// /// Uses the `iri` method to cache native IRI instances. #[pyo3[signature = (iri, *, absolute = None)]] - pub fn named_individual(&self, iri: String, absolute: Option) -> PyResult { + pub fn named_individual( + &self, + iri: String, + absolute: Option, + ) -> PyResult { Ok(model::NamedIndividual(self.iri(iri, absolute)?)) } @@ -592,23 +823,27 @@ impl PyIndexedOntology { /// Convenience method to add a Declare(NamedIndividual(iri)) axiom. #[pyo3[signature = (iri, *, absolute = None)]] pub fn declare_individual(&mut self, iri: String, absolute: Option) -> PyResult { - Ok(self.ontology.declare::>(self.named_individual(iri, absolute)?.into())) + Ok(self.declare::>( + self.named_individual(iri, absolute)?.into(), + )) } - /// anonymous_individual(self, iri: str) -> model.AnonymousIndividual /// /// Convenience method to create an AnonymousIndividual from a string. pub fn anonymous_individual(&self, name: String) -> model::AnonymousIndividual { - AnonymousIndividual(name.into()) + model::AnonymousIndividual(name.into()) } - /// get_descendants(self, parent: str, iri_is_absolute: Optional[bool] = None) -> Set[str] /// /// Gets all direct and indirect subclasses of a class. #[pyo3[signature = (parent_iri, *, iri_is_absolute = None)]] - pub fn get_descendants(&self, parent_iri: String, iri_is_absolute: Option) -> PyResult> { + pub fn get_descendants( + &self, + parent_iri: String, + iri_is_absolute: Option, + ) -> PyResult> { let mut descendants = HashSet::new(); let parent_iri: IRI = self.iri(parent_iri, iri_is_absolute)?.into(); @@ -617,11 +852,15 @@ impl PyIndexedOntology { Ok(descendants) } - /// get_ancestors(onto: PyIndexedOntology, child: str, iri_is_absolute: Optional[bool] = None) -> Set[str] + /// get_ancestors(self, onto: PyIndexedOntology, 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)]] - pub fn get_ancestors(&self, child_iri: String, iri_is_absolute: Option) -> PyResult> { + pub fn get_ancestors( + &self, + child_iri: String, + iri_is_absolute: Option, + ) -> PyResult> { let mut ancestors = HashSet::new(); let child_iri: IRI = self.iri(child_iri, iri_is_absolute)?.into(); @@ -630,14 +869,67 @@ impl PyIndexedOntology { Ok(ancestors) } + + /// build_iri_index(self) -> None + /// + /// Builds an index by iri (IRIMappedIndex). + pub fn build_iri_index(&mut self) -> () { + if self.iri_index.is_some() { + return; + } + + let mut iri_index = IRIMappedIndex::::new(); + + for c in self.set_index.members() { + iri_index.index_insert(c.clone()); + } + + self.iri_index = Some(iri_index); + } + + /// component_index(self) -> None + /// + /// Builds an index by component kind (ComponentMappedIndex). + pub fn build_component_index(&mut self) { + if self.component_index.is_some() { + return; + } + + let mut component_index = ComponentMappedIndex::::new(); + + for c in self.set_index.members() { + component_index.index_insert(c.clone()); + } + + self.component_index = Some(component_index); + } + + /// build_indexes(self) -> None + /// + /// Builds indexes to allow (a quicker) access to axioms and entities. + pub fn build_indexes(&mut self) { + match (&self.iri_index, &self.component_index) { + (Some(_), Some(_)) => return, + (Some(_), None) => return self.build_component_index(), + (None, Some(_)) => return self.build_iri_index(), + _ => {} + } + + let mut component_index = ComponentMappedIndex::::new(); + let mut iri_index = IRIMappedIndex::::new(); + + for c in self.set_index.members() { + component_index.index_insert(c.clone()); + iri_index.index_insert(c.clone()); + } + + self.component_index = Some(component_index); + self.iri_index = Some(iri_index); + } } impl PyIndexedOntology { - fn recurse_descendants( - &self, - superclass: &IRI, - descendants: &mut HashSet, - ) { + fn recurse_descendants(&self, superclass: &IRI, descendants: &mut HashSet) { descendants.insert(superclass.into()); if self.classes_to_subclasses.contains_key(superclass) { for cls2 in &mut self.classes_to_subclasses[superclass].iter() { @@ -646,11 +938,7 @@ impl PyIndexedOntology { } } - fn recurse_ancestors( - &self, - subclass: &IRI, - ancestors: &mut HashSet, - ) { + fn recurse_ancestors(&self, subclass: &IRI, ancestors: &mut HashSet) { ancestors.insert(subclass.into()); if self.classes_to_superclasses.contains_key(subclass) { for cls2 in &mut self.classes_to_superclasses[subclass].iter() { @@ -659,94 +947,73 @@ impl PyIndexedOntology { } } - pub fn insert(&mut self, ax: &AnnotatedComponent) -> () { - let b = Build::new(); - - match ax.kind() { - ComponentKind::AnnotationAssertion => match ax.clone().component { - Component::AnnotationAssertion(AnnotationAssertion { subject, ann }) => match ann { - Annotation { - ap, - av: AnnotationValue::Literal(Literal::Simple { literal }), - } => { - if AnnotationBuiltIn::Label.underlying().eq(&ap.0.to_string()) { - let _ = &self - .labels_to_iris - .insert(literal.clone(), b.iri(subject.deref())); - } - } - _ => (), - }, - _ => (), - }, - ComponentKind::SubClassOf => { - match ax.clone().component { - Component::SubClassOf(SubClassOf { sup, sub }) => { - match sup { - ClassExpression::Class(c) => { - match sub { - ClassExpression::Class(d) => { - //Direct subclasses only - let _ = &self - .classes_to_subclasses - .entry(c.0.clone()) - .or_insert(HashSet::new()) - .insert(d.0.clone()); - let _ = &self - .classes_to_superclasses - .entry(d.0.clone()) - .or_insert(HashSet::new()) - .insert(c.0.clone()); - } - _ => (), - } - } - _ => (), - } - } - _ => (), - } - } - _ => (), + pub fn insert(&mut self, ax: ArcAnnotatedComponent) -> bool { + if let Some(ref mut iri_index) = &mut self.iri_index { + iri_index.index_insert(ax.clone()); + } + if let Some(ref mut component_index) = &mut self.component_index { + component_index.index_insert(ax.clone()); } + self.set_index.index_insert(ax) } - pub fn from(iro: IRIMappedOntology>>) -> PyIndexedOntology { - let mut ino = PyIndexedOntology::default(); + fn get_id(&mut self) -> Option<&OntologyID> { + let components = if let Some(component_index) = &self.component_index { + Box::new(component_index.component_for_kind(ComponentKind::OntologyID)) + as Box>> + } else { + Box::new((&self.set_index).into_iter()) + }; - for ax in iro.iter() { - ino.insert(&ax); - } + components + .filter_map(|c| match c { + horned_owl::model::AnnotatedComponent:: { + component: Component::OntologyID(id), + ann: _, + } => Some(id), + _ => None, + }) + .next() + } - ino.ontology = iro; + pub fn from_set_ontology( + value: SetOntology, + index_strategy: IndexCreationStrategy, + ) -> Self { + let mut pio = Self::new(index_strategy); - ino + for cmp in value { + pio.insert(Arc::new(cmp)); + } + + pio } - fn get_id(&mut self) -> Option<&OntologyID> { - match self.ontology.component_for_kind(ComponentKind::OntologyID).next() { - Some(horned_owl::model::AnnotatedComponent:: { - component: Component::OntologyID(id), - ann: _ - }) => Some(id), - _ => None - } + pub fn from_rdf_ontology( + value: RDFOntology, + index_strategy: IndexCreationStrategy, + ) -> Self { + let mut pio = Self::new(index_strategy); + let (set_index, _, _) = value.index(); + + pio.set_index = set_index; + + pio } } +/// @deprecated("please use `PyIndexedOntology.get_descendants` instead") /// get_descendants(onto: PyIndexedOntology, parent: str) -> Set[str] /// -/// DEPRECATED: please use `PyIndexedOntology::get_descendants` instead /// Gets all direct and indirect subclasses of a class. #[pyfunction] pub fn get_descendants(onto: &PyIndexedOntology, parent: String) -> PyResult> { onto.get_descendants(parent, Some(true)) } - +/// @deprecated(please use `PyIndexedOntology.get_ancestors` instead) /// get_ancestors(onto: PyIndexedOntology, child: str) -> Set[str] /// -/// DEPRECATED: please use `PyIndexedOntology::get_ancestors` instead /// Gets all direct and indirect super classes of a class. #[pyfunction] pub fn get_ancestors(onto: &PyIndexedOntology, child: String) -> PyResult> { diff --git a/test/test_id.py b/test/test_id.py index 5fab819..ca6d2c5 100644 --- a/test/test_id.py +++ b/test/test_id.py @@ -1,6 +1,6 @@ import unittest -from test.test_base import simple_ontology +from test_base import simple_ontology class IdTestCase(unittest.TestCase): diff --git a/test/test_label.py b/test/test_label.py index 28ec73a..6ce04fd 100644 --- a/test/test_label.py +++ b/test/test_label.py @@ -30,6 +30,9 @@ def test_update_label(self): self.assertEqual(expected, actual) + all_labels = o.get_annotations("A", "rdfs:label") + self.assertEqual([expected], all_labels) + def test_add_label(self): o = simple_ontology()