diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..c642d5ef --- /dev/null +++ b/.coveragerc @@ -0,0 +1,18 @@ +[run] +omit = + tests/* + +[report] +exclude_lines = + pragma: no cover + def __repr__ + if self.debug: + if settings.DEBUG + raise AssertionError + raise NotImplementedError + if 0: + if __name__ == .__main__.: + if TYPE_CHECKING: + class .*\bProtocol\): + @(abc\.)?abstractmethod + pass \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index be929a87..6bee0285 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,3 +24,9 @@ jobs: wget https://files.dice-research.org/projects/Ontolearn/KGs.zip unzip KGs.zip python -m pytest -p no:warnings -x + + - name: Coverage report + run: | + pip install coverage + coverage run -m pytest + coverage report -m \ No newline at end of file diff --git a/README.md b/README.md index b492cb35..d121e51c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,9 @@ # 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.2.0-blue)](https://pypi.org/project/owlapy/1.2.0/) +[![Docs](https://img.shields.io/badge/documentation-1.2.0-yellow)](https://dice-group.github.io/owlapy/usage/main.html) + +![OWLAPY](docs/_static/images/owlapy_logo.png) OWLAPY is a Python Framework for creating and manipulating OWL Ontologies. @@ -17,10 +22,17 @@ pip3 install owlapy ``` +```shell +# To download RDF knowledge graphs +wget https://files.dice-research.org/projects/Ontolearn/KGs.zip -O ./KGs.zip && unzip KGs.zip +pytest -p no:warnings -x # Running 103 tests takes ~ 30 mins +``` + ## Usage -In this example we start with a simple atomic class expression and move to some more complex -ones and finally render and print the last of them in description logics syntax. + +### Creating OWL Class Expressions +
Click me! ```python from owlapy.class_expression import OWLClass, OWLObjectIntersectionOf, OWLObjectSomeValuesFrom @@ -29,17 +41,13 @@ from owlapy import owl_expression_to_sparql, owl_expression_to_dl # Create the male class male = OWLClass("http://example.com/society#male") - # Create an object property using the iri as a string for 'hasChild' property. hasChild = OWLObjectProperty("http://example.com/society#hasChild") - # Create an existential restrictions hasChild_male = OWLObjectSomeValuesFrom(hasChild, male) - # Let's make it more complex by intersecting with another class 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 (and vice-versa) print(owl_expression_to_dl(teacher_that_hasChild_male)) # (∃ hasChild.male) ⊓ teacher @@ -57,7 +65,56 @@ class. In the above examples we have introduced 3 types of class expressions: Like we showed in this example, you can create all kinds of class expressions using the OWL objects in [owlapy api](https://dice-group.github.io/owlapy/autoapi/owlapy/index.html). -Check also the [examples](https://github.com/dice-group/owlapy/tree/develop/examples) folder. +
+ +### Logical Inference + +
Click me! + +```python +from owlapy.owl_ontology_manager import OntologyManager +from owlapy.owlapi_adaptor import OWLAPIAdaptor + +ontology_path = "KGs/Family/family-benchmark_rich_background.owl" +# Available OWL Reasoners: 'HermiT', 'Pellet', 'JFact', 'Openllet' +owlapi_adaptor = OWLAPIAdaptor(path=ontology_path, name_reasoner="Pellet") +onto = OntologyManager().load_ontology(ontology_path) +# Iterate over defined owl Classes in the signature +for i in onto.classes_in_signature(): + # Performing type inference with Pellet + instances=owlapi_adaptor.instances(i,direct=False) + print(f"Class:{i}\t Num instances:{len(instances)}") +owlapi_adaptor.stopJVM() +``` + +
+ +### Ontology Enrichment + +
Click me! + +An Ontology can be enriched by inferring many different axioms. +```python +from owlapy.owlapi_adaptor import OWLAPIAdaptor + +adaptor = OWLAPIAdaptor(path="KGs/Family/family-benchmark_rich_background.owl", name_reasoner="Pellet") +# Infer missing class assertions +adaptor.infer_axioms_and_save(output_path="KGs/Family/inferred_family-benchmark_rich_background.ttl", + output_format="ttl", + inference_types=[ + "InferredClassAssertionAxiomGenerator", + "InferredEquivalentClassAxiomGenerator", + "InferredDisjointClassesAxiomGenerator", + "InferredSubClassAxiomGenerator", + "InferredInverseObjectPropertiesAxiomGenerator", + "InferredEquivalentClassAxiomGenerator"]) +adaptor.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. ## How to cite Currently, we are working on our manuscript describing our framework. diff --git a/docs/_static/images/favicon.ico b/docs/_static/images/favicon.ico new file mode 100644 index 00000000..42e8969e Binary files /dev/null and b/docs/_static/images/favicon.ico differ diff --git a/docs/_static/images/owlapy_logo.png b/docs/_static/images/owlapy_logo.png new file mode 100644 index 00000000..0ee84621 Binary files /dev/null and b/docs/_static/images/owlapy_logo.png differ diff --git a/docs/conf.py b/docs/conf.py index b5ba4898..55a57422 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,7 +13,7 @@ project = 'OWLAPY' author = 'Ontolearn Team' -release = '1.1.1' +release = '1.2.0' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration @@ -66,6 +66,12 @@ html_static_path = ['_static'] +html_logo = '_static/images/owlapy_logo.png' + +html_favicon = '_static/images/favicon.ico' + +html_extra_path = ["googlec4c425077889c69c.html"] + if stanford_theme_mod: html_theme = 'sphinx_rtd_theme' diff --git a/docs/googlec4c425077889c69c.html b/docs/googlec4c425077889c69c.html new file mode 100644 index 00000000..097d4e5f --- /dev/null +++ b/docs/googlec4c425077889c69c.html @@ -0,0 +1 @@ +google-site-verification: googlec4c425077889c69c.html \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 5afacc29..083391bc 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,6 +14,6 @@ Welcome to OWLAPY! usage/usage_examples usage/ontologies usage/reasoner - usage/reasoning_details usage/owlapi_adaptor + usage/further_resources autoapi/owlapy/index \ No newline at end of file diff --git a/docs/usage/further_resources.md b/docs/usage/further_resources.md new file mode 100644 index 00000000..46a55f2a --- /dev/null +++ b/docs/usage/further_resources.md @@ -0,0 +1,59 @@ +# Further Resources + +Currently, we are working on our manuscript describing our framework. +If you want to attribute our library, please use our +[GitHub page](https://github.com/dice-group/owlapy) for reference. + +## More Inside the Project + +Examples and test cases provide a good starting point to get to know +the project better. Find them in the folders +[examples](https://github.com/dice-group/owlapy/tree/develop/examples) and [tests](https://github.com/dice-group/owlapy/tree/develop/tests). + +## Contribution + +Feel free to create a pull request and we will take a look on it. +Your commitment is well appreciated! + +## Questions + +In case you have any question, please contact: `caglardemir8@gmail.com` +or open an issue on our [GitHub issues page](https://github.com/dice-group/owlapy/issues). + +## Coverage Report +The coverage report is generated using [coverage.py](https://coverage.readthedocs.io/en/7.6.1/). + +``` +Name Stmts Miss Cover Missing +---------------------------------------------------------------------------------- +owlapy/__init__.py 4 0 100% +owlapy/class_expression/__init__.py 8 0 100% +owlapy/class_expression/class_expression.py 34 2 94% 58, 62 +owlapy/class_expression/nary_boolean_expression.py 24 0 100% +owlapy/class_expression/owl_class.py 33 1 97% 44 +owlapy/class_expression/restriction.py 313 26 92% 41, 49, 68, 71, 89, 170, 245-246, 302, 305, 335, 340, 343, 426, 451, 499, 502, 579-580, 616, 659, 662, 700, 703, 751, 823 +owlapy/converter.py 397 189 52% 52-68, 75-76, 79, 82, 152, 157, 169, 176, 184, 246-257, 264-282, 294, 304-307, 313-359, 366-387, 394-401, 417-420, 431, 451, 460-481, 489-491, 498-511, 515-521, 525-548, 552-555, 559-560, 564-576, 580-587, 591-592, 620, 624-628 +owlapy/iri.py 79 7 91% 54, 69, 82, 97, 128, 133, 150 +owlapy/meta_classes.py 11 0 100% +owlapy/namespaces.py 27 3 89% 36, 40, 43 +owlapy/owl_annotation.py 17 4 76% 17, 25, 43, 51 +owlapy/owl_axiom.py 518 157 70% 36, 39, 42, 45, 59, 111-113, 116, 136-138, 141, 144, 147-150, 153, 182-184, 187, 190, 193, 196-200, 203, 253-256, 259-261, 264, 288, 291, 294, 332-335, 338-340, 343, 398-401, 404-406, 409, 533-536, 539, 561-563, 566, 569, 572, 575, 578-581, 584, 620-623, 626, 645-648, 652, 656, 674-675, 683, 692, 695-697, 700, 711, 733-737, 745, 753, 761, 764-766, 769, 786-788, 791, 794, 797-800, 803, 822-824, 827, 830, 833-836, 839, 858-860, 863, 866, 869-872, 875, 905-908, 911, 982-985, 988, 1018, 1044, 1071-1073, 1076, 1091, 1103, 1116, 1129, 1142, 1157, 1172, 1185-1187, 1190, 1208, 1227-1230, 1233, 1254-1257, 1260 +owlapy/owl_data_ranges.py 40 1 98% 46 +owlapy/owl_datatype.py 20 2 90% 33-34 +owlapy/owl_individual.py 20 1 95% 37 +owlapy/owl_literal.py 286 73 74% 49, 77, 86, 90, 99, 103, 112, 116, 125, 129, 138, 142, 151, 155, 164, 169, 173, 203, 208, 217, 221, 244, 247-249, 252, 258, 262, 288, 293, 302, 306, 311, 323, 329, 332-334, 337, 340, 346, 350, 355, 373, 376-378, 381, 387, 391, 415, 418-420, 423, 429, 433, 454, 459, 462-464, 467, 473, 477, 489-491, 494, 497-499, 502 +owlapy/owl_object.py 27 4 85% 24, 79-81 +owlapy/owl_ontology.py 391 40 90% 86, 97-100, 103, 109-111, 249, 292-295, 304, 312, 329, 341, 345, 358, 371, 376, 379-381, 384, 423, 433, 449-450, 473-474, 553-554, 595, 599, 603, 629, 736, 742, 750 +owlapy/owl_ontology_manager.py 568 167 71% 48, 140, 151, 155, 168-169, 177, 200, 208-211, 312-318, 341-350, 355-376, 396, 466, 469, 474-496, 501-511, 521-527, 539, 542-543, 583, 588-593, 603, 608, 625, 634-645, 650-665, 676, 681, 691, 703, 707, 743, 749, 760, 766, 771-795, 800-807, 825-831, 850, 853, 859-862, 888 +owlapy/owl_property.py 69 11 84% 17, 24, 32, 40, 67, 76, 126, 158, 162, 174, 193 +owlapy/owl_reasoner.py 841 175 79% 452-455, 572, 584-586, 591-597, 604, 653-659, 665-669, 727-734, 760, 795-799, 825-828, 856-858, 860-862, 871, 884-886, 888-890, 897, 902-904, 924, 928-929, 942-944, 965, 1010-1012, 1113, 1121, 1124, 1127, 1130, 1133, 1136, 1139, 1142, 1145, 1160-1162, 1168, 1172, 1175, 1178, 1181, 1184, 1187, 1193, 1196, 1210, 1240-1243, 1251-1290, 1305, 1318-1328, 1353-1356, 1372, 1386, 1456-1460, 1488, 1498-1502, 1510-1514, 1555-1561, 1573, 1632, 1635, 1638, 1641, 1644, 1647, 1650, 1653, 1657, 1661, 1665, 1668, 1671, 1674, 1677, 1680, 1683, 1687, 1691, 1694, 1697 +owlapy/owlapi_adaptor.py 130 65 50% 18, 74-76, 91-96, 110-115, 151-152, 164-165, 179-180, 195-196, 214, 232, 251, 271, 287, 305, 320, 333, 346, 361, 376, 390, 404, 419, 434, 450, 454-483, 511 +owlapy/owlapi_mapper.py 103 14 86% 35, 51, 72, 76, 80, 84, 88, 133-136, 141, 145, 149 +owlapy/parser.py 371 16 96% 316, 327, 400-401, 416, 577, 618, 656, 667, 721, 723, 751-752, 763, 779-780 +owlapy/providers.py 38 3 92% 41, 54, 56 +owlapy/render.py 290 46 84% 79-114, 143-158, 176, 180, 186, 206, 222, 231, 236, 241, 375, 379, 386, 405, 421, 430, 435, 440 +owlapy/utils.py 766 227 70% 164, 168, 172, 178, 184-188, 192-196, 200, 204, 208, 214, 218, 222, 226, 230, 236, 242, 248, 252, 256, 260, 264-267, 271-274, 278, 285, 300-302, 305-314, 317, 320, 323, 326, 329, 333-339, 343, 354, 358, 362, 366, 370, 374-378, 382-386, 390-394, 398-402, 406, 410, 414-419, 423-428, 432-437, 441, 445, 449-453, 457-461, 465-469, 473-477, 481-485, 489, 493-497, 501, 505-510, 514-519, 523-528, 532, 536-540, 545, 554, 558, 562, 566, 570, 574, 578, 582-587, 591-597, 601, 605, 609, 614, 619, 624, 628, 632, 636, 640, 644-647, 651-654, 658, 662, 666, 671, 676, 681, 685, 736, 740, 746, 748, 751, 753, 796, 852, 866-868, 877, 919-920, 940, 1039, 1044, 1049, 1071, 1075, 1083, 1087, 1092, 1164-1182, 1195-1197, 1202-1206 +owlapy/vocab.py 92 4 96% 32, 35, 113-114 +---------------------------------------------------------------------------------- +TOTAL 5517 1238 78% +``` \ No newline at end of file diff --git a/docs/usage/main.md b/docs/usage/main.md index 3e2cc555..fa94d25d 100644 --- a/docs/usage/main.md +++ b/docs/usage/main.md @@ -1,6 +1,6 @@ # About owlapy -**Version:** owlapy 1.1.1 +**Version:** owlapy 1.2.0 **GitHub repository:** [https://github.com/dice-group/owlapy](https://github.com/dice-group/owlapy) diff --git a/docs/usage/ontologies.md b/docs/usage/ontologies.md index c2bd646f..098eaa2f 100644 --- a/docs/usage/ontologies.md +++ b/docs/usage/ontologies.md @@ -202,10 +202,10 @@ When an _OWLOntologyManager_ object is created, a new world is also created as a By calling the method `load_ontology(iri)` the ontology is loaded to this world. It possible to create several isolated “worlds”, sometimes -called “universe of speech”. This makes it possible in particular to load +called “universe of speech”. This makes it possible, in particular, to load the same ontology several times, independently, that is to say, without -the modifications made on one copy affecting the other copy. Sometimes the need to [isolate an ontology](reasoning_details.md#isolated-world) -arise. What that means is that you can have multiple reference of the same ontology in different +the modifications made on one copy affecting the other copy. Sometimes the need to isolate +an ontology arise. What that means is that you can have multiple reference of the same ontology in different worlds. ------------------------------------------------------------------------------------- diff --git a/docs/usage/owlapi_adaptor.md b/docs/usage/owlapi_adaptor.md index c7cda17e..3cfb288a 100644 --- a/docs/usage/owlapi_adaptor.md +++ b/docs/usage/owlapi_adaptor.md @@ -48,6 +48,10 @@ Isn't that awesome! `OWLAPIAdaptor` uses HermiT reasoner by default. You can choose between: "HermiT", "Pellet", "JFact" and "Openllet". +You can use the reasoning method directly from the adaptor but +for classes that require an [OWLReasoner](owlapi.owl_reasoner.OWLReasoner) +you can use [SyncReasoner](https://dice-group.github.io/owlapy/autoapi/owlapy/owl_reasoner/index.html#owlapy.owl_reasoner.SyncReasoner). + _**owlapi version**: 5.1.9_ ## Examples diff --git a/docs/usage/reasoner.md b/docs/usage/reasoner.md index 45432294..59bd898b 100644 --- a/docs/usage/reasoner.md +++ b/docs/usage/reasoner.md @@ -13,14 +13,16 @@ onto = manager.load_ontology(IRI.create("KGs/Family/father.owl")) ``` In our Owlapy library, we provide several **reasoners** to choose -from. Currently, there are the following reasoners available: +from: - [**OntologyReasoner**](owlapy.owl_reasoner.OntologyReasoner) Or differently Structural Reasoner, is the base reasoner in Owlapy. The functionalities of this reasoner are limited. It does not provide full reasoning in _ALCH_. Furthermore, it has no support for instances of complex class expressions, which is covered by the - other reasoners (SyncReasoner and FIC). We recommend to use the other reasoners for any heavy reasoning tasks. + other reasoners (SyncReasoner and FIC). This reasoner is used as + a base reasoner for FIC which overwrites the `instances` method. + We recommend using the other reasoners for any reasoning tasks. **Initialization:** @@ -31,30 +33,27 @@ from. Currently, there are the following reasoners available: ``` The structural reasoner requires an ontology ([OWLOntology](owlapy.owl_ontology.OWLOntology)). - The second argument is `isolate` argument which isolates the world (therefore the ontology) where the reasoner is - performing the reasoning. More on that on _[Reasoning Details](reasoning_details.md#isolated-world)_. - [**SyncReasoner**](owlapy.owl_reasoner.SyncReasoner) - Can perform full reasoning in _ALCH_ due to the use of HermiT/Pellet and provides support for - complex class expression instances (when using the method `instances`). SyncReasoner is more useful when - your main goal is reasoning over the ontology. + Can perform full reasoning in _ALCH_ due to the use of reasoners from + owlapi like HermiT, Pellet, etc. and provides support for + complex class expression instances (when using the method `instances`). + SyncReasoner is more useful when your main goal is reasoning over the ontology, + and you are familiarized with the java reasoners (HermiT, Pellet, ...). **Initialization:** ```python - from owlapy.owl_reasoner import SyncReasoner, BaseReasoner + from owlapy.owl_reasoner import SyncReasoner - sync_reasoner = SyncReasoner(onto, BaseReasoner.HERMIT, infer_property_values = True) + sync_reasoner = SyncReasoner(ontology_path="KGs/Mutagenesis/mutagenesis.owl", reasoner="HermiT") ``` - Sync Reasoner requires an ontology and a base reasoner of type [BaseReasoner](owlapy.owl_reasoner.BaseReasoner) - which is just an enumeration with two possible values: `BaseReasoner.HERMIT` and `BaseReasoner.PELLET`. - You can set the `infer_property_values` argument to `True` if you want the reasoner to infer - property values. `infer_data_property_values` is an additional argument when the base reasoner is set to - `BaseReasoner.PELLET`. The argument `isolated` is inherited from the base class + Sync Reasoner is made available by [owlapi adaptor](owlapi_adaptor.md) and requires the ontology path + together with a reasoner name from the possible set of reasoners: `"Hermit"`, `"Pellet"`, `"JFact"`, `"Openllet"`. - [**FastInstanceCheckerReasoner**](owlapy.owl_reasoner.FastInstanceCheckerReasoner) **(FIC)** @@ -74,7 +73,8 @@ from. Currently, there are the following reasoners available: ``` Besides the ontology, FIC requires a base reasoner to delegate any reasoning tasks not covered by it. This base reasoner - can be any other reasoner in Owlapy. `property_cache` specifies whether to cache property values. This + can be any other reasoner in Owlapy (usually it's [OntologyReasoner](owlapy.owl_reasoner.OntologyReasoner)). + `property_cache` specifies whether to cache property values. This requires more memory, but it speeds up the reasoning processes. If `negation_default` argument is set to `True` the missing facts in the ontology means false. The argument `sub_properties` is another boolean argument to specify whether you want to take sub properties in consideration @@ -83,15 +83,11 @@ from. Currently, there are the following reasoners available: ## Usage of the Reasoner All the reasoners available in the Owlapy library inherit from the class: [OWLReasonerEx](owlapy.owl_reasoner.OWLReasonerEx). This class provides some -extra convenient methods compared to its base class [OWLReasoner](owlapy.owl_reasoner.OWLReasoner), which is an -abstract class. -Further on, in this guide, we use -[SyncReasoner](owlapy.owl_reasoner.SyncReasoner). +extra convenient methods compared to its base abstract class [OWLReasoner](owlapy.owl_reasoner.OWLReasoner). +Further on, in this guide, we use [FastInstanceCheckerReasoner](owlapy.owl_reasoner.FastInstanceCheckerReasoner) to show the capabilities of a reasoner in Owlapy. -To give examples we consider the _father_ dataset. -If you are not already familiar with this small dataset, -you can find an overview of it [here](ontologies.md). +As mentioned earlier we will use the _father_ dataset to give examples. ## Class Reasoning @@ -109,9 +105,9 @@ from owlapy.iri import IRI namespace = "http://example.com/father#" male = OWLClass(IRI(namespace, "male")) -male_super_classes = sync_reasoner.super_classes(male) -male_sub_classes = sync_reasoner.sub_classes(male) -male_equivalent_classes = sync_reasoner.equivalent_classes(male) +male_super_classes = fic_reasoner.super_classes(male) +male_sub_classes = fic_reasoner.sub_classes(male) +male_equivalent_classes = fic_reasoner.equivalent_classes(male) ``` We define the _male_ class by creating an [OWLClass](owlapy.class_expression.owl_class.OWLClass) object. The @@ -127,6 +123,10 @@ means that it will return only the named classes. >**NOTE**: The extra arguments `direct` and `only_named` are also used in other methods that reason upon the class, object property, or data property hierarchy. +>**NOTE**: SyncReasoner implements OWLReasoner where we can specify the `only_named` argument +> in some methods but in java reasoners there is no use for such argument and therefore this +> argument is trivial when used in SyncReasoner's methods. + You can get all the types of a certain individual using `types` method: @@ -134,7 +134,7 @@ You can get all the types of a certain individual using `types` method: ```python anna = list(onto.individuals_in_signature()).pop() -anna_types = sync_reasoner.types(anna) +anna_types = fic_reasoner.types(anna) ``` We retrieve _anna_ as the first individual on the list of individuals @@ -144,7 +144,7 @@ of the 'Father' ontology. The `type` method only returns named classes. ## Object Properties and Data Properties Reasoning Owlapy reasoners offers some convenient methods for working with object properties and data properties. Below we show some of them, but you can always check all the methods in the -[SyncReasoner](owlapy.owl_reasoner.SyncReasoner) +[OWLReasoner](owlapy.owl_reasoner.OWLReasoner) class documentation. You can get all the object properties that an individual has by using the @@ -153,7 +153,7 @@ following method: ```python anna = individuals[0] -object_properties = sync_reasoner.ind_object_properties(anna) +object_properties = fic_reasoner.ind_object_properties(anna) ``` In this example, `object_properties` contains all the object properties that _anna_ has, which in our case would only be _hasChild_. @@ -162,7 +162,7 @@ Now we can get the individuals of this object property for _anna_. ```python for op in object_properties: - object_properties_values = sync_reasoner.object_property_values(anna, op) + object_properties_values = fic_reasoner.object_property_values(anna, op) for individual in object_properties_values: print(individual) ``` @@ -190,16 +190,16 @@ from owlapy.owl_property import OWLObjectProperty hasChild = OWLObjectProperty(IRI(namespace, "hasChild")) -equivalent_to_hasChild = sync_reasoner.equivalent_object_properties(hasChild) -hasChild_sub_properties = sync_reasoner.sub_object_properties(hasChild) +equivalent_to_hasChild = fic_reasoner.equivalent_object_properties(hasChild) +hasChild_sub_properties = fic_reasoner.sub_object_properties(hasChild) ``` In case you want to get the domains and ranges of an object property use the following: ```python -hasChild_domains = sync_reasoner.object_property_domains(hasChild) -hasChild_ranges = sync_reasoner.object_property_ranges(hasChild) +hasChild_domains = fic_reasoner.object_property_domains(hasChild) +hasChild_ranges = fic_reasoner.object_property_ranges(hasChild) ``` > **NOTE:** Again, you can do the same for data properties but instead of the word 'object' in the @@ -217,15 +217,15 @@ Let us now show a simple example by finding the instances of the class _male_ an ```python -male_individuals = sync_reasoner.instances(male) +male_individuals = fic_reasoner.instances(male) for ind in male_individuals: print(ind) ``` ----------------------------------------------------------------------- -In this guide we covered the main functionalities of the reasoners in Owlapy. More -details are provided in the next guide. +In this guide we covered the main functionalities of the reasoners in Owlapy. +In the next one, we speak about owlapi adaptor and how can make use of owlapi in owlapy. diff --git a/examples/ontology_reasoning.py b/examples/ontology_reasoning.py index 03959372..c48f655b 100644 --- a/examples/ontology_reasoning.py +++ b/examples/ontology_reasoning.py @@ -4,7 +4,7 @@ from owlapy.owl_individual import OWLNamedIndividual from owlapy.owl_ontology_manager import OntologyManager from owlapy.owl_property import OWLDataProperty, OWLObjectProperty -from owlapy.owl_reasoner import OntologyReasoner, SyncReasoner, BaseReasoner +from owlapy.owl_reasoner import OntologyReasoner data_file = '../KGs/Test/test_ontology.owl' NS = 'http://www.semanticweb.org/stefan/ontologies/2023/1/untitled-ontology-11#' @@ -130,7 +130,7 @@ # ---------------------------------------- Reasoning ---------------------------------------- -reasoner = SyncReasoner(onto, BaseReasoner.HERMIT) +reasoner = FastInstanceCheckerReasoner(onto) # Instances t1 = list(reasoner.instances(N)) diff --git a/examples/using_owlapi_adaptor.py b/examples/using_owlapi_adaptor.py index 7c9dbaec..44d82039 100644 --- a/examples/using_owlapi_adaptor.py +++ b/examples/using_owlapi_adaptor.py @@ -20,11 +20,11 @@ print("Individuals that are brother and father at the same time:") [print(_) for _ in instances] -# Convert from owlapy to owlapi -py_to_pi = adaptor.convert_to_owlapi(brother_and_father) +# Map the class expression from owlapy to owlapi +py_to_pi = adaptor.mapper.map_(brother_and_father) -# Convert from owlapi to owlapy -pi_to_py = adaptor.convert_from_owlapi(py_to_pi, "http://www.benchmark.org/family#") +# Map the class expression from owlapi to owlapy +pi_to_py = adaptor.mapper.map_(py_to_pi) print("----------------------") print(f"Owlapy ce: {pi_to_py}") diff --git a/owlapy/__init__.py b/owlapy/__init__.py index 4b50bc94..f408d9d5 100644 --- a/owlapy/__init__.py +++ b/owlapy/__init__.py @@ -1,4 +1,4 @@ from .render import owl_expression_to_dl, owl_expression_to_manchester from .parser import dl_to_owl_expression, manchester_to_owl_expression from .converter import owl_expression_to_sparql -__version__ = '1.1.1' +__version__ = '1.2.1' diff --git a/owlapy/iri.py b/owlapy/iri.py index 2a65f33f..c7234414 100644 --- a/owlapy/iri.py +++ b/owlapy/iri.py @@ -43,7 +43,7 @@ def __init__(self, namespace: Union[str, Namespaces], remainder: str): if isinstance(namespace, Namespaces): namespace = namespace.ns else: - assert namespace[-1] in ("/", ":", "#") + assert namespace[-1] in ("/", ":", "#"), "It should be a valid IRI based on /, :, and #" import sys self._namespace = sys.intern(namespace) self._remainder = remainder @@ -156,14 +156,6 @@ def reminder(self) -> str: Returns: The string corresponding to the reminder of the IRI. """ - return self.reminder() - - def get_short_form(self) -> str: - """Gets the short form. - - Returns: - A string that represents the short form. - """ return self._remainder def get_namespace(self) -> str: diff --git a/owlapy/owl_axiom.py b/owlapy/owl_axiom.py index 4fd4119f..2792738c 100644 --- a/owlapy/owl_axiom.py +++ b/owlapy/owl_axiom.py @@ -723,7 +723,8 @@ class OWLAnnotationAssertionAxiom(OWLAnnotationAxiom): _subject: OWLAnnotationSubject _annotation: OWLAnnotation - def __init__(self, subject: OWLAnnotationSubject, annotation: OWLAnnotation): + def __init__(self, subject: OWLAnnotationSubject, annotation: OWLAnnotation, + annotations: Optional[Iterable['OWLAnnotation']] = None): """Get an annotation assertion axiom - with annotations. Args: @@ -732,7 +733,7 @@ def __init__(self, subject: OWLAnnotationSubject, annotation: OWLAnnotation): """ assert isinstance(subject, OWLAnnotationSubject) assert isinstance(annotation, OWLAnnotation) - + super().__init__(annotations) self._subject = subject self._annotation = annotation @@ -769,7 +770,7 @@ def __hash__(self): return hash((self._subject, self._annotation)) def __repr__(self): - return f'OWLAnnotationAssertionAxiom({self._subject}, {self._annotation})' + return f'OWLAnnotationAssertionAxiom({self._subject}, {self._annotation}, {self.annotations()})' class OWLSubAnnotationPropertyOfAxiom(OWLAnnotationAxiom): """An annotation subproperty axiom SubAnnotationPropertyOf( AP1 AP2 ) states that the annotation property AP1 is a subproperty of the annotation property AP2. diff --git a/owlapy/owl_ontology_manager.py b/owlapy/owl_ontology_manager.py index 2d5ff52e..587ddb5d 100644 --- a/owlapy/owl_ontology_manager.py +++ b/owlapy/owl_ontology_manager.py @@ -27,7 +27,7 @@ from owlapy.owl_ontology import OWLOntology, Ontology, ToOwlready2 from owlapy.owl_property import OWLDataProperty, OWLObjectInverseOf, OWLObjectProperty, \ OWLProperty - +from typing import Union class OWLOntologyChange(metaclass=ABCMeta): """Represents an ontology change.""" @@ -849,10 +849,18 @@ def __init__(self, world_store=None): else: self._world = owlready2.World(filename=world_store) - def create_ontology(self, iri: IRI) -> 'Ontology': + def create_ontology(self, iri:Union[str,IRI]=None) -> 'Ontology': + if isinstance(iri, str): + iri=IRI.create(iri) + else: + assert isinstance(iri, IRI), "iri either must be string or an instance of IRI Class" return Ontology(self, iri, load=False) - def load_ontology(self, iri: IRI) -> 'Ontology': + def load_ontology(self, iri: Union[str,IRI]=None) -> 'Ontology': + if isinstance(iri, str): + iri=IRI.create(iri) + else: + assert isinstance(iri, IRI), "iri either must be string or an instance of IRI Class" return Ontology(self, iri, load=True) def apply_change(self, change: OWLOntologyChange): @@ -879,8 +887,7 @@ def save_ontology(self, ontology: OWLOntology, document_iri: IRI): filename = document_iri.as_str()[len('file:/'):] ont_x.save(file=filename) else: - # TODO XXX - raise NotImplementedError + raise NotImplementedError("Couldn't save because the namespace of document_iri does not start with **file:/**") def save_world(self): """Saves the actual state of the quadstore in the SQLite3 file. diff --git a/owlapy/owl_reasoner.py b/owlapy/owl_reasoner.py index db5ba271..318ad2c9 100644 --- a/owlapy/owl_reasoner.py +++ b/owlapy/owl_reasoner.py @@ -1,14 +1,11 @@ """OWL Reasoner""" import operator -import os -import types from abc import ABCMeta, abstractmethod from collections import defaultdict -from enum import Enum, auto from functools import singledispatchmethod, reduce from itertools import chain, repeat from types import MappingProxyType, FunctionType -from typing import DefaultDict, Iterable, Dict, Mapping, Set, Type, TypeVar, Optional, FrozenSet, List, cast +from typing import DefaultDict, Iterable, Dict, Mapping, Set, Type, TypeVar, Optional, FrozenSet import logging import owlready2 @@ -20,20 +17,27 @@ OWLDataAllValuesFrom from owlapy.class_expression import OWLClass from owlapy.iri import IRI -from owlapy.owl_axiom import OWLAxiom, OWLSubClassOfAxiom +from owlapy.owl_axiom import OWLSubClassOfAxiom from owlapy.owl_data_ranges import OWLDataRange, OWLDataComplementOf, OWLDataUnionOf, OWLDataIntersectionOf from owlapy.owl_datatype import OWLDatatype -from owlapy.owl_ontology import OWLOntology, Ontology, _parse_concept_to_owlapy, ToOwlready2 +from owlapy.owl_object import OWLEntity +from owlapy.owl_ontology import OWLOntology, Ontology, _parse_concept_to_owlapy from owlapy.owl_ontology_manager import OntologyManager from owlapy.owl_property import OWLObjectPropertyExpression, OWLDataProperty, OWLObjectProperty, OWLObjectInverseOf, \ OWLPropertyExpression, OWLDataPropertyExpression from owlapy.owl_individual import OWLNamedIndividual from owlapy.owl_literal import OWLLiteral +from owlapy.owlapi_adaptor import OWLAPIAdaptor from owlapy.utils import LRUCache logger = logging.getLogger(__name__) +_P = TypeVar('_P', bound=OWLPropertyExpression) +# TODO:CD:The name of the classes defined with metaclass=ABCMeta should reflect that +# TODO:CD: An instance cannot be created from those classes. +# TODO:CD: We should move those Abstract Base Classes into a respective package, e.g. +# TODO:CD: owlapy/abstract_owl_reasoner/abstract_owl_reasoner.py should contain OWLReasoner and OWLReasonerEx class OWLReasoner(metaclass=ABCMeta): """An OWLReasoner reasons over a set of axioms (the set of reasoner axioms) that is based on the imports closure of a particular ontology - the "root" ontology.""" @@ -188,16 +192,19 @@ def equivalent_data_properties(self, dp: OWLDataProperty) -> Iterable[OWLDataPro pass @abstractmethod - def data_property_values(self, ind: OWLNamedIndividual, pe: OWLDataProperty, direct: bool = True) \ + def data_property_values(self, e: OWLEntity, pe: OWLDataProperty, direct: bool = True) \ -> Iterable['OWLLiteral']: - """Gets the data property values for the specified individual and data property expression. + """Gets the data property values for the specified entity and data property expression. Args: - ind: The individual that is the subject of the data property values. - pe: The data property expression whose values are to be retrieved for the specified individual. + e: The owl entity (usually an individual) that is the subject of the data property values. + pe: The data property expression whose values are to be retrieved for the specified entity. direct: Specifies if the direct values should be retrieved (True), or if all values should be retrieved (False), so that sub properties are taken into account. + Note: Can be used to get values, for example, of 'label' property of owl entities such as classes and properties + too (not only individuals). + Returns: A set of OWLLiterals containing literals such that for each literal l in the set, the set of reasoner axioms entails DataPropertyAssertion(pe ind l). @@ -221,12 +228,6 @@ def object_property_values(self, ind: OWLNamedIndividual, pe: OWLObjectPropertyE """ pass - @abstractmethod - def flush(self) -> None: - """Flushes any changes stored in the buffer, which causes the reasoner to take into consideration the changes - the current root ontology specified by the changes.""" - pass - @abstractmethod def instances(self, ce: OWLClassExpression, direct: bool = False) -> Iterable[OWLNamedIndividual]: """Gets the individuals which are instances of the specified class expression. @@ -386,11 +387,6 @@ def get_root_ontology(self) -> OWLOntology: this ontology and its import's closure.""" pass - @abstractmethod - def is_isolated(self): - """Return True if this reasoner is using an isolated ontology.""" - pass - @abstractmethod def super_classes(self, ce: OWLClassExpression, direct: bool = False, only_named: bool = True) \ -> Iterable[OWLClassExpression]: @@ -409,19 +405,6 @@ class expression with respect to the imports closure of the root ontology. If ce is equivalent to owl:Thing then nothing will be returned. """ pass - - -class BaseReasoner(Enum): - """Enumeration class for base reasoner when calling sync_reasoner. - - Attributes: - PELLET: Pellet base reasoner. - HERMIT: HermiT base reasoner. - """ - PELLET = auto() - HERMIT = auto() - - class OWLReasonerEx(OWLReasoner, metaclass=ABCMeta): """Extra convenience methods for OWL Reasoners""" @@ -503,64 +486,24 @@ def ind_object_properties(self, ind: OWLNamedIndividual, direct: bool = True) -> except StopIteration: pass - class OntologyReasoner(OWLReasonerEx): __slots__ = '_ontology', '_world' - + # TODO: CD: We will remove owlready2 from owlapy _ontology: Ontology _world: owlready2.World - def __init__(self, ontology: Ontology, isolate: bool = False): + def __init__(self, ontology: Ontology): """ Base reasoner in Ontolearn, used to reason in the given ontology. Args: ontology: The ontology that should be used by the reasoner. - isolate: Whether to isolate the reasoner in a new world + copy of the original ontology. - Useful if you create multiple reasoner instances in the same script. """ super().__init__(ontology) assert isinstance(ontology, Ontology) - - if isolate: - self._isolated = True - new_manager = OntologyManager() - self._ontology = new_manager.load_ontology(ontology.get_original_iri()) - self._world = new_manager._world - print("INFO OWLReasoner :: Using isolated ontology\n" - "INFO OWLReasoner :: Changes you make in the original ontology won't be reflected to the isolated" - " ontology\n" - "INFO OWLReasoner :: To make changes on the isolated ontology use the method " - "`update_isolated_ontology()`") - - else: - self._isolated = False - self._ontology = ontology - self._world = ontology._world - - def update_isolated_ontology(self, axioms_to_add: List[OWLAxiom] = None, - axioms_to_remove: List[OWLAxiom] = None): - """ - Add or remove axioms to the isolated ontology that the reasoner is using. - - Args: - axioms_to_add (List[OWLAxiom]): Axioms to add to the isolated ontology. - axioms_to_remove (List[OWLAxiom]): Axioms to remove from the isolated ontology. - """ - if self._isolated: - if axioms_to_add is None and axioms_to_remove is None: - raise ValueError(f"At least one argument should be specified in method: " - f"{self.update_isolated_ontology.__name__}") - manager = self._ontology.get_owl_ontology_manager() - if axioms_to_add is not None: - for axiom in axioms_to_add: - manager.add_axiom(self._ontology, axiom) - if axioms_to_remove is not None: - for axiom in axioms_to_remove: - manager.remove_axiom(self._ontology, axiom) - else: - raise AssertionError(f"Misuse of method '{self.update_isolated_ontology.__name__}'. The reasoner is not " - f"using an isolated ontology.") + self._isolated = False + self._ontology = ontology + self._world = ontology._world def data_property_domains(self, pe: OWLDataProperty, direct: bool = False) -> Iterable[OWLClassExpression]: domains = {d.get_domain() for d in self.get_root_ontology().data_property_domain_axioms(pe)} @@ -660,9 +603,9 @@ def same_individuals(self, ind: OWLNamedIndividual) -> Iterable[OWLNamedIndividu yield from (OWLNamedIndividual(IRI.create(d_i.iri)) for d_i in i.equivalent_to if isinstance(d_i, owlready2.Thing)) - def data_property_values(self, ind: OWLNamedIndividual, pe: OWLDataProperty, direct: bool = True) \ + def data_property_values(self, e: OWLEntity, pe: OWLDataProperty, direct: bool = True) \ -> Iterable[OWLLiteral]: - i: owlready2.Thing = self._world[ind.str] + i: owlready2.Thing = self._world[e.str] p: owlready2.DataPropertyClass = self._world[pe.str] retrieval_func = p._get_values_for_individual if direct else p._get_indirect_values_for_individual for val in retrieval_func(i): @@ -706,9 +649,6 @@ def object_property_values(self, ind: OWLNamedIndividual, pe: OWLObjectPropertyE else: raise NotImplementedError(pe) - def flush(self) -> None: - pass - def instances(self, ce: OWLClassExpression, direct: bool = False) -> Iterable[OWLNamedIndividual]: if direct: if isinstance(ce, OWLClass): @@ -981,17 +921,6 @@ def _sup_or_sub_data_properties(self, dp: OWLDataProperty, direct: bool = False, yield from self._sup_or_sub_data_properties_recursive(dp, seen_set, super_or_sub) def super_data_properties(self, dp: OWLDataProperty, direct: bool = False) -> Iterable[OWLDataProperty]: - """Gets the stream of data properties that are the strict (potentially direct) super properties of the - specified data property with respect to the imports closure of the root ontology. - - Args: - dp (OWLDataProperty): The data property whose super properties are to be retrieved. - direct (bool): Specifies if the direct super properties should be retrieved (True) or if the all - super properties (ancestors) should be retrieved (False). - - Returns: - Iterable of super properties. - """ yield from self._sup_or_sub_data_properties(dp, direct, "super") def sub_data_properties(self, dp: OWLDataProperty, direct: bool = False) -> Iterable[OWLDataProperty]: @@ -1077,35 +1006,30 @@ def types(self, ind: OWLNamedIndividual, direct: bool = False) -> Iterable[OWLCl yield OWLClass(IRI.create(c.iri)) # Anonymous classes are ignored - def _sync_reasoner(self, other_reasoner: BaseReasoner = None, - infer_property_values: bool = True, - infer_data_property_values: bool = True, debug: bool = False) -> None: - """Call Owlready2's sync_reasoner method, which spawns a Java process on a temp file to infer more. - - Args: - other_reasoner: Set to BaseReasoner.PELLET (default) or BaseReasoner.HERMIT. - infer_property_values: Whether to infer property values. - infer_data_property_values: Whether to infer data property values (only for PELLET). - """ - assert other_reasoner is None or isinstance(other_reasoner, BaseReasoner) - with self.get_root_ontology()._onto: - if other_reasoner == BaseReasoner.HERMIT: - owlready2.sync_reasoner_hermit(self._world, infer_property_values=infer_property_values, debug=debug) - else: - owlready2.sync_reasoner_pellet(self._world, - infer_property_values=infer_property_values, - infer_data_property_values=infer_data_property_values, - debug=debug) + # Deprecated + # def _sync_reasoner(self, other_reasoner: BaseReasoner = None, + # infer_property_values: bool = True, + # infer_data_property_values: bool = True, debug: bool = False) -> None: + # """Call Owlready2's sync_reasoner method, which spawns a Java process on a temp file to infer more. + # + # Args: + # other_reasoner: Set to BaseReasoner.PELLET (default) or BaseReasoner.HERMIT. + # infer_property_values: Whether to infer property values. + # infer_data_property_values: Whether to infer data property values (only for PELLET). + # """ + # assert other_reasoner is None or isinstance(other_reasoner, BaseReasoner) + # with self.get_root_ontology()._onto: + # if other_reasoner == BaseReasoner.HERMIT: + # owlready2.sync_reasoner_hermit(self._world, infer_property_values=infer_property_values, debug=debug) + # else: + # owlready2.sync_reasoner_pellet(self._world, + # infer_property_values=infer_property_values, + # infer_data_property_values=infer_data_property_values, + # debug=debug) def get_root_ontology(self) -> OWLOntology: return self._ontology - def is_isolated(self): - """Return True if this reasoner is using an isolated ontology.""" - return self._isolated - - -_P = TypeVar('_P', bound=OWLPropertyExpression) class FastInstanceCheckerReasoner(OWLReasonerEx): """Tries to check instances fast (but maybe incomplete).""" @@ -1152,10 +1076,7 @@ def __init__(self, ontology: OWLOntology, base_reasoner: OWLReasoner, *, :func:`OWLReasoner_FastInstanceChecker.instances` retrieval. """ super().__init__(ontology) - if base_reasoner.is_isolated(): - self._ontology = base_reasoner.get_root_ontology() - else: - self._ontology = ontology + self._ontology = ontology self._base_reasoner = base_reasoner self._property_cache = property_cache self._negation_default = negation_default @@ -1185,13 +1106,6 @@ def reset(self): """The reset method shall reset any cached state.""" self._init() - def is_isolated(self): - return self._base_reasoner.is_isolated() - - def is_using_triplestore(self): - # TODO: Deprecated! Remove after it is removed from OWLReasoner in owlapy - pass - def data_property_domains(self, pe: OWLDataProperty, direct: bool = False) -> Iterable[OWLClassExpression]: yield from self._base_reasoner.data_property_domains(pe, direct=direct) @@ -1216,9 +1130,9 @@ def different_individuals(self, ce: OWLNamedIndividual) -> Iterable[OWLNamedIndi def same_individuals(self, ce: OWLNamedIndividual) -> Iterable[OWLNamedIndividual]: yield from self._base_reasoner.same_individuals(ce) - def data_property_values(self, ind: OWLNamedIndividual, pe: OWLDataProperty, direct: bool = True) \ + def data_property_values(self, e: OWLEntity, pe: OWLDataProperty, direct: bool = True) \ -> Iterable[OWLLiteral]: - yield from self._base_reasoner.data_property_values(ind, pe, direct) + yield from self._base_reasoner.data_property_values(e, pe, direct) def all_data_property_values(self, pe: OWLDataProperty, direct: bool = True) -> Iterable[OWLLiteral]: yield from self._base_reasoner.all_data_property_values(pe, direct) @@ -1227,9 +1141,6 @@ def object_property_values(self, ind: OWLNamedIndividual, pe: OWLObjectPropertyE -> Iterable[OWLNamedIndividual]: yield from self._base_reasoner.object_property_values(ind, pe, direct) - def flush(self) -> None: - self._base_reasoner.flush() - def instances(self, ce: OWLClassExpression, direct: bool = False) -> Iterable[OWLNamedIndividual]: if direct: if not self.__warned & 2: @@ -1685,84 +1596,88 @@ def _retrieve_triples(self, pe: OWLPropertyExpression) -> Iterable: yield from relations -class SyncReasoner(OntologyReasoner): - __slots__ = '_cnt', '_conv', '_base_reasoner' +class SyncReasoner(OWLReasonerEx): - _conv: ToOwlready2 - _base_reasoner: BaseReasoner - - def __init__(self, ontology: Ontology, base_reasoner: Optional[BaseReasoner] = None, - infer_property_values: bool = True, infer_data_property_values: bool = True, isolate: bool = False): + def __init__(self, ontology_path: str, reasoner="HermiT"): """ - OWL Reasoner with support for Complex Class Expression Instances + sync_reasoner. + OWL reasoner that syncs to other reasoners like HermiT,Pellet,etc. Args: - ontology: The ontology that should be used by the reasoner. - base_reasoner: Set to BaseReasoner.PELLET (default) or BaseReasoner.HERMIT. - infer_property_values: Whether to infer property values. - infer_data_property_values: Whether to infer data property values (only for PELLET). - isolate: Whether to isolate the reasoner in a new world + copy of the original ontology. - Useful if you create multiple reasoner instances in the same script. + ontology_path: Path of ontology that should be used by the reasoner. + reasoner: Choose from (case-sensitive): ["HermiT", "Pellet", "JFact", "Openllet"]. Default: "HermiT". """ + self.manager = OntologyManager() + self.ontology = self.manager.load_ontology(IRI.create("file://" + ontology_path)) + super().__init__(self.ontology) + self.adaptor = OWLAPIAdaptor(ontology_path, reasoner) - super().__init__(ontology, isolate) - if isolate: - new_manager = OntologyManager() - self.reference_ontology = new_manager.load_ontology(ontology.get_original_iri()) - self.reference_iri = IRI.create(f'file:/isolated_ontology_{id(self.reference_ontology)}.owl') - new_manager.save_ontology(self.reference_ontology, self.reference_iri) + def instances(self, ce: OWLClassExpression, direct: bool = False) -> Iterable[OWLNamedIndividual]: + yield from self.adaptor.instances(ce, direct) - self._cnt = 1 - self._conv = ToOwlready2(world=self._world) - self._base_reasoner = base_reasoner - self._sync_reasoner(self._base_reasoner, infer_property_values, infer_data_property_values) - self.infer_property_values = infer_property_values - self.infer_data_property_values = infer_data_property_values - - def update_isolated_ontology(self, axioms_to_add: List[OWLAxiom] = None, - axioms_to_remove: List[OWLAxiom] = None): - if self._isolated: - if axioms_to_add is None and axioms_to_remove is None: - raise ValueError(f"At least one argument should be specified in method: " - f"{self.update_isolated_ontology.__name__}") - self._ontology = self.reference_ontology - super().update_isolated_ontology(axioms_to_add, axioms_to_remove) - self.reference_ontology.get_owl_ontology_manager().save_ontology(self._ontology, self.reference_iri) - new_manager = OntologyManager() - self._ontology = new_manager.load_ontology(IRI.create(f'file://isolated_ontology_' - f'{id(self.reference_ontology)}.owl')) - self._world = new_manager._world - self._sync_reasoner(self._base_reasoner, self.infer_property_values, self.infer_data_property_values) - else: - raise AssertionError(f"Misuse of method '{self.update_isolated_ontology.__name__}'. The reasoner is not " - f"using an isolated ontology.") + def data_property_domains(self, pe: OWLDataProperty, direct: bool = False) -> Iterable[OWLClassExpression]: + yield from self.adaptor.data_property_domains(pe, direct) - def instances(self, ce: OWLClassExpression, direct: bool = False) -> Iterable[OWLNamedIndividual]: - if direct: - logging.warning("direct not implemented") - with self._world.get_ontology("http://temp.classes/"): - temp_pred = cast(owlready2.ThingClass, types.new_class("TempCls%d" % self._cnt, (owlready2.owl.Thing,))) - temp_pred.equivalent_to = [self._conv.map_concept(ce)] - if self._base_reasoner == BaseReasoner.HERMIT: - owlready2.sync_reasoner_hermit(self._world.get_ontology("http://temp.classes/"), - self.infer_property_values) - else: - owlready2.sync_reasoner_pellet(self._world.get_ontology("http://temp.classes/"), - self.infer_property_values, self.infer_data_property_values) - instances = list(temp_pred.instances(world=self._world)) - temp_pred.equivalent_to = [] - try: - owlready2.destroy_entity(temp_pred) - except AttributeError as e: - logger.info(f"AttributeError: {e} Source: {__file__} (you can ignore this)") - self._cnt += 1 - for i in instances: - yield OWLNamedIndividual(IRI.create(i.iri)) - - def __del__(self): - if self._isolated: - file_path = f"isolated_ontology_{id(self.reference_ontology)}.owl" - try: - os.remove(file_path) - except OSError as e: - logger.warning(f"Error deleting {file_path}: {e}") + def object_property_domains(self, pe: OWLObjectProperty, direct: bool = False) -> Iterable[OWLClassExpression]: + yield from self.adaptor.object_property_domains(pe, direct) + + def object_property_ranges(self, pe: OWLObjectProperty, direct: bool = False) -> Iterable[OWLClassExpression]: + yield from self.adaptor.object_property_ranges(pe, direct) + + def equivalent_classes(self, ce: OWLClassExpression, only_named: bool = True) -> Iterable[OWLClassExpression]: + yield from self.adaptor.equivalent_classes(ce) + + def disjoint_classes(self, ce: OWLClassExpression, only_named: bool = True) -> Iterable[OWLClassExpression]: + yield from self.adaptor.disjoint_classes(ce) + + def different_individuals(self, ind: OWLNamedIndividual) -> Iterable[OWLNamedIndividual]: + yield from self.adaptor.different_individuals(ind) + + def same_individuals(self, ind: OWLNamedIndividual) -> Iterable[OWLNamedIndividual]: + yield from self.adaptor.same_individuals(ind) + + def data_property_values(self, e: OWLEntity, pe: OWLDataProperty, direct: bool = True) -> Iterable[OWLLiteral]: + yield from self.adaptor.data_property_values(e, pe) + + def object_property_values(self, ind: OWLNamedIndividual, pe: OWLObjectPropertyExpression, direct: bool = False) -> \ + Iterable[OWLNamedIndividual]: + yield from self.adaptor.object_property_values(ind, pe) + + def sub_classes(self, ce: OWLClassExpression, direct: bool = False, only_named: bool = True) -> Iterable[ + OWLClassExpression]: + yield from self.adaptor.sub_classes(ce, direct) + + def super_classes(self, ce: OWLClassExpression, direct: bool = False, only_named: bool = True) -> Iterable[ + OWLClassExpression]: + yield from self.adaptor.super_classes(ce, direct) + + def equivalent_object_properties(self, op: OWLObjectPropertyExpression) -> Iterable[OWLObjectPropertyExpression]: + yield from self.adaptor.equivalent_object_properties(op) + + def equivalent_data_properties(self, dp: OWLDataProperty) -> Iterable[OWLDataProperty]: + yield from self.adaptor.equivalent_data_properties(dp) + + def disjoint_object_properties(self, op: OWLObjectPropertyExpression) -> Iterable[OWLObjectPropertyExpression]: + yield from self.adaptor.disjoint_object_properties(op) + + def disjoint_data_properties(self, dp: OWLDataProperty) -> Iterable[OWLDataProperty]: + yield from self.adaptor.disjoint_data_properties(dp) + + def super_data_properties(self, dp: OWLDataProperty, direct: bool = False) -> Iterable[OWLDataProperty]: + yield from self.adaptor.super_data_properties(dp, direct) + + def sub_data_properties(self, dp: OWLDataProperty, direct: bool = False) -> Iterable[OWLDataProperty]: + yield from self.adaptor.sub_data_properties(dp, direct) + + def super_object_properties(self, op: OWLObjectPropertyExpression, direct: bool = False) -> Iterable[ + OWLObjectPropertyExpression]: + yield from self.adaptor.super_object_properties(op, direct) + + def sub_object_properties(self, op: OWLObjectPropertyExpression, direct: bool = False) -> Iterable[ + OWLObjectPropertyExpression]: + yield from self.adaptor.sub_object_properties(op, direct) + + def types(self, ind: OWLNamedIndividual, direct: bool = False) -> Iterable[OWLClass]: + yield from self.adaptor.types(ind, direct) + + def get_root_ontology(self) -> OWLOntology: + return self.ontology \ No newline at end of file diff --git a/owlapy/owlapi_adaptor.py b/owlapy/owlapi_adaptor.py index 78299eeb..856cef2b 100644 --- a/owlapy/owlapi_adaptor.py +++ b/owlapy/owlapi_adaptor.py @@ -1,13 +1,22 @@ +"""Owlapi Adaptor + +Part of the docstrings are taken directly from owlapi +""" import jpype.imports import os import pkg_resources -from owlapy import manchester_to_owl_expression from owlapy.class_expression import OWLClassExpression -from owlapy.iri import IRI +from owlapy.owl_axiom import OWLAxiom from owlapy.owl_individual import OWLNamedIndividual -from owlapy.render import owl_expression_to_manchester -from typing import List +from owlapy.owl_object import OWLEntity +from owlapy.owl_property import OWLDataProperty, OWLObjectProperty +from typing import List, Iterable + + +def to_list(stream_obj): + """Converts Java Stream object to Python list""" + return stream_obj.collect(jpype.JClass("java.util.stream.Collectors").toList()) class OWLAPIAdaptor: @@ -21,9 +30,8 @@ class OWLAPIAdaptor: manager: The OWL ontology manager. ontology: The loaded OWL ontology. reasoner: Choose from (case-sensitive): ["HermiT", "Pellet", "JFact", "Openllet"]. Default: "HermiT". - parser: The Manchester OWL Syntax parser. - renderer: The Manchester OWL Syntax renderer. """ + def __init__(self, path: str, name_reasoner: str = "HermiT"): """ Initialize the OWLAPIAdaptor with a path to an ontology and a reasoner name. @@ -38,7 +46,8 @@ def __init__(self, path: str, name_reasoner: str = "HermiT"): """ self.path = path assert name_reasoner in ["HermiT", "Pellet", "JFact", "Openllet"], \ - f"'{name_reasoner}' is not implemented. Available reasoners: ['HermiT', 'Pellet', 'JFact', 'Openllet']" + (f"'{name_reasoner}' is not implemented. Available reasoners: ['HermiT', 'Pellet', 'JFact', 'Openllet']. " + f"This field is case sensitive.") self.name_reasoner = name_reasoner # Attributes are initialized as JVM is started # () Manager is needed to load an ontology @@ -47,9 +56,8 @@ def __init__(self, path: str, name_reasoner: str = "HermiT"): self.ontology = None # () Create a reasoner for the loaded ontology self.reasoner = None - self.parser = None - # () A manchester renderer to render owlapi ce to manchester syntax - self.renderer = None + # () For mapping entities/expressions from/to owlapi + self.mapper = None # () Set up the necessary attributes by making use of the java packages self._setup() @@ -75,9 +83,34 @@ def _setup(self): if not jpype.isJVMStarted(): self._startJVM() - # Import Java classes + # Imports + from owlapy.owlapi_mapper import OWLAPIMapper from org.semanticweb.owlapi.apibinding import OWLManager from java.io import File + 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(), + } + if self.name_reasoner == "HermiT": from org.semanticweb.HermiT import ReasonerFactory elif self.name_reasoner == "Pellet": @@ -89,19 +122,16 @@ def _setup(self): else: raise NotImplementedError("Not implemented") - from org.semanticweb.owlapi.manchestersyntax.parser import ManchesterOWLSyntaxClassExpressionParser - from org.semanticweb.owlapi.util import BidirectionalShortFormProviderAdapter, SimpleShortFormProvider - from org.semanticweb.owlapi.expression import ShortFormEntityChecker - from java.util import HashSet - from org.semanticweb.owlapi.manchestersyntax.renderer import ManchesterOWLSyntaxOWLObjectRendererImpl - # () Manager is needed to load an ontology self.manager = OWLManager.createOWLOntologyManager() # () Load a local ontology using the manager self.ontology = self.manager.loadOntologyFromOntologyDocument(File(self.path)) + self.mapper = OWLAPIMapper(self.ontology) + # () Create a reasoner for the loaded ontology if self.name_reasoner == "HermiT": self.reasoner = ReasonerFactory().createReasoner(self.ontology) + assert self.reasoner.getReasonerName() == "HermiT" elif self.name_reasoner == "JFact": self.reasoner = JFactFactory().createReasoner(self.ontology) elif self.name_reasoner == "Pellet": @@ -109,58 +139,446 @@ def _setup(self): elif self.name_reasoner == "Openllet": self.reasoner = OpenlletReasonerFactory().getInstance().createReasoner(self.ontology) - # () Create a manchester parser and all the necessary attributes for parsing a manchester string to owlapi ce - ontology_set = HashSet() - ontology_set.add(self.ontology) - bidi_provider = BidirectionalShortFormProviderAdapter(self.manager, ontology_set, SimpleShortFormProvider()) - entity_checker = ShortFormEntityChecker(bidi_provider) - self.parser = ManchesterOWLSyntaxClassExpressionParser(self.manager.getOWLDataFactory(), entity_checker) - # A manchester renderer to render owlapi ce to manchester syntax - self.renderer = ManchesterOWLSyntaxOWLObjectRendererImpl() + def has_consistent_ontology(self) -> bool: + """ + Check if the used ontology is consistent. + + Returns: + bool: True if the ontology is consistent, False otherwise. + """ + return self.reasoner.isConsistent() - def convert_to_owlapi(self, ce: OWLClassExpression): + def instances(self, ce: OWLClassExpression, direct=False) -> List[OWLNamedIndividual]: """ - Converts an OWLAPY class expression to an OWLAPI class expression. + Get the instances for a given class expression using HermiT. Args: - ce (OWLClassExpression): The class expression in OWLAPY format to be converted. + ce (OWLClassExpression): The class expression in OWLAPY format. + direct (bool): Whether to get direct instances or not. Defaults to False. Returns: - The class expression in OWLAPI format. + list: A list of individuals classified by the given class expression. """ - return self.parser.parse(owl_expression_to_manchester(ce)) + inds = self.reasoner.getInstances(self.mapper.map_(ce), direct).getFlattened() + return [self.mapper.map_(ind) for ind in inds] - def convert_from_owlapi(self, ce, namespace: str) -> OWLClassExpression: + def equivalent_classes(self, ce: OWLClassExpression) -> List[OWLClassExpression]: """ - Converts an OWLAPI class expression to an OWLAPY class expression. + Gets the set of named classes that are equivalent to the specified class expression with + respect to the set of reasoner axioms. Args: - ce: The class expression in OWLAPI format. - namespace (str): The ontology's namespace where the class expression belongs. + ce (OWLClassExpression): The class expression whose equivalent classes are to be retrieved. Returns: - OWLClassExpression: The class expression in OWLAPY format. + Equivalent classes of the given class expression. """ - return manchester_to_owl_expression(str(self.renderer.render(ce)), namespace) + classes = self.reasoner.getEquivalentClasses(self.mapper.map_(ce)).getEntities() + yield from [self.mapper.map_(cls) for cls in classes] - def instances(self, ce: OWLClassExpression)->List[OWLNamedIndividual]: + def disjoint_classes(self, ce: OWLClassExpression) -> List[OWLClassExpression]: """ - Get the instances for a given class expression using HermiT. + Gets the classes that are disjoint with the specified class expression. Args: - ce (OWLClassExpression): The class expression in OWLAPY format. + ce (OWLClassExpression): The class expression whose disjoint classes are to be retrieved. Returns: - list: A list of individuals classified by the given class expression. + Disjoint classes of the given class expression. """ - inds = self.reasoner.getInstances(self.convert_to_owlapi(ce), False).getFlattened() - return [OWLNamedIndividual(IRI.create(str(ind)[1:-1])) for ind in inds] + classes = self.reasoner.getDisjointClasses(self.mapper.map_(ce)).getFlattened() + yield from [self.mapper.map_(cls) for cls in classes] - def has_consistent_ontology(self) -> bool: + def sub_classes(self, ce: OWLClassExpression, direct=False) -> List[OWLClassExpression]: """ - Check if the used ontology is consistent. + Gets the set of named classes that are the strict (potentially direct) subclasses of the + specified class expression with respect to the reasoner axioms. + Args: + ce (OWLClassExpression): The class expression whose strict (direct) subclasses are to be retrieved. + direct (bool, optional): Specifies if the direct subclasses should be retrieved (True) or if + all subclasses (descendant) classes should be retrieved (False). Defaults to False. Returns: - bool: True if the ontology is consistent, False otherwise. + The subclasses of the given class expression depending on `direct` field. """ - return self.reasoner.isConsistent() + classes = self.reasoner.getSubClasses(self.mapper.map_(ce), direct).getFlattened() + yield from [self.mapper.map_(cls) for cls in classes] + + def super_classes(self, ce: OWLClassExpression, direct=False) -> List[OWLClassExpression]: + """ + Gets the stream of named classes that are the strict (potentially direct) super classes of + the specified class expression with respect to the imports closure of the root ontology. + + Args: + ce (OWLClassExpression): The class expression whose strict (direct) subclasses are to be retrieved. + direct (bool, optional): Specifies if the direct superclasses should be retrieved (True) or if + all superclasses (descendant) classes should be retrieved (False). Defaults to False. + + Returns: + The subclasses of the given class expression depending on `direct` field. + """ + classes = self.reasoner.getSuperClasses(self.mapper.map_(ce), direct).getFlattened() + yield from [self.mapper.map_(cls) for cls in classes] + + def data_property_domains(self, p: OWLDataProperty, direct: bool = False): + """Gets the class expressions that are the direct or indirect domains of this property with respect to the + imports closure of the root ontology. + + Args: + p: The property expression whose domains are to be retrieved. + direct: Specifies if the direct domains should be retrieved (True), or if all domains should be retrieved + (False). + + Returns: + :Let N = equivalent_classes(DataSomeValuesFrom(pe rdfs:Literal)). If direct is True: then if N is not + empty then the return value is N, else the return value is the result of + super_classes(DataSomeValuesFrom(pe rdfs:Literal), true). If direct is False: then the result of + super_classes(DataSomeValuesFrom(pe rdfs:Literal), false) together with N if N is non-empty. + (Note, rdfs:Literal is the top datatype). + """ + yield from [self.mapper.map_(ce) for ce in + self.reasoner.getDataPropertyDomains(self.mapper.map_(p), direct).getFlattened()] + + def object_property_domains(self, p: OWLObjectProperty, direct: bool = False): + """Gets the class expressions that are the direct or indirect domains of this property with respect to the + imports closure of the root ontology. + + Args: + p: The property expression whose domains are to be retrieved. + direct: Specifies if the direct domains should be retrieved (True), or if all domains should be retrieved + (False). + + Returns: + :Let N = equivalent_classes(ObjectSomeValuesFrom(pe owl:Thing)). If direct is True: then if N is not empty + then the return value is N, else the return value is the result of + super_classes(ObjectSomeValuesFrom(pe owl:Thing), true). If direct is False: then the result of + super_classes(ObjectSomeValuesFrom(pe owl:Thing), false) together with N if N is non-empty. + """ + yield from [self.mapper.map_(ce) for ce in + self.reasoner.getObjectPropertyDomains(self.mapper.map_(p), direct).getFlattened()] + + def object_property_ranges(self, p: OWLObjectProperty, direct: bool = False): + """Gets the class expressions that are the direct or indirect ranges of this property with respect to the + imports closure of the root ontology. + + Args: + p: The property expression whose ranges are to be retrieved. + direct: Specifies if the direct ranges should be retrieved (True), or if all ranges should be retrieved + (False). + + Returns: + :Let N = equivalent_classes(ObjectSomeValuesFrom(ObjectInverseOf(pe) owl:Thing)). If direct is True: then + if N is not empty then the return value is N, else the return value is the result of + super_classes(ObjectSomeValuesFrom(ObjectInverseOf(pe) owl:Thing), true). If direct is False: then + the result of super_classes(ObjectSomeValuesFrom(ObjectInverseOf(pe) owl:Thing), false) together with N + if N is non-empty. + """ + yield from [self.mapper.map_(ce) for ce in + self.reasoner.getObjectPropertyRanges(self.mapper.map_(p), direct).getFlattened()] + + def sub_object_properties(self, p: OWLObjectProperty, direct: bool = False): + """Gets the stream of simplified object property expressions that are the strict (potentially direct) + subproperties of the specified object property expression with respect to the imports closure of the root + ontology. + + Args: + p: The object property expression whose strict (direct) subproperties are to be retrieved. + direct: Specifies if the direct subproperties should be retrieved (True) or if the all subproperties + (descendants) should be retrieved (False). + + Returns: + If direct is True, simplified object property expressions, such that for each simplified object property + expression, P, the set of reasoner axioms entails DirectSubObjectPropertyOf(P, pe). + If direct is False, simplified object property expressions, such that for each simplified object property + expression, P, the set of reasoner axioms entails StrictSubObjectPropertyOf(P, pe). + If pe is equivalent to owl:bottomObjectProperty then nothing will be returned. + """ + yield from [self.mapper.map_(pe) for pe in + self.reasoner.getSubObjectProperties(self.mapper.map_(p), direct).getFlattened()] + + def super_object_properties(self, p: OWLObjectProperty, direct: bool = False): + """Gets the stream of object properties that are the strict (potentially direct) super properties of the + specified object property with respect to the imports closure of the root ontology. + + Args: + p (OWLObjectPropertyExpression): The object property expression whose super properties are to be + retrieved. + direct (bool): Specifies if the direct super properties should be retrieved (True) or if the all + super properties (ancestors) should be retrieved (False). + + Returns: + Iterable of super properties. + """ + yield from [self.mapper.map_(pe) for pe in + self.reasoner.getSuperObjectProperties(self.mapper.map_(p), direct).getFlattened()] + + def sub_data_properties(self, p: OWLDataProperty, direct: bool = False): + """Gets the set of named data properties that are the strict (potentially direct) subproperties of the + specified data property expression with respect to the imports closure of the root ontology. + + Args: + p: The data property whose strict (direct) subproperties are to be retrieved. + direct: Specifies if the direct subproperties should be retrieved (True) or if the all subproperties + (descendants) should be retrieved (False). + + Returns: + If direct is True, each property P where the set of reasoner axioms entails DirectSubDataPropertyOf(P, pe). + If direct is False, each property P where the set of reasoner axioms entails + StrictSubDataPropertyOf(P, pe). If pe is equivalent to owl:bottomDataProperty then nothing will be + returned. + """ + yield from [self.mapper.map_(pe) for pe in + self.reasoner.getSubDataProperties(self.mapper.map_(p), direct).getFlattened()] + + def super_data_properties(self, p: OWLDataProperty, direct: bool = False): + """Gets the stream of data properties that are the strict (potentially direct) super properties of the + specified data property with respect to the imports closure of the root ontology. + + Args: + p (OWLDataProperty): The data property whose super properties are to be retrieved. + direct (bool): Specifies if the direct super properties should be retrieved (True) or if the all + super properties (ancestors) should be retrieved (False). + + Returns: + Iterable of super properties. + """ + yield from [self.mapper.map_(pe) for pe in + self.reasoner.getSuperDataProperties(self.mapper.map_(p), direct).getFlattened()] + + def different_individuals(self, i: OWLNamedIndividual): + """Gets the individuals that are different from the specified individual with respect to the set of + reasoner axioms. + + Args: + i: The individual whose different individuals are to be retrieved. + + Returns: + All individuals x where the set of reasoner axioms entails DifferentIndividuals(ind x). + """ + yield from [self.mapper.map_(ind) for ind in + self.reasoner.getDifferentIndividuals(self.mapper.map_(i)).getFlattened()] + + def same_individuals(self, i: OWLNamedIndividual): + """Gets the individuals that are the same as the specified individual with respect to the set of + reasoner axioms. + + Args: + i: The individual whose same individuals are to be retrieved. + + Returns: + All individuals x where the root ontology imports closure entails SameIndividual(ind x). + """ + yield from [self.mapper.map_(ind) for ind in + to_list(self.reasoner.sameIndividuals(self.mapper.map_(i)))] + + def equivalent_object_properties(self, p: OWLObjectProperty): + """Gets the simplified object properties that are equivalent to the specified object property with respect + to the set of reasoner axioms. + + Args: + p: The object property whose equivalent object properties are to be retrieved. + + Returns: + All simplified object properties e where the root ontology imports closure entails + EquivalentObjectProperties(op e). If op is unsatisfiable with respect to the set of reasoner axioms + then owl:bottomDataProperty will be returned. + """ + yield from [self.mapper.map_(pe) for pe in + to_list(self.reasoner.equivalentObjectProperties(self.mapper.map_(p)))] + + def equivalent_data_properties(self, p: OWLDataProperty): + """Gets the data properties that are equivalent to the specified data property with respect to the set of + reasoner axioms. + + Args: + p: The data property whose equivalent data properties are to be retrieved. + + Returns: + All data properties e where the root ontology imports closure entails EquivalentDataProperties(dp e). + If dp is unsatisfiable with respect to the set of reasoner axioms then owl:bottomDataProperty will + be returned. + """ + yield from [self.mapper.map_(pe) for pe in + to_list(self.reasoner.getEquivalentDataProperties(self.mapper.map_(p)))] + + def object_property_values(self, i: OWLNamedIndividual, p: OWLObjectProperty): + """Gets the object property values for the specified individual and object property expression. + + Args: + i: The individual that is the subject of the object property values. + p: The object property expression whose values are to be retrieved for the specified individual. + + Returns: + The named individuals such that for each individual j, the set of reasoner axioms entails + ObjectPropertyAssertion(pe ind j). + """ + yield from [self.mapper.map_(ind) for ind in + self.reasoner.getObjectPropertyValues(self.mapper.map_(i), self.mapper.map_(p)).getFlattened()] + + def data_property_values(self, e: OWLEntity, p: OWLDataProperty): + """Gets the data property values for the specified entity and data property expression. + + Args: + e: The entity (usually an individual) that is the subject of the data property values. + p: The data property expression whose values are to be retrieved for the specified individual. + + Returns: + A set of OWLLiterals containing literals such that for each literal l in the set, the set of reasoner + axioms entails DataPropertyAssertion(pe ind l). + """ + yield from [self.mapper.map_(literal) for literal in + to_list(self.reasoner.dataPropertyValues(self.mapper.map_(e), self.mapper.map_(p)))] + + def disjoint_object_properties(self, p: OWLObjectProperty): + """Gets the simplified object properties that are disjoint with the specified object property with respect + to the set of reasoner axioms. + + Args: + p: The object property whose disjoint object properties are to be retrieved. + + Returns: + All simplified object properties e where the root ontology imports closure entails + EquivalentObjectProperties(e ObjectPropertyComplementOf(op)) or + StrictSubObjectPropertyOf(e ObjectPropertyComplementOf(op)). + """ + yield from [self.mapper.map_(pe) for pe in + self.reasoner.getDisjointObjectProperties(self.mapper.map_(p)).getFlattened()] + + def disjoint_data_properties(self, p: OWLDataProperty): + """Gets the data properties that are disjoint with the specified data property with respect + to the set of reasoner axioms. + + Args: + p: The data property whose disjoint data properties are to be retrieved. + + Returns: + All data properties e where the root ontology imports closure entails + EquivalentDataProperties(e DataPropertyComplementOf(dp)) or + StrictSubDataPropertyOf(e DataPropertyComplementOf(dp)). + """ + yield from [self.mapper.map_(pe) for pe in + self.reasoner.getDisjointDataProperties(self.mapper.map_(p)).getFlattened()] + + def types(self, i: 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. + direct: Specifies if the direct types should be retrieved (True), or if all types should be retrieved + (False). + + Returns: + If direct is True, each named class C where the set of reasoner axioms entails + DirectClassAssertion(C, ind). If direct is False, each named class C where the set of reasoner axioms + entails ClassAssertion(C, ind). + """ + yield from [self.mapper.map_(ind) for ind in + self.reasoner.getTypes(self.mapper.map_(i), direct).getFlattened()] + + def infer_axioms(self, inference_types: list[str]) -> Iterable[OWLAxiom]: + """ + Infer the specified inference type of axioms for the ontology managed by this instance's reasoner and + return them. + + Args: + inference_types: Axiom inference types: Avaliable options (can set more than 1): + ["InferredClassAssertionAxiomGenerator", "InferredSubClassAxiomGenerator", + "InferredDisjointClassesAxiomGenerator", "InferredEquivalentClassAxiomGenerator", + "InferredEquivalentDataPropertiesAxiomGenerator","InferredEquivalentObjectPropertyAxiomGenerator", + "InferredInverseObjectPropertiesAxiomGenerator","InferredSubDataPropertyAxiomGenerator", + "InferredSubObjectPropertyAxiomGenerator","InferredDataPropertyCharacteristicAxiomGenerator", + "InferredObjectPropertyCharacteristicAxiomGenerator" + ] + + Returns: + Iterable of inferred axioms. + """ + from java.util import ArrayList + from org.semanticweb.owlapi.util import InferredOntologyGenerator + + generators = ArrayList() + for i in inference_types: + if java_object := self.inference_types_mapping.get(i, None): + generators.add(java_object) + iog = InferredOntologyGenerator(self.reasoner, generators) + inferred_axioms = list(iog.getAxiomGenerators()) + for ia in inferred_axioms: + for axiom in ia.createAxioms(self.manager.getOWLDataFactory(), self.reasoner): + yield self.mapper.map_(axiom) + + def infer_axioms_and_save(self, output_path: str = None, output_format: str = None, inference_types: list[str] = None): + """ + Generates inferred axioms for the ontology managed by this instance's reasoner and saves them to a file. + This function uses the OWL API to generate inferred class assertion axioms based on the ontology and reasoner + associated with this instance. The inferred axioms are saved to the specified output file in the desired format. + + Args: + output_path : The name of the file where the inferred axioms will be saved. + output_format : The format in which to save the inferred axioms. Supported formats are: + - "ttl" or "turtle" for Turtle format + - "rdf/xml" for RDF/XML format + - "owl/xml" for OWL/XML format + If not specified, the format of the original ontology is used. + inference_types: Axiom inference types: Avaliable options (can set more than 1): + ["InferredClassAssertionAxiomGenerator", "InferredSubClassAxiomGenerator", + "InferredDisjointClassesAxiomGenerator", "InferredEquivalentClassAxiomGenerator", + "InferredEquivalentDataPropertiesAxiomGenerator","InferredEquivalentObjectPropertyAxiomGenerator", + "InferredInverseObjectPropertiesAxiomGenerator","InferredSubDataPropertyAxiomGenerator", + "InferredSubObjectPropertyAxiomGenerator","InferredDataPropertyCharacteristicAxiomGenerator", + "InferredObjectPropertyCharacteristicAxiomGenerator" + ] + + Returns: + None (the file is saved to the specified directory) + """ + from java.io import File, FileOutputStream + from java.util import ArrayList + from org.semanticweb.owlapi.util import InferredOntologyGenerator + from org.semanticweb.owlapi.formats import TurtleDocumentFormat, RDFXMLDocumentFormat, OWLXMLDocumentFormat + if output_format == "ttl" or output_format == "turtle": + document_format = TurtleDocumentFormat() + elif output_format == "rdf/xml": + document_format = RDFXMLDocumentFormat() + elif output_format == "owl/xml": + document_format = OWLXMLDocumentFormat() + else: + document_format = self.manager.getOntologyFormat(self.ontology) + generators = ArrayList() + + for i in inference_types: + if java_object := self.inference_types_mapping.get(i, None): + generators.add(java_object) + iog = InferredOntologyGenerator(self.reasoner, generators) + inferred_axioms_ontology = self.manager.createOntology() + iog.fillOntology(self.manager.getOWLDataFactory(), inferred_axioms_ontology) + inferred_ontology_file = File(output_path).getAbsoluteFile() + output_stream = FileOutputStream(inferred_ontology_file) + self.manager.saveOntology(inferred_axioms_ontology, document_format, output_stream) + + def generate_and_save_inferred_class_assertion_axioms(self, output="temp.ttl", output_format: str = None): + """ + Generates inferred class assertion axioms for the ontology managed by this instance's reasoner and saves them to a file. + This function uses the OWL API to generate inferred class assertion axioms based on the ontology and reasoner + associated with this instance. The inferred axioms are saved to the specified output file in the desired format. + Parameters: + ----------- + output : str, optional + The name of the file where the inferred axioms will be saved. Default is "temp.ttl". + output_format : str, optional + The format in which to save the inferred axioms. Supported formats are: + - "ttl" or "turtle" for Turtle format + - "rdf/xml" for RDF/XML format + - "owl/xml" for OWL/XML format + If not specified, the format of the original ontology is used. + Notes: + ------ + - The function supports saving in multiple formats: Turtle, RDF/XML, and OWL/XML. + - The inferred axioms are generated using the reasoner associated with this instance and the OWL API's + InferredClassAssertionAxiomGenerator. + - The inferred axioms are added to a new ontology which is then saved in the specified format. + Example: + -------- + >>> instance.generate_inferred_class_assertion_axioms(output="inferred_axioms.ttl", format="ttl") + This will save the inferred class assertion axioms to the file "inferred_axioms.ttl" in Turtle format. + """ + self.infer_axioms_and_save(output, output_format, ["InferredClassAssertionAxiomGenerator"]) diff --git a/owlapy/owlapi_mapper.py b/owlapy/owlapi_mapper.py new file mode 100644 index 00000000..9e2ec046 --- /dev/null +++ b/owlapy/owlapi_mapper.py @@ -0,0 +1,486 @@ +from functools import singledispatchmethod +from typing import Iterable + +import jpype.imports + +import owlapy.owl_ontology +from owlapy import owl_expression_to_manchester, manchester_to_owl_expression +from owlapy.class_expression import OWLClassExpression, OWLDataOneOf, OWLFacetRestriction, OWLDatatypeRestriction +from owlapy.iri import IRI +from owlapy.owl_axiom import OWLDeclarationAxiom, OWLAnnotation, OWLAnnotationProperty, OWLClassAssertionAxiom, \ + OWLDataPropertyAssertionAxiom, OWLDataPropertyDomainAxiom, OWLDataPropertyRangeAxiom, OWLObjectPropertyDomainAxiom, \ + OWLObjectPropertyRangeAxiom, OWLObjectPropertyAssertionAxiom, OWLEquivalentClassesAxiom, \ + OWLEquivalentDataPropertiesAxiom, OWLEquivalentObjectPropertiesAxiom, OWLDisjointClassesAxiom, \ + OWLDisjointDataPropertiesAxiom, OWLDisjointObjectPropertiesAxiom, OWLHasKeyAxiom, OWLSubDataPropertyOfAxiom, \ + OWLSubClassOfAxiom, OWLSubObjectPropertyOfAxiom, OWLAsymmetricObjectPropertyAxiom, OWLDatatypeDefinitionAxiom, \ + OWLDifferentIndividualsAxiom, OWLDisjointUnionAxiom, OWLFunctionalDataPropertyAxiom, \ + OWLFunctionalObjectPropertyAxiom, OWLInverseFunctionalObjectPropertyAxiom, OWLInverseObjectPropertiesAxiom, \ + OWLIrreflexiveObjectPropertyAxiom, OWLNegativeDataPropertyAssertionAxiom, OWLReflexiveObjectPropertyAxiom, \ + OWLNegativeObjectPropertyAssertionAxiom, OWLSameIndividualAxiom, OWLSymmetricObjectPropertyAxiom, \ + OWLTransitiveObjectPropertyAxiom, OWLAnnotationAssertionAxiom, OWLAnnotationPropertyDomainAxiom, \ + OWLAnnotationPropertyRangeAxiom, OWLSubAnnotationPropertyOfAxiom +from owlapy.owl_data_ranges import OWLDataIntersectionOf, OWLDataComplementOf, OWLDataUnionOf, OWLNaryDataRange +from owlapy.owl_datatype import OWLDatatype +from owlapy.owl_individual import OWLNamedIndividual +from owlapy.owl_literal import OWLLiteral +from owlapy.owl_property import OWLObjectProperty, OWLDataProperty +from owlapy.vocab import OWLFacet + +if jpype.isJVMStarted(): + from org.semanticweb.owlapi.model import IRI as owlapi_IRI + 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 + from java.util.stream import Stream + from uk.ac.manchester.cs.owl.owlapi import (OWLAnonymousClassExpressionImpl, OWLCardinalityRestrictionImpl, + OWLClassExpressionImpl, OWLClassImpl, OWLDataAllValuesFromImpl, + OWLDataCardinalityRestrictionImpl, OWLDataExactCardinalityImpl, + OWLDataHasValueImpl, OWLDataMaxCardinalityImpl, OWLDataUnionOfImpl, + OWLDataMinCardinalityImpl, OWLDataSomeValuesFromImpl, + OWLNaryBooleanClassExpressionImpl, OWLObjectAllValuesFromImpl, + OWLObjectCardinalityRestrictionImpl, OWLObjectComplementOfImpl, + OWLObjectExactCardinalityImpl, OWLObjectHasSelfImpl, + OWLObjectHasValueImpl, OWLObjectIntersectionOfImpl, + OWLObjectMaxCardinalityImpl, OWLObjectMinCardinalityImpl, + OWLObjectOneOfImpl, OWLObjectSomeValuesFromImpl, OWLNaryDataRangeImpl, + OWLObjectUnionOfImpl, OWLQuantifiedDataRestrictionImpl, + OWLQuantifiedObjectRestrictionImpl, OWLQuantifiedRestrictionImpl, + OWLValueRestrictionImpl, OWLLiteralImplBoolean, OWLLiteralImplString, + OWLLiteralImplDouble, OWLLiteralImplFloat, OWLLiteralImplInteger, + OWLDisjointClassesAxiomImpl, OWLDeclarationAxiomImpl, OWLAnnotationImpl, + OWLAnnotationPropertyImpl, OWLClassAssertionAxiomImpl, + OWLDataPropertyAssertionAxiomImpl, OWLDataPropertyDomainAxiomImpl, + OWLDataPropertyRangeAxiomImpl, OWLEquivalentClassesAxiomImpl, + OWLEquivalentDataPropertiesAxiomImpl, OWLDataIntersectionOfImpl, + OWLEquivalentObjectPropertiesAxiomImpl, OWLDataOneOfImpl, + OWLObjectPropertyDomainAxiomImpl, OWLObjectPropertyRangeAxiomImpl, + OWLObjectPropertyAssertionAxiomImpl, OWLDisjointDataPropertiesAxiomImpl, + OWLDisjointObjectPropertiesAxiomImpl, OWLHasKeyAxiomImpl, + OWLSubClassOfAxiomImpl, OWLSubDataPropertyOfAxiomImpl, + OWLSubObjectPropertyOfAxiomImpl, OWLAsymmetricObjectPropertyAxiomImpl, + OWLDatatypeDefinitionAxiomImpl, OWLDatatypeImpl, OWLObjectPropertyImpl, + OWLDataPropertyImpl, OWLNamedIndividualImpl, OWLDisjointUnionAxiomImpl, + OWLDifferentIndividualsAxiomImpl, OWLFunctionalDataPropertyAxiomImpl, + OWLFunctionalObjectPropertyAxiomImpl, OWLSameIndividualAxiomImpl, + OWLInverseFunctionalObjectPropertyAxiomImpl, OWLDataComplementOfImpl, + OWLInverseObjectPropertiesAxiomImpl,OWLReflexiveObjectPropertyAxiomImpl, + OWLIrreflexiveObjectPropertyAxiomImpl, OWLAnnotationAssertionAxiomImpl, + OWLNegativeDataPropertyAssertionAxiomImpl, OWLFacetRestrictionImpl, + OWLNegativeObjectPropertyAssertionAxiomImpl, OWLDatatypeRestrictionImpl, + OWLSymmetricObjectPropertyAxiomImpl, + OWLTransitiveObjectPropertyAxiomImpl, + OWLAnnotationPropertyDomainAxiomImpl, + OWLAnnotationPropertyRangeAxiomImpl, + OWLSubAnnotationPropertyOfAxiomImpl + ) + +else: + raise ImportError("Jpype JVM is not started! Tip: Import OWLAPIMapper after JVM has started") + + +def init(the_class): + cls_name = the_class.__class__.__name__ + if "Impl" in cls_name: + return globals().get(cls_name.split(".")[-1].replace("Impl", "")) + else: + return globals().get(cls_name + "Impl") + + +class OWLAPIMapper: + + def __init__(self, ontology=None): + # TODO: CD: Please use class type of ontology + # TODO: CD: if ontology is None, then we should throw an exception with a useful information + # assert isinstance(ontology, OWLAPIMapper) + self.ontology = ontology + self.manager = ontology.getOWLOntologyManager() + + # () Get the name space. + 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. + ontology_set = HashSet() + ontology_set.add(self.ontology) + bidi_provider = BidirectionalShortFormProviderAdapter(self.manager, ontology_set, SimpleShortFormProvider()) + entity_checker = ShortFormEntityChecker(bidi_provider) + self.parser = ManchesterOWLSyntaxClassExpressionParser(self.manager.getOWLDataFactory(), entity_checker) + self.renderer = ManchesterOWLSyntaxOWLObjectRendererImpl() + + @singledispatchmethod + def map_(self, e): + """ (owlapy <--> owlapi) entity mapping. + + Args: + e: OWL entity/expression. + """ + if isinstance(e, Iterable): + return self.map_(list(e)) + + raise NotImplementedError(f"Not implemented type: {e}") + + @map_.register + def _(self, e: IRI): + return owlapi_IRI.create(e.str) + + @map_.register + def _(self, e: owlapi_IRI): + return IRI.create(str(e.getIRIString())) + + @map_.register(OWLNamedIndividual) + @map_.register(OWLDataProperty) + @map_.register(OWLObjectProperty) + @map_.register(OWLDatatype) + @map_.register(OWLAnnotationProperty) + def _(self, e): + return init(e)(self.map_(e.iri)) + + @map_.register(OWLNamedIndividualImpl) + @map_.register(OWLDataPropertyImpl) + @map_.register(OWLObjectPropertyImpl) + @map_.register(OWLDatatypeImpl) + @map_.register(OWLAnnotationPropertyImpl) + def _(self, e): + return init(e)(self.map_(e.getIRI())) + + @map_.register + def _(self, e: OWLClassExpression): + return self.parser.parse(owl_expression_to_manchester(e)) + + @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(OWLObjectExactCardinalityImpl) + @map_.register(OWLObjectHasSelfImpl) + @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) + 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) + + @map_.register + def _(self, e: OWLLiteral): + if e.is_string(): + return OWLLiteralImplString(e.get_literal()) + elif e.is_boolean(): + return OWLLiteralImplBoolean(e.parse_boolean()) + elif e.is_integer(): + return OWLLiteralImplInteger(e.parse_integer()) + elif e.is_double(): + return OWLLiteralImplDouble(e.parse_double()) + else: + raise NotImplementedError(f"Type of this literal: {e} cannot be mapped!") + + @map_.register(OWLLiteralImplBoolean) + def _(self, e): + raw_value = str(e.getLiteral()) + if raw_value.lower() == "true": + return OWLLiteral(True) + return OWLLiteral(False) + + @map_.register(OWLLiteralImplDouble) + @map_.register(OWLLiteralImplFloat) + def _(self, e): + return OWLLiteral(float(str(e.getLiteral()))) + + @map_.register(OWLLiteralImplString) + def _(self, e): + return OWLLiteral(str(e.getLiteral())) + + @map_.register + def _(self, e: OWLLiteralImplInteger): + return OWLLiteral(int(str(e.getLiteral()))) + + @map_.register(OWLDataIntersectionOf) + @map_.register(OWLDataOneOf) + @map_.register(OWLDataUnionOf) + @map_.register(OWLNaryDataRange) + def _(self, e): + return init(e)(self.map_(e.operands())) + + @map_.register(OWLDataIntersectionOfImpl) + @map_.register(OWLDataOneOfImpl) + @map_.register(OWLDataUnionOfImpl) + @map_.register(OWLNaryDataRangeImpl) + def _(self, e): + return init(e)(self.map_(e.getOperandsAsList())) + + @map_.register(OWLDataComplementOfImpl) + def _(self, e): + return OWLDataComplementOf(self.map_(e.getDataRange())) + + @map_.register(OWLDataComplementOf) + def _(self, e): + return OWLDataComplementOfImpl(self.map_(e.get_data_range())) + + @map_.register(OWLFacet) + def _(self, e): + return owlapi_OWLFacet.getFacetBySymbolicName(e.symbolic_form) + + @map_.register(owlapi_OWLFacet) + def _(self, e): + return OWLFacet.from_str(str(e.getSymbolicForm())) + + @map_.register(OWLFacetRestriction) + def _(self, e): + return OWLFacetRestrictionImpl(self.map_(e.get_facet()), self.map_(e.get_facet_value())) + + @map_.register(OWLFacetRestrictionImpl) + def _(self, e): + return OWLFacetRestriction(self.map_(e.getFacet()), self.map_(e.getFacetValue())) + + @map_.register(OWLDatatypeRestriction) + def _(self, e): + return OWLDatatypeRestrictionImpl(self.map_(e.get_datatype()), self.map_(e.get_facet_restrictions())) + + @map_.register(OWLDatatypeRestrictionImpl) + def _(self, e): + return OWLDatatypeRestriction(self.map_(e.getDatatype()), self.map_(e.facetRestrictionsAsList())) + + @map_.register + def _(self, e: OWLAnnotation): + return OWLAnnotationImpl(self.map_(e.get_property()), self.map_(e.get_value()), Stream.empty()) + + @map_.register + def _(self, e: OWLAnnotationImpl): + return OWLAnnotation(self.map_(e.getProperty()), self.map_(e.getValue())) + + @map_.register + def _(self, e: OWLAnnotationAssertionAxiom): + return OWLAnnotationAssertionAxiomImpl(self.map_(e.get_subject()), self.map_(e.get_property()), + self.map_(e.get_value()), self.map_(e.annotations())) + + @map_.register + def _(self, e: OWLAnnotationAssertionAxiomImpl): + return OWLAnnotationAssertionAxiom(self.map_(e.getSubject()), + OWLAnnotation(self.map_(e.getProperty()), self.map_(e.getValue())), + self.map_(e.annotationsAsList())) + + @map_.register(OWLDeclarationAxiom) + def _(self, e): + return OWLDeclarationAxiomImpl(self.map_(e.get_entity()), self.map_(e.annotations())) + + @map_.register(OWLDeclarationAxiomImpl) + def _(self, e): + return OWLDeclarationAxiom(self.map_(e.getEntity()), self.map_(e.annotationsAsList())) + + @map_.register + def _(self, e: OWLClassAssertionAxiom): + return OWLClassAssertionAxiomImpl(self.map_(e.get_individual()), self.map_(e.get_class_expression()), + self.map_(e.annotations())) + + @map_.register(OWLClassAssertionAxiomImpl) + def _(self, e): + return OWLClassAssertionAxiom(self.map_(e.getIndividual()), self.map_(e.getClassExpression()), + self.map_(e.annotationsAsList())) + + @map_.register(OWLObjectPropertyAssertionAxiom) + @map_.register(OWLDataPropertyAssertionAxiom) + @map_.register(OWLNegativeDataPropertyAssertionAxiom) + @map_.register(OWLNegativeObjectPropertyAssertionAxiom) + def _(self, e): + return init(e)(self.map_(e.get_subject()), self.map_(e.get_property()), self.map_(e.get_object()), + self.map_(e.annotations())) + + @map_.register(OWLObjectPropertyAssertionAxiomImpl) + @map_.register(OWLDataPropertyAssertionAxiomImpl) + @map_.register(OWLNegativeDataPropertyAssertionAxiomImpl) + @map_.register(OWLNegativeObjectPropertyAssertionAxiomImpl) + def _(self, e): + return init(e)(self.map_(e.getSubject()), self.map_(e.getProperty()), self.map_(e.getObject()), + self.map_(e.annotationsAsList())) + + @map_.register(OWLObjectPropertyDomainAxiom) + @map_.register(OWLDataPropertyDomainAxiom) + @map_.register(OWLAnnotationPropertyDomainAxiom) + @map_.register(OWLAnnotationPropertyRangeAxiom) + def _(self, e): + return init(e)(self.map_(e.get_property()), self.map_(e.get_domain()), self.map_(e.annotations())) + + @map_.register(OWLObjectPropertyDomainAxiomImpl) + @map_.register(OWLDataPropertyDomainAxiomImpl) + @map_.register(OWLAnnotationPropertyDomainAxiomImpl) + @map_.register(OWLAnnotationPropertyRangeAxiomImpl) + def _(self, e): + return init(e)(self.map_(e.getProperty()), self.map_(e.getDomain()), self.map_(e.annotationsAsList())) + + @map_.register(OWLObjectPropertyRangeAxiom) + @map_.register(OWLDataPropertyRangeAxiom) + def _(self, e): + return init(e)(self.map_(e.get_property()), self.map_(e.get_range()), self.map_(e.annotations())) + + @map_.register(OWLObjectPropertyRangeAxiomImpl) + @map_.register(OWLDataPropertyRangeAxiomImpl) + def _(self, e): + return init(e)(self.map_(e.getProperty()), self.map_(e.getRange()), self.map_(e.annotationsAsList())) + + @map_.register(OWLEquivalentDataPropertiesAxiom) + @map_.register(OWLEquivalentObjectPropertiesAxiom) + def _(self, e): + return init(e)(self.map_(e.properties()), self.map_(e.annotations())) + + @map_.register(OWLEquivalentClassesAxiomImpl) + @map_.register(OWLEquivalentDataPropertiesAxiomImpl) + @map_.register(OWLEquivalentObjectPropertiesAxiomImpl) + def _(self, e): + return init(e)(self.map_(e.getOperandsAsList()), self.map_(e.annotationsAsList())) + + @map_.register(OWLEquivalentClassesAxiom) + @map_.register(OWLDisjointClassesAxiom) + def _(self, e): + return init(e)(self.map_(e.class_expressions()), self.map_(e.annotations())) + + @map_.register(OWLDisjointDataPropertiesAxiom) + @map_.register(OWLDisjointObjectPropertiesAxiom) + def _(self, e): + return init(e)(self.map_(e.properties()), self.map_(e.annotations())) + + @map_.register(OWLDisjointClassesAxiomImpl) + @map_.register(OWLDisjointDataPropertiesAxiomImpl) + @map_.register(OWLDisjointObjectPropertiesAxiomImpl) + def _(self, e): + return init(e)(self.map_(e.getOperandsAsList()), self.map_(e.annotationsAsList())) + + @map_.register + def _(self, e: OWLHasKeyAxiom): + return OWLHasKeyAxiomImpl(self.map_(e.get_class_expression()), self.map_(e.get_property_expressions()), + self.map_(e.annotations())) + + @map_.register(OWLHasKeyAxiomImpl) + def _(self, e): + return OWLHasKeyAxiom(self.map_(e.getClassExpression()), self.map_(e.getOperandsAsList()), + self.map_(e.annotationsAsList())) + + @map_.register + def _(self, e: OWLSubClassOfAxiom): + return OWLSubClassOfAxiomImpl(self.map_(e.get_sub_class()), self.map_(e.get_super_class()), + self.map_(e.annotations())) + + @map_.register(OWLSubClassOfAxiomImpl) + def _(self, e): + return OWLSubClassOfAxiom(self.map_(e.getSubClass()), self.map_(e.getSuperClass()), + self.map_(e.annotationsAsList())) + + @map_.register(OWLSubDataPropertyOfAxiom) + @map_.register(OWLSubObjectPropertyOfAxiom) + @map_.register(OWLSubAnnotationPropertyOfAxiom) + def _(self, e): + return init(e)(self.map_(e.get_sub_property()), self.map_(e.get_super_property()), self.map_(e.annotations())) + + @map_.register(OWLSubDataPropertyOfAxiomImpl) + @map_.register(OWLSubObjectPropertyOfAxiomImpl) + @map_.register(OWLSubAnnotationPropertyOfAxiomImpl) + def _(self, e): + return init(e)(self.map_(e.getSubProperty()), self.map_(e.getSuperProperty()), + self.map_(e.annotationsAsList())) + + @map_.register(OWLAsymmetricObjectPropertyAxiom) + @map_.register(OWLFunctionalDataPropertyAxiom) + @map_.register(OWLFunctionalObjectPropertyAxiom) + @map_.register(OWLInverseFunctionalObjectPropertyAxiom) + @map_.register(OWLIrreflexiveObjectPropertyAxiom) + @map_.register(OWLReflexiveObjectPropertyAxiom) + @map_.register(OWLSymmetricObjectPropertyAxiom) + @map_.register(OWLTransitiveObjectPropertyAxiom) + def _(self, e): + return init(e)(self.map_(e.get_property()), self.map_(e.annotations())) + + @map_.register(OWLAsymmetricObjectPropertyAxiomImpl) + @map_.register(OWLFunctionalDataPropertyAxiomImpl) + @map_.register(OWLFunctionalObjectPropertyAxiomImpl) + @map_.register(OWLInverseFunctionalObjectPropertyAxiomImpl) + @map_.register(OWLIrreflexiveObjectPropertyAxiomImpl) + @map_.register(OWLReflexiveObjectPropertyAxiomImpl) + @map_.register(OWLSymmetricObjectPropertyAxiomImpl) + @map_.register(OWLTransitiveObjectPropertyAxiomImpl) + def _(self, e): + return init(e)(self.map_(e.getProperty()), self.map_(e.annotationsAsList())) + + @map_.register(OWLDatatypeDefinitionAxiom) + def _(self, e): + return OWLDatatypeDefinitionAxiomImpl(self.map_(e.get_datatype()), self.map_(e.get_datarange()), + self.map_(e.annotations())) + + @map_.register(OWLDatatypeDefinitionAxiomImpl) + def _(self, e): + return OWLDatatypeDefinitionAxiom(self.map_(e.getDatatype()), self.map_(e.getDataRange()), + self.map_(e.annotationsAsList())) + + @map_.register(OWLDifferentIndividualsAxiom) + @map_.register(OWLSameIndividualAxiom) + def _(self, e): + return OWLDifferentIndividualsAxiomImpl(self.map_(e.individuals()), self.map_(e.annotations())) + + @map_.register(OWLDifferentIndividualsAxiomImpl) + @map_.register(OWLSameIndividualAxiomImpl) + def _(self, e): + return OWLDifferentIndividualsAxiom(self.map_(e.getIndividualsAsList()), self.map_(e.annotationsAsList())) + + @map_.register(OWLDisjointUnionAxiom) + def _(self, e): + return OWLDisjointUnionAxiomImpl(self.map_(e.get_owl_class()), self.map_(e.get_class_expressions()).stream(), + self.map_(e.annotations())) + + @map_.register(OWLDisjointUnionAxiomImpl) + def _(self, e): + return OWLDisjointUnionAxiom(self.map_(e.getOWLClass()), self.map_(e.getOperandsAsList()), + self.map_(e.annotationsAsList())) + + @map_.register(OWLInverseObjectPropertiesAxiom) + def _(self, e): + return OWLInverseObjectPropertiesAxiomImpl(self.map_(e.get_first_property()), + self.map_(e.get_second_property()), self.map_(e.annotations())) + + @map_.register(OWLInverseObjectPropertiesAxiomImpl) + def _(self, e): + return OWLInverseObjectPropertiesAxiom(self.map_(e.getFirstProperty()), self.map_(e.getSecondProperty()), + self.map_(e.annotationsAsList())) + + @map_.register(List) + @map_.register(Set) + def _(self, e): + python_list = list() + casted_list = list(e) + if e and len(casted_list) > 0: + for obj in list(e): + python_list.append(self.map_(obj)) + return python_list + + @map_.register(list) + @map_.register(set) + def _(self, e): + java_list = ArrayList() + if e is not None and len(e) > 0: + for item in e: + java_list.add(self.map_(item)) + return java_list diff --git a/owlapy/render.py b/owlapy/render.py index 68874433..a8bb5a8e 100644 --- a/owlapy/render.py +++ b/owlapy/render.py @@ -7,10 +7,10 @@ from owlapy import namespaces from .iri import IRI -from .owl_individual import OWLNamedIndividual +from .owl_individual import OWLNamedIndividual, OWLIndividual from .owl_literal import OWLLiteral from .owl_object import OWLObjectRenderer, OWLEntity, OWLObject -from .owl_property import OWLObjectInverseOf, OWLPropertyExpression +from .owl_property import OWLObjectInverseOf, OWLPropertyExpression, OWLDataProperty, OWLObjectProperty from .class_expression import OWLClassExpression, OWLBooleanClassExpression, OWLClass, OWLObjectSomeValuesFrom, \ OWLObjectAllValuesFrom, OWLObjectUnionOf, OWLObjectIntersectionOf, OWLObjectComplementOf, OWLObjectMinCardinality, \ OWLObjectExactCardinality, OWLObjectMaxCardinality, OWLObjectHasSelf, OWLDataSomeValuesFrom, OWLDataAllValuesFrom, \ @@ -20,8 +20,11 @@ from .owl_data_ranges import OWLNaryDataRange, OWLDataComplementOf, OWLDataUnionOf, OWLDataIntersectionOf from .class_expression import OWLObjectHasValue, OWLFacetRestriction, OWLDatatypeRestriction, OWLObjectOneOf from .owl_datatype import OWLDatatype - - +from .owl_reasoner import OWLReasoner +from typing import Union, Tuple +import requests +import warnings +import abc _DL_SYNTAX = types.SimpleNamespace( SUBCLASS="⊑", EQUIVALENT_TO="≡", @@ -49,7 +52,7 @@ def _simple_short_form_provider(e: OWLEntity) -> str: iri: IRI = e.iri - sf = iri.get_short_form() + sf = iri.reminder for ns in [namespaces.XSD, namespaces.OWL, namespaces.RDFS, namespaces.RDF]: if iri.get_namespace() == ns: return "%s:%s" % (ns.prefix, sf) @@ -57,6 +60,104 @@ def _simple_short_form_provider(e: OWLEntity) -> str: return sf +mapper = { + 'OWLNamedIndividual': "http://www.w3.org/2002/07/owl#NamedIndividual", + 'OWLObjectProperty': "http://www.w3.org/2002/07/owl#ObjectProperty", + 'OWLDataProperty': "http://www.w3.org/2002/07/owl#DatatypeProperty", + 'OWLClass': "http://www.w3.org/2002/07/owl#Class" +} + + +def translating_short_form_provider(e: OWLEntity, reasoner, rules: dict[str:str] = None) -> str: + """ + e: entity. + reasoner: OWLReasoner or Triplestore(from Ontolearn) + rules: A mapping from OWLEntity to predicates, + Keys in rules can be general or specific iris, e.g., + IRI to IRI s.t. the second IRI must be a predicate leading to literal + """ + label_iri = "http://www.w3.org/2000/01/rdf-schema#label" + + def get_label(entity, r, predicate=label_iri): + if isinstance(r, OWLReasoner): + values = list(r.data_property_values(entity, OWLDataProperty(predicate))) + if values: + return str(values[0].get_literal()) + else: + return _simple_short_form_provider(entity) + else: + # else we have a TripleStore + sparql = f"""select ?o where {{ <{entity.str}> <{predicate}> ?o}}""" + if results := list(r.query(sparql)): + return str(results[0]) + else: + return _simple_short_form_provider(entity) + + if rules is None: + return get_label(e, reasoner) + else: + # Check if a rule exist for a specific IRI: + # (e.g "http://www.example.org/SomeSpecificClass":"http://www.example.org/SomePredicate") + # WARNING: If the entity is an OWLClass, the rule specified for that class will only be used to replace the + # class itself not individuals belonging to that class. The reason for that is that the entity can also be a + # property and properties does not classify individuals. So to avoid confusion, the specified predicate in the + # rules will only be used to 'label' the specified entity. + if specific_predicate := rules.get(e.str, None): + return get_label(e, reasoner, specific_predicate) + # Check if a rule exist for a general IRI: + # (e.g "http://www.w3.org/2002/07/owl#NamedIndividual":"http://www.example.org/SomePredicate") + # then it will label any entity of that type using the value retrieved from the given predicate. + elif general_predicate := rules.get(mapper[e.__class__.__name__], None): + return get_label(e, reasoner, general_predicate) + # No specific rule set, use http://www.w3.org/2000/01/rdf-schema#label (by default) + else: + return get_label(e, reasoner) + + +def translating_short_form_endpoint(e: OWLEntity, endpoint: str, + rules: dict[abc.ABCMeta:str] = None) -> str: + """ + Translates an OWLEntity to a short form string using provided rules and an endpoint. + + Parameters: + e (OWLEntity): The OWL entity to be translated. + endpoint (str): The endpoint of a triple store to query against. + rules (dict[abc.ABCMeta:str], optional): A dictionary mapping OWL classes to string IRIs leading to a literal. + + Returns: + str: The translated short form of the OWL entity. If no matching rules are found, a simple short form is returned. + + This function iterates over the provided rules to check if the given OWL entity is an instance of any specified class. + If a match is found, it constructs a SPARQL query to retrieve the literal value associated with the entity and predicate. + If a literal is found, it is returned as the short form. If no literals are found, the SPARQL query and entity information + are printed for debugging purposes. If no matching rules are found, a warning is issued and a simple short form is returned. + + + Example: + >>> e = OWLEntity("http://example.org/entity") + >>> endpoint = "http://example.org/sparql" + >>> rules = {SomeOWLClass: "http://example.org/predicate"} + >>> translating_short_form_endpoint(e, endpoint, rules) + """ + # () Iterate over rules + for owlapy_class, str_predicate in rules.items(): + # () Check whether an OWL entity is an instance of specified class + if isinstance(e, owlapy_class): + sparql = f"""select ?o where {{ <{e.str}> <{str_predicate}> ?o}}""" + response = requests.post(url=endpoint, data={"query": sparql}) + results = response.json()["results"]["bindings"] + if len(results) > 0: + return results[0]["o"]["value"] + else: + print(sparql) + print(f"No literal found\n{sparql}\n{e}") + continue + + warnings.warn(f"No matching rules for OWL Entity:{e}!") + # No mathing rule found + return _simple_short_form_provider(e) + + class DLSyntaxObjectRenderer(OWLObjectRenderer): """DL Syntax renderer for OWL Objects.""" __slots__ = '_sfp' @@ -226,7 +327,7 @@ def _render_operands(self, c: OWLNaryBooleanClassExpression) -> List[str]: def _render_nested(self, c: OWLClassExpression) -> str: if isinstance(c, OWLBooleanClassExpression) or isinstance(c, OWLRestriction) \ - or isinstance(c, OWLNaryDataRange): + or isinstance(c, OWLNaryDataRange): return "(%s)" % self.render(c) else: return self.render(c) @@ -420,7 +521,7 @@ def _render_operands(self, c: OWLNaryBooleanClassExpression) -> List[str]: def _render_nested(self, c: OWLClassExpression) -> str: if isinstance(c, OWLBooleanClassExpression) or isinstance(c, OWLRestriction) \ - or isinstance(c, OWLNaryDataRange): + or isinstance(c, OWLNaryDataRange): return "(%s)" % self.render(c) else: return self.render(c) diff --git a/setup.py b/setup.py index 10587f1e..80b77624 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.1.1", + version="1.2.1", packages=find_packages(), include_package_data=True, package_data={ @@ -13,6 +13,7 @@ }, install_requires=[ "pandas>=1.5.0", + "requests>=2.32.3", "rdflib>=6.0.2", "parsimonious>=0.8.1", "pytest>=8.1.1", diff --git a/tests/test_owlapi_adaptor.py b/tests/test_owlapi_adaptor.py index 5864d5a2..47d5e9a2 100644 --- a/tests/test_owlapi_adaptor.py +++ b/tests/test_owlapi_adaptor.py @@ -76,14 +76,14 @@ def test_conversion(self): class_expression = data_factory.getOWLObjectIntersectionOf(nitrogen_class, some_values_from) # compare them with the adaptor converted expression - ce_converted = self.adaptor.convert_to_owlapi(self.ce) + ce_converted = self.adaptor.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.adaptor.convert_from_owlapi(class_expression, self.ns) - ce_2 = self.adaptor.convert_from_owlapi(ce_converted, self.ns) + ce_1 = self.adaptor.mapper.map_(class_expression) + ce_2 = self.adaptor.mapper.map_(ce_converted) self.assertEqual(ce_1, ce_2) self.assertEqual(ce_1, self.ce) diff --git a/tests/test_owlapy_ontology_management.py b/tests/test_owlapy_ontology_management.py index 6eb09c3b..fefbf0c4 100644 --- a/tests/test_owlapy_ontology_management.py +++ b/tests/test_owlapy_ontology_management.py @@ -2,8 +2,7 @@ import unittest from pandas import Timedelta -from owlapy.owl_reasoner import FastInstanceCheckerReasoner, OntologyReasoner, \ - SyncReasoner +from owlapy.owl_reasoner import FastInstanceCheckerReasoner, OntologyReasoner, SyncReasoner from owlapy.providers import owl_datatype_max_inclusive_restriction, owl_datatype_min_inclusive_restriction, \ owl_datatype_min_max_exclusive_restriction, owl_datatype_min_max_inclusive_restriction @@ -20,7 +19,7 @@ OWLDifferentIndividualsAxiom, OWLDisjointClassesAxiom, OWLDisjointDataPropertiesAxiom, \ OWLDisjointObjectPropertiesAxiom, OWLEquivalentDataPropertiesAxiom, OWLEquivalentObjectPropertiesAxiom, \ OWLDataPropertyDomainAxiom, OWLDataPropertyRangeAxiom, OWLSubClassOfAxiom, OWLObjectPropertyDomainAxiom, \ - OWLDataPropertyAssertionAxiom, OWLObjectPropertyAssertionAxiom, OWLDeclarationAxiom + OWLDataPropertyAssertionAxiom, OWLObjectPropertyAssertionAxiom from owlapy.owl_data_ranges import OWLDataComplementOf, OWLDataIntersectionOf, OWLDataUnionOf from owlapy.owl_individual import OWLNamedIndividual from owlapy.owl_literal import DoubleOWLDatatype, OWLLiteral, DurationOWLDatatype, IntegerOWLDatatype, \ @@ -800,19 +799,16 @@ def test_mapping_rev_data_properties(self): self.assertEqual(owl_ce, from_owlready.map_concept(ce)) -class Owlapy_Owlready2_ComplexCEInstances_Test(unittest.TestCase): +class Owlapy_Owlready2_SyncReasoner(unittest.TestCase): # noinspection DuplicatedCode def test_instances(self): ns = "http://example.com/father#" - mgr = OntologyManager() - onto = mgr.load_ontology(IRI.create("file://KGs/Family/father.owl")) male = OWLClass(IRI.create(ns, 'male')) female = OWLClass(IRI.create(ns, 'female')) has_child = OWLObjectProperty(IRI(ns, 'hasChild')) - # reasoner = OWLReasoner_Owlready2(onto) - reasoner = SyncReasoner(onto) + reasoner = SyncReasoner("KGs/Family/father.owl") inst = frozenset(reasoner.instances(female)) target_inst = frozenset({OWLNamedIndividual(IRI(ns, 'anna')), @@ -829,34 +825,6 @@ def test_instances(self): target_inst = frozenset({OWLNamedIndividual(IRI(ns, 'anna'))}) self.assertEqual(inst, target_inst) - def test_isolated_ontology(self): - - ns = "http://example.com/father#" - mgr = OntologyManager() - onto = mgr.load_ontology(IRI.create("file://KGs/Family/father.owl")) - - reasoner1 = OntologyReasoner(onto) - ccei_reasoner = SyncReasoner(onto, isolate=True) - - new_individual = OWLNamedIndividual(IRI(ns, 'bob')) - male_ce = OWLClass(IRI(ns, "male")) - axiom1 = OWLDeclarationAxiom(new_individual) - axiom2 = OWLClassAssertionAxiom(new_individual, male_ce) - mgr.add_axiom(onto, axiom1) - mgr.add_axiom(onto, axiom2) - - self.assertIn(new_individual, reasoner1.instances(male_ce)) - self.assertNotIn(new_individual, ccei_reasoner.instances(male_ce)) - - ccei_reasoner.update_isolated_ontology(axioms_to_add=[axiom1, axiom2]) - - self.assertIn(new_individual, ccei_reasoner.instances(male_ce)) - - ccei_reasoner.update_isolated_ontology(axioms_to_remove=[axiom2, axiom1]) - - self.assertIn(new_individual, reasoner1.instances(male_ce)) - self.assertNotIn(new_individual, ccei_reasoner.instances(male_ce)) - if __name__ == '__main__': unittest.main() diff --git a/tests/test_owlapy_owl2sparql_converter.py b/tests/test_owlapy_owl2sparql_converter.py index 6b5dd21b..f943f0e0 100644 --- a/tests/test_owlapy_owl2sparql_converter.py +++ b/tests/test_owlapy_owl2sparql_converter.py @@ -14,7 +14,7 @@ from owlapy.converter import Owl2SparqlConverter from rdflib import Graph -PATH_FAMILY = 'KGs/Family/family-benchmark_rich_background.owl' +PATH_FAMILY = '../KGs/Family/family-benchmark_rich_background.owl' # checks whether all individuals returned by the reasoner are found in results generated by the sparql query @@ -29,9 +29,9 @@ def check_reasoner_instances_in_sparql_results(sparql_results: rdflib.query.Resu else: sparql_results_set.add(individual_iri_str.split('/')[-1]) for result in reasoner_results: - if result.iri.get_short_form() not in sparql_results_set: + if result.iri.reminder not in sparql_results_set: print() - print(result.iri.get_short_form(), "Not found in SPARQL results set") + print(result.iri.reminder, "Not found in SPARQL results set") return False return True