From 741259fd9605cf5e5dcca84dad6bcf61a1abf2a4 Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Tue, 26 Nov 2024 13:27:24 +0100 Subject: [PATCH 1/3] Assertion info enriched --- owlapy/iri.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/owlapy/iri.py b/owlapy/iri.py index c2bf836a..ce73e928 100644 --- a/owlapy/iri.py +++ b/owlapy/iri.py @@ -59,7 +59,7 @@ def create(iri:str | Namespaces, remainder:str=None) -> 'IRI': assert isinstance(iri, str) and remainder is None, \ f"iri must be string if remainder is None. Currently, {type(iri)} and {type(remainder)}" # Extract reminder from input string - assert "/" in iri, "Input must contain /" + assert "/" in iri, f"Input must contain /\tCurrently, {iri}" # assert ":" in iri, "Input must contain :" assert " " not in iri, f"Input must not contain whitespace. Currently:{iri}." index = 1 + max(iri.rfind("/"), iri.rfind(":"), iri.rfind("#")) From fa17bc931b70666e00aef33ba0064af8c8b5ff03 Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Tue, 26 Nov 2024 13:28:01 +0100 Subject: [PATCH 2/3] WIP:RDFLib based ontology and manager implemented --- owlapy/owl_ontology.py | 215 ++++++++++++++++++++++++++++++++- owlapy/owl_ontology_manager.py | 22 +++- 2 files changed, 233 insertions(+), 4 deletions(-) diff --git a/owlapy/owl_ontology.py b/owlapy/owl_ontology.py index fc4cd5eb..ec37d316 100644 --- a/owlapy/owl_ontology.py +++ b/owlapy/owl_ontology.py @@ -6,6 +6,7 @@ from typing import Iterable, TypeVar, Final, Optional, Union, cast import logging import owlready2 +import rdflib from pandas import Timedelta from owlapy import namespaces from owlapy.abstracts.abstract_owl_ontology import AbstractOWLOntology @@ -826,9 +827,10 @@ def properties_in_signature(self) -> Iterable[OWLProperty]: def individuals_in_signature(self) -> Iterable[OWLNamedIndividual]: for i in self._onto.individuals(): yield OWLNamedIndividual(IRI.create(i.iri)) + def get_abox_axioms(self)->Iterable: - # @TODO: CD: Return all information between owl classes, e.g. subclass or disjoint raise NotImplementedError("will be implemented in future") + def get_tbox_axioms(self)->Iterable: # @TODO: CD: Return all information between owl classes, e.g. subclass or disjoint raise NotImplementedError("will be implemented in future") @@ -1132,6 +1134,215 @@ def save(self, path:str=None, document_iri: Optional[IRI] = None): raise NotImplementedError("document_iri must be None for the time being") self.manager.saveOntology(self.owlapi_ontology, self.manager.getOntologyFormat(self.owlapi_ontology), document_iri) +class RDFLibOntology(AbstractOWLOntology): + + def __init__(self,path:str): + assert os.path.exists(path) + import rdflib + + self.rdflib_graph=rdflib.Graph().parse(path) + + def __len__(self) -> int: + return len([t for t in self._onto.get_triples()]) + + def classes_in_signature(self) -> Iterable[OWLClass]: + for c in self._onto.classes(): + yield OWLClass(IRI.create(c.iri)) + + def data_properties_in_signature(self) -> Iterable[OWLDataProperty]: + for dp in self._onto.data_properties(): + yield OWLDataProperty(IRI.create(dp.iri)) + + def object_properties_in_signature(self) -> Iterable[OWLObjectProperty]: + for op in self._onto.object_properties(): + yield OWLObjectProperty(IRI.create(op.iri)) + + def properties_in_signature(self) -> Iterable[OWLProperty]: + yield from self.object_properties_in_signature() + yield from self.data_properties_in_signature() + + def individuals_in_signature(self) -> Iterable[OWLNamedIndividual]: + for (s,p,o) in self.rdflib_graph.subjects(rdflib.RDF.type, rdflib.OWL.NamedIndividual): + print(s,p,o) + # for i in self._onto.individuals(): + # yield OWLNamedIndividual(IRI.create(i.iri)) + + def get_abox_axioms(self)->Iterable: + results=[] + for owl_class in self.rdflib_graph.subjects(rdflib.RDF.type, rdflib.OWL.Class): + if isinstance(owl_class, rdflib.term.URIRef): + str_owl_class=owl_class.n3()[1:-1] + for (_,p,o) in self.rdflib_graph.triples(triple=(owl_class,None, None)): + if isinstance(o, rdflib.term.BNode): + continue + str_iri_predicate=p.n3() + str_iri_object = o.n3()[1:-1] + axiom=None + if str_iri_predicate=="": + # Ignore for the timebing + pass + elif str_iri_predicate=="": + axiom=OWLSubClassOfAxiom(sub_class=OWLClass(str_owl_class),super_class=OWLClass(str_iri_object)) + + elif str_iri_predicate=="": + axiom=OWLEquivalentClassesAxiom([OWLClass(str_owl_class), OWLClass(str_iri_object)]) + else: + raise NotImplementedError(f"{str_iri_predicate} unsure") + if axiom: + results.append(axiom) + + + + return results + + + def get_tbox_axioms(self)->Iterable: + # @TODO: CD: Return all information between owl classes, e.g. subclass or disjoint + raise NotImplementedError("will be implemented in future") + def get_abox_axioms_between_individuals(self)->Iterable: + # @TODO: CD: Return all information between owl_individuals, i.e., triples with object properties + raise NotImplementedError("will be implemented in future") + def get_abox_axioms_between_individuals_and_classes(self)->Iterable: + # @TODO: CD: Return all type information about individuals, i.e., individual type Class + raise NotImplementedError("will be implemented in future") + # @TODO:CD:Unsure it is working + def equivalent_classes_axioms(self, c: OWLClass) -> Iterable[OWLEquivalentClassesAxiom]: + raise NotImplementedError("will be implemented in future") + c_x: owlready2.ThingClass = self._world[c.str] + # TODO: Should this also return EquivalentClasses general class axioms? Compare to java owlapi + for ec_x in c_x.equivalent_to: + yield OWLEquivalentClassesAxiom([c, _parse_concept_to_owlapy(ec_x)]) + # @TODO:CD:Unsure it is working + def general_class_axioms(self) -> Iterable[OWLClassAxiom]: + raise NotImplementedError("will be implemented in future") + # TODO: At the moment owlready2 only supports SubClassOf general class axioms. (18.02.2023) + for ca in self._onto.general_class_axioms(): + yield from (OWLSubClassOfAxiom(_parse_concept_to_owlapy(ca.left_side), _parse_concept_to_owlapy(c)) + for c in ca.is_a) + + + def data_property_domain_axioms(self, pe: OWLDataProperty) -> Iterable[OWLDataPropertyDomainAxiom]: + raise NotImplementedError("will be implemented in future") + p_x: owlready2.DataPropertyClass = self._world[pe.str] + domains = set(p_x.domains_indirect()) + if len(domains) == 0: + yield OWLDataPropertyDomainAxiom(pe, OWLThing) + else: + for dom in domains: + if isinstance(dom, (owlready2.ThingClass, owlready2.ClassConstruct)): + yield OWLDataPropertyDomainAxiom(pe, _parse_concept_to_owlapy(dom)) + else: + logger.warning("Construct %s not implemented at %s", dom, pe) + pass # XXX TODO + + def data_property_range_axioms(self, pe: OWLDataProperty) -> Iterable[OWLDataPropertyRangeAxiom]: + raise NotImplementedError("will be implemented in future") + p_x: owlready2.DataPropertyClass = self._world[pe.str] + ranges = set(chain.from_iterable(super_prop.range for super_prop in p_x.ancestors())) + if len(ranges) == 0: + pass + # TODO + else: + for rng in ranges: + if rng in _Datatype_map: + yield OWLDataPropertyRangeAxiom(pe, _Datatype_map[rng]) + elif isinstance(rng, owlready2.ClassConstruct): + yield OWLDataPropertyRangeAxiom(pe, _parse_datarange_to_owlapy(rng)) + else: + logger.warning("Datatype %s not implemented at %s", rng, pe) + pass # XXX TODO + + def object_property_domain_axioms(self, pe: OWLObjectProperty) -> Iterable[OWLObjectPropertyDomainAxiom]: + raise NotImplementedError("will be implemented in future") + p_x: owlready2.ObjectPropertyClass = self._world[pe.str] + domains = set(p_x.domains_indirect()) + if len(domains) == 0: + yield OWLObjectPropertyDomainAxiom(pe, OWLThing) + else: + for dom in domains: + if isinstance(dom, (owlready2.ThingClass, owlready2.ClassConstruct)): + yield OWLObjectPropertyDomainAxiom(pe, _parse_concept_to_owlapy(dom)) + else: + logger.warning("Construct %s not implemented at %s", dom, pe) + pass # XXX TODO + + def object_property_range_axioms(self, pe: OWLObjectProperty) -> Iterable[OWLObjectPropertyRangeAxiom]: + raise NotImplementedError("will be implemented in future") + p_x: owlready2.ObjectPropertyClass = self._world[pe.str] + + ranges = set(chain.from_iterable(super_prop.range for super_prop in p_x.ancestors())) + if len(ranges) == 0: + yield OWLObjectPropertyRangeAxiom(pe, OWLThing) + else: + for rng in ranges: + if isinstance(rng, (owlready2.ThingClass, owlready2.ClassConstruct)): + yield OWLObjectPropertyRangeAxiom(pe, _parse_concept_to_owlapy(rng)) + else: + logger.warning("Construct %s not implemented at %s", rng, pe) + pass # XXX TODO + + def add_axiom(self, axiom: Union[OWLAxiom, Iterable[OWLAxiom]]): + raise NotImplementedError("will be implemented in future") + self.is_modified = True + if isinstance(axiom, OWLAxiom): + _add_axiom(axiom, self, self._world) + else: + for ax in axiom: + _add_axiom(ax, self, self._world) + + def remove_axiom(self, axiom: Union[OWLAxiom, Iterable[OWLAxiom]]): + raise NotImplementedError("will be implemented in future") + self.is_modified = True + if isinstance(axiom, OWLAxiom): + _remove_axiom(axiom, self, self._world) + else: + for ax in axiom: + _remove_axiom(ax, self, self._world) + + def save(self, path: Union[str,IRI] = None, inplace:bool=False, rdf_format = "rdfxml"): + raise NotImplementedError("will be implemented in future") + # convert it into str. + if isinstance(path, IRI): + path = path.as_str() + # Sanity checking + if inplace is False: + assert isinstance(path,str), f"path must be string if inplace is set to False. Current path is {type(path)}" + # Get the current ontology defined in the world. + ont_x:owlready2.namespace.Ontology + ont_x = self._world.get_ontology(self.get_ontology_id().get_ontology_iri().as_str()) + + if inplace: + if os.path.exists(self._iri.as_str()): + print(f"Saving {self} inplace...") + ont_x.save(file=self._iri.as_str(), format=rdf_format) + else: + print(f"Saving {self} inplace with name of demo.owl...") + self._world.get_ontology(self.get_ontology_id().get_ontology_iri().as_str()).save(file="demo.owl") + else: + print(f"Saving {path}..") + ont_x.save(file=path,format=rdf_format) + + def get_ontology_id(self): + raise NotImplementedError("will be implemented in future") + def get_owl_ontology_manager(self): + raise NotImplementedError("will be implemented in future") + + def __eq__(self, other): + raise NotImplementedError("will be implemented in future") + if type(other) is type(self): + return self._onto.loaded == other._onto.loaded and self._onto.base_iri == other._onto.base_iri + return NotImplemented + + def __hash__(self): + raise NotImplementedError("will be implemented in future") + return hash(self._onto.base_iri) + + def __repr__(self): + raise NotImplementedError("will be implemented in future") + return f'RDFLibOntology({self._onto.base_iri}, loaded:{self._onto.loaded})' + + + OWLREADY2_FACET_KEYS = MappingProxyType({ OWLFacet.MIN_INCLUSIVE: "min_inclusive", OWLFacet.MIN_EXCLUSIVE: "min_exclusive", @@ -1145,7 +1356,7 @@ def save(self, path:str=None, document_iri: Optional[IRI] = None): OWLFacet.FRACTION_DIGITS: "fraction_digits" }) - +######################### Below classes must be outside of this script ######################### class ToOwlready2: __slots__ = '_world' diff --git a/owlapy/owl_ontology_manager.py b/owlapy/owl_ontology_manager.py index 4297969b..3561ffb2 100644 --- a/owlapy/owl_ontology_manager.py +++ b/owlapy/owl_ontology_manager.py @@ -7,7 +7,7 @@ from owlapy.abstracts.abstract_owl_ontology_manager import AbstractOWLOntologyChange, AbstractOWLOntologyManager from owlapy.iri import IRI from owlapy.meta_classes import HasIRI -from owlapy.owl_ontology import Ontology, SyncOntology +from .owl_ontology import Ontology, SyncOntology, RDFLibOntology from owlapy.abstracts.abstract_owl_ontology import AbstractOWLOntology from owlapy.static_funcs import startJVM @@ -142,4 +142,22 @@ def getOntologyFormat(self,*args): return self.owlapi_manager.getOntologyFormat(*args) def saveOntology(self,*args)->None: - self.owlapi_manager.saveOntology(*args) \ No newline at end of file + self.owlapi_manager.saveOntology(*args) + + + +class RDFLibOntologyManager(AbstractOWLOntologyManager): + + def __init__(self): + pass + def create_ontology(self, iri: str = None) -> RDFLibOntology: + assert isinstance(iri,str) + return RDFLibOntology(iri) + def load_ontology(self, path: str = None) -> Ontology: + assert isinstance(path,str) + assert os.path.exists(path) + return RDFLibOntology(path) + def apply_change(self, change: AbstractOWLOntologyChange): + raise NotImplementedError("Change is not yet implemented.") + def save_world(self): + raise NotImplementedError("Change is not yet implemented.") From 67941c1bc4299ff0486f4f91539550116047cc83 Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Tue, 26 Nov 2024 15:14:18 +0100 Subject: [PATCH 3/3] Command line interface example added and rdflib based ontology generated --- README.md | 29 +++++++++++-- owlapy/owl_ontology.py | 99 ++++++++++++++++++++++++++++-------------- tests/test_ontology.py | 17 ++++++++ 3 files changed, 110 insertions(+), 35 deletions(-) create mode 100644 tests/test_ontology.py diff --git a/README.md b/README.md index 5d35a36e..b771603e 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,6 @@ or ```bash pip3 install owlapy ``` - - ```shell # To download RDF knowledge graphs wget https://files.dice-research.org/projects/Ontolearn/KGs.zip -O ./KGs.zip && unzip KGs.zip @@ -30,6 +28,32 @@ pytest -p no:warnings -x # Running 147 tests ~ 35 secs ## Examples +### OWL Reasoning from Command line + +
Click me! + +```shell +owlapy --path_ontology "KGs/Family/family-benchmark_rich_background.owl" --inference_types "all" --out_ontology "enriched_family.owl" +``` + +```--inference_types``` can be specified by selecting one from + +``` +["InferredClassAssertionAxiomGenerator", +"InferredSubClassAxiomGenerator", +"InferredDisjointClassesAxiomGenerator", +"InferredEquivalentClassAxiomGenerator", +"InferredEquivalentDataPropertiesAxiomGenerator", +"InferredEquivalentObjectPropertyAxiomGenerator", +"InferredInverseObjectPropertiesAxiomGenerator", +"InferredSubDataPropertyAxiomGenerator", +"InferredSubObjectPropertyAxiomGenerator", +"InferredDataPropertyCharacteristicAxiomGenerator", +"InferredObjectPropertyCharacteristicAxiomGenerator"] +``` + +
+ ### Exploring OWL Ontology
Click me! @@ -175,7 +199,6 @@ stopJVM() Check also the [examples](https://github.com/dice-group/owlapy/tree/develop/examples) and [tests](https://github.com/dice-group/owlapy/tree/develop/tests) folders. - ### Sklearn to OWL Ontology
Click me! diff --git a/owlapy/owl_ontology.py b/owlapy/owl_ontology.py index ec37d316..43cf6ed7 100644 --- a/owlapy/owl_ontology.py +++ b/owlapy/owl_ontology.py @@ -1141,64 +1141,99 @@ def __init__(self,path:str): import rdflib self.rdflib_graph=rdflib.Graph().parse(path) + self.str_owl_classes=[ x.n3()[1:-1] for x in self.rdflib_graph.subjects(rdflib.RDF.type, rdflib.OWL.Class) if not isinstance(x,rdflib.term.BNode)] + self.str_owl_individuals=[ x.n3()[1:-1] for x in self.rdflib_graph.subjects(rdflib.RDF.type, rdflib.OWL.NamedIndividual) if not isinstance(x,rdflib.term.BNode)] def __len__(self) -> int: - return len([t for t in self._onto.get_triples()]) + return len(self.rdflib_graph) + + def get_tbox_axioms(self) -> Iterable[OWLSubClassOfAxiom | OWLEquivalentClassesAxiom]: + results = [] + for owl_class in self.rdflib_graph.subjects(rdflib.RDF.type, rdflib.OWL.Class): + if isinstance(owl_class, rdflib.term.URIRef): + str_owl_class = owl_class.n3()[1:-1] + for (_, p, o) in self.rdflib_graph.triples(triple=(owl_class, None, None)): + if isinstance(o, rdflib.term.BNode): + continue + str_iri_predicate = p.n3() + str_iri_object = o.n3()[1:-1] + if str_iri_predicate == "": + # Ignore for the timebing + axiom = OWLDeclarationAxiom(OWLClass(str_owl_class)) + elif str_iri_predicate == "": + axiom = OWLSubClassOfAxiom(sub_class=OWLClass(str_owl_class), + super_class=OWLClass(str_iri_object)) + + elif str_iri_predicate == "": + axiom = OWLEquivalentClassesAxiom([OWLClass(str_owl_class), OWLClass(str_iri_object)]) + else: + raise NotImplementedError(f"{str_iri_predicate} unsure") + if axiom: + results.append(axiom) + return results + + def get_abox_axioms(self)->Iterable: + results=[] + for owl_individual in self.rdflib_graph.subjects(rdflib.RDF.type, rdflib.OWL.NamedIndividual): + if isinstance(owl_individual, rdflib.term.URIRef): + str_owl_individual=owl_individual.n3()[1:-1] + for (_,p,o) in self.rdflib_graph.triples(triple=(owl_individual,None, None)): + if isinstance(o, rdflib.term.BNode): + continue + str_iri_predicate=p.n3()[1:-1] + str_iri_object = o.n3()[1:-1] + axiom=None + if str_iri_predicate=="http://www.w3.org/1999/02/22-rdf-syntax-ns#type": + if str_iri_object in self.str_owl_classes: + axiom = OWLClassAssertionAxiom(OWLNamedIndividual(str_owl_individual), OWLClass(str_iri_object)) + elif str_iri_object=="http://www.w3.org/2002/07/owl#NamedIndividual": + # axiom= OWLDeclarationAxiom(OWLNamedIndividual(str_owl_individual)) + continue + else: + raise RuntimeError(f"Incorrect Parsing:\t{str_owl_individual}\t{str_iri_predicate}\t{str_iri_object}") + elif str_iri_object in self.str_owl_individuals: + axiom = OWLObjectPropertyAssertionAxiom(OWLNamedIndividual(str_owl_individual), + OWLObjectProperty(str_iri_predicate), + OWLNamedIndividual(str_iri_object)) + else: + + raise NotImplementedError("") + if axiom: + results.append(axiom) + return results def classes_in_signature(self) -> Iterable[OWLClass]: + raise NotImplementedError() for c in self._onto.classes(): yield OWLClass(IRI.create(c.iri)) def data_properties_in_signature(self) -> Iterable[OWLDataProperty]: + raise NotImplementedError() + for dp in self._onto.data_properties(): yield OWLDataProperty(IRI.create(dp.iri)) def object_properties_in_signature(self) -> Iterable[OWLObjectProperty]: + raise NotImplementedError() + for op in self._onto.object_properties(): yield OWLObjectProperty(IRI.create(op.iri)) def properties_in_signature(self) -> Iterable[OWLProperty]: + raise NotImplementedError() + yield from self.object_properties_in_signature() yield from self.data_properties_in_signature() def individuals_in_signature(self) -> Iterable[OWLNamedIndividual]: + raise NotImplementedError() + for (s,p,o) in self.rdflib_graph.subjects(rdflib.RDF.type, rdflib.OWL.NamedIndividual): print(s,p,o) # for i in self._onto.individuals(): # yield OWLNamedIndividual(IRI.create(i.iri)) - def get_abox_axioms(self)->Iterable: - results=[] - for owl_class in self.rdflib_graph.subjects(rdflib.RDF.type, rdflib.OWL.Class): - if isinstance(owl_class, rdflib.term.URIRef): - str_owl_class=owl_class.n3()[1:-1] - for (_,p,o) in self.rdflib_graph.triples(triple=(owl_class,None, None)): - if isinstance(o, rdflib.term.BNode): - continue - str_iri_predicate=p.n3() - str_iri_object = o.n3()[1:-1] - axiom=None - if str_iri_predicate=="": - # Ignore for the timebing - pass - elif str_iri_predicate=="": - axiom=OWLSubClassOfAxiom(sub_class=OWLClass(str_owl_class),super_class=OWLClass(str_iri_object)) - - elif str_iri_predicate=="": - axiom=OWLEquivalentClassesAxiom([OWLClass(str_owl_class), OWLClass(str_iri_object)]) - else: - raise NotImplementedError(f"{str_iri_predicate} unsure") - if axiom: - results.append(axiom) - - - return results - - - def get_tbox_axioms(self)->Iterable: - # @TODO: CD: Return all information between owl classes, e.g. subclass or disjoint - raise NotImplementedError("will be implemented in future") def get_abox_axioms_between_individuals(self)->Iterable: # @TODO: CD: Return all information between owl_individuals, i.e., triples with object properties raise NotImplementedError("will be implemented in future") diff --git a/tests/test_ontology.py b/tests/test_ontology.py new file mode 100644 index 00000000..43944159 --- /dev/null +++ b/tests/test_ontology.py @@ -0,0 +1,17 @@ +import unittest +from owlapy.owl_ontology_manager import SyncOntologyManager, OntologyManager, RDFLibOntologyManager +from owlapy.owl_ontology import SyncOntology, Ontology, RDFLibOntology +from owlapy.owl_ontology import RDFLibOntology + + +class TestOntology(unittest.TestCase): + def test_counting(self): + o_sync: SyncOntology + o_sync = SyncOntologyManager().load_ontology(path="KGs/Family/father.owl") + o_owlready: Ontology + o_owlready = OntologyManager().load_ontology(path="KGs/Family/father.owl") + o_rdf: RDFLibOntology + o_rdf = RDFLibOntologyManager().load_ontology(path="KGs/Family/father.owl") + + assert len({i for i in o_sync.get_tbox_axioms()})==len({i for i in o_rdf.get_tbox_axioms()}) + assert len({i for i in o_sync.get_abox_axioms()})==len({i for i in o_rdf.get_abox_axioms()})