From 208c92536c6e54133ca07174f18c1a5290e6d8cb Mon Sep 17 00:00:00 2001 From: Luke Friedrichs Date: Thu, 7 Nov 2024 22:23:54 +0100 Subject: [PATCH 01/23] changing _values to frozenset to allow for hashing --- owlapy/class_expression/restriction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/owlapy/class_expression/restriction.py b/owlapy/class_expression/restriction.py index 911a718a..cce981a1 100644 --- a/owlapy/class_expression/restriction.py +++ b/owlapy/class_expression/restriction.py @@ -403,7 +403,7 @@ def __init__(self, values: OWLIndividual | Iterable[OWLIndividual]): else: for _ in values: assert isinstance(_, OWLIndividual) - self._values = {i for i in values} + self._values = frozenset(values) def individuals(self) -> Iterable[OWLIndividual]: """Gets the individuals that are in the oneOf. These individuals represent the exact instances (extension) From 13e2ca3ffa85ac8433ba18b921c5c8dd00c9268d Mon Sep 17 00:00:00 2001 From: Luke Friedrichs Date: Fri, 8 Nov 2024 09:46:23 +0100 Subject: [PATCH 02/23] removed slots --- owlapy/owl_reasoner.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/owlapy/owl_reasoner.py b/owlapy/owl_reasoner.py index 211460d9..fc9cae95 100644 --- a/owlapy/owl_reasoner.py +++ b/owlapy/owl_reasoner.py @@ -36,15 +36,6 @@ class StructuralReasoner(AbstractOWLReasoner): """Tries to check instances fast (but maybe incomplete).""" - __slots__ = '_ontology', '_world', \ - '_ind_set', '_cls_to_ind', \ - '_has_prop', 'class_cache', \ - '_objectsomevalues_cache', '_datasomevalues_cache', '_objectcardinality_cache', \ - '_property_cache', \ - '_obj_prop', '_obj_prop_inv', '_data_prop', \ - '_negation_default', '_sub_properties', \ - '__warned' - _ontology: Ontology _world: owlready2.World _cls_to_ind: Dict[OWLClass, FrozenSet[OWLNamedIndividual]] # Class => individuals From 60e113ff47e666c6dd38b27e79ad9771bfed8208 Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Fri, 8 Nov 2024 09:58:38 +0100 Subject: [PATCH 03/23] save_owl_class_expressions and test included --- owlapy/util_owl_static_funcs.py | 46 ++++++++++++++++++++++++++++++ tests/test_save_owl_expressions.py | 25 ++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 owlapy/util_owl_static_funcs.py create mode 100644 tests/test_save_owl_expressions.py diff --git a/owlapy/util_owl_static_funcs.py b/owlapy/util_owl_static_funcs.py new file mode 100644 index 00000000..3ed34a92 --- /dev/null +++ b/owlapy/util_owl_static_funcs.py @@ -0,0 +1,46 @@ +from .owl_ontology import Ontology +from .owl_ontology_manager import OntologyManager +from typing import List +from .class_expression import OWLClassExpression, OWLClass +from .iri import IRI +from .owl_axiom import OWLEquivalentClassesAxiom + +def save_owl_class_expressions(expressions: OWLClassExpression | List[OWLClassExpression], + path: str = 'predictions', + rdf_format: str = 'rdfxml', + namespace:str=None) -> None: + """ + + """ + + assert isinstance(expressions, OWLClassExpression) or isinstance(expressions[0], + OWLClassExpression), "expressions must be either OWLClassExpression or a list of OWLClassExpression" + assert rdf_format == 'rdfxml', f'Format {rdf_format} not implemented. Please use rdfxml' + + if isinstance(expressions, OWLClassExpression): + expressions = [expressions] + + namespace= 'https://dice-research.org/predictions#' if namespace is None else namespace + assert "#" == namespace[-1], "namespace must end with #" + # () + manager = OntologyManager() + # () + ontology:Ontology = manager.create_ontology(namespace) + # () Iterate over concepts + for th, i in enumerate(expressions): + cls_a = OWLClass(IRI.create(namespace, str(th))) + equivalent_classes_axiom = OWLEquivalentClassesAxiom([cls_a, i]) + try: + ontology.add_axiom(equivalent_classes_axiom) + except AttributeError: + print(traceback.format_exc()) + print("Exception at creating OWLEquivalentClassesAxiom") + print(equivalent_classes_axiom) + print(cls_a) + print(i) + print(expressions) + exit(1) + print(ontology) + ontology.save(path=path, inplace=False, rdf_format=rdf_format) + + # ontology.save(IRI.create(path)) diff --git a/tests/test_save_owl_expressions.py b/tests/test_save_owl_expressions.py new file mode 100644 index 00000000..3beda87b --- /dev/null +++ b/tests/test_save_owl_expressions.py @@ -0,0 +1,25 @@ +from owlapy.util_owl_static_funcs import save_owl_class_expressions +from owlapy.class_expression import OWLClass, OWLObjectIntersectionOf, OWLObjectSomeValuesFrom +from owlapy.owl_property import OWLObjectProperty +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 +import rdflib + +class TestRunningExamples: + def test_readme(self): + # Using owl classes to create a complex class expression + male = OWLClass("http://example.com/society#male") + hasChild = OWLObjectProperty("http://example.com/society#hasChild") + hasChild_male = OWLObjectSomeValuesFrom(hasChild, male) + teacher = OWLClass("http://example.com/society#teacher") + teacher_that_hasChild_male = OWLObjectIntersectionOf([hasChild_male, teacher]) + + expressions= [male, teacher_that_hasChild_male] + save_owl_class_expressions(expressions=expressions, + namespace="https://ontolearn.org/predictions#", + path="owl_class_expressions.owl", + rdf_format= 'rdfxml') + g=rdflib.Graph().parse("owl_class_expressions.owl") + assert len(g)==22 \ No newline at end of file From 3f6d2881f0f860b5114ade2ed8bc85d2182c8779 Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Fri, 8 Nov 2024 10:00:54 +0100 Subject: [PATCH 04/23] docstring added --- owlapy/util_owl_static_funcs.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/owlapy/util_owl_static_funcs.py b/owlapy/util_owl_static_funcs.py index 3ed34a92..e8012707 100644 --- a/owlapy/util_owl_static_funcs.py +++ b/owlapy/util_owl_static_funcs.py @@ -10,9 +10,32 @@ def save_owl_class_expressions(expressions: OWLClassExpression | List[OWLClassEx rdf_format: str = 'rdfxml', namespace:str=None) -> None: """ + Saves a set of OWL class expressions to an ontology file in RDF/XML format. - """ + This function takes one or more OWL class expressions, creates an ontology, + and saves the expressions as OWL equivalent class axioms in the specified RDF format. + By default, it saves the file to the specified path using the 'rdfxml' format. + + Args: + expressions (OWLClassExpression | List[OWLClassExpression]): A single or a list of OWL class expressions + to be saved as equivalent class axioms. + path (str, optional): The file path where the ontology will be saved. Defaults to 'predictions'. + rdf_format (str, optional): RDF serialization format for saving the ontology. Currently only + supports 'rdfxml'. Defaults to 'rdfxml'. + namespace (str, optional): The namespace URI used for the ontology. If None, defaults to + 'https://dice-research.org/predictions#'. Must end with '#'. + + Raises: + AssertionError: If `expressions` is neither an OWLClassExpression nor a list of OWLClassExpression. + AssertionError: If `rdf_format` is not 'rdfxml'. + AssertionError: If `namespace` does not end with a '#'. + Example: + >>> from some_module import OWLClassExpression + >>> expr1 = OWLClassExpression("SomeExpression1") + >>> expr2 = OWLClassExpression("SomeExpression2") + >>> save_owl_class_expressions([expr1, expr2], path="my_ontology.owl", rdf_format="rdfxml") + """ assert isinstance(expressions, OWLClassExpression) or isinstance(expressions[0], OWLClassExpression), "expressions must be either OWLClassExpression or a list of OWLClassExpression" assert rdf_format == 'rdfxml', f'Format {rdf_format} not implemented. Please use rdfxml' @@ -40,7 +63,4 @@ def save_owl_class_expressions(expressions: OWLClassExpression | List[OWLClassEx print(i) print(expressions) exit(1) - print(ontology) - ontology.save(path=path, inplace=False, rdf_format=rdf_format) - - # ontology.save(IRI.create(path)) + ontology.save(path=path, inplace=False, rdf_format=rdf_format) \ No newline at end of file From c901ed0ab43ee899026e43ddfc603b728e21f04f Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Fri, 8 Nov 2024 10:06:28 +0100 Subject: [PATCH 05/23] Ruff is being used with explicit command --- .github/workflows/ruff.yml | 8 -------- .github/workflows/test.yml | 4 ++++ 2 files changed, 4 insertions(+), 8 deletions(-) delete mode 100644 .github/workflows/ruff.yml diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml deleted file mode 100644 index b6690e05..00000000 --- a/.github/workflows/ruff.yml +++ /dev/null @@ -1,8 +0,0 @@ -name: Ruff -on: [push, pull_request] -jobs: - ruff: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: chartboost/ruff-action@v1 \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6bee0285..e08a82a1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,6 +19,10 @@ jobs: python -m pip install --upgrade pip pip install -e . + - name: Lint with ruff + run: | + ruff check owlapy --line-length=200 + - name: Test with pytest run: | wget https://files.dice-research.org/projects/Ontolearn/KGs.zip From 7eaf5be60cbcd780793556c882a4f127e6fe4d5a Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Fri, 8 Nov 2024 10:08:58 +0100 Subject: [PATCH 06/23] Ruff Linter error fixes --- .github/workflows/ruff.yml | 8 -------- .github/workflows/test.yml | 4 +++- owlapy/util_owl_static_funcs.py | 11 +---------- setup.py | 1 + 4 files changed, 5 insertions(+), 19 deletions(-) delete mode 100644 .github/workflows/ruff.yml diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml deleted file mode 100644 index b6690e05..00000000 --- a/.github/workflows/ruff.yml +++ /dev/null @@ -1,8 +0,0 @@ -name: Ruff -on: [push, pull_request] -jobs: - ruff: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: chartboost/ruff-action@v1 \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6bee0285..a0e107e9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,9 @@ jobs: run: | python -m pip install --upgrade pip pip install -e . - + - name: Lint with ruff + run: | + ruff check owlapy --line-length=200 - name: Test with pytest run: | wget https://files.dice-research.org/projects/Ontolearn/KGs.zip diff --git a/owlapy/util_owl_static_funcs.py b/owlapy/util_owl_static_funcs.py index e8012707..20ee01e5 100644 --- a/owlapy/util_owl_static_funcs.py +++ b/owlapy/util_owl_static_funcs.py @@ -53,14 +53,5 @@ def save_owl_class_expressions(expressions: OWLClassExpression | List[OWLClassEx for th, i in enumerate(expressions): cls_a = OWLClass(IRI.create(namespace, str(th))) equivalent_classes_axiom = OWLEquivalentClassesAxiom([cls_a, i]) - try: - ontology.add_axiom(equivalent_classes_axiom) - except AttributeError: - print(traceback.format_exc()) - print("Exception at creating OWLEquivalentClassesAxiom") - print(equivalent_classes_axiom) - print(cls_a) - print(i) - print(expressions) - exit(1) + ontology.add_axiom(equivalent_classes_axiom) ontology.save(path=path, inplace=False, rdf_format=rdf_format) \ No newline at end of file diff --git a/setup.py b/setup.py index bf8d39f5..71d8ca61 100644 --- a/setup.py +++ b/setup.py @@ -13,6 +13,7 @@ "pandas>=1.5.0", "requests>=2.32.3", "rdflib>=6.0.2", + "ruff>=0.7.2", "parsimonious>=0.8.1", "pytest>=8.1.1", "sortedcontainers>=2.4.0", From fb67bf358fed6a925a54d9465abf98d243c03677 Mon Sep 17 00:00:00 2001 From: Luke Friedrichs Date: Fri, 8 Nov 2024 10:10:21 +0100 Subject: [PATCH 07/23] lazy compute all individuals + remove LRU cache --- owlapy/owl_reasoner.py | 40 ++++++++++------------------------------ 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/owlapy/owl_reasoner.py b/owlapy/owl_reasoner.py index fc9cae95..7654844d 100644 --- a/owlapy/owl_reasoner.py +++ b/owlapy/owl_reasoner.py @@ -40,13 +40,9 @@ class StructuralReasoner(AbstractOWLReasoner): _world: owlready2.World _cls_to_ind: Dict[OWLClass, FrozenSet[OWLNamedIndividual]] # Class => individuals _has_prop: Mapping[Type[_P], LRUCache[_P, FrozenSet[OWLNamedIndividual]]] # Type => Property => individuals - _ind_set: FrozenSet[OWLNamedIndividual] # ObjectSomeValuesFrom => individuals - _objectsomevalues_cache: LRUCache[OWLClassExpression, FrozenSet[OWLNamedIndividual]] # DataSomeValuesFrom => individuals - _datasomevalues_cache: LRUCache[OWLClassExpression, FrozenSet[OWLNamedIndividual]] # ObjectCardinalityRestriction => individuals - _objectcardinality_cache: LRUCache[OWLClassExpression, FrozenSet[OWLNamedIndividual]] # ObjectProperty => { individual => individuals } _obj_prop: Dict[OWLObjectProperty, Mapping[OWLNamedIndividual, Set[OWLNamedIndividual]]] # ObjectProperty => { individual => individuals } @@ -81,12 +77,6 @@ def __init__(self, ontology: AbstractOWLOntology, *, class_cache: bool = True, self._init() def _init(self, cache_size=128): - - individuals = self._ontology.individuals_in_signature() - self._ind_set = frozenset(individuals) - self._objectsomevalues_cache = LRUCache(maxsize=cache_size) - self._datasomevalues_cache = LRUCache(maxsize=cache_size) - self._objectcardinality_cache = LRUCache(maxsize=cache_size) if self.class_cache: self._cls_to_ind = dict() @@ -96,11 +86,12 @@ def _init(self, cache_size=128): self._data_prop = dict() else: self._has_prop = MappingProxyType({ - OWLDataProperty: LRUCache(maxsize=cache_size), - OWLObjectProperty: LRUCache(maxsize=cache_size), - OWLObjectInverseOf: LRUCache(maxsize=cache_size), + OWLDataProperty: dict(), + OWLObjectProperty: dict(), + OWLObjectInverseOf: dict(), }) + def reset(self): """The reset method shall reset any cached state.""" self._init() @@ -627,7 +618,8 @@ def _lazy_cache_obj_prop(self, pe: OWLObjectPropertyExpression) -> None: opc[s] = set() opc[s] |= {o} else: - for s in self._ind_set: + all_ = frozenset(self._ontology.individuals_in_signature()) + for s in all_: individuals = set(self.object_property_values(s, pe, not self._sub_properties)) if individuals: opc[s] = individuals @@ -667,8 +659,8 @@ def _some_values_subject_index(self, pe: OWLPropertyExpression) -> FrozenSet[OWL func = self.data_property_values else: func = self.object_property_values - - for s in self._ind_set: + all_ = frozenset(self._ontology.individuals_in_signature()) + for s in all_: try: next(iter(func(s, pe, not self._sub_properties))) subs |= {s} @@ -773,9 +765,6 @@ def _(self, ce: OWLObjectIntersectionOf) -> FrozenSet[OWLNamedIndividual]: @_find_instances.register def _(self, ce: OWLObjectSomeValuesFrom) -> FrozenSet[OWLNamedIndividual]: - if ce in self._objectsomevalues_cache: - return self._objectsomevalues_cache[ce] - p = ce.get_property() assert isinstance(p, OWLObjectPropertyExpression) if not self._property_cache and ce.get_filler().is_owl_thing(): @@ -785,13 +774,12 @@ def _(self, ce: OWLObjectSomeValuesFrom) -> FrozenSet[OWLNamedIndividual]: ind = self._find_some_values(p, filler_ind) - self._objectsomevalues_cache[ce] = ind return ind @_find_instances.register def _(self, ce: OWLObjectComplementOf) -> FrozenSet[OWLNamedIndividual]: if self._negation_default: - all_ = self._ind_set + all_ = frozenset(self._ontology.individuals_in_signature()) complement_ind = self._find_instances(ce.get_operand()) return all_ ^ complement_ind else: @@ -827,7 +815,7 @@ def _(self, ce: OWLObjectMinCardinality) -> FrozenSet[OWLNamedIndividual]: @_find_instances.register def _(self, ce: OWLObjectMaxCardinality) -> FrozenSet[OWLNamedIndividual]: - all_ = self._ind_set + all_ = frozenset(self._ontology.individuals_in_signature()) min_ind = self._find_instances(OWLObjectMinCardinality(cardinality=ce.get_cardinality() + 1, property=ce.get_property(), filler=ce.get_filler())) @@ -838,9 +826,6 @@ def _(self, ce: OWLObjectExactCardinality) -> FrozenSet[OWLNamedIndividual]: return self._get_instances_object_card_restriction(ce) def _get_instances_object_card_restriction(self, ce: OWLObjectCardinalityRestriction): - if ce in self._objectcardinality_cache: - return self._objectcardinality_cache[ce] - p = ce.get_property() assert isinstance(p, OWLObjectPropertyExpression) @@ -862,14 +847,10 @@ def _get_instances_object_card_restriction(self, ce: OWLObjectCardinalityRestric ind = self._find_some_values(p, filler_ind, min_count=min_count, max_count=max_count) - self._objectcardinality_cache[ce] = ind return ind @_find_instances.register def _(self, ce: OWLDataSomeValuesFrom) -> FrozenSet[OWLNamedIndividual]: - if ce in self._datasomevalues_cache: - return self._datasomevalues_cache[ce] - pe = ce.get_property() filler = ce.get_filler() assert isinstance(pe, OWLDataProperty) @@ -958,7 +939,6 @@ def include(lv: OWLLiteral): raise ValueError r = frozenset(ind) - self._datasomevalues_cache[ce] = r return r @_find_instances.register From a4ee29930e89794067342a6a4cd256dfe33ec329 Mon Sep 17 00:00:00 2001 From: Luke Friedrichs Date: Fri, 8 Nov 2024 10:21:08 +0100 Subject: [PATCH 08/23] move type annotations into init --- owlapy/owl_reasoner.py | 59 ++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 37 deletions(-) diff --git a/owlapy/owl_reasoner.py b/owlapy/owl_reasoner.py index 7654844d..6333f2e1 100644 --- a/owlapy/owl_reasoner.py +++ b/owlapy/owl_reasoner.py @@ -36,24 +36,6 @@ class StructuralReasoner(AbstractOWLReasoner): """Tries to check instances fast (but maybe incomplete).""" - _ontology: Ontology - _world: owlready2.World - _cls_to_ind: Dict[OWLClass, FrozenSet[OWLNamedIndividual]] # Class => individuals - _has_prop: Mapping[Type[_P], LRUCache[_P, FrozenSet[OWLNamedIndividual]]] # Type => Property => individuals - # ObjectSomeValuesFrom => individuals - # DataSomeValuesFrom => individuals - # ObjectCardinalityRestriction => individuals - # ObjectProperty => { individual => individuals } - _obj_prop: Dict[OWLObjectProperty, Mapping[OWLNamedIndividual, Set[OWLNamedIndividual]]] - # ObjectProperty => { individual => individuals } - _obj_prop_inv: Dict[OWLObjectProperty, Mapping[OWLNamedIndividual, Set[OWLNamedIndividual]]] - # DataProperty => { individual => literals } - _data_prop: Dict[OWLDataProperty, Mapping[OWLNamedIndividual, Set[OWLLiteral]]] - class_cache: bool - _property_cache: bool - _negation_default: bool - _sub_properties: bool - def __init__(self, ontology: AbstractOWLOntology, *, class_cache: bool = True, property_cache: bool = True, negation_default: bool = True, sub_properties: bool = False): """Fast instance checker. @@ -67,31 +49,34 @@ def __init__(self, ontology: AbstractOWLOntology, *, class_cache: bool = True, """ super().__init__(ontology) assert isinstance(ontology, Ontology) - self._world = ontology._world - self._ontology = ontology - self.class_cache = class_cache - self._property_cache = property_cache - self._negation_default = negation_default - self._sub_properties = sub_properties - self.__warned = 0 + self._world: owlready2.World = ontology._world + self._ontology: Ontology = ontology + self.class_cache: bool = class_cache + self._property_cache: bool = property_cache + self._negation_default: bool = negation_default + self._sub_properties: bool = sub_properties + self.__warned: int = 0 self._init() - def _init(self, cache_size=128): + def _init(self): if self.class_cache: - self._cls_to_ind = dict() + # Class => individuals + self._cls_to_ind: Dict[OWLClass, FrozenSet[OWLNamedIndividual]] = {} if self._property_cache: - self._obj_prop = dict() - self._obj_prop_inv = dict() - self._data_prop = dict() + # ObjectProperty => { individual => individuals } + self._obj_prop: Dict[OWLObjectProperty, Mapping[OWLNamedIndividual, Set[OWLNamedIndividual]]] = dict() + # ObjectProperty => { individual => individuals } + self._obj_prop_inv: Dict[OWLObjectProperty, Mapping[OWLNamedIndividual, Set[OWLNamedIndividual]]] = dict() + # DataProperty => { individual => literals } + self._data_prop: Dict[OWLDataProperty, Mapping[OWLNamedIndividual, Set[OWLLiteral]]] = dict() else: - self._has_prop = MappingProxyType({ - OWLDataProperty: dict(), - OWLObjectProperty: dict(), - OWLObjectInverseOf: dict(), - }) - - + self._has_prop: Mapping[Type[_P], Dict[_P, FrozenSet[OWLNamedIndividual]]] = { + OWLDataProperty: {}, + OWLObjectProperty: {}, + OWLObjectInverseOf: {}, + } + def reset(self): """The reset method shall reset any cached state.""" self._init() From df6b6c1804171d51568ae98b62c355d342335ece Mon Sep 17 00:00:00 2001 From: Luke Friedrichs Date: Fri, 8 Nov 2024 10:25:05 +0100 Subject: [PATCH 09/23] removed LRU import --- owlapy/owl_reasoner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/owlapy/owl_reasoner.py b/owlapy/owl_reasoner.py index 6333f2e1..2589b91e 100644 --- a/owlapy/owl_reasoner.py +++ b/owlapy/owl_reasoner.py @@ -27,7 +27,7 @@ OWLPropertyExpression, OWLDataPropertyExpression from owlapy.owl_individual import OWLNamedIndividual from owlapy.owl_literal import OWLLiteral -from owlapy.utils import LRUCache, run_with_timeout +from owlapy.utils import run_with_timeout from owlapy.abstracts.abstract_owl_reasoner import AbstractOWLReasoner logger = logging.getLogger(__name__) From 5038f0d3d63c8c34e4488987fde3c3c3d366c02d Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Fri, 8 Nov 2024 16:42:55 +0100 Subject: [PATCH 10/23] simple tests about hashings are added --- tests/test_hashing.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/test_hashing.py diff --git a/tests/test_hashing.py b/tests/test_hashing.py new file mode 100644 index 00000000..b177022c --- /dev/null +++ b/tests/test_hashing.py @@ -0,0 +1,33 @@ +from owlapy.class_expression import OWLClass, OWLObjectIntersectionOf +from owlapy.owl_property import OWLObjectProperty +from owlapy.class_expression import OWLObjectSomeValuesFrom +from owlapy.converter import owl_expression_to_sparql +from owlapy.render import owl_expression_to_dl +from owlapy.iri import IRI +from owlapy.class_expression import OWLObjectUnionOf, OWLObjectIntersectionOf +class TestHashing: + def test_simple(self): + memory = dict() + # An OWL Class can be used as a key in a dictionary. + memory[OWLClass("http://example.com/father#A")] = OWLClass("http://example.com/father#A") + memory[OWLClass("http://example.com/father#B")] = OWLClass("http://example.com/father#B") + memory[OWLClass("http://example.com/father#C")] = OWLClass("http://example.com/father#C") + + unions = set() + intersections = set() + for k, v in memory.items(): + assert k == v + # An OWLObjectUnionOf over two OWL Classes can be added into a set. + unions.add(OWLObjectUnionOf((k, v))) + # Since the order doesn't matter in an OWLObjectUnionOf the following also holds + assert OWLObjectUnionOf((v, k)) in unions + + # An OWLObjectUnionOf over two OWL Classes can be added into a set. + intersections.add(OWLObjectIntersectionOf((k, v))) + # Since the order doesn't matter in an OWLObjectUnionOf the following also holds + assert OWLObjectIntersectionOf((v, k)) in intersections + # OWLObjectUnionOf and OWLObjectIntersectionOf can also be used as keys + for i in unions | intersections: + memory[i]=i + for k, v in memory.items(): + assert k == v From 5e3a9e08b71ad7435a31326124afe8b43876138f Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Fri, 8 Nov 2024 16:44:12 +0100 Subject: [PATCH 11/23] Raised RuntimeError if two objects not inherting from same abstract class are compared --- owlapy/iri.py | 4 +++- owlapy/owl_object.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/owlapy/iri.py b/owlapy/iri.py index 66166bb4..af6a3238 100644 --- a/owlapy/iri.py +++ b/owlapy/iri.py @@ -45,6 +45,7 @@ def __init__(self, namespace: Union[str, Namespaces], remainder: str=""): else: assert namespace[-1] in ("/", ":", "#"), "It should be a valid IRI based on /, :, and #" import sys + # https://docs.python.org/3.2/library/sys.html?highlight=sys.intern#sys.intern self._namespace = sys.intern(namespace) self._remainder = remainder @@ -94,7 +95,8 @@ def __repr__(self): def __eq__(self, other): if type(other) is type(self): return self._namespace is other._namespace and self._remainder == other._remainder - return NotImplemented + else: + raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") def __hash__(self): return hash((self._namespace, self._remainder)) diff --git a/owlapy/owl_object.py b/owlapy/owl_object.py index 02894034..2f89a877 100644 --- a/owlapy/owl_object.py +++ b/owlapy/owl_object.py @@ -75,7 +75,8 @@ class OWLNamedObject(OWLObject, HasIRI, metaclass=ABCMeta): def __eq__(self, other): if type(other) is type(self): return self._iri == other._iri - return NotImplemented + else: + raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") def __lt__(self, other): if type(other) is type(self): From 1bf6604ba080d15b9a30b9e3d111fc056521d424 Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Fri, 8 Nov 2024 17:03:45 +0100 Subject: [PATCH 12/23] RuntimeError raised if __eq__ different object types --- owlapy/class_expression/restriction.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/owlapy/class_expression/restriction.py b/owlapy/class_expression/restriction.py index cce981a1..dfe697db 100644 --- a/owlapy/class_expression/restriction.py +++ b/owlapy/class_expression/restriction.py @@ -272,7 +272,8 @@ def __repr__(self): def __eq__(self, other): if type(other) is type(self): return self._filler == other._filler and self._property == other._property - return NotImplemented + else: + raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") def __hash__(self): return hash((self._filler, self._property)) From 86787928747a190f9632844f0e09c5bd8d516859 Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Fri, 8 Nov 2024 17:04:03 +0100 Subject: [PATCH 13/23] el concept construct tested via hashing --- tests/test_hashing.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/tests/test_hashing.py b/tests/test_hashing.py index b177022c..22b7489e 100644 --- a/tests/test_hashing.py +++ b/tests/test_hashing.py @@ -1,12 +1,19 @@ -from owlapy.class_expression import OWLClass, OWLObjectIntersectionOf from owlapy.owl_property import OWLObjectProperty -from owlapy.class_expression import OWLObjectSomeValuesFrom +from owlapy.class_expression import OWLObjectSomeValuesFrom, OWLObjectAllValuesFrom from owlapy.converter import owl_expression_to_sparql from owlapy.render import owl_expression_to_dl from owlapy.iri import IRI -from owlapy.class_expression import OWLObjectUnionOf, OWLObjectIntersectionOf +from owlapy.class_expression import OWLClass, OWLObjectUnionOf, OWLObjectIntersectionOf + class TestHashing: - def test_simple(self): + + def test_el_description_logic_hash(self): + """ + EL allows complex concepts of the following form: + C := \top | A | C1 u C2 | \existr.C + where A is a concept and r a role name. + For more, refer to https://www.emse.fr/~zimmermann/Teaching/KRR/el.html + """ memory = dict() # An OWL Class can be used as a key in a dictionary. memory[OWLClass("http://example.com/father#A")] = OWLClass("http://example.com/father#A") @@ -22,7 +29,7 @@ def test_simple(self): # Since the order doesn't matter in an OWLObjectUnionOf the following also holds assert OWLObjectUnionOf((v, k)) in unions - # An OWLObjectUnionOf over two OWL Classes can be added into a set. + # This also works for intersections. intersections.add(OWLObjectIntersectionOf((k, v))) # Since the order doesn't matter in an OWLObjectUnionOf the following also holds assert OWLObjectIntersectionOf((v, k)) in intersections @@ -31,3 +38,16 @@ def test_simple(self): memory[i]=i for k, v in memory.items(): assert k == v + + atomic_concepts={OWLClass("http://example.com/father#A"),OWLClass("http://example.com/father#B")} + properties={OWLObjectProperty("http://example.com/society#hasChild")} + memory = dict() + for ac in atomic_concepts: + for op in properties: + # OWLObjectSomeValuesFrom can be used as a key. + memory[OWLObjectSomeValuesFrom(property=op, filler=ac)] = OWLObjectSomeValuesFrom(property=op, filler=ac) + # TODO: https://github.com/dice-group/owlapy/issues/103 + # memory[OWLObjectAllValuesFrom(property=op, filler=ac)] = OWLObjectAllValuesFrom(property=op, filler=ac) + + for k, v in memory.items(): + assert k == v \ No newline at end of file From 254e164f162a3c28f162b5d18bd4b7776bc275bc Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Fri, 8 Nov 2024 17:20:04 +0100 Subject: [PATCH 14/23] RuntimeErrors are included in all restrictions if types are not matching. Hash functions are taking a tuple where the first item corresponds to the string represenntation of the class --- owlapy/class_expression/restriction.py | 63 ++++++++++++++------------ tests/test_hashing.py | 3 +- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/owlapy/class_expression/restriction.py b/owlapy/class_expression/restriction.py index dfe697db..79ed3f8f 100644 --- a/owlapy/class_expression/restriction.py +++ b/owlapy/class_expression/restriction.py @@ -276,7 +276,7 @@ def __eq__(self, other): raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") def __hash__(self): - return hash((self._filler, self._property)) + return hash(("OWLObjectSomeValuesFrom",self._filler, self._property)) def get_property(self) -> OWLObjectPropertyExpression: # documented in parent @@ -301,10 +301,10 @@ def __eq__(self, other): if type(other) is type(self): return self._filler == other._filler and self._property == other._property else: - return False + raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") def __hash__(self): - return hash((self._filler, self._property)) + return hash(("OWLObjectAllValuesFrom",self._filler, self._property)) def get_property(self) -> OWLObjectPropertyExpression: # documented in parent @@ -340,10 +340,11 @@ def __eq__(self, other): if type(other) is type(self): return self._property == other._property else: - return False + raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") + def __hash__(self): - return hash(self._property) + return hash(("OWLObjectHasSelf", self._property)) def __repr__(self): return f'OWLObjectHasSelf({self._property})' @@ -431,13 +432,14 @@ def as_object_union_of(self) -> OWLClassExpression: return OWLObjectUnionOf(map(lambda _: OWLObjectOneOf(_), self.individuals())) def __hash__(self): - return hash(self._values) + return hash(("OWLObjectOneOf", self._values)) def __eq__(self, other): if type(other) is type(self): return self._values == other._values else: - return False + raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") + def __repr__(self): return f'OWLObjectOneOf({self._values})' @@ -500,10 +502,12 @@ def __eq__(self, other): return self._property == other._property \ and self._cardinality == other._cardinality \ and self._filler == other._filler - return NotImplemented + else: + raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") + def __hash__(self): - return hash((self._property, self._cardinality, self._filler)) + return hash(("OWLDataCardinalityRestriction",self._property, self._cardinality, self._filler)) class OWLDataMinCardinality(OWLDataCardinalityRestriction): @@ -618,9 +622,10 @@ def __eq__(self, other): if type(other) is type(self): return self._filler == other._filler and self._property == other._property else: - return False + raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") + def __hash__(self): - return hash((self._filler, self._property)) + return hash(("OWLDataSomeValuesFrom",self._filler, self._property)) def get_property(self) -> OWLDataPropertyExpression: # documented in parent @@ -661,10 +666,10 @@ def __eq__(self, other): if type(other) is type(self): return self._filler == other._filler and self._property == other._property else: - return False + raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") def __hash__(self): - return hash((self._filler, self._property)) + return hash(("OWLDataAllValuesFrom",self._filler, self._property)) def get_property(self) -> OWLDataPropertyExpression: # documented in parent @@ -707,7 +712,7 @@ def __eq__(self, other): return NotImplemented def __hash__(self): - return hash((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. @@ -736,6 +741,18 @@ 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})' + + def __hash__(self): + return hash(("OWLDataOneOf",self._values)) + + def __eq__(self, other): + if type(other) is type(self): + return {i for i in self._values} == {j for j in other._values} + else: + raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") + # 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. @@ -749,18 +766,6 @@ def operands(self) -> Iterable[OWLLiteral]: # documented in parent yield from self.values() - def __hash__(self): - return hash(self._values) - - def __eq__(self, other): - if type(other) is type(self): - return {i for i in self._values} == {j for j in other._values} - else: - return False - - def __repr__(self): - return f'OWLDataOneOf({self._values})' - class OWLDatatypeRestriction(OWLDataRange): """A datatype restriction DatatypeRestriction( DT F1 lt1 ... Fn ltn ) consists of a unary datatype DT and n pairs @@ -828,10 +833,12 @@ def get_facet_value(self) -> 'OWLLiteral': def __eq__(self, other): if type(other) is type(self): return self._facet == other._facet and self._literal == other._literal - return NotImplemented + else: + raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") + def __hash__(self): - return hash((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/tests/test_hashing.py b/tests/test_hashing.py index 22b7489e..f30dfc72 100644 --- a/tests/test_hashing.py +++ b/tests/test_hashing.py @@ -46,8 +46,7 @@ def test_el_description_logic_hash(self): for op in properties: # OWLObjectSomeValuesFrom can be used as a key. memory[OWLObjectSomeValuesFrom(property=op, filler=ac)] = OWLObjectSomeValuesFrom(property=op, filler=ac) - # TODO: https://github.com/dice-group/owlapy/issues/103 - # memory[OWLObjectAllValuesFrom(property=op, filler=ac)] = OWLObjectAllValuesFrom(property=op, filler=ac) + memory[OWLObjectAllValuesFrom(property=op, filler=ac)] = OWLObjectAllValuesFrom(property=op, filler=ac) for k, v in memory.items(): assert k == v \ No newline at end of file From f0c5592f737a71593065bb9df79230a22c090532 Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Fri, 8 Nov 2024 17:28:54 +0100 Subject: [PATCH 15/23] Reverted: __eq__ does not throw runtime error if two objects not beloning to the same class. This is too restrictive --- owlapy/class_expression/restriction.py | 57 ++++++++++---------------- owlapy/owl_object.py | 3 +- 2 files changed, 24 insertions(+), 36 deletions(-) diff --git a/owlapy/class_expression/restriction.py b/owlapy/class_expression/restriction.py index 79ed3f8f..09549c3c 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 owlapy.vocab import OWLFacet +from ..vocab import OWLFacet from datetime import datetime, date from pandas import Timedelta @@ -65,7 +65,7 @@ def __init__(self, value: _T): def __eq__(self, other): if type(other) is type(self): return self._v == other._v - return NotImplemented + return False def __hash__(self): return hash(self._v) @@ -273,7 +273,7 @@ def __eq__(self, other): if type(other) is type(self): return self._filler == other._filler and self._property == other._property else: - raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") + return False def __hash__(self): return hash(("OWLObjectSomeValuesFrom",self._filler, self._property)) @@ -301,7 +301,7 @@ def __eq__(self, other): if type(other) is type(self): return self._filler == other._filler and self._property == other._property else: - raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") + return False def __hash__(self): return hash(("OWLObjectAllValuesFrom",self._filler, self._property)) @@ -340,7 +340,7 @@ def __eq__(self, other): if type(other) is type(self): return self._property == other._property else: - raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") + return False def __hash__(self): @@ -430,17 +430,13 @@ 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: - raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") - - + return False def __repr__(self): return f'OWLObjectOneOf({self._values})' @@ -488,27 +484,22 @@ def __init__(self, cardinality: int, property: OWLDataPropertyExpression, filler assert isinstance(filler, OWLDataRange), "filler must be an OWLDataRange" super().__init__(cardinality, filler) self._property = property - - def get_property(self) -> OWLDataPropertyExpression: - # documented in parent - return self._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())})" def __eq__(self, other): if type(other) is type(self): - return self._property == other._property \ - and self._cardinality == other._cardinality \ - and self._filler == other._filler + return (self._property == other._property and self._cardinality == other._cardinality + and self._filler == other._filler) else: - raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") - - - def __hash__(self): - return hash(("OWLDataCardinalityRestriction",self._property, self._cardinality, self._filler)) + return False + 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 @@ -622,8 +613,7 @@ def __eq__(self, other): if type(other) is type(self): return self._filler == other._filler and self._property == other._property else: - raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") - + return False def __hash__(self): return hash(("OWLDataSomeValuesFrom",self._filler, self._property)) @@ -666,8 +656,7 @@ def __eq__(self, other): if type(other) is type(self): return self._filler == other._filler and self._property == other._property else: - raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") - + return False def __hash__(self): return hash(("OWLDataAllValuesFrom",self._filler, self._property)) @@ -709,8 +698,8 @@ def __repr__(self): def __eq__(self, other): if type(other) is type(self): return self._v == other._v and self._property == other._property - return NotImplemented - + else: + return False def __hash__(self): return hash(("OWLDataHasValue",self._v, self._property)) @@ -751,8 +740,7 @@ def __eq__(self, other): if type(other) is type(self): return {i for i in self._values} == {j for j in other._values} else: - raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") - + 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. @@ -798,8 +786,8 @@ def __eq__(self, other): if type(other) is type(self): return self._type == other._type \ and self._facet_restrictions == other._facet_restrictions - return NotImplemented - + else: + return False def __hash__(self): return hash((self._type, self._facet_restrictions)) @@ -834,8 +822,7 @@ def __eq__(self, other): if type(other) is type(self): return self._facet == other._facet and self._literal == other._literal else: - raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") - + return False def __hash__(self): return hash(("OWLFacetRestriction",self._facet, self._literal)) diff --git a/owlapy/owl_object.py b/owlapy/owl_object.py index 2f89a877..00032820 100644 --- a/owlapy/owl_object.py +++ b/owlapy/owl_object.py @@ -76,7 +76,8 @@ def __eq__(self, other): if type(other) is type(self): return self._iri == other._iri else: - raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") + return False + # raise RuntimeError(f"Invalid equality checking:{self} cannot be compared with {other}") def __lt__(self, other): if type(other) is type(self): From aa99ec675a0a860e8fa4602a79f95172a61fb8b9 Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Fri, 8 Nov 2024 17:31:52 +0100 Subject: [PATCH 16/23] OWLDatatypeRestriction has also updated --- owlapy/class_expression/restriction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/owlapy/class_expression/restriction.py b/owlapy/class_expression/restriction.py index 09549c3c..0e6658a9 100644 --- a/owlapy/class_expression/restriction.py +++ b/owlapy/class_expression/restriction.py @@ -789,7 +789,7 @@ def __eq__(self, other): else: return False def __hash__(self): - return hash((self._type, self._facet_restrictions)) + return hash(("OWLDatatypeRestriction", self._type, self._facet_restrictions)) def __repr__(self): return f'OWLDatatypeRestriction({repr(self._type)}, {repr(self._facet_restrictions)})' From 0dbc2de9a5dcea2d9491d35b4767aeceda22eea0 Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Fri, 8 Nov 2024 17:35:33 +0100 Subject: [PATCH 17/23] version incremanted --- owlapy/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/owlapy/__init__.py b/owlapy/__init__.py index 84aec36b..78a0a08e 100644 --- a/owlapy/__init__.py +++ b/owlapy/__init__.py @@ -3,7 +3,7 @@ from .converter import owl_expression_to_sparql, owl_expression_to_sparql_with_confusion_matrix from .owl_ontology_manager import OntologyManager -__version__ = '1.3.2' +__version__ = '1.3.3' __all__ = [ 'owl_expression_to_dl', 'owl_expression_to_manchester', diff --git a/setup.py b/setup.py index 71d8ca61..e3e8e579 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="owlapy", description="OWLAPY is a Python Framework for creating and manipulating OWL Ontologies.", - version="1.3.2", + version="1.3.3", packages=find_packages(), include_package_data=True, package_data={'owlapy': ['jar_dependencies/*.jar'],}, From 70aa0abda4726cfdd60d96cfc0857da810d9c374 Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Fri, 8 Nov 2024 20:48:51 +0100 Subject: [PATCH 18/23] NotImplementedErrors message added --- owlapy/owl_ontology_manager.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/owlapy/owl_ontology_manager.py b/owlapy/owl_ontology_manager.py index 4d58d58a..2df6cdd7 100644 --- a/owlapy/owl_ontology_manager.py +++ b/owlapy/owl_ontology_manager.py @@ -103,8 +103,7 @@ def apply_change(self, change: AbstractOWLOntologyChange): ont_x.imported_ontologies.append( self._world.get_ontology(change.get_import_declaration().str)) else: - # TODO XXX - raise NotImplementedError + raise NotImplementedError("Change is not yet implemented.") def save_world(self): """Saves the actual state of the quadstore in the SQLite3 file. @@ -139,4 +138,4 @@ def get_owlapi_manager(self): return self.owlapi_manager def apply_change(self, change: AbstractOWLOntologyChange): - raise NotImplementedError() + raise NotImplementedError("A change cannot be applied at the moment.") From f2204693a176d0164c717526126606362363b50b Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Fri, 8 Nov 2024 22:03:13 +0100 Subject: [PATCH 19/23] New example added --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/README.md b/README.md index 9eb842a7..f5c2b4e5 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,54 @@ pytest -p no:warnings -x # Running 142 tests ~ 30 secs ## Examples +### Exploring OWL Ontology + +
Click me! + +```python +from owlapy.owl_ontology_manager import SyncOntologyManager + +ontology_path = "KGs/Family/father.owl" +onto = SyncOntologyManager().load_ontology(ontology_path) + +print({owl_class.reminder for owl_class in onto.classes_in_signature()}) +# {'Thing', 'female', 'male', 'person'} + +print({individual.reminder for individual in onto.individuals_in_signature()}) +# {'michelle', 'stefan', 'martin', 'anna', 'heinz', 'markus'} + +print({object_property.reminder for object_property in onto.object_properties_in_signature()}) +# {'hasChild'} + +for owl_subclass_of_axiom in onto.get_tbox_axioms(): + print(owl_subclass_of_axiom) + +# OWLEquivalentClassesAxiom([OWLClass(IRI('http://example.com/father#', 'male')), OWLObjectComplementOf(OWLClass(IRI('http://example.com/father#', 'female')))],[]) +# OWLSubClassOfAxiom(sub_class=OWLClass(IRI('http://example.com/father#', 'female')),super_class=OWLClass(IRI('http://example.com/father#', 'person')),annotations=[]) +# OWLSubClassOfAxiom(sub_class=OWLClass(IRI('http://example.com/father#', 'male')),super_class=OWLClass(IRI('http://example.com/father#', 'person')),annotations=[]) +# OWLSubClassOfAxiom(sub_class=OWLClass(IRI('http://example.com/father#', 'person')),super_class=OWLClass(IRI('http://www.w3.org/2002/07/owl#', 'Thing')),annotations=[]) +# OWLObjectPropertyRangeAxiom(OWLObjectProperty(IRI('http://example.com/father#', 'hasChild')),OWLClass(IRI('http://example.com/father#', 'person')),[]) +# OWLObjectPropertyDomainAxiom(OWLObjectProperty(IRI('http://example.com/father#', 'hasChild')),OWLClass(IRI('http://example.com/father#', 'person')),[]) + + +for axiom in onto.get_abox_axioms(): + print(axiom) + +# OWLClassAssertionAxiom(individual=OWLNamedIndividual(IRI('http://example.com/father#', 'anna')),class_expression=OWLClass(IRI('http://example.com/father#', 'female')),annotations=[]) +# OWLClassAssertionAxiom(individual=OWLNamedIndividual(IRI('http://example.com/father#', 'michelle')),class_expression=OWLClass(IRI('http://example.com/father#', 'female')),annotations=[]) +# OWLClassAssertionAxiom(individual=OWLNamedIndividual(IRI('http://example.com/father#', 'martin')),class_expression=OWLClass(IRI('http://example.com/father#', 'male')),annotations=[]) +# OWLClassAssertionAxiom(individual=OWLNamedIndividual(IRI('http://example.com/father#', 'markus')),class_expression=OWLClass(IRI('http://example.com/father#', 'male')),annotations=[]) +# OWLClassAssertionAxiom(individual=OWLNamedIndividual(IRI('http://example.com/father#', 'heinz')),class_expression=OWLClass(IRI('http://example.com/father#', 'male')),annotations=[]) +# OWLClassAssertionAxiom(individual=OWLNamedIndividual(IRI('http://example.com/father#', 'stefan')),class_expression=OWLClass(IRI('http://example.com/father#', 'male')),annotations=[]) +# OWLObjectPropertyAssertionAxiom(subject=OWLNamedIndividual(IRI('http://example.com/father#', 'markus')),property_=OWLObjectProperty(IRI('http://example.com/father#', 'hasChild')),object_=OWLNamedIndividual(IRI('http://example.com/father#', 'anna')),annotations=[]) +# OWLObjectPropertyAssertionAxiom(subject=OWLNamedIndividual(IRI('http://example.com/father#', 'martin')),property_=OWLObjectProperty(IRI('http://example.com/father#', 'hasChild')),object_=OWLNamedIndividual(IRI('http://example.com/father#', 'heinz')),annotations=[]) +# OWLObjectPropertyAssertionAxiom(subject=OWLNamedIndividual(IRI('http://example.com/father#', 'stefan')),property_=OWLObjectProperty(IRI('http://example.com/father#', 'hasChild')),object_=OWLNamedIndividual(IRI('http://example.com/father#', 'markus')),annotations=[]) +# OWLObjectPropertyAssertionAxiom(subject=OWLNamedIndividual(IRI('http://example.com/father#', 'anna')),property_=OWLObjectProperty(IRI('http://example.com/father#', 'hasChild')),object_=OWLNamedIndividual(IRI('http://example.com/father#', 'heinz')),annotations=[]) + +``` + +
+ ### Creating OWL Class Expressions
Click me! From de17806e27462d0cd928a1a6c54efcefe5f966c4 Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Fri, 8 Nov 2024 22:09:46 +0100 Subject: [PATCH 20/23] sub_class and super_class properties are added into OWLSubClassOfAxiom --- owlapy/owl_axiom.py | 10 +++++++++- tests/test_sync_ontology.py | 7 +++++++ tests/test_sync_reasoner.py | 23 ++++++++++++++++++++++- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/owlapy/owl_axiom.py b/owlapy/owl_axiom.py index 6c10ea5d..3c0cfd96 100644 --- a/owlapy/owl_axiom.py +++ b/owlapy/owl_axiom.py @@ -523,6 +523,13 @@ def __init__(self, sub_class: OWLClassExpression, super_class: OWLClassExpressio self._super_class = super_class super().__init__(annotations=annotations) + @property + def sub_class(self) -> OWLClassExpression: + return self._sub_class + @property + def super_class(self) -> OWLClassExpression: + return self._super_class + def get_sub_class(self) -> OWLClassExpression: return self._sub_class @@ -533,7 +540,8 @@ def __eq__(self, other): if type(other) is type(self): return self._super_class == other._super_class and self._sub_class == other._sub_class \ and self._annotations == other._annotations - return NotImplemented + else: + return False def __hash__(self): return hash((self._super_class, self._sub_class, *self._annotations)) diff --git a/tests/test_sync_ontology.py b/tests/test_sync_ontology.py index c35d7462..f02c198e 100644 --- a/tests/test_sync_ontology.py +++ b/tests/test_sync_ontology.py @@ -80,6 +80,13 @@ class TestSyncReasoner(unittest.TestCase): manager = SyncOntologyManager() onto = manager.load_ontology(ontology_path) + def test_interface_father_dataset(self): + ontology_path = "KGs/Family/father.owl" + onto = SyncOntologyManager().load_ontology(ontology_path) + assert {owl_class.reminder for owl_class in onto.classes_in_signature()}=={'male', 'female', 'Thing', 'person'} + assert {individual.reminder for individual in onto.individuals_in_signature()}=={'markus', 'anna', 'martin', 'stefan', 'heinz', 'michelle'} + assert {object_property.reminder for object_property in onto.object_properties_in_signature()}=={'hasChild'} + # NOTE AB: The name of "assertCountEqual" may be misleading,but it's essentially an order-insensitive "assertEqual". def test_classes_in_signature(self): diff --git a/tests/test_sync_reasoner.py b/tests/test_sync_reasoner.py index f45cb8a8..9b157457 100644 --- a/tests/test_sync_reasoner.py +++ b/tests/test_sync_reasoner.py @@ -3,7 +3,7 @@ from jpype import JDouble from owlapy.class_expression import OWLClass, OWLDataSomeValuesFrom, OWLObjectIntersectionOf, OWLNothing, OWLThing, \ - OWLClassExpression, OWLObjectSomeValuesFrom + OWLClassExpression, OWLObjectSomeValuesFrom, OWLObjectOneOf from owlapy.iri import IRI from owlapy.owl_axiom import OWLDisjointClassesAxiom, OWLDeclarationAxiom, OWLClassAssertionAxiom, OWLSubClassOfAxiom, \ OWLEquivalentClassesAxiom, OWLSubDataPropertyOfAxiom, OWLSubObjectPropertyOfAxiom @@ -15,6 +15,7 @@ from owlapy.owl_reasoner import SyncReasoner from owlapy.providers import owl_datatype_min_inclusive_restriction + NS = 'http://www.semanticweb.org/stefan/ontologies/2023/1/untitled-ontology-11#' a = OWLNamedIndividual(IRI(NS, "a")) @@ -82,6 +83,26 @@ class TestSyncReasoner(unittest.TestCase): ce = OWLObjectIntersectionOf([nitrogen38, has_charge_more_than_0_85]) reasoner = SyncReasoner(ontology_path) + def test_father_dataset(self): + ontology_path = "KGs/Family/father.owl" + # Available OWL Reasoners: 'HermiT', 'Pellet', 'JFact', 'Openllet' + owl_reasoners = dict() + owl_reasoners["HermiT"] = SyncReasoner(ontology=ontology_path, reasoner="HermiT") + owl_reasoners["Pellet"] = SyncReasoner(ontology=ontology_path, reasoner="Pellet") + owl_reasoners["JFact"] = SyncReasoner(ontology=ontology_path, reasoner="JFact") + owl_reasoners["Openllet"] = SyncReasoner(ontology=ontology_path, reasoner="Openllet") + + for k, reasoner in owl_reasoners.items(): + exist_haschild_female = OWLObjectSomeValuesFrom( + property=OWLObjectProperty('http://example.com/father#hasChild'), + filler=OWLClass('http://example.com/father#female')) + + exist_haschild_anna = OWLObjectSomeValuesFrom(property=OWLObjectProperty('http://example.com/father#hasChild'), + filler=OWLObjectOneOf( + OWLNamedIndividual('http://example.com/father#anna'))) + assert reasoner.instances(ce=exist_haschild_female) == reasoner.instances(ce=exist_haschild_anna) == { + OWLNamedIndividual('http://example.com/father#markus')} + def test_consistency_check(self): self.assertEqual(self.reasoner.has_consistent_ontology(), True) From 32027f582110fa855f986d48dc9234106c0bd368 Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Fri, 8 Nov 2024 22:11:40 +0100 Subject: [PATCH 21/23] __repr__ of SyncOntology extended. We should perhaps introduce __str__ --- owlapy/owl_ontology.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/owlapy/owl_ontology.py b/owlapy/owl_ontology.py index d527383e..03a2d4af 100644 --- a/owlapy/owl_ontology.py +++ b/owlapy/owl_ontology.py @@ -126,7 +126,6 @@ def __eq__(self, other): def _check_expression(expr: OWLObject, ontology: AbstractOWLOntology, world: owlready2.namespace.World): """ - @TODO:CD: Documentation Creates all entities (individuals, classes, properties) that appear in the given (complex) class expression and do not exist in the given ontology yet @@ -1035,7 +1034,6 @@ def _get_imports_enum(self, include_imports_closure: bool): else: imports = Imports.EXCLUDED return imports - def get_signature(self, include_imports_closure: bool = True): """Gets the entities that are in the signature of this ontology. @@ -1045,6 +1043,7 @@ def get_signature(self, include_imports_closure: bool = True): Returns: Entities in signature. """ + # @TODO: CD: Is this class method redundant given that we have the individuals_in_signature ? 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]: @@ -1056,7 +1055,6 @@ def get_abox_axioms(self, include_imports_closure: bool = True) -> Iterable[OWLA Returns: ABox axioms. """ - return self.mapper.map_(self.owlapi_ontology.getABoxAxioms(self._get_imports_enum(include_imports_closure))) def get_tbox_axioms(self, include_imports_closure: bool = True) -> Iterable[OWLAxiom]: @@ -1100,7 +1098,14 @@ def __hash__(self): return int(self.owlapi_ontology.getOntologyID().hashCode()) def __repr__(self): - return f'SyncOntology({self.manager}, {self.path}, {self.new})' + return (f'SyncOntology:' + f'\t|Tbox|={len(self.get_tbox_axioms())}' + f'\t|Abox|={len(self.get_abox_axioms())}' + f'\t|Individuals|={len(self.individuals_in_signature())}' + f'\t|Classes|={len(self.classes_in_signature())}' + f'\t|Object Properties|={len(self.object_properties_in_signature())}' + f'\t|Data Properties|={len(self.data_properties_in_signature())}' + f'\n{self.manager}\tPath:{self.path}\tNew:{self.new}') OWLREADY2_FACET_KEYS = MappingProxyType({ From 1aaf53c168340da9e8bc836d1dca1737ff7706a9 Mon Sep 17 00:00:00 2001 From: Caglar Demir Date: Fri, 8 Nov 2024 22:13:16 +0100 Subject: [PATCH 22/23] __init__ of SyncReasoner is refactored by moving few assigments outside of the class. @property introduced in few places --- owlapy/owl_individual.py | 5 +- owlapy/owl_property.py | 4 ++ owlapy/owl_reasoner.py | 99 ++++++++++++++++++++-------------------- 3 files changed, 57 insertions(+), 51 deletions(-) diff --git a/owlapy/owl_individual.py b/owlapy/owl_individual.py index 3d98ff7b..2e0fd626 100644 --- a/owlapy/owl_individual.py +++ b/owlapy/owl_individual.py @@ -35,11 +35,12 @@ def __init__(self, iri: Union[IRI, str]): self._iri = iri else: self._iri = IRI.create(iri) - @property def iri(self) -> IRI: return self._iri - @property def str(self): return self._iri.as_str() + @property + def reminder(self): + return self._iri.reminder \ No newline at end of file diff --git a/owlapy/owl_property.py b/owlapy/owl_property.py index cef05361..4f3ee257 100644 --- a/owlapy/owl_property.py +++ b/owlapy/owl_property.py @@ -113,6 +113,10 @@ class OWLObjectProperty(OWLObjectPropertyExpression, OWLProperty): _iri: IRI + @property + def reminder(self): + return self._iri.reminder + def get_named_property(self) -> 'OWLObjectProperty': # documented in parent return self diff --git a/owlapy/owl_reasoner.py b/owlapy/owl_reasoner.py index 2589b91e..5069338f 100644 --- a/owlapy/owl_reasoner.py +++ b/owlapy/owl_reasoner.py @@ -1013,53 +1013,9 @@ def __init__(self, ontology: Union[SyncOntology, str], reasoner="HermiT"): self._owlapi_manager = self.manager.get_owlapi_manager() self._owlapi_ontology = self.ontology.get_owlapi_ontology() - # super().__init__(self.ontology) self.mapper = self.ontology.mapper - from org.semanticweb.owlapi.util import (InferredClassAssertionAxiomGenerator, InferredSubClassAxiomGenerator, - InferredEquivalentClassAxiomGenerator, - InferredDisjointClassesAxiomGenerator, - InferredEquivalentDataPropertiesAxiomGenerator, - InferredEquivalentObjectPropertyAxiomGenerator, - InferredInverseObjectPropertiesAxiomGenerator, - InferredSubDataPropertyAxiomGenerator, - InferredSubObjectPropertyAxiomGenerator, - InferredDataPropertyCharacteristicAxiomGenerator, - InferredObjectPropertyCharacteristicAxiomGenerator) - - self.inference_types_mapping = {"InferredClassAssertionAxiomGenerator": InferredClassAssertionAxiomGenerator(), - "InferredSubClassAxiomGenerator": InferredSubClassAxiomGenerator(), - "InferredDisjointClassesAxiomGenerator": InferredDisjointClassesAxiomGenerator(), - "InferredEquivalentClassAxiomGenerator": InferredEquivalentClassAxiomGenerator(), - "InferredInverseObjectPropertiesAxiomGenerator": InferredInverseObjectPropertiesAxiomGenerator(), - "InferredEquivalentDataPropertiesAxiomGenerator": InferredEquivalentDataPropertiesAxiomGenerator(), - "InferredEquivalentObjectPropertyAxiomGenerator": InferredEquivalentObjectPropertyAxiomGenerator(), - "InferredSubDataPropertyAxiomGenerator": InferredSubDataPropertyAxiomGenerator(), - "InferredSubObjectPropertyAxiomGenerator": InferredSubObjectPropertyAxiomGenerator(), - "InferredDataPropertyCharacteristicAxiomGenerator": InferredDataPropertyCharacteristicAxiomGenerator(), - "InferredObjectPropertyCharacteristicAxiomGenerator": InferredObjectPropertyCharacteristicAxiomGenerator(), - } - - # () Create a reasoner using the ontology - if reasoner == "HermiT": - from org.semanticweb.HermiT import ReasonerFactory - self._owlapi_reasoner = ReasonerFactory().createReasoner(self._owlapi_ontology) - assert self._owlapi_reasoner.getReasonerName() == "HermiT" - elif reasoner == "JFact": - from uk.ac.manchester.cs.jfact import JFactFactory - self._owlapi_reasoner = JFactFactory().createReasoner(self._owlapi_ontology) - elif reasoner == "Pellet": - from openllet.owlapi import PelletReasonerFactory - self._owlapi_reasoner = PelletReasonerFactory().createReasoner(self._owlapi_ontology) - elif reasoner == "Openllet": - from openllet.owlapi import OpenlletReasonerFactory - self._owlapi_reasoner = OpenlletReasonerFactory().getInstance().createReasoner(self._owlapi_ontology) - elif reasoner == "Structural": - from org.semanticweb.owlapi.reasoner.structural import StructuralReasonerFactory - self._owlapi_reasoner = StructuralReasonerFactory().createReasoner(self._owlapi_ontology) - else: - raise NotImplementedError("Not implemented") - - self.reasoner = reasoner + self.inference_types_mapping = import_and_include_axioms_generators() + self._owlapi_reasoner = initialize_reasoner(reasoner,self._owlapi_ontology) def _instances(self, ce: OWLClassExpression, direct=False) -> Set[OWLNamedIndividual]: """ @@ -1378,11 +1334,11 @@ def disjoint_data_properties(self, p: OWLDataProperty): yield from [self.mapper.map_(pe) for pe in self._owlapi_reasoner.getDisjointDataProperties(self.mapper.map_(p)).getFlattened()] - def types(self, i: OWLNamedIndividual, direct: bool = False): + def types(self, individual: OWLNamedIndividual, direct: bool = False): """Gets the named classes which are (potentially direct) types of the specified named individual. Args: - i: The individual whose types are to be retrieved. + individual: The individual whose types are to be retrieved. direct: Specifies if the direct types should be retrieved (True), or if all types should be retrieved (False). @@ -1392,7 +1348,7 @@ def types(self, i: OWLNamedIndividual, direct: bool = False): entails ClassAssertion(C, ind). """ yield from [self.mapper.map_(ind) for ind in - self._owlapi_reasoner.getTypes(self.mapper.map_(i), direct).getFlattened()] + self._owlapi_reasoner.getTypes(self.mapper.map_(individual), direct).getFlattened()] def has_consistent_ontology(self) -> bool: """ @@ -1544,3 +1500,48 @@ def unsatisfiable_classes(self): def get_root_ontology(self) -> AbstractOWLOntology: return self.ontology + +def initialize_reasoner(reasoner:str, owlapi_ontology): + # () Create a reasoner using the ontology + if reasoner == "HermiT": + from org.semanticweb.HermiT import ReasonerFactory + owlapi_reasoner = ReasonerFactory().createReasoner(owlapi_ontology) + assert owlapi_reasoner.getReasonerName() == "HermiT" + elif reasoner == "JFact": + from uk.ac.manchester.cs.jfact import JFactFactory + owlapi_reasoner = JFactFactory().createReasoner(owlapi_ontology) + elif reasoner == "Pellet": + from openllet.owlapi import PelletReasonerFactory + owlapi_reasoner = PelletReasonerFactory().createReasoner(owlapi_ontology) + elif reasoner == "Openllet": + from openllet.owlapi import OpenlletReasonerFactory + owlapi_reasoner = OpenlletReasonerFactory().getInstance().createReasoner(owlapi_ontology) + elif reasoner == "Structural": + from org.semanticweb.owlapi.reasoner.structural import StructuralReasonerFactory + owlapi_reasoner = StructuralReasonerFactory().createReasoner(owlapi_ontology) + else: + raise NotImplementedError("Not implemented") + return owlapi_reasoner +def import_and_include_axioms_generators(): + from org.semanticweb.owlapi.util import (InferredClassAssertionAxiomGenerator, InferredSubClassAxiomGenerator, + InferredEquivalentClassAxiomGenerator, + InferredDisjointClassesAxiomGenerator, + InferredEquivalentDataPropertiesAxiomGenerator, + InferredEquivalentObjectPropertyAxiomGenerator, + InferredInverseObjectPropertiesAxiomGenerator, + InferredSubDataPropertyAxiomGenerator, + InferredSubObjectPropertyAxiomGenerator, + InferredDataPropertyCharacteristicAxiomGenerator, + InferredObjectPropertyCharacteristicAxiomGenerator) + + return {"InferredClassAssertionAxiomGenerator": InferredClassAssertionAxiomGenerator(), + "InferredSubClassAxiomGenerator": InferredSubClassAxiomGenerator(), + "InferredDisjointClassesAxiomGenerator": InferredDisjointClassesAxiomGenerator(), + "InferredEquivalentClassAxiomGenerator": InferredEquivalentClassAxiomGenerator(), + "InferredInverseObjectPropertiesAxiomGenerator": InferredInverseObjectPropertiesAxiomGenerator(), + "InferredEquivalentDataPropertiesAxiomGenerator": InferredEquivalentDataPropertiesAxiomGenerator(), + "InferredEquivalentObjectPropertyAxiomGenerator": InferredEquivalentObjectPropertyAxiomGenerator(), + "InferredSubDataPropertyAxiomGenerator": InferredSubDataPropertyAxiomGenerator(), + "InferredSubObjectPropertyAxiomGenerator": InferredSubObjectPropertyAxiomGenerator(), + "InferredDataPropertyCharacteristicAxiomGenerator": InferredDataPropertyCharacteristicAxiomGenerator(), + "InferredObjectPropertyCharacteristicAxiomGenerator": InferredObjectPropertyCharacteristicAxiomGenerator()} From d77a4831695c219cd154f03b46c4a8be804a3559 Mon Sep 17 00:00:00 2001 From: Alkid Date: Mon, 11 Nov 2024 14:14:39 +0100 Subject: [PATCH 23/23] Version increment to 1.3.3 --- README.md | 4 ++-- docs/conf.py | 2 +- docs/usage/main.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f5c2b4e5..2cfcbc91 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # OWLAPY [![Coverage](https://img.shields.io/badge/coverage-78%25-green)](https://dice-group.github.io/owlapy/usage/further_resources.html#coverage-report) -[![Pypi](https://img.shields.io/badge/pypi-1.3.2-blue)](https://pypi.org/project/owlapy/1.3.2/) -[![Docs](https://img.shields.io/badge/documentation-1.3.2-yellow)](https://dice-group.github.io/owlapy/usage/main.html) +[![Pypi](https://img.shields.io/badge/pypi-1.3.3-blue)](https://pypi.org/project/owlapy/1.3.3/) +[![Docs](https://img.shields.io/badge/documentation-1.3.3-yellow)](https://dice-group.github.io/owlapy/usage/main.html) ![OWLAPY](docs/_static/images/owlapy_logo.png) diff --git a/docs/conf.py b/docs/conf.py index 9e37f605..641cf1fb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,7 +13,7 @@ project = 'OWLAPY' author = 'Ontolearn Team' -release = '1.3.2' +release = '1.3.3' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/docs/usage/main.md b/docs/usage/main.md index 36b77c2b..c646825c 100644 --- a/docs/usage/main.md +++ b/docs/usage/main.md @@ -1,6 +1,6 @@ # About owlapy -**Version:** owlapy 1.3.2 +**Version:** owlapy 1.3.3 **GitHub repository:** [https://github.com/dice-group/owlapy](https://github.com/dice-group/owlapy)