Source code for oaklib.interfaces.owl_interface

import inspect
import itertools
from abc import ABC
from dataclasses import dataclass
from enum import Enum
from typing import Any, Callable, Iterable, List, Optional, Tuple, Type, Union

# TODO: add funowl to dependencies
import funowl
from funowl import (
    IRI,
    AnnotationAssertion,
    AsymmetricObjectProperty,
    Axiom,
    Class,
    ClassExpression,
    DisjointClasses,
    EquivalentClasses,
    IrreflexiveObjectProperty,
    Literal,
    ObjectAllValuesFrom,
    ObjectIntersectionOf,
    ObjectPropertyChain,
    ObjectPropertyExpression,
    ObjectSomeValuesFrom,
    ObjectUnionOf,
    Ontology,
    ReflexiveObjectProperty,
    SubClassOf,
    SubObjectPropertyOf,
    SymmetricObjectProperty,
    TransitiveObjectProperty,
)
from funowl.writers import FunctionalWriter

from oaklib.datamodels.vocabulary import (
    OWL_ASYMMETRIC_PROPERTY,
    OWL_IRREFLEXIVE_PROPERTY,
    OWL_REFLEXIVE_PROPERTY,
    OWL_SYMMETRIC_PROPERTY,
    OWL_TRANSITIVE_PROPERTY,
)
from oaklib.interfaces.basic_ontology_interface import BasicOntologyInterface
from oaklib.types import CURIE


class OwlProfile(Enum):
    EL = "EL"
    DL = "DL"
    OWL_FULL = "OWL-Full"
    RL = "RL"
    QC = "QL"


@dataclass
class ReasonerConfiguration:
    reasoner: str = None
    reasoner_version: str = None
    implements_profiles: List[OwlProfile] = None


@dataclass
class AxiomFilter:
    type: Optional[Type[Axiom]] = None
    about: Optional[Union[CURIE, List[CURIE]]] = None
    references: Optional[CURIE] = None
    func: Callable = None
    ontologies: Optional[List[CURIE]] = None

    def set_type(self, axiom_type: Union[str, Type[Axiom]]) -> None:
        if isinstance(axiom_type, str):
            matches = [obj for n, obj in inspect.getmembers(funowl) if n == axiom_type]
            if len(matches) == 1:
                self.type = matches[0]
            elif len(matches) == 0:
                raise ValueError(f"No such axiom type: {axiom_type}")
            else:
                raise ValueError(f"Multiple matches {axiom_type} => {matches}")
        else:
            self.type = axiom_type


[docs] @dataclass class OwlInterface(BasicOntologyInterface, ABC): """ presents ontology as an OWL ontology using an OWL datamodel We leverage the :ref:`_funowl_datamodel` Currently there is one implementation, the :ref:`funowl_implementation` In future the SqlDatabase implementation will implement this, as well as: - owlery - robot/owlapi via py4j """ functional_writer: FunctionalWriter = None def owl_ontology(self) -> Ontology: raise NotImplementedError def axioms(self, reasoner: Optional[ReasonerConfiguration] = None) -> Iterable[Axiom]: raise NotImplementedError def filter_axioms( self, conditions: AxiomFilter, reasoner: Optional[ReasonerConfiguration] = None ) -> Iterable[Axiom]: if reasoner is not None: raise ValueError for axiom in self.axioms(reasoner=reasoner): if self._axiom_matches(axiom, conditions): yield axiom def set_axioms(self, axioms: List[Axiom]) -> None: raise NotImplementedError
[docs] def subclass_axioms( self, subclass: CURIE = None, superclass: CURIE = None, reasoner: Optional[ReasonerConfiguration] = None, ) -> Iterable[SubClassOf]: """ Gets all SubClassOf axioms matching criterion :param subclass: if specified, constrains to axioms where this is the subclass :param superclass: if specified, constrains to axioms where this is the superclass :param reasoner: :return: """ for axiom in self.axioms(reasoner=reasoner): if isinstance(axiom, SubClassOf): if subclass is not None: m = False if isinstance(axiom.subClassExpression, IRI): if self._entity_matches(axiom.subClassExpression, subclass): m = True if not m: continue if superclass is not None: m = False if isinstance(axiom.superClassExpression, IRI): if self._entity_matches(axiom.superClassExpression, subclass): m = True if not m: continue yield axiom
[docs] def equivalence_axioms( self, about: CURIE = None, references: CURIE = None, reasoner: Optional[ReasonerConfiguration] = None, ) -> Iterable[EquivalentClasses]: """ All EquivalentClasses axioms matching criteria :param about: :param references: :param reasoner: :return: """ return self.filter_axioms( reasoner=reasoner, conditions=AxiomFilter(type=EquivalentClasses, about=about, references=references), )
[docs] def annotation_assertion_axioms( self, subject: CURIE = None, property: CURIE = None, value: Any = None ) -> Iterable[AnnotationAssertion]: """ Filters all matching annotation axioms :param subject: :param property: :param value: :return: """ for axiom in self.axioms(): # TODO: use indexing to speed this up if isinstance(axiom, AnnotationAssertion): if subject is not None: if not self._entity_matches(axiom.subject, subject): continue if property is not None: if not self._entity_matches(axiom.property, property): continue if value is not None: if not self._entity_matches(axiom.value, value): continue yield axiom
[docs] def disjoint_pairs(self, subjects: Iterable[CURIE] = None) -> Iterable[Tuple[CURIE, CURIE]]: """ Gets all disjoint pairs of entities :param subjects: :return: """ for axiom in self.axioms(): if isinstance(axiom, DisjointClasses): if isinstance(axiom.classExpressions, list): for c1, c2 in itertools.combinations(axiom.classExpressions, 2): if not subjects or (c1 in subjects or c2 in subjects): yield c1, c2
[docs] def is_disjoint(self, subject: CURIE, object: CURIE) -> bool: """ Checks if two entities are declared or entailed disjoint. :param subject: :param object: :return: """ raise NotImplementedError
def owl_classes(self) -> Iterable[Class]: raise NotImplementedError def owl_individuals(self) -> Iterable[Class]: raise NotImplementedError
[docs] def is_satisfiable(self, curie: CURIE) -> bool: """ Note: this may move to the validation interface :param curie: :return: """ raise NotImplementedError
[docs] def reasoner_configurations(self) -> List[ReasonerConfiguration]: """ Lists all available reasoner configurations :return: """ return []
def entity_iri_to_curie(self, entity: IRI) -> CURIE: raise NotImplementedError def _entity_matches(self, entity: Any, curie: Union[CURIE, Any]): if isinstance(entity, IRI): return curie == self.entity_iri_to_curie(entity) elif isinstance(entity, Literal): return curie == entity else: return False def _axiom_matches(self, axiom: Axiom, conditions: AxiomFilter) -> bool: if conditions.type is not None: if not isinstance(axiom, conditions.type): return False if conditions.about is not None: if isinstance(conditions.about, list): if not any(e for e in self._axiom_is_about_curies(axiom) if e in conditions.about): return False else: if not any(e for e in self._axiom_is_about_curies(axiom) if e == conditions.about): return False if conditions.references is not None: if not any( e for e in self._axiom_references_curies(axiom) if e == conditions.references ): return False return True
[docs] def axiom_is_about(self, axiom: Axiom) -> Iterable[IRI]: """ Gives an axiom, yield all of the entity IRIs which this axiom is *about* For example, a SubClassOf axiom is about the IRI in the subClassOf expression We use a consistent definition of *about* as in the OWLAPI :param axiom: :return: entity IRI iterator """ if isinstance(axiom, SubClassOf): for e in self._expression_is_about(axiom.subClassExpression): yield e elif isinstance(axiom, EquivalentClasses): for x in axiom.classExpressions: for e in self._expression_is_about(x): yield e else: # TODO: all axiom types pass
[docs] def axiom_references(self, axiom: Axiom) -> Iterable[IRI]: """ Gives an axiom, yield all of the entity IRIs which this axiom references (i.e. entities in the signature) :param axiom: :return: entity IRI iterator """ if isinstance(axiom, SubClassOf): for e in self._expression_references(axiom.subClassExpression): yield e for e in self._expression_references(axiom.superClassExpression): yield e elif isinstance(axiom, EquivalentClasses) or isinstance(axiom, DisjointClasses): for x in axiom.classExpressions: for e in self._expression_references(x): yield e else: # TODO: all axiom types pass
def _expression_references( self, ex: Union[ClassExpression, ObjectPropertyExpression] ) -> Iterable[IRI]: # TODO: generalize signature if isinstance(ex, IRI): # https://github.com/hsolbrig/funowl/issues/19 try: if ex.full_uri(self.functional_writer.g): yield ex except AttributeError: pass elif isinstance(ex, ObjectIntersectionOf) or isinstance(ex, ObjectUnionOf): for x in ex.classExpressions: for r in self._expression_references(x): yield r elif isinstance(ex, ObjectSomeValuesFrom) or isinstance(ex, ObjectAllValuesFrom): for x in self._expression_references(ex.objectPropertyExpression): yield x for x in self._expression_references(ex.classExpression): yield x def _expression_is_about(self, ex: ClassExpression) -> Iterable[IRI]: # TODO: generalize signature if isinstance(ex, IRI): yield ex def _axiom_references_curies(self, axiom: Axiom) -> Iterable[CURIE]: for e in self.axiom_references(axiom): yield self.entity_iri_to_curie(e) def _axiom_is_about_curies(self, axiom: Axiom) -> List[CURIE]: return [self.entity_iri_to_curie(e) for e in self.axiom_is_about(axiom)]
[docs] def property_characteristics(self, property: CURIE) -> Iterable[CURIE]: """ Gets all property characteristics for a given property :param property: :return: """ pc_tuples = [ (TransitiveObjectProperty, OWL_TRANSITIVE_PROPERTY), (SymmetricObjectProperty, OWL_SYMMETRIC_PROPERTY), (AsymmetricObjectProperty, OWL_ASYMMETRIC_PROPERTY), (ReflexiveObjectProperty, OWL_REFLEXIVE_PROPERTY), (IrreflexiveObjectProperty, OWL_IRREFLEXIVE_PROPERTY), ] pcs = tuple([pc[0] for pc in pc_tuples]) for axiom in self.axioms(): if isinstance(axiom, pcs): for pc, pc_curie in pc_tuples: if isinstance(axiom, pc): if isinstance(axiom.objectPropertyExpression, IRI): if self.entity_iri_to_curie(axiom.objectPropertyExpression) == property: yield pc_curie
[docs] def transitive_object_properties(self) -> Iterable[CURIE]: """ Gets all transitive object properties :return: """ for axiom in self.axioms(): if isinstance(axiom, TransitiveObjectProperty): if isinstance(axiom.objectPropertyExpression, IRI): yield self.entity_iri_to_curie(axiom.objectPropertyExpression)
[docs] def simple_subproperty_of_chains(self) -> Iterable[Tuple[CURIE, List[CURIE]]]: """ Gets all property chains of where the super property is a chain of IRIs :return: """ for axiom in self.axioms(): if isinstance(axiom, SubObjectPropertyOf): if isinstance(axiom.superObjectPropertyExpression, ObjectPropertyChain): if isinstance(axiom.subObjectPropertyExpression, IRI): chain_exprs = axiom.superObjectPropertyExpression.objectPropertyExpressions if all(isinstance(p, IRI) for p in chain_exprs): chain = [self.entity_iri_to_curie(p) for p in chain_exprs] yield self.entity_iri_to_curie(axiom.subObjectPropertyExpression), chain