Source code for mowl.ontology.normalize

from de.tudresden.inf.lat.jcel.ontology.normalization import OntologyNormalizer
from de.tudresden.inf.lat.jcel.ontology.axiom.extension import IntegerOntologyObjectFactoryImpl
from de.tudresden.inf.lat.jcel.owlapi.translator import ReverseAxiomTranslator
from de.tudresden.inf.lat.jcel.owlapi.translator import Translator
from org.semanticweb.owlapi.model.parameters import Imports
from uk.ac.manchester.cs.owl.owlapi import OWLClassImpl, OWLObjectSomeValuesFromImpl, \
    OWLObjectIntersectionOfImpl
from org.semanticweb.owlapi.model import OWLAxiom, OWLOntology, AxiomType, ClassExpressionType, IRI
from org.semanticweb.owlapi.apibinding import OWLManager

from java.util import HashSet
from jpype import java

import os
import logging
logger = logging.getLogger(__name__)
handler = logging.StreamHandler()
logger.addHandler(handler)
logger.setLevel(logging.INFO)

from mowl.owlapi import OWLAPIAdapter
from deprecated.sphinx import versionchanged

[docs] class ELNormalizer(): """This class wraps the normalization functionality found in the Java library :class:`Jcel`. \ The normalization process transforms an ontology into 7 normal forms in the description \ logic EL language. """ def __init__(self): return
[docs] def get_cache_path(self, ontology_path): """Generate the cache file path for a given ontology path. :param ontology_path: Path to the original ontology file :type ontology_path: str :rtype: str """ if ontology_path.endswith(".owl"): return ontology_path[:-4] + "_mowl_el_normalized.owl" else: return ontology_path + "_mowl_el_normalized.owl"
def _save_normalized_ontology(self, axioms_dict, cache_path): """Save normalized axioms to an OWL file. :param axioms_dict: Dictionary of normalized axioms :type axioms_dict: dict :param cache_path: Path to save the normalized ontology :type cache_path: str """ manager = OWLManager.createOWLOntologyManager() normalized_ont = manager.createOntology() # Collect all OWL axioms from the normalized axioms dictionary for key in ["gci0", "gci1", "gci2", "gci3", "gci0_bot", "gci1_bot", "gci3_bot"]: for axiom in axioms_dict.get(key, []): manager.addAxiom(normalized_ont, axiom.owl_axiom) # Save to file manager.saveOntology(normalized_ont, IRI.create("file:" + os.path.abspath(cache_path))) logger.info(f"Saved normalized ontology to cache: {cache_path}") def _load_cached_ontology(self, cache_path): """Load a cached normalized ontology from file. :param cache_path: Path to the cached ontology file :type cache_path: str :rtype: OWLOntology """ manager = OWLManager.createOWLOntologyManager() cached_ont = manager.loadOntologyFromOntologyDocument(java.io.File(cache_path)) return cached_ont
[docs] def normalize(self, ontology, load=False, ontology_path=None, use_cache=True): """Performs the normalization. :param ontology: Input ontology :type ontology: :class:`org.semanticweb.owlapi.model.OWLOntology` :param load: Load the GCIs if the ontology is already normalized. Default is False. :type load: bool, optional :param ontology_path: Path to the original ontology file. If provided and use_cache is \ True, the normalized ontology will be cached to avoid re-normalization on subsequent \ calls. The cache file will be named ``<ontology_path>_mowl_el_normalized.owl``. :type ontology_path: str, optional :param use_cache: Whether to use caching. Default is True. If False, the ontology will \ be re-normalized even if a cache file exists. :type use_cache: bool, optional :rtype: Dictionary where the keys are labels for each normal form and the values are a \ list of axioms of each normal form. """ # Type check if not isinstance(ontology, OWLOntology): raise TypeError(f"Parameter 'ontology' must be of \ type org.semanticweb.owlapi.model.OWLOntology. Found: {type(ontology)}") if ontology_path is not None and not isinstance(ontology_path, str): raise TypeError("Parameter 'ontology_path' must be of type str") if not isinstance(use_cache, bool): raise TypeError("Parameter 'use_cache' must be of type bool") # Check for cached normalized ontology cache_path = None if ontology_path is not None and use_cache: cache_path = self.get_cache_path(ontology_path) if os.path.exists(cache_path): logger.info(f"Loading normalized ontology from cache: {cache_path}") cached_ont = self._load_cached_ontology(cache_path) axioms_dict = self.__load_normalized_ontology(cached_ont) # Add ABox axioms from original ontology abox = ontology.getABoxAxioms(Imports.fromBoolean(True)) axioms_dict["class_assertion"] = [ClassAssertion(axiom) for axiom in abox if axiom.getAxiomType() == AxiomType.CLASS_ASSERTION and axiom.getClassExpression().getClassExpressionType() == ClassExpressionType.OWL_CLASS] axioms_dict["object_property_assertion"] = [ObjectPropertyAssertion(axiom) for axiom in abox if axiom.getAxiomType() == AxiomType.OBJECT_PROPERTY_ASSERTION] return axioms_dict # jreasoner = JcelReasoner(ontology, False) # root_ont = jreasoner.getRootOntology() abox = ontology.getABoxAxioms(Imports.fromBoolean(True)) if load: logger.info("Loading normalized ontology") axioms_dict = self.__load_normalized_ontology(ontology) else: ontology = self.preprocess_ontology(ontology) root_ont = ontology translator = Translator(ontology.getOWLOntologyManager().getOWLDataFactory(), IntegerOntologyObjectFactoryImpl()) # translator = jreasoner.getTranslator() axioms = HashSet() axioms.addAll(root_ont.getAxioms()) translator.getTranslationRepository().addAxiomEntities(root_ont) for ont in root_ont.getImportsClosure(): axioms.addAll(ont.getAxioms()) translator.getTranslationRepository().addAxiomEntities(ont) intAxioms = translator.translateSA(axioms) normalizer = OntologyNormalizer() factory = IntegerOntologyObjectFactoryImpl() normalized_ontology = normalizer.normalize(intAxioms, factory) self.rTranslator = ReverseAxiomTranslator(translator, ontology) axioms_dict = self.__revert_translation(normalized_ontology) axioms_dict["class_assertion"] = [ClassAssertion(axiom) for axiom in abox if axiom.getAxiomType() == AxiomType.CLASS_ASSERTION and axiom.getClassExpression().getClassExpressionType() == ClassExpressionType.OWL_CLASS] axioms_dict["object_property_assertion"] = [ObjectPropertyAssertion(axiom) for axiom in abox if axiom.getAxiomType() == AxiomType.OBJECT_PROPERTY_ASSERTION] # Save normalized ontology to cache if ontology_path is provided if ontology_path is not None and use_cache and not load: cache_path = self.get_cache_path(ontology_path) self._save_normalized_ontology(axioms_dict, cache_path) return axioms_dict
def __load_normalized_ontology(self, ontology): axioms_dict = { "gci0": [], "gci1": [], "gci2": [], "gci3": [], "gci0_bot": [], "gci1_bot": [], "gci3_bot": [], "class_assertion": [], "object_property_assertion": []} axioms = ontology.getTBoxAxioms(Imports.fromBoolean(True)) for axiom in axioms: key, value = process_axiom(axiom) axioms_dict[key].append(value) return axioms_dict def __revert_translation(self, normalized_ontology): axioms_dict = { "gci0": [], "gci1": [], "gci2": [], "gci3": [], "gci0_bot": [], "gci1_bot": [], "gci3_bot": [], "class_assertion": [], "object_property_assertion": []} for ax in normalized_ontology: try: axiom = self.rTranslator.visit(ax) key, value = process_axiom(axiom) axioms_dict[key].append(value) except Exception as e: logging.info("Reverse translation. Ignoring axiom: %s", ax) logging.info(e) return axioms_dict # TODO: This method is missing unit tests
[docs] def preprocess_ontology(self, ontology): """Preprocesses the ontology to remove axioms that are not supported by the normalization \ process. :param ontology: Input ontology :type ontology: :class:`org.semanticweb.owlapi.model.OWLOntology` :rtype: :class:`org.semanticweb.owlapi.model.OWLOntology` """ # Type check if not isinstance(ontology, OWLOntology): raise TypeError("Parameter 'ontology' must be of \ type org.semanticweb.owlapi.model.OWLOntology") tbox_axioms = ontology.getTBoxAxioms(Imports.fromBoolean(True)) new_tbox_axioms = HashSet() for axiom in tbox_axioms: axiom_as_str = axiom.toString() if "UnionOf" in axiom_as_str: continue elif "MinCardinality" in axiom_as_str: continue elif "ComplementOf" in axiom_as_str: continue elif "AllValuesFrom" in axiom_as_str: continue elif "MaxCardinality" in axiom_as_str: continue elif "ExactCardinality" in axiom_as_str: continue elif "Annotation" in axiom_as_str: continue elif "ObjectHasSelf" in axiom_as_str: continue elif "urn:swrl" in axiom_as_str: continue elif "EquivalentObjectProperties" in axiom_as_str: continue elif "SymmetricObjectProperty" in axiom_as_str: continue elif "AsymmetricObjectProperty" in axiom_as_str: continue elif "ObjectOneOf" in axiom_as_str: continue elif "ObjectHasValue" in axiom_as_str: continue elif "DataSomeValuesFrom" in axiom_as_str: continue elif "DataAllValuesFrom" in axiom_as_str: continue elif "DataHasValue" in axiom_as_str: continue elif "DataPropertyRange" in axiom_as_str: continue elif "DataPropertyDomain" in axiom_as_str: continue elif "FunctionalDataProperty" in axiom_as_str: continue elif "DisjointUnion" in axiom_as_str: continue elif "HasKey" in axiom_as_str: continue else: new_tbox_axioms.add(axiom) owl_manager = OWLAPIAdapter().owl_manager new_ontology = owl_manager.createOntology(new_tbox_axioms) return new_ontology
def process_axiom(axiom: OWLAxiom): # Type check if not isinstance(axiom, OWLAxiom): raise TypeError("Parameter 'axiom' must be of type org.semanticweb.owlapi.model.OWLAxiom") subclass = axiom.getSubClass() superclass = axiom.getSuperClass() if type(subclass) == OWLObjectIntersectionOfImpl: superclass = superclass.toStringID() if superclass.contains("owl#Nothing"): return "gci1_bot", GCI1_BOT(axiom) return "gci1", GCI1(axiom) elif type(subclass) == OWLObjectSomeValuesFromImpl: superclass = superclass.toStringID() if superclass.contains("owl#Nothing"): return "gci3_bot", GCI3_BOT(axiom) return "gci3", GCI3(axiom) elif type(subclass) == OWLClassImpl: if type(superclass) == OWLClassImpl: superclass = superclass.toStringID() if superclass.contains("owl#Nothing"): return "gci0_bot", GCI0_BOT(axiom) return "gci0", GCI0(axiom) elif type(superclass) == OWLObjectSomeValuesFromImpl: return "gci2", GCI2(axiom) else: logging.info("Superclass type not recognized. Ignoring axiom: %s", axiom) else: logging.info("Subclass type not recognized. Ignoring axiom: %s", axiom)
[docs] class Axiom(): """Base class for all axioms in the :math:`\\mathcal{EL}` language""" def __init__(self, axiom): self._axiom = axiom return def __eq__(self, other): return self._axiom.equals(other._axiom) @property def owl_axiom(self): """Returns the axiom :rtype: :class:`org.semanticweb.owlapi.model.OWLAxiom` """ return self._axiom
[docs] @versionchanged(version="0.4.0", reason="Included individuals in the return of this method.") @staticmethod def get_entities(gcis): """Returns all the classes and object properties that appear in the GCIs :param gcis: List of GCIs :type gcis: list :rtype: tuple(set, set) """ classes = set() object_properties = set() individuals = set() for gci in gcis: new_classes, new_obj_props, new_inds = gci.get_entities() classes |= new_classes object_properties |= new_obj_props individuals |= new_inds return classes, object_properties, individuals
[docs] class GCI(Axiom): """Base class for all GCI types in the :math:`\\mathcal{EL}` language""" def __init__(self, axiom): super().__init__(axiom) @property def owl_subclass(self): """Returns the subclass of the GCI :rtype: :class:`org.semanticweb.owlapi.model.OWLClassExpression` """ return self._axiom.getSubClass() @property def owl_superclass(self): """Returns the superclass of the GCI :rtype: :class:`org.semanticweb.owlapi.model.OWLClassExpression` """ return self._axiom.getSuperClass()
[docs] class ClassAssertion(Axiom): """Class assertion axiom of the form :math:`A(a)` :param axiom: Axiom of the form :math:`A(a)` :type axiom: :class:`org.semanticweb.owlapi.model.OWLAxiom` """ def __init__(self, axiom): super().__init__(axiom) self._class_ = None self._individual = None @property def class_(self): """Returns the class of the class assertion :rtype: :class:`org.semanticweb.owlapi.model.OWLClass` """ if not self._class_: self._class_ = str(self._axiom.getClassExpression().toStringID()) return self._class_ @property def individual(self): """Returns the individual of the class assertion :rtype: :class:`org.semanticweb.owlapi.model.OWLNamedIndividual` """ if not self._individual: self._individual = str(self._axiom.getIndividual().toStringID()) return self._individual
[docs] def get_entities(self): return set([self.class_]), set(), set([self.individual])
[docs] class ObjectPropertyAssertion(Axiom): """Object property assertion axiom of the form :math:`R(a,b)` :param axiom: Axiom of the form :math:`R(a,b)` :type axiom: :class:`org.semanticweb.owlapi.model.OWLAxiom` """ def __init__(self, axiom): super().__init__(axiom) self._object_property = None self._subject = None self._object = None @property def object_property(self): """Returns the object property of the object property assertion :rtype: :class:`org.semanticweb.owlapi.model.OWLObjectProperty` """ if not self._object_property: self._object_property = str(self._axiom.getProperty().toStringID()) return self._object_property @property def subject(self): """Returns the subject of the object property assertion :rtype: :class:`org.semanticweb.owlapi.model.OWLNamedIndividual` """ if not self._subject: self._subject = str(self._axiom.getSubject().toStringID()) return self._subject @property def object_(self): """Returns the object of the object property assertion :rtype: :class:`org.semanticweb.owlapi.model.OWLNamedIndividual` """ if not self._object: self._object = str(self._axiom.getObject().toStringID()) return self._object
[docs] def get_entities(self): return set(), set([self.object_property]), set([self.subject, self.object_])
[docs] class GCI0(GCI): """ GCI of the form :math:`C \\sqsubseteq D` :param axiom: Axiom of the form :math:`C \\sqsubseteq D` :type axiom: :class:`org.semanticweb.owlapi.model.OWLAxiom` """ def __init__(self, axiom): super().__init__(axiom) self._subclass = None self._superclass = None @property def subclass(self): """Returns the subclass of the GCI: :math:`C` :rtype: :class:`org.semanticweb.owlapi.model.OWLClass` """ if not self._subclass: self._subclass = str(self.owl_subclass.toStringID()) return self._subclass @property def superclass(self): """" Returns the superclass of the GCI: :math:`D` or :math:`\\bot` :rtype: :class:`org.semanticweb.owlapi.model.OWLClass` """ if not self._superclass: self._superclass = str(self.owl_superclass.toStringID()) return self._superclass
[docs] def get_entities(self): return set([self.subclass, self.superclass]), set(), set()
[docs] class GCI0_BOT(GCI0): """ GCI of the form :math:`C \\sqsubseteq \\bot` :param axiom: Axiom of the form :math:`C \\sqsubseteq \\bot` :type axiom: :class:`org.semanticweb.owlapi.model.OWLAxiom` """ def __init__(self, axiom): super().__init__(axiom) if "owl#Nothing" not in self.superclass: raise ValueError("Superclass in GCI0_BOT must be the bottom concept.")
[docs] class GCI1(GCI): """ GCI of the form :math:`C \\sqcap D \\sqsubseteq E` :param axiom: Axiom of the form :math:`C \\sqcap D \\sqsubseteq E` :type axiom: :class:`org.semanticweb.owlapi.model.OWLAxiom` """ def __init__(self, axiom): super().__init__(axiom) self._left_subclass = None self._right_subclass = None self._superclass = None def _process_left_side(self): operands = self.owl_subclass.getOperandsAsList() left_subclass = operands[0].toStringID() right_subclass = operands[1].toStringID() self._left_subclass = str(left_subclass) self._right_subclass = str(right_subclass) @property def left_subclass(self): """" Returns the left operand of the subclass of the GCI: :math:`C` :rtype: :class:`org.semanticweb.owlapi.model.OWLClass` """ if not self._left_subclass: self._process_left_side() return self._left_subclass @property def right_subclass(self): """ Returns the right operand of the subclass of the GCI: :math:`D` :rtype: :class:`org.semanticweb.owlapi.model.OWLClass` """ if not self._right_subclass: self._process_left_side() return self._right_subclass @property def superclass(self): """ Returns the superclass of the GCI: :math:`E` or :math:`\\bot` :rtype: :class:`org.semanticweb.owlapi.model.OWLClass` """ if not self._superclass: self._superclass = str(self.owl_superclass.toStringID()) return self._superclass
[docs] def get_entities(self): return set([self.left_subclass, self.right_subclass, self.superclass]), set(), set()
[docs] class GCI1_BOT(GCI1): """ GCI of the form :math:`C \\sqcap D \\sqsubseteq \\bot` :param axiom: Axiom of the form :math:`C \\sqcap D \\sqsubseteq \\bot` :type axiom: :class:`org.semanticweb.owlapi.model.OWLAxiom` """ def __init__(self, axiom): super().__init__(axiom) if "owl#Nothing" not in self.superclass: raise ValueError("Superclass in GCI1_BOT must be the bottom concept.")
[docs] class GCI2(GCI): """ GCI of the form :math:`C \\sqsubseteq \\exists R.D` :param axiom: Axiom of the form :math:`C \\sqsubseteq \\exists R.D` :type axiom: :class:`org.semanticweb.owlapi.model.OWLAxiom` """ def __init__(self, axiom): super().__init__(axiom) self._subclass = None self._object_property = None self._filler = None def _process_right_side(self): object_property = str(self.owl_superclass.getProperty().toString()) filler = str(self.owl_superclass.getFiller().toStringID()) self._object_property = object_property[1:-1] if object_property.startswith("<") \ else object_property self._filler = filler @property def subclass(self): """ Returns the subclass of the GCI: :math:`C` :rtype: :class:`org.semanticweb.owlapi.model.OWLClass` """ if not self._subclass: self._subclass = str(self.owl_subclass.toStringID()) return self._subclass @property def object_property(self): """ Returns the object property of the GCI: :math:`R` :rtype: :class:`org.semanticweb.owlapi.model.OWLObjectProperty` """ if not self._object_property: self._process_right_side() return self._object_property @property def filler(self): """ Returns the filler of the GCI: :math:`D` :rtype: :class:`org.semanticweb.owlapi.model.OWLClass` """ if not self._filler: self._process_right_side() return self._filler
[docs] def get_entities(self): return set([self.subclass, self.filler]), set([self.object_property]), set()
[docs] class GCI3(GCI): """ GCI of the form :math:`\\exists R.C \\sqsubseteq D` :param axiom: Axiom of the form :math:`\\exists R.C \\sqsubseteq D` :type axiom: :class:`org.semanticweb.owlapi.model.OWLAxiom` """ def __init__(self, axiom): super().__init__(axiom) self._object_property = None self._filler = None self._superclass = None def _process_left_side(self): object_property = str(self.owl_subclass.getProperty().toString()) filler = str(self.owl_subclass.getFiller().toStringID()) self._object_property = object_property[1:-1] if object_property.startswith("<") \ else object_property self._filler = filler @property def object_property(self): """ Returns the object property of the GCI: :math:`R` :rtype: :class:`org.semanticweb.owlapi.model.OWLObjectProperty` """ if not self._object_property: self._process_left_side() return self._object_property @property def filler(self): """ Returns the filler of the GCI: :math:`C` :rtype: :class:`org.semanticweb.owlapi.model.OWLClass` """ if not self._filler: self._process_left_side() return self._filler @property def superclass(self): """ Returns the superclass of the GCI: :math:`D` or :math:`\\bot` :rtype: :class:`org.semanticweb.owlapi.model.OWLClass` """ if not self._superclass: self._superclass = str(self.owl_superclass.toStringID()) return self._superclass
[docs] def get_entities(self): return set([self.filler, self.superclass]), set([self.object_property]), set()
[docs] class GCI3_BOT(GCI3): """ GCI of the form :math:`\\exists R.C \\sqsubseteq \\bot` :param axiom: Axiom of the form :math:`\\exists R.C \\sqsubseteq \\bot` :type axiom: :class:`org.semanticweb.owlapi.model.OWLAxiom` """ def __init__(self, axiom): super().__init__(axiom) if "owl#Nothing" not in self.superclass: raise ValueError("Superclass in GCI3_BOT must be the bottom concept.")