diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a0e107e9..a0d19860 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,6 +25,7 @@ jobs: run: | wget https://files.dice-research.org/projects/Ontolearn/KGs.zip unzip KGs.zip + pip install scikit-learn python -m pytest -p no:warnings -x - name: Coverage report diff --git a/.gitignore b/.gitignore index 64e33d7f..6fc4aaf4 100644 --- a/.gitignore +++ b/.gitignore @@ -148,4 +148,4 @@ cython_debug/ .vscode/ # Project related files -/KGs/ \ No newline at end of file +KGs/ \ No newline at end of file diff --git a/README.md b/README.md index 6dd942c2..5d35a36e 100644 --- a/README.md +++ b/README.md @@ -30,29 +30,6 @@ pytest -p no:warnings -x # Running 147 tests ~ 35 secs ## Examples -### Sklearn to OWL Ontology - -
Click me! - -```python -from owlapy.owl_ontology_manager import SyncOntologyManager -from owlapy.util_owl_static_funcs import csv_to_rdf_kg -import pandas as pd -from sklearn.datasets import load_iris -data = load_iris() -df = pd.DataFrame(data.data, columns=data.feature_names) -df.to_csv("iris_dataset.csv", index=False) -path_kg = "iris_kg.owl" -# Construct an RDF Knowledge Graph from a CSV file -csv_to_rdf_kg(path_csv="iris_dataset.csv", path_kg=path_kg, namespace="http://owlapy.com/iris") -onto = SyncOntologyManager().load_ontology(path_kg) -assert len(onto.get_abox_axioms()) == 750 - -``` - -
- - ### Exploring OWL Ontology
Click me! @@ -101,7 +78,8 @@ for axiom in onto.get_abox_axioms():
-### Creating OWL Class Expressions +### OWL Knowledge Engineering +
Click me! ```python @@ -111,7 +89,7 @@ from owlapy import owl_expression_to_sparql, owl_expression_to_dl from owlapy.owl_ontology_manager import OntologyManager from owlapy.owl_axiom import OWLDeclarationAxiom, OWLClassAssertionAxiom from owlapy.owl_individual import OWLNamedIndividual, IRI - +from owlapy.static_funcs import create_ontology # Using owl classes to create a complex class expression male = OWLClass("http://example.com/society#male") hasChild = OWLObjectProperty("http://example.com/society#hasChild") @@ -119,23 +97,19 @@ hasChild_male = OWLObjectSomeValuesFrom(hasChild, male) teacher = OWLClass("http://example.com/society#teacher") teacher_that_hasChild_male = OWLObjectIntersectionOf([hasChild_male, teacher]) -# You can render and print owl class expressions in Description Logics syntax or convert it to SPARQL for example. +# You can render and print owl class expressions in Description Logics syntax or convert it to SPARQL for example. print(owl_expression_to_dl(teacher_that_hasChild_male)) # (∃ hasChild.male) ⊓ teacher print(owl_expression_to_sparql(teacher_that_hasChild_male)) # SELECT DISTINCT ?x WHERE { ?x ?s_1 . ?s_1 a . ?x a . } } -# Create an Ontology, add the axioms and save the Ontology. -manager = OntologyManager() -new_iri = IRI.create("file:/example_ontology.owl") -ontology = manager.create_ontology(new_iri) - +# Create an ontology via ontology manager directly +ontology = create_ontology("file:/example_ontology.owl",with_owlapi=False) john = OWLNamedIndividual("http://example.com/society#john") male_declaration_axiom = OWLDeclarationAxiom(male) hasChild_declaration_axiom = OWLDeclarationAxiom(hasChild) john_declaration_axiom = OWLDeclarationAxiom(john) john_a_male_assertion_axiom = OWLClassAssertionAxiom(john, male) ontology.add_axiom([male_declaration_axiom, hasChild_declaration_axiom, john_declaration_axiom, john_a_male_assertion_axiom]) -ontology.save() - +ontology.save(inplace=True) ``` Every OWL object that can be used to classify individuals, is considered a class expression and @@ -201,5 +175,29 @@ 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! + +```python +from owlapy.owl_ontology_manager import SyncOntologyManager +from owlapy.util_owl_static_funcs import csv_to_rdf_kg +import pandas as pd +from sklearn.datasets import load_iris +data = load_iris() +df = pd.DataFrame(data.data, columns=data.feature_names) +df.to_csv("iris_dataset.csv", index=False) +path_kg = "iris_kg.owl" +# Construct an RDF Knowledge Graph from a CSV file +csv_to_rdf_kg(path_csv="iris_dataset.csv", path_kg=path_kg, namespace="http://owlapy.com/iris") +onto = SyncOntologyManager().load_ontology(path_kg) +assert len(onto.get_abox_axioms()) == 750 + +``` + +
+ + ## How to cite Currently, we are working on our manuscript describing our framework. diff --git a/owlapy/class_expression/restriction.py b/owlapy/class_expression/restriction.py index 0e6658a9..827cf534 100644 --- a/owlapy/class_expression/restriction.py +++ b/owlapy/class_expression/restriction.py @@ -10,7 +10,7 @@ from ..owl_individual import OWLIndividual from ..owl_datatype import OWLDatatype from ..owl_object import OWLObject -from ..vocab import OWLFacet +from ..vocab import OWLFacet from datetime import datetime, date from pandas import Timedelta @@ -53,7 +53,7 @@ class OWLHasValueRestriction(Generic[_T], OWLRestriction, HasFiller[_T], metacla """Represent a HasValue restriction in the OWL 2 Args: - _T: The value type. + value: The value type _T. """ __slots__ = () @@ -143,12 +143,14 @@ def get_filler(self) -> OWLClassExpression: return self._filler -class OWLObjectCardinalityRestriction(OWLCardinalityRestriction[OWLClassExpression], OWLQuantifiedObjectRestriction): +class OWLObjectCardinalityRestriction(OWLCardinalityRestriction[OWLClassExpression], OWLQuantifiedObjectRestriction, + metaclass=ABCMeta): """Represents Object Property Cardinality Restrictions in the OWL 2 specification.""" __slots__ = () _property: OWLObjectPropertyExpression # @TODO: CD: property shows the in-built function + @abstractmethod def __init__(self, cardinality: int, property: OWLObjectPropertyExpression, filler: OWLClassExpression): super().__init__(cardinality, filler) @@ -169,6 +171,7 @@ def __eq__(self, other): and self._filler == other._filler else: return False + def __hash__(self): return hash((self._property, self._cardinality, self._filler)) @@ -342,7 +345,6 @@ def __eq__(self, other): else: return False - def __hash__(self): return hash(("OWLObjectHasSelf", self._property)) @@ -430,13 +432,16 @@ def as_object_union_of(self) -> OWLClassExpression: if len(self._values) == 1: return self return OWLObjectUnionOf(map(lambda _: OWLObjectOneOf(_), self.individuals())) + def __hash__(self): return hash(("OWLObjectOneOf", self._values)) + def __eq__(self, other): if type(other) is type(self): return self._values == other._values else: return False + def __repr__(self): return f'OWLObjectOneOf({self._values})' @@ -466,6 +471,7 @@ def __init__(self, filler: OWLDataRange): assert isinstance(filler, OWLDataRange), "filler must be an OWLDataRange" self._filler = filler # @TODO:CD: define it as @property + def get_filler(self) -> OWLDataRange: # documented in parent (HasFiller) return self._filler @@ -479,13 +485,16 @@ class OWLDataCardinalityRestriction(OWLCardinalityRestriction[OWLDataRange], _property: OWLDataPropertyExpression # @TODO: CD: property shows the in-built function + @abstractmethod def __init__(self, cardinality: int, property: OWLDataPropertyExpression, filler: OWLDataRange): assert isinstance(filler, OWLDataRange), "filler must be an OWLDataRange" super().__init__(cardinality, filler) self._property = property + def __hash__(self): return hash(("OWLDataCardinalityRestriction",self._property, self._cardinality, self._filler)) + def __repr__(self): return f"{type(self).__name__}(" \ f"property={repr(self.get_property())},{self.get_cardinality()},filler={repr(self.get_filler())})" @@ -501,6 +510,7 @@ def get_property(self) -> OWLDataPropertyExpression: # documented in parent return self._property + class OWLDataMinCardinality(OWLDataCardinalityRestriction): """A minimum cardinality expression DataMinCardinality( n DPE DR ) consists of a nonnegative integer n, a data property expression DPE, and a unary data range DR, and it contains all those individuals that are connected by @@ -511,6 +521,7 @@ class OWLDataMinCardinality(OWLDataCardinalityRestriction): type_index: Final = 3015 # @TODO: CD: property shows the in-built function + def __init__(self, cardinality: int, property: OWLDataPropertyExpression, filler: OWLDataRange): """ Args: @@ -534,6 +545,7 @@ class OWLDataMaxCardinality(OWLDataCardinalityRestriction): type_index: Final = 3017 # @TODO: CD: property shows the in-built function + def __init__(self, cardinality: int, property: OWLDataPropertyExpression, filler: OWLDataRange): """ Args: @@ -557,6 +569,7 @@ class OWLDataExactCardinality(OWLDataCardinalityRestriction): type_index: Final = 3016 # @TODO: CD: property shows the in-built function + def __init__(self, cardinality: int, property: OWLDataPropertyExpression, filler: OWLDataRange): """ Args: @@ -593,6 +606,7 @@ class OWLDataSomeValuesFrom(OWLQuantifiedDataRestriction): _property: OWLDataPropertyExpression # @TODO: CD: property shows the in-built function + def __init__(self, property: OWLDataPropertyExpression, filler: OWLDataRange): """Gets an OWLDataSomeValuesFrom restriction. @@ -614,6 +628,7 @@ def __eq__(self, other): return self._filler == other._filler and self._property == other._property else: return False + def __hash__(self): return hash(("OWLDataSomeValuesFrom",self._filler, self._property)) @@ -636,6 +651,7 @@ class OWLDataAllValuesFrom(OWLQuantifiedDataRestriction): _property: OWLDataPropertyExpression # @TODO:CD:property shows the in-built function + def __init__(self, property: OWLDataPropertyExpression, filler: OWLDataRange): """Gets an OWLDataAllValuesFrom restriction. @@ -657,6 +673,7 @@ def __eq__(self, other): return self._filler == other._filler and self._property == other._property else: return False + def __hash__(self): return hash(("OWLDataAllValuesFrom",self._filler, self._property)) @@ -674,17 +691,20 @@ class OWLDataHasValue(OWLHasValueRestriction[OWLLiteral], OWLDataRestriction): __slots__ = '_property' # @TODO:CD:What is _v? even if it is inherited from somewhere, we should add it into docstring. # @TODO:CD: We should also name the class attributes in a more meaningful manner. + # AB re: _v is indeed inherited and represents the value of this DataHasValue CE. It's name is kept general + # because the value can have different types depending on the implementor of the base class. type_index: Final = 3014 _property: OWLDataPropertyExpression # @TODO: CD: property shows the in-built function + def __init__(self, property: OWLDataPropertyExpression, value: OWLLiteral): """Gets an OWLDataHasValue restriction. Args: property: The data property that the restriction acts along. - filler: The literal value. + value: The literal value. Returns: An OWLDataHasValue restriction along the specified property with the specified literal. @@ -700,8 +720,9 @@ def __eq__(self, other): return self._v == other._v and self._property == other._property else: return False + def __hash__(self): - return hash(("OWLDataHasValue",self._v, self._property)) + return hash(("OWLDataHasValue", self._v, self._property)) def as_some_values_from(self) -> OWLClassExpression: """A convenience method that obtains this restriction as an existential restriction with a nominal filler. @@ -730,6 +751,7 @@ def __init__(self, values: Union[OWLLiteral, Iterable[OWLLiteral]]): for _ in values: assert isinstance(_, OWLLiteral) self._values = tuple(values) + def __repr__(self): return f'OWLDataOneOf({self._values})' @@ -742,6 +764,7 @@ def __eq__(self, other): else: return False # TODO:CD: define it as @property as the name of the class method does not correspond to an action + def values(self) -> Iterable[OWLLiteral]: """Gets the values that are in the oneOf. @@ -770,7 +793,7 @@ class OWLDatatypeRestriction(OWLDataRange): _facet_restrictions: Sequence['OWLFacetRestriction'] def __init__(self, type_: OWLDatatype, facet_restrictions: Union['OWLFacetRestriction', - Iterable['OWLFacetRestriction']]): + Iterable['OWLFacetRestriction']]): self._type = type_ if isinstance(facet_restrictions, OWLFacetRestriction): facet_restrictions = facet_restrictions, @@ -788,6 +811,7 @@ def __eq__(self, other): and self._facet_restrictions == other._facet_restrictions else: return False + def __hash__(self): return hash(("OWLDatatypeRestriction", self._type, self._facet_restrictions)) @@ -825,7 +849,7 @@ def __eq__(self, other): return False def __hash__(self): - return hash(("OWLFacetRestriction",self._facet, self._literal)) + return hash(("OWLFacetRestriction", self._facet, self._literal)) def __repr__(self): return f'OWLFacetRestriction({self._facet}, {repr(self._literal)})' diff --git a/owlapy/owl_axiom.py b/owlapy/owl_axiom.py index eace5b2b..caa84eea 100644 --- a/owlapy/owl_axiom.py +++ b/owlapy/owl_axiom.py @@ -281,6 +281,9 @@ def __init__(self, class_expressions: List[OWLClassExpression], annotations: Optional[Iterable['OWLAnnotation']] = None): super().__init__(class_expressions=class_expressions, annotations=annotations) + def __iter__(self): + yield from self._class_expressions + def contains_named_equivalent_class(self) -> bool: return any(isinstance(ce, OWLClass) for ce in self._class_expressions) @@ -1259,6 +1262,14 @@ def __init__(self, property_: _P, range_: _R, annotations: Optional[Iterable[OWL self._range = range_ super().__init__(property_=property_, annotations=annotations) + @property + def prop(self): + return self._property + + @property + def range(self): + return self._range + def get_range(self) -> _R: return self._range @@ -1290,6 +1301,10 @@ def __init__(self, property_: OWLObjectPropertyExpression, domain: OWLClassExpre annotations: Optional[Iterable[OWLAnnotation]] = None): super().__init__(property_=property_, domain=domain, annotations=annotations) + @property + def prop(self): + return self._property + class OWLDataPropertyDomainAxiom(OWLPropertyDomainAxiom[OWLDataPropertyExpression]): """ A data property domain axiom DataPropertyDomain( DPE CE ) states that the domain of the diff --git a/owlapy/owl_ontology.py b/owlapy/owl_ontology.py index 6be14694..930360d0 100644 --- a/owlapy/owl_ontology.py +++ b/owlapy/owl_ontology.py @@ -996,7 +996,7 @@ def __init__(self, manager: _SM, path: Union[IRI, str], new: bool = False): raise NotImplementedError("Cant initialize a new ontology using path. Use IRI instead") else: # means we are loading an existing ontology self.owlapi_ontology = self.manager.get_owlapi_manager().loadOntologyFromOntologyDocument(File(file_path)) - self.mapper = OWLAPIMapper(self) + self.mapper = OWLAPIMapper() def __eq__(self, other): if isinstance(other, SyncOntology): @@ -1063,6 +1063,7 @@ def get_signature(self, include_imports_closure: bool = True): Entities in signature. """ # @TODO: CD: Is this class method redundant given that we have the individuals_in_signature ? + # AB re: This method does not return only individuals. return self.mapper.map_(self.owlapi_ontology.getSignature(self._get_imports_enum(include_imports_closure))) def get_abox_axioms(self, include_imports_closure: bool = True) -> Iterable[OWLAxiom]: diff --git a/owlapy/owlapi_mapper.py b/owlapy/owlapi_mapper.py index b72c940b..67ac16f8 100644 --- a/owlapy/owlapi_mapper.py +++ b/owlapy/owlapi_mapper.py @@ -2,8 +2,11 @@ from typing import Iterable, TypeVar import jpype.imports -from owlapy import owl_expression_to_manchester, manchester_to_owl_expression -from owlapy.class_expression import OWLClassExpression, OWLDataOneOf, OWLFacetRestriction, OWLDatatypeRestriction +from owlapy.class_expression import OWLDataOneOf, OWLFacetRestriction, OWLDatatypeRestriction, \ + OWLClass, OWLObjectComplementOf, OWLObjectUnionOf, OWLObjectIntersectionOf, \ + OWLObjectHasSelf, OWLObjectHasValue, OWLObjectSomeValuesFrom, OWLObjectAllValuesFrom, OWLObjectMinCardinality, \ + OWLObjectMaxCardinality, OWLObjectExactCardinality, OWLDataSomeValuesFrom, OWLDataAllValuesFrom, OWLDataHasValue, \ + OWLDataMinCardinality, OWLDataMaxCardinality, OWLDataExactCardinality, OWLObjectOneOf from owlapy.iri import IRI from owlapy.owl_axiom import OWLDeclarationAxiom, OWLAnnotation, OWLAnnotationProperty, OWLClassAssertionAxiom, \ OWLDataPropertyAssertionAxiom, OWLDataPropertyDomainAxiom, OWLDataPropertyRangeAxiom, OWLObjectPropertyDomainAxiom, \ @@ -30,26 +33,18 @@ startJVM() from org.semanticweb.owlapi.model import IRI as owlapi_IRI, OWLOntologyID as owlapi_OWLOntologyID from org.semanticweb.owlapi.vocab import OWLFacet as owlapi_OWLFacet -from org.semanticweb.owlapi.manchestersyntax.parser import ManchesterOWLSyntaxClassExpressionParser -from org.semanticweb.owlapi.manchestersyntax.renderer import ManchesterOWLSyntaxOWLObjectRendererImpl -from org.semanticweb.owlapi.util import BidirectionalShortFormProviderAdapter, SimpleShortFormProvider -from org.semanticweb.owlapi.expression import ShortFormEntityChecker -from java.util import HashSet, ArrayList, List, Set, LinkedHashSet, Optional +from java.util import ArrayList, List, Set, LinkedHashSet, Optional from java.util.stream import Stream -from uk.ac.manchester.cs.owl.owlapi import (OWLAnonymousClassExpressionImpl, OWLCardinalityRestrictionImpl, - OWLClassExpressionImpl, OWLClassImpl, OWLDataAllValuesFromImpl, - OWLDataCardinalityRestrictionImpl, OWLDataExactCardinalityImpl, - OWLDataHasValueImpl, OWLDataMaxCardinalityImpl, OWLDataUnionOfImpl, +from uk.ac.manchester.cs.owl.owlapi import (OWLClassImpl, OWLDataAllValuesFromImpl, OWL2DatatypeImpl, + OWLDataExactCardinalityImpl,OWLDataHasValueImpl, + OWLDataMaxCardinalityImpl, OWLDataUnionOfImpl, OWLDataMinCardinalityImpl, OWLDataSomeValuesFromImpl, - OWLNaryBooleanClassExpressionImpl, OWLObjectAllValuesFromImpl, - OWLObjectCardinalityRestrictionImpl, OWLObjectComplementOfImpl, + OWLObjectAllValuesFromImpl, OWLObjectComplementOfImpl, OWLObjectExactCardinalityImpl, OWLObjectHasSelfImpl, OWLObjectHasValueImpl, OWLObjectIntersectionOfImpl, OWLObjectMaxCardinalityImpl, OWLObjectMinCardinalityImpl, OWLObjectOneOfImpl, OWLObjectSomeValuesFromImpl, OWLNaryDataRangeImpl, - OWLObjectUnionOfImpl, OWLQuantifiedDataRestrictionImpl, - OWLQuantifiedObjectRestrictionImpl, OWLQuantifiedRestrictionImpl, - OWLValueRestrictionImpl, OWLLiteralImplBoolean, OWLLiteralImplString, + OWLObjectUnionOfImpl,OWLLiteralImplBoolean, OWLLiteralImplString, OWLLiteralImplDouble, OWLLiteralImplFloat, OWLLiteralImplInteger, OWLDisjointClassesAxiomImpl, OWLDeclarationAxiomImpl, OWLAnnotationImpl, OWLAnnotationPropertyImpl, OWLClassAssertionAxiomImpl, @@ -80,6 +75,9 @@ def init(the_class): + """Since classes names in owlapi and owlapy are pretty much similar with the small difference that in owlapi they + usually have the 'Impl' part then we can create the mapping class name dynamically reducing the amount of code + significantly. That's what this method does.""" cls_name = the_class.__class__.__name__ if "Impl" in cls_name: return globals().get(cls_name.split(".")[-1].replace("Impl", "")) @@ -91,29 +89,7 @@ def init(the_class): class OWLAPIMapper: - - def __init__(self, ontology: _SO): - self.manager = ontology.manager.get_owlapi_manager() - self.ontology = ontology.get_owlapi_ontology() - - # () Get the name space. (used for rendering class expressions) - self.namespace = self.ontology.getOntologyID().getOntologyIRI().orElse(None) - if self.namespace is not None: - self.namespace = str(self.namespace) - if self.namespace[-1] not in ["/", "#", ":"]: - self.namespace += "#" - else: - self.namespace = "http://www.anonymous.org/anonymous#" - - # () Create a manchester parser and a renderer using the given ontology. - ontology_set = HashSet() - ontology_set.add(self.ontology) - bidi_provider = BidirectionalShortFormProviderAdapter(self.manager, ontology_set, SimpleShortFormProvider()) - entity_checker = ShortFormEntityChecker(bidi_provider) - bidi_provider.add(self.manager.getOWLDataFactory().getOWLNothing()) - bidi_provider.add(self.manager.getOWLDataFactory().getOWLThing()) - self.parser = ManchesterOWLSyntaxClassExpressionParser(self.manager.getOWLDataFactory(), entity_checker) - self.renderer = ManchesterOWLSyntaxOWLObjectRendererImpl() + """A bridge between owlapy and owlapi owl-related classes.""" @singledispatchmethod def map_(self, e): @@ -140,6 +116,7 @@ def _(self, e: owlapi_IRI): @map_.register(OWLObjectProperty) @map_.register(OWLDatatype) @map_.register(OWLAnnotationProperty) + @map_.register(OWLClass) def _(self, e): return init(e)(self.map_(e.iri)) @@ -148,45 +125,65 @@ def _(self, e): @map_.register(OWLObjectPropertyImpl) @map_.register(OWLDatatypeImpl) @map_.register(OWLAnnotationPropertyImpl) + @map_.register(OWLClassImpl) def _(self, e): return init(e)(self.map_(e.getIRI())) + @map_.register(OWL2DatatypeImpl) + def _(self, e): + return OWLDatatype(self.map_(e.getIRI())) + @map_.register - def _(self, e: OWLClassExpression): - return self.parser.parse(owl_expression_to_manchester(e)) + def _(self, e: OWLObjectComplementOf): + return init(e)(self.map_(e.get_operand())) - @map_.register(OWLAnonymousClassExpressionImpl) - @map_.register(OWLCardinalityRestrictionImpl) - @map_.register(OWLClassExpressionImpl) - @map_.register(OWLClassImpl) - @map_.register(OWLDataAllValuesFromImpl) - @map_.register(OWLDataCardinalityRestrictionImpl) - @map_.register(OWLDataExactCardinalityImpl) - @map_.register(OWLDataHasValueImpl) - @map_.register(OWLDataMaxCardinalityImpl) - @map_.register(OWLDataMinCardinalityImpl) - @map_.register(OWLDataSomeValuesFromImpl) - @map_.register(OWLNaryBooleanClassExpressionImpl) - @map_.register(OWLObjectAllValuesFromImpl) - @map_.register(OWLObjectCardinalityRestrictionImpl) - @map_.register(OWLObjectComplementOfImpl) + @map_.register + def _(self, e: OWLObjectComplementOfImpl): + return init(e)(self.map_(e.getOperand())) + + @map_.register(OWLObjectMinCardinality) + @map_.register(OWLObjectMaxCardinality) + @map_.register(OWLObjectExactCardinality) + @map_.register(OWLDataMinCardinality) + @map_.register(OWLDataMaxCardinality) + @map_.register(OWLDataExactCardinality) + def _(self, e): + return init(e)(self.map_(e.get_property()), e.get_cardinality(), self.map_(e.get_filler())) + + @map_.register(OWLObjectMinCardinalityImpl) + @map_.register(OWLObjectMaxCardinalityImpl) @map_.register(OWLObjectExactCardinalityImpl) + @map_.register(OWLDataMinCardinalityImpl) + @map_.register(OWLDataMaxCardinalityImpl) + @map_.register(OWLDataExactCardinalityImpl) + def _(self, e): + return init(e)(e.getCardinality(), self.map_(e.getProperty()), self.map_(e.getFiller())) + + @map_.register(OWLObjectHasSelf) + def _(self, e): + return init(e)(self.map_(e.get_property())) + @map_.register(OWLObjectHasSelfImpl) + def _(self, e): + return init(e)(self.map_(e.getProperty())) + + @map_.register(OWLObjectHasValue) + @map_.register(OWLObjectSomeValuesFrom) + @map_.register(OWLObjectAllValuesFrom) + @map_.register(OWLDataSomeValuesFrom) + @map_.register(OWLDataAllValuesFrom) + @map_.register(OWLDataHasValue) + def _(self, e): + return init(e)(self.map_(e.get_property()), self.map_(e.get_filler())) + @map_.register(OWLObjectHasValueImpl) - @map_.register(OWLObjectIntersectionOfImpl) - @map_.register(OWLObjectMaxCardinalityImpl) - @map_.register(OWLObjectMinCardinalityImpl) - @map_.register(OWLObjectOneOfImpl) @map_.register(OWLObjectSomeValuesFromImpl) - @map_.register(OWLObjectUnionOfImpl) - @map_.register(OWLQuantifiedDataRestrictionImpl) - @map_.register(OWLQuantifiedObjectRestrictionImpl) - @map_.register(OWLQuantifiedRestrictionImpl) - @map_.register(OWLValueRestrictionImpl) + @map_.register(OWLObjectAllValuesFromImpl) + @map_.register(OWLDataSomeValuesFromImpl) + @map_.register(OWLDataAllValuesFromImpl) + @map_.register(OWLDataHasValueImpl) def _(self, e): - # Cant recognize the classes as implementation of org.semanticweb.owlapi.model.OWLClassExpression, so we - # have to register all possible implementations - return manchester_to_owl_expression(str(self.renderer.render(e)), self.namespace) + return init(e)(self.map_(e.getProperty()), self.map_(e.getFiller())) @map_.register def _(self, e: OWLLiteral): @@ -225,6 +222,8 @@ def _(self, e: OWLLiteralImplInteger): @map_.register(OWLDataOneOf) @map_.register(OWLDataUnionOf) @map_.register(OWLNaryDataRange) + @map_.register(OWLObjectIntersectionOf) + @map_.register(OWLObjectUnionOf) def _(self, e): return init(e)(self.map_(e.operands())) @@ -232,9 +231,16 @@ def _(self, e): @map_.register(OWLDataOneOfImpl) @map_.register(OWLDataUnionOfImpl) @map_.register(OWLNaryDataRangeImpl) + @map_.register(OWLObjectIntersectionOfImpl) + @map_.register(OWLObjectUnionOfImpl) + @map_.register(OWLObjectOneOfImpl) def _(self, e): return init(e)(self.map_(e.getOperandsAsList())) + @map_.register(OWLObjectOneOf) + def _(self, e): + return init(e)(self.map_(e.operands()).stream()) + @map_.register(OWLDataComplementOfImpl) def _(self, e): return OWLDataComplementOf(self.map_(e.getDataRange())) @@ -502,6 +508,7 @@ def _(self, e): @map_.register(list) @map_.register(set) + @map_.register(frozenset) def _(self, e): java_list = ArrayList() if e is not None and len(e) > 0: diff --git a/owlapy/render.py b/owlapy/render.py index 9856c5b7..f6e6543f 100644 --- a/owlapy/render.py +++ b/owlapy/render.py @@ -21,9 +21,12 @@ from .class_expression import OWLObjectHasValue, OWLFacetRestriction, OWLDatatypeRestriction, OWLObjectOneOf from .owl_datatype import OWLDatatype from .abstracts.abstract_owl_reasoner import AbstractOWLReasoner +from .owl_axiom import (OWLEquivalentClassesAxiom, OWLSubClassOfAxiom, + OWLObjectPropertyRangeAxiom, OWLObjectPropertyDomainAxiom) import requests import warnings import abc + _DL_SYNTAX = types.SimpleNamespace( SUBCLASS="⊑", EQUIVALENT_TO="≡", @@ -45,8 +48,7 @@ WEDGE="⋀", IMPLIES="←", COMMA=",", - SELF="Self", -) + SELF="Self") def _simple_short_form_provider(e: OWLEntity) -> str: @@ -350,8 +352,8 @@ def _render_nested(self, c: OWLClassExpression) -> str: COMMA=",", SELF="Self", VALUE="value", -) - + RANGE="Range", + DOMAIN="Domain") class ManchesterOWLSyntaxOWLObjectRenderer(OWLObjectRenderer): """Manchester Syntax renderer for OWL Objects""" @@ -376,7 +378,7 @@ def set_short_form_provider(self, short_form_provider: Callable[[OWLEntity], str @singledispatchmethod def render(self, o: OWLObject) -> str: assert isinstance(o, OWLObject), f"Tried to render non-OWLObject {o} of {type(o)}" - raise NotImplementedError + raise NotImplementedError(f"We cannot render {o}\t{type(o)}") @render.register def _(self, o: OWLClass) -> str: @@ -515,6 +517,29 @@ def _(self, t: OWLDatatype): def _(self, t: OWLLiteral) -> str: return t.get_literal() + @render.register + def _(self, equiv: OWLEquivalentClassesAxiom) -> str: + # TODO:CD:Can we assume that the size of equiv will be 2 ? + return (" %s " % _MAN_SYNTAX.EQUIVALENT_TO).join([self.render(i) for i in equiv]) + + @render.register + def _(self, equiv: OWLSubClassOfAxiom) -> str: + return (" %s " % _MAN_SYNTAX.SUBCLASS).join([self.render(i) for i in [equiv.sub_class,equiv.super_class]]) + + @render.register + def _(self, axiom: OWLObjectPropertyRangeAxiom) -> str: + # objectPropertyFrame ObjectProperty: IRI Range: annotations description ObjectPropertyRange(T(annotations) IRI T(description)) + return f"{self.render(axiom.prop)} {_MAN_SYNTAX.RANGE} {self.render(axiom.range)}" + @render.register + def _(self, axiom: OWLObjectPropertyDomainAxiom) -> str: + # objectPropertyFrame ObjectProperty: IRI Domain: annotations description ObjectPropertyDomain(T(annotations) IRI T(description)) + return f"{self.render(axiom.prop)} {_MAN_SYNTAX.DOMAIN} {self.render(axiom.get_domain())}" + + @render.register + def _(self, axiom: OWLObjectPropertyDomainAxiom) -> str: + # objectPropertyFrame ObjectProperty: IRI Domain: annotations description ObjectPropertyDomain(T(annotations) IRI T(description)) + return f"{self.render(axiom.prop)} {_MAN_SYNTAX.DOMAIN} {self.render(axiom.get_domain())}" + def _render_operands(self, c: OWLNaryBooleanClassExpression) -> List[str]: return [self._render_nested(_) for _ in c.operands()] diff --git a/owlapy/static_funcs.py b/owlapy/static_funcs.py index 228aa050..39d5a1f7 100644 --- a/owlapy/static_funcs.py +++ b/owlapy/static_funcs.py @@ -57,3 +57,12 @@ def stopJVM() -> None: if jpype.isJVMStarted(): jpype.detachThreadFromJVM() jpype.shutdownJVM() + +def create_ontology(iri,with_owlapi=False): + """ A convenient function""" + if with_owlapi: + from .owl_ontology_manager import SyncOntologyManager + return SyncOntologyManager().create_ontology(iri) + else: + from .owl_ontology_manager import OntologyManager + return OntologyManager().create_ontology(iri) \ No newline at end of file diff --git a/tests/test_owlapi_adaptor.py b/tests/test_owlapi_adaptor.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/test_owlapi_mapper.py b/tests/test_owlapi_mapper.py new file mode 100644 index 00000000..9a9796ba --- /dev/null +++ b/tests/test_owlapi_mapper.py @@ -0,0 +1,159 @@ +import unittest + +from jpype import JDouble + +from owlapy.class_expression import OWLClass, OWLDataSomeValuesFrom, OWLObjectIntersectionOf, OWLObjectComplementOf, \ + OWLObjectMinCardinality, OWLObjectMaxCardinality, OWLObjectExactCardinality, OWLDataMinCardinality, \ + OWLDataMaxCardinality, OWLDataExactCardinality, OWLObjectHasSelf, OWLObjectHasValue, OWLObjectSomeValuesFrom, \ + OWLObjectAllValuesFrom, OWLDataAllValuesFrom, OWLDataHasValue, OWLObjectUnionOf, OWLObjectOneOf, \ + OWLFacetRestriction, OWLDatatypeRestriction, OWLDataOneOf +from owlapy.iri import IRI +from owlapy.owl_axiom import OWLAnnotationProperty, OWLAnnotation, OWLAnnotationAssertionAxiom +from owlapy.owl_data_ranges import OWLDataIntersectionOf, OWLDataUnionOf, OWLDataComplementOf +from owlapy.owl_datatype import OWLDatatype +from owlapy.owl_individual import OWLNamedIndividual +from owlapy.owl_literal import OWLLiteral, IntegerOWLDatatype, BooleanOWLDatatype, DoubleOWLDatatype +from owlapy.owl_property import OWLDataProperty, OWLObjectProperty +from owlapy.owlapi_mapper import OWLAPIMapper +from owlapy.providers import owl_datatype_min_inclusive_restriction +from owlapy.vocab import OWLFacet + + +class TestOWLAPIMapper(unittest.TestCase): + + mapper = OWLAPIMapper() + test_ns = "http://test_namespace#" + i = OWLNamedIndividual(test_ns + "test_i") + c = OWLClass(test_ns + "test_c") + dp = OWLDataProperty(test_ns + "test_dp") + op = OWLObjectProperty(test_ns + "test_op") + dt = OWLDatatype(IRI.create(test_ns + "test_dt")) + ap = OWLAnnotationProperty(IRI.create(test_ns + "test_ap")) + + def test_random_complex_ce_mapping(self): + + # construct the class expression in owlapy + ns = "http://dl-learner.org/mutagenesis#" + nitrogen38 = OWLClass(IRI.create(ns, "Nitrogen-38")) + charge = OWLDataProperty(IRI.create(ns, "charge")) + has_charge_more_than_0_85 = OWLDataSomeValuesFrom(charge, owl_datatype_min_inclusive_restriction(0.85)) + ce = OWLObjectIntersectionOf([nitrogen38, has_charge_more_than_0_85]) + + # construct the class expression in owlapi + from org.semanticweb.owlapi.model import IRI as IRIowlapi + from org.semanticweb.owlapi.vocab import OWLFacet + from uk.ac.manchester.cs.owl.owlapi import OWLDataFactoryImpl + + nitrogenIRI = IRIowlapi.create(ns + "Nitrogen-38") + charge_iri = IRIowlapi.create(ns + "charge") + data_factory = OWLDataFactoryImpl() + nitrogen_class = data_factory.getOWLClass(nitrogenIRI) + + charge_property = data_factory.getOWLDataProperty(charge_iri) + double_datatype = data_factory.getDoubleOWLDatatype() + facet_restriction = data_factory.getOWLFacetRestriction(OWLFacet.MIN_INCLUSIVE, JDouble(0.85)) + datatype_restriction = data_factory.getOWLDatatypeRestriction(double_datatype, facet_restriction) + some_values_from = data_factory.getOWLDataSomeValuesFrom(charge_property, datatype_restriction) + + class_expression = data_factory.getOWLObjectIntersectionOf(nitrogen_class, some_values_from) + + # compare them with the mapped expression + ce_converted = self.mapper.map_(ce) + self.assertEqual(class_expression, ce_converted) + + # map back to owlapy and check for equality + ce_1 = self.mapper.map_(class_expression) + ce_2 = self.mapper.map_(ce_converted) + + self.assertEqual(ce_1, ce_2) + self.assertEqual(ce_1, ce) + self.assertEqual(ce_2, ce) + + def test_entity_mapping(self): + + iri = IRI.create(self.test_ns + "test") + + self.assertEqual(iri, self.mapper.map_(self.mapper.map_(iri))) + self.assertEqual(self.i, self.mapper.map_(self.mapper.map_(self.i))) + self.assertEqual(self.c, self.mapper.map_(self.mapper.map_(self.c))) + self.assertEqual(self.dp, self.mapper.map_(self.mapper.map_(self.dp))) + self.assertEqual(self.op, self.mapper.map_(self.mapper.map_(self.op))) + self.assertEqual(self.dt, self.mapper.map_(self.mapper.map_(self.dt))) + self.assertEqual(self.ap, self.mapper.map_(self.mapper.map_(self.ap))) + + def test_ce_mapping(self): + + lit = OWLLiteral(2) + + oco = OWLObjectComplementOf(self.c) + ominc = OWLObjectMinCardinality(1, self.op, self.c) + oec = OWLObjectExactCardinality(2, self.op, self.c) + omaxc = OWLObjectMaxCardinality(3, self.op, self.c) + dminc = OWLDataMinCardinality(1, self.dp, IntegerOWLDatatype) + dec = OWLDataExactCardinality(2, self.dp, IntegerOWLDatatype) + dmaxc = OWLDataMaxCardinality(3, self.dp, IntegerOWLDatatype) + ohs = OWLObjectHasSelf(self.op) + ohv = OWLObjectHasValue(self.op, self.i) + osv = OWLObjectSomeValuesFrom(self.op, self.c) + oav = OWLObjectAllValuesFrom(self.op, self.c) + dsv = OWLDataSomeValuesFrom(self.dp, IntegerOWLDatatype) + dav = OWLDataAllValuesFrom(self.dp, IntegerOWLDatatype) + dhv = OWLDataHasValue(self.dp, lit) + ooo = OWLObjectOneOf(self.i) + ooo2 = OWLObjectOneOf([self.i]) + oio = OWLObjectIntersectionOf([ohs, ohv]) + ouo = OWLObjectUnionOf([oco, ominc]) + + self.assertEqual(lit, self.mapper.map_(self.mapper.map_(lit))) + self.assertEqual(oco, self.mapper.map_(self.mapper.map_(oco))) + self.assertEqual(ominc, self.mapper.map_(self.mapper.map_(ominc))) + self.assertEqual(oec, self.mapper.map_(self.mapper.map_(oec))) + self.assertEqual(omaxc, self.mapper.map_(self.mapper.map_(omaxc))) + self.assertEqual(dminc, self.mapper.map_(self.mapper.map_(dminc))) + self.assertEqual(dec, self.mapper.map_(self.mapper.map_(dec))) + self.assertEqual(dmaxc, self.mapper.map_(self.mapper.map_(dmaxc))) + self.assertEqual(ohv, self.mapper.map_(self.mapper.map_(ohv))) + self.assertEqual(osv, self.mapper.map_(self.mapper.map_(osv))) + self.assertEqual(oav, self.mapper.map_(self.mapper.map_(oav))) + self.assertEqual(dsv, self.mapper.map_(self.mapper.map_(dsv))) + self.assertEqual(dav, self.mapper.map_(self.mapper.map_(dav))) + self.assertEqual(dhv, self.mapper.map_(self.mapper.map_(dhv))) + self.assertEqual(ooo2, self.mapper.map_(self.mapper.map_(ooo))) + self.assertEqual(ooo2, self.mapper.map_(self.mapper.map_(ooo2))) + self.assertEqual(oio, self.mapper.map_(self.mapper.map_(oio))) + self.assertEqual(ouo, self.mapper.map_(self.mapper.map_(ouo))) + + def test_datarange_mapping(self): + + lit = OWLLiteral(0.1) + bdt = BooleanOWLDatatype + f = OWLFacet.MAX_EXCLUSIVE + fr = OWLFacetRestriction(f, lit) + dtr = OWLDatatypeRestriction(DoubleOWLDatatype, fr) + dio = OWLDataIntersectionOf([dtr, bdt]) + doo = OWLDataOneOf([lit, OWLLiteral(True)]) + duo = OWLDataUnionOf([dtr, DoubleOWLDatatype, IntegerOWLDatatype]) + dco = OWLDataComplementOf(dtr) + + self.assertEqual(lit, self.mapper.map_(self.mapper.map_(lit))) + self.assertEqual(bdt, self.mapper.map_(self.mapper.map_(bdt))) + self.assertEqual(f, self.mapper.map_(self.mapper.map_(f))) + self.assertEqual(fr, self.mapper.map_(self.mapper.map_(fr))) + self.assertEqual(dtr, self.mapper.map_(self.mapper.map_(dtr))) + self.assertCountEqual(list(dio.operands()), list(self.mapper.map_(self.mapper.map_(dio)).operands())) + self.assertCountEqual(list(doo.operands()), list(self.mapper.map_(self.mapper.map_(doo)).operands())) + self.assertCountEqual(list(duo.operands()), list(self.mapper.map_(self.mapper.map_(duo)).operands())) + self.assertEqual(dco, self.mapper.map_(self.mapper.map_(dco))) + + def test_axiom_mapping(self): + + ap = OWLAnnotationProperty(IRI.create(self.test_ns + "test_annotation")) + av = OWLLiteral("Value of annotation") + a = OWLAnnotation(ap, av) + aa = OWLAnnotationAssertionAxiom(IRI.create(self.test_ns + "test_annotation_subject"), a) + + + self.assertEqual(ap, self.mapper.map_(self.mapper.map_(ap))) + self.assertEqual(av, self.mapper.map_(self.mapper.map_(av))) + self.assertEqual(a, self.mapper.map_(self.mapper.map_(a))) + self.assertEqual(aa, self.mapper.map_(self.mapper.map_(aa))) diff --git a/tests/test_sync_reasoner.py b/tests/test_sync_reasoner.py index 9b157457..d3ebccbf 100644 --- a/tests/test_sync_reasoner.py +++ b/tests/test_sync_reasoner.py @@ -1,7 +1,6 @@ import os import unittest -from jpype import JDouble from owlapy.class_expression import OWLClass, OWLDataSomeValuesFrom, OWLObjectIntersectionOf, OWLNothing, OWLThing, \ OWLClassExpression, OWLObjectSomeValuesFrom, OWLObjectOneOf from owlapy.iri import IRI @@ -166,39 +165,6 @@ def test_instances_retrieval(self): self.assertIn(instance, expected) self.assertEqual(len(list(instances)), len(expected)) - def test_conversion(self): - # construct the class expression in owlapi - from org.semanticweb.owlapi.model import IRI as IRIowlapi - from org.semanticweb.owlapi.vocab import OWLFacet - - nitrogenIRI = IRIowlapi.create(self.ns + "Nitrogen-38") - charge_iri = IRIowlapi.create(self.ns + "charge") - - data_factory = self.reasoner.manager.get_owlapi_manager().getOWLDataFactory() - nitrogen_class = data_factory.getOWLClass(nitrogenIRI) - - charge_property = data_factory.getOWLDataProperty(charge_iri) - double_datatype = data_factory.getDoubleOWLDatatype() - facet_restriction = data_factory.getOWLFacetRestriction(OWLFacet.MIN_INCLUSIVE, JDouble(0.85)) - datatype_restriction = data_factory.getOWLDatatypeRestriction(double_datatype, facet_restriction) - some_values_from = data_factory.getOWLDataSomeValuesFrom(charge_property, datatype_restriction) - - class_expression = data_factory.getOWLObjectIntersectionOf(nitrogen_class, some_values_from) - - # compare them with the converted expression - ce_converted = self.reasoner.mapper.map_(self.ce) - print(ce_converted) - print(class_expression) - self.assertEqual(class_expression, ce_converted) - - # convert back to owlapy and check for equality - ce_1 = self.reasoner.mapper.map_(class_expression) - ce_2 = self.reasoner.mapper.map_(ce_converted) - - self.assertEqual(ce_1, ce_2) - self.assertEqual(ce_1, self.ce) - self.assertEqual(ce_2, self.ce) - def test_equivalent_classes(self): self.assertCountEqual(list(reasoner2.equivalent_classes(N)), [N, Q])