Repository: dice-group/owlapy (language: python)

Issue:
Refactoring owlapi adaptor
- Fixed bug where JVM was never shutting down because of an always false check.
- Removed _\_\_enter___ and _\_\_exit___ methods from OWLAPIAdaptor because that structure does not allow for creation of new instance of OWLAPIAdaptor. That is because if the JVM is stopped then you cannot restart it ( this is a [limitation ](https://jpype.readthedocs.io/en/v0.7.5/api.html#:~:text=Due%20to%20limitations%20in%20the%20JPype%2C%20it%20is%20not%20possible%20to%20restart%20the%20JVM%20after%20being%20terminated.)of JPype)
- Currently when initializing an object of OWLAPIAdaptor the JVM is started automatically (this is required to perform the successful initialization of the object, therefore the option to manually start the JVM is not possible)
- _**New method:**_ To stop the JVM the user can use the method `OWLAPIAdaptor.stopJVM()`. This method can be called when the user is finished with using its adaptor objects.
- Updated examples and documentation in accordance
- Version update for the new release



Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 4224f44d..64e33d7f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -145,4 +145,7 @@ cython_debug/
 .idea/
 
 # VS Code
-.vscode/
\ No newline at end of file
+.vscode/
+
+# Project related files
+/KGs/
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index 1637db26..74dab002 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2021
+Copyright (c) 2024 Caglar Demir
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 7581ca0e..b492cb35 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,11 @@
 # OWLAPY
 
-OWLAPY is a Python Framework that serves as a base structure for creating and manipulating 
-OWL Ontologies. 
+OWLAPY is a Python Framework for creating and manipulating OWL Ontologies.
 
 Have a look at the [Documentation](https://dice-group.github.io/owlapy/).
 
 ## Installation
+
 ### Installation from Source
 ``` bash
 git clone https://github.com/dice-group/owlapy
@@ -57,5 +57,7 @@ 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.
+
 ## How to cite
 Currently, we are working on our manuscript describing our framework.
diff --git a/docs/conf.py b/docs/conf.py
index bf08a50a..b5ba4898 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -13,7 +13,7 @@
 
 project = 'OWLAPY'
 author = 'Ontolearn Team'
-release = '0.1.2'
+release = '1.1.1'
 
 # -- General configuration ---------------------------------------------------
 # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
diff --git a/docs/index.rst b/docs/index.rst
index 9458bc87..5afacc29 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -12,4 +12,8 @@ Welcome to OWLAPY!
 
    usage/main
    usage/usage_examples
+   usage/ontologies
+   usage/reasoner
+   usage/reasoning_details
+   usage/owlapi_adaptor
    autoapi/owlapy/index
\ No newline at end of file
diff --git a/docs/usage/main.md b/docs/usage/main.md
index 54935778..3e2cc555 100644
--- a/docs/usage/main.md
+++ b/docs/usage/main.md
@@ -1,6 +1,6 @@
 # About owlapy
 
-**Version:** owlapy 1.0.1
+**Version:** owlapy 1.1.1
 
 **GitHub repository:** [https://github.com/dice-group/owlapy](https://github.com/dice-group/owlapy)
 
@@ -8,7 +8,7 @@
 
 **Contact**: [onto-learn@lists.uni-paderborn.de](mailto:onto-learn@lists.uni-paderborn.de)
 
-**License:** GNU Affero General Public License v3 or later (AGPLv3+)
+**License:** MIT License
 
 --------------------------------------------------------------------------------------------
 ## What is owlapy?
@@ -16,7 +16,7 @@ Owlapy is an open-source software library in python that is used to represent en
 in OWL 2 Web Ontology Language.
 
 We identified the gap of having a library that will serve as a base structure 
-for representing OWL entities in python and like that, owlapy was created. Owlapy 
+for representing OWL entities and for manipulating OWL Ontologies in python, and like that, owlapy was created. Owlapy 
 is loosely based on its java-counterpart, _owlapi_. Owlapy is currently utilized 
 by powerful libraries such as [Ontolearn](https://github.com/dice-group/Ontolearn)
 and [OntoSample](https://github.com/alkidbaci/OntoSample). 
@@ -27,7 +27,9 @@ focus on knowledge graphs and class expression learnings.
 ---------------------------------------
 
 ## What does owlapy have to offer?
-
+- Create, manipulate and save Ontologies.
+- Retrieving information from the signature of the ontology.
+- Reasoning over ontology.
 - Represent every notation in 
 [OWL 2 Structural Specification and Functional-Style Syntax](https://www.w3.org/TR/owl2-syntax/)
 including: 
diff --git a/docs/usage/ontologies.md b/docs/usage/ontologies.md
new file mode 100644
index 00000000..c2bd646f
--- /dev/null
+++ b/docs/usage/ontologies.md
@@ -0,0 +1,219 @@
+# Ontologies
+To get started with Structured Machine Learning, the first thing
+required is an [Ontology](https://www.w3.org/TR/owl2-overview/) with
+[Named Individuals](https://www.w3.org/TR/owl-syntax/#Named_Individuals).
+In this guide we show the basics of working with ontologies in Owlapy.
+We will use the _father_ ontology for the following examples. 
+
+## Loading an Ontology
+
+To load an ontology as well as to manage it, you will need an 
+[OWLOntologyManager](owlapy.owl_ontology_manager.OWLOntologyManager)
+An ontology can be loaded using the following Python code:
+
+```python
+from owlapy.iri import IRI
+from owlapy.owl_ontology_manager import OntologyManager
+
+manager = OntologyManager()
+onto = manager.load_ontology(IRI.create("file://KGs/Family/father.owl"))
+```
+
+First, we import the `IRI` class and a suitable OWLOntologyManager. To
+load a file from our computer, we have to reference it with an
+[IRI](owlapy.iri.IRI). Secondly, we need the
+Ontology Manager. Owlapy contains one such manager: The
+[OntologyManager](owlapy.owl_ontology_manager.OntologyManager).
+
+Now, we can already inspect the contents of the ontology. For example,
+to list all individuals:
+
+<!--pytest-codeblocks:cont-->
+```python
+for ind in onto.individuals_in_signature():
+    print(ind)
+```
+
+You can get the object properties in the signature:
+
+<!--pytest-codeblocks:cont-->
+```python
+onto.object_properties_in_signature()
+```
+
+For more methods, see the abstract class [OWLOntology](owlapy.owl_ontology.Ontology)
+or the concrete implementation [Ontology](owlapy.owl_ontology.Ontology).
+
+## Modifying an Ontology
+
+Axioms in ontology serve as the basis for defining the vocabulary of a domain and for 
+making statements about the relationships between individuals and concepts in that domain.
+They provide a formal and precise way to represent knowledge and allow for automated 
+reasoning and inference. Axioms can be **added**, **modified**, or **removed** from an ontology, 
+allowing the ontology to evolve and adapt as new knowledge is gained.
+
+In owlapy we also have different axioms represented by different classes. You can check all
+the axioms classes [here](owlapy.owl_axioms). Some frequently used axioms are:
+
+- [OWLDeclarationAxiom](owlapy.owl_axiom.OWLDeclarationAxiom)
+- [OWLObjectPropertyAssertionAxiom](owlapy.owl_axiom.OWLObjectPropertyAssertionAxiom)
+- [OWLDataPropertyAssertionAxiom](owlapy.owl_axiom.OWLDataPropertyAssertionAxiom)
+- [OWLClassAssertionAxiom](owlapy.owl_axiom.OWLClassAssertionAxiom)
+- [OWLSubClassOfAxiom](owlapy.owl_axiom.OWLSubClassOfAxiom)
+- [OWLEquivalentClassesAxiom](owlapy.owl_axiom.OWLEquivalentClassesAxiom)
+
+
+#### Add a new Class
+
+Let's suppose you want to add a new class in our example ontology `KGs/Family/father.owl` 
+It can be done as follows:
+
+<!--pytest-codeblocks:cont-->
+
+```python
+from owlapy.class_expression import OWLClass
+from owlapy.owl_axiom import OWLDeclarationAxiom
+
+iri = IRI('http://example.com/father#', 'child')
+child_class = OWLClass(iri)
+child_class_declaration_axiom = OWLDeclarationAxiom(child_class)
+
+manager.add_axiom(onto, child_class_declaration_axiom)
+```
+In this example, we added the class 'child' to the _father.owl_ ontology.
+Firstly we create an instance of [OWLClass](owlapy.class_expression.owl_class.OWLClass) to represent the concept 
+of 'child' by using an [IRI](owlapy.iri.IRI). 
+On the other side, an instance of `IRI` is created by passing two arguments which are
+the namespace of the ontology and the remainder 'child'. To declare this new class we need
+an axiom of type `OWLDeclarationAxiom`. We simply pass the `child_class` to create an 
+instance of this axiom. The final step is to add this axiom to the ontology using the 
+[OWLOntologyManager](owlapy.owl_ontology_manager.OWLOntologyManager). We use the `add_axiom` method
+of the `manager` to add into the ontology
+`onto` the axiom `child_class_declaration_axiom`.
+
+#### Add a new Object Property / Data Property
+
+The idea is the same as adding a new class. Instead of `OWLClass`, for object properties,
+you can use the class [OWLObjectProperty](owlapy.owl_property.OWLObjectProperty) and for data
+properties you can use the class [OWLDataProperty](owlapy.owl_property.OWLDataProperty).
+
+<!--pytest-codeblocks:cont-->
+
+```python
+from owlapy.owl_property import OWLObjectProperty, OWLDataProperty
+
+# adding the object property 'hasParent'
+hasParent_op = OWLObjectProperty(IRI('http://example.com/father#', 'hasParent'))
+hasParent_op_declaration_axiom = OWLDeclarationAxiom(hasParent_op)
+manager.add_axiom(onto, hasParent_op_declaration_axiom)
+
+# adding the data property 'hasAge' 
+hasAge_dp = OWLDataProperty(IRI('http://example.com/father#', 'hasAge'))
+hasAge_dp_declaration_axiom = OWLDeclarationAxiom(hasAge_dp)
+manager.add_axiom(onto, hasAge_dp_declaration_axiom)
+```
+
+See the [owlapy](owlapy) for more OWL entities that you can add as a declaration axiom.
+
+#### Add an Assertion Axiom
+
+To assign a class to a specific individual use the following code:
+
+<!--pytest-codeblocks:cont-->
+
+```python
+from owlapy.owl_axiom import OWLClassAssertionAxiom
+
+individuals = list(onto.individuals_in_signature())
+heinz = individuals[1]  # get the 2nd individual in the list which is 'heinz'
+
+class_assertion_axiom = OWLClassAssertionAxiom(heinz, child_class)
+
+manager.add_axiom(onto, class_assertion_axiom)
+```
+We have used the previous method `individuals_in_signature()` to get all the individuals 
+and converted them to a list, so we can access them by using indexes. In this example, we
+want to assert a class axiom for the individual `heinz`. 
+We have used the class `OWLClassAssertionAxiom`
+where the first argument is the 'individual' `heinz` and the second argument is 
+the 'class_expression'. As the class expression, we used the previously defined class 
+`child_Class`. Finally, add the axiom by using `add_axiom` method of the [OWLOntologyManager](owlapy.owl_ontology_manager.OWLOntologyManager).
+
+Let's show one more example using a `OWLDataPropertyAssertionAxiom` to assign the age of 17 to
+heinz. 
+
+<!--pytest-codeblocks:cont-->
+
+```python
+from owlapy.owl_literal import OWLLiteral
+from owlapy.owl_axiom import OWLDataPropertyAssertionAxiom
+
+literal_17 = OWLLiteral(17)
+dp_assertion_axiom = OWLDataPropertyAssertionAxiom(heinz, hasAge_dp, literal_17)
+
+manager.add_axiom(onto, dp_assertion_axiom)
+```
+
+[OWLLiteral](owlapy.owl_literal.OWLLiteral) is a class that represents the literal values in
+Owlapy. We have stored the integer literal value of '18' in the variable `literal_17`.
+Then we construct the `OWLDataPropertyAssertionAxiom` by passing as the first argument, the 
+individual `heinz`, as the second argument the data property `hasAge_dp`, and the third 
+argument the literal value `literal_17`. Finally, add it to the ontology by using `add_axiom` 
+method.
+
+Check the [owlapy](owlapy) to see all the OWL 
+assertion axioms that you can use.
+
+
+#### Remove an Axiom
+
+To remove an axiom you can use the `remove_axiom` method of the ontology manager as follows:
+
+<!--pytest-codeblocks:cont-->
+```python
+manager.remove_axiom(onto,dp_assertion_axiom)
+```
+The first argument is the ontology you want to remove the axiom from and the second 
+argument is the axiom you want to remove.
+
+
+## Save an Ontology
+
+If you modified an ontology, you may want to save it as a new file. To do this
+you can use the `save_ontology` method of the [OWLOntologyManager](owlapy.owl_ontology_manager.OWLOntologyManager).
+It requires two arguments, the first is the ontology you want to save and The second
+is the IRI of the new ontology.
+
+<!--pytest-codeblocks:cont-->
+```python
+manager.save_ontology(onto, IRI.create('file:/' + 'test' + '.owl'))
+```
+ The above line of code will save the ontology `onto` in the file *test.owl* which will be
+created in the same directory as the file you are running this code.
+
+
+## Worlds
+
+Owlready2 stores every triple in a ‘World’ object, and it can handle several Worlds in parallel.
+Owlready2 uses an optimized quadstore to store the world. Each world object is stored in a separate quadstore and 
+by default the quadstore is stored in memory,
+but it can also be stored in an SQLite3 file. The method `save_world()` of the ontology manager does the latter.
+When an _OWLOntologyManager_ object is created, a new world is also created as an attribute of the manager.
+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
+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
+worlds.
+
+-------------------------------------------------------------------------------------
+
+It is important that an ontology is associated with a reasoner which is used to inferring knowledge 
+from the ontology, i.e. to perform ontology reasoning.
+In the next guide we will see how to use a reasoner in Owlapy. 
+
+
+
+
diff --git a/docs/usage/owlapi_adaptor.md b/docs/usage/owlapi_adaptor.md
new file mode 100644
index 00000000..c7cda17e
--- /dev/null
+++ b/docs/usage/owlapi_adaptor.md
@@ -0,0 +1,58 @@
+# Owlapi Adaptor
+
+As mentioned earlier, owlapy is loosely based in [owlapi](https://github.com/owlcs/owlapi),
+a library for ontology modification in java.
+
+We have created [OWLAPIAdaptor](owlapy.owlapi_adaptor.OWLAPIAdaptor), 
+an adaptor class that facilitates the conversion of
+owl class expressions from owlapy to owlapi and vice-versa. 
+This adaptor is still considered experimental, and it's in the 
+initial phase of development.
+
+We are able to use owlapi via [Jpype](https://jpype.readthedocs.io/en/latest/),
+a python module that provides access to Java via python. To start executing 
+Java code via jpype, one needs to start the java virtual machine (JVM).
+This is automatically done when initializing a `OWLAPIAdaptor` object.
+
+## Initialization
+
+To use the adaptor you have to start the JVM via jpype, which is done automatically 
+when you create an _OWLAPIAdaptor_ object. After you are finished you can stop
+the JVM by either using `jpype.shutdownJVM()` or the static method from the 
+adaptor `stopJVM()`. This will free the resources used by JPype and the java 
+packages.
+
+```python
+from owlapy.owlapi_adaptor import OWLAPIAdaptor
+
+adaptor = OWLAPIAdaptor("KGs/Family/father.owl")
+#  Use the adaptor
+print(f"Is the ontology consistent? {adaptor.has_consistent_ontology()}")
+
+#  Stop the JVM
+adaptor.stopJVM()
+```
+
+In the above code snipped, we created an adaptor for the father ontology 
+by passing the local path of that ontology. Then we print whether 
+the ontology is consistent or not.
+
+## Notes
+
+An important note is that when initialising the adaptor you are basically
+starting a JVM in the background, and therefore you are able to import and 
+use java classes as you would do in python. That means that you can 
+play around with owlapi code in python as long as your JVM is started.
+Isn't that awesome!
+
+`OWLAPIAdaptor` uses HermiT reasoner by default. You can choose between:
+"HermiT", "Pellet", "JFact" and "Openllet".
+
+_**owlapi version**: 5.1.9_
+
+## Examples
+
+You can check a usage example in the [_examples_](https://github.com/dice-group/owlapy/tree/develop/examples) folder.
+
+[Test cases](https://github.com/dice-group/owlapy/tree/develop/tests) for the adaptor can also serve as an example, so you can
+check that out as well.
diff --git a/docs/usage/reasoner.md b/docs/usage/reasoner.md
new file mode 100644
index 00000000..45432294
--- /dev/null
+++ b/docs/usage/reasoner.md
@@ -0,0 +1,231 @@
+# Reasoners
+
+To validate facts about statements in the ontology, the help of a reasoner
+component is required.
+
+For this guide we will also consider the 'father' ontology that we slightly described [here](ontologies.md):
+
+```python
+from owlapy.owl_ontology_manager import OntologyManager
+
+manager = OntologyManager()
+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: 
+
+- [**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.
+
+    **Initialization:**
+
+   ```python
+   from owlapy.owl_reasoner import OntologyReasoner
+   
+   structural_reasoner = OntologyReasoner(onto)
+   ```
+
+    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.
+
+    **Initialization:**
+
+    ```python
+    from owlapy.owl_reasoner import SyncReasoner, BaseReasoner
+    
+    sync_reasoner = SyncReasoner(onto, BaseReasoner.HERMIT, infer_property_values = True)
+    ```
+    
+    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
+
+
+- [**FastInstanceCheckerReasoner**](owlapy.owl_reasoner.FastInstanceCheckerReasoner) **(FIC)**
+
+    FIC also provides support for complex class expression but the rest of the methods are the same as in
+  the base reasoner.
+  It has a cache storing system that allows for faster execution of some reasoning functionalities. Due to this
+  feature, FIC is more appropriate to be used in concept learning.
+
+    **Initialization:**
+
+    ```python
+    from owlapy.owl_reasoner import FastInstanceCheckerReasoner
+    
+    fic_reasoner = FastInstanceCheckerReasoner(onto, structural_reasoner, property_cache = True,
+                                                   negation_default = True, sub_properties = False)
+    ```
+    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
+  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
+  for `instances()` method.
+
+## 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).
+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).
+
+
+## Class Reasoning
+
+Using an [OWLOntology](owlapy.owl_ontology.OWLOntology) you can list all the classes in the signature, 
+but a reasoner can give you more than that. You can get the subclasses, superclasses or the 
+equivalent classes of a class in the ontology:
+
+<!--pytest-codeblocks:cont-->
+
+```python
+from owlapy.class_expression import OWLClass
+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)
+```
+
+We define the _male_ class by creating an [OWLClass](owlapy.class_expression.owl_class.OWLClass) object. The 
+methods `super_classes` and `sub_classes` have 2 more boolean arguments: `direct` and `only_named`. 
+If `direct=True` then only the direct classes in the 
+hierarchy will be returned, else it will return every class in the hierarchy depending 
+on the method(sub_classes or super_classes).
+By default, its value is _False_. 
+The next argument `only_named` specifies whether you want
+to show only named classes or complex classes as well. By default, its value is _True_ which 
+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.
+
+You can get all the types of a certain individual using `types` method:
+
+<!--pytest-codeblocks:cont-->
+
+```python
+anna = list(onto.individuals_in_signature()).pop()
+
+anna_types = sync_reasoner.types(anna)
+```
+
+We retrieve _anna_ as the first individual on the list of individuals 
+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)
+class documentation. 
+
+You can get all the object properties that an individual has by using the 
+following method:
+
+<!--pytest-codeblocks:cont-->
+```python
+anna = individuals[0] 
+object_properties = sync_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_.
+Now we can get the individuals of this object property for _anna_.
+
+<!--pytest-codeblocks:cont-->
+```python
+for op in object_properties:
+    object_properties_values = sync_reasoner.object_property_values(anna, op)
+    for individual in object_properties_values:
+        print(individual)
+```
+
+In this example we iterated over the `object_properties`, assuming that there
+are more than 1, and we use the reasoner
+to get the values for each object property `op` of the individual `anna`. The values 
+are individuals which we store in the variable `object_properties_values` and are 
+printed in the end. The method `object_property_values` requires as the
+first argument, an [OWLNamedIndividual](owlapy.owl_individual.OWLNamedIndividual) that is the subject of the object property values and 
+the second argument an [OWLObjectProperty](owlapy.owl_property.OWLObjectProperty) whose values are to be retrieved for the 
+specified individual.  
+
+> **NOTE:** You can as well get all the data properties of an individual in the same way by using 
+`ind_data_properties` instead of `ind_object_properties` and `data_property_values` instead of 
+`object_property_values`. Keep in mind that `data_property_values` returns literal values 
+(type of [OWLLiteral](owlapy.owl_literal.OWLLiteral)).
+
+In the same way as with classes, you can also get the sub object properties or equivalent object properties.
+
+<!--pytest-codeblocks:cont-->
+
+```python
+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)
+```
+
+In case you want to get the domains and ranges of an object property use the following:
+
+<!--pytest-codeblocks:cont-->
+```python
+hasChild_domains = sync_reasoner.object_property_domains(hasChild)
+hasChild_ranges = sync_reasoner.object_property_ranges(hasChild)
+```
+
+> **NOTE:** Again, you can do the same for data properties but instead of the word 'object' in the 
+> method name you should use 'data'.
+
+
+## Find Instances
+
+The method `instances` is a very convenient method. It takes only 1 argument that is basically
+a class expression and returns all the individuals belonging to that class expression. In Owlapy 
+we have implemented a Python class for each type of class expression.
+The argument is of type [OWLClassExpression](owlapy.class_expression.class_expression.OWLClassExpression).
+
+Let us now show a simple example by finding the instances of the class _male_ and printing them:
+
+<!--pytest-codeblocks:cont-->
+```python
+male_individuals = sync_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.
+
+
+
diff --git a/docs/usage/reasoning_details.md b/docs/usage/reasoning_details.md
new file mode 100644
index 00000000..5f325138
--- /dev/null
+++ b/docs/usage/reasoning_details.md
@@ -0,0 +1,243 @@
+# Reasoning Details
+
+In the previous guide we explained how to [use reasoners](reasoner.md) in Owlapy. Here we cover a detailed explanation of 
+the Owlapy reasoners, particularly [SyncReasoner](owlapy.owl_reasoner.SyncReasoner).
+Before we continue to talk about its [capabilities](#capabilities) we have to explain briefly 
+the term _sync_reasoner_.
+
+## Sync Reasoner
+
+_sync_reasoner_ is a definition used in owlready2 to run [HermiT](http://www.hermit-reasoner.com/) 
+or [Pellet](https://github.com/stardog-union/pellet) and
+automatically apply the facts deduced to the quadstore. In simple terms, by running HermiT or Pellet,
+one can infer more knowledge from the ontology (the specification are not mentioned here). 
+We make use of this functionality in Owlapy, and it is represented by SyncReasoner. 
+We explained the concept of "Worlds" in [Working with Ontologies](ontologies.md#worlds). Having 
+that in mind you need to know that sync_reasoner is applied to the World object.
+After this particular reasoner is instantiated, because the facts are applied to the quadstore, changes made
+in the ontology by using the ontology manager will not be reflected to the ontology. The reasoner will use the state of
+the ontology at the moment it is instantiated.
+
+There are 2 boolean parameters for sync_reasoner that you can specify when creating an instance of 
+[SyncReasoner](owlapy.owl_reasoner.SyncReasoner).
+The first one `infer_property_values` tells HermiT or Pellet whether to infer (or not) property values. The same idea but
+for data properties is specified by the parameter `infer_data_property_values` which is only relevant to Pellet.
+
+> Note: HermiT and Pellet are Java programs, so you will need to install
+a Java virtual machine to use them. If you don’t have Java, you may install
+it from www.java.com (for Windows and macOS) or from the packages
+of your Linux distribution (the packages are often named “jre” or “jdk” for
+Java Runtime Environment and Java Development Kit).
+
+## Isolated World
+
+In [_Working with Ontologies_](ontologies.md#worlds) we mentioned that we can have multiple reference of in different
+worlds, which we can use to isolate an ontology to a specific World. For simplicity the terms "isolated world" and 
+"isolated ontology" can be used interchangeably in this guide.
+The isolation comes in handy when we use multiple reasoners in the same script. If we create
+an instance of _SyncReasoner_ it will apply sync_reasoner in the world object
+of the ontology and this will affect also the other reasoner/s which is/are using the same world. 
+To overcome this issue you can set the argument `isolate=True` when
+initializing a reasoner.
+[FastInstanceCheckerReasoner](owlapy.owl_reasoner.FastInstanceCheckerReasoner) (FIC)
+does not have this argument because it uses a base reasoner to delegate most of its methods. Therefore,
+if the base reasoner has `isolate=True` then FIC will also operate in the isolated world of it's base reasoner.
+
+### Modifying an isolated ontology
+
+When a reasoner is operating in an isolated ontology, every axiom added to the original ontology before or after 
+the initialization, will not be reflected to the isolated ontology. To update the isolated ontology and add
+or remove any axiom, you can use `update_isolated_ontology(axioms_to_add, axioms_to_remove)`. This method accepts
+a list of axioms for every argument (i.e. the axioms that you want to add and the axioms that you want to remove).
+
+## Capabilities
+
+_SyncReasoner_ provides full reasoning in 
+_ALCH_. We have adapted and build upon 
+[owlready2](https://owlready2.readthedocs.io/en/latest/) reasoner to provide 
+our own implementation in python. Below we give more details about each
+functionality of our reasoner:
+
+
+- #### Sub and Super Classes
+
+    You can retrieve sub (super) classes of a given class expression. Depending on
+    your preferences you can retrieve the whole chain of sub (super) classes or only the
+    direct sub (super) classes (`direct` argument). It is also possible to get anonymous classes in addition
+    to named classes (`only_named` argument). Class equivalence entails subsumption of classes to each other.
+
+- #### Equivalent Classes
+
+    You are able to get the equivalent classes of a given class expression. It can be 
+    decided whether only named classes should be returned 
+    or anonymous classes as well. If two classes are subclasses of each other they
+    are considered equivalent.
+
+- #### Disjoint Classes
+
+    Every class that is explicitly defined as disjoint with another class will be returned.
+    In addition, every subclass and equivalent class of the disjoint classes will be
+    returned. If a target class does not have explicitly-defined disjoint classes the search
+    is transferred to the superclasses of that target class.
+
+- #### Equivalent Properties
+
+    You are able to get equivalent properties of a given object or data property.
+    If two properties are sub-properties of each other, they are considered equivalent.
+
+- #### Sub and Super Properties
+
+    Our reasoner has support also for sub and super properties of a 
+    given property. You can set the `direct` argument like in sub (super) classes.
+    Properties equivalence entails subsumption of properties to each other.
+
+- #### Disjoint Properties
+
+    Similarly to disjoint classes, you can get the disjoint properties of a property.
+    Same rules apply.
+
+- #### Property values
+
+    Given an individual(instance) and an object property you can get all the object values.
+    Similarly, given an individual and a data property you can get all the literal values.
+    You can set whether you want only the direct values or all of them.
+
+- #### Property domain and range
+
+    Easily retrieval available for domain and range for object properties and domain for data properties.
+
+- #### Instances
+
+    This functionality enables you to get instances for a given named(atomic) class or complex class expression.
+    For the moment direct instances of complex class expressions is not possible.
+
+- #### Types
+
+    This functionality enables you to get the types of a given instance. It returns only
+    named(atomic) classes. You can set the `direct` attribute.
+
+- #### Same and Different Individuals
+
+    Given an individual you can get the individuals that are explicitly defined as same or different to that individual.
+
+
+## Concrete Example
+
+You can find the associated [code](https://github.com/dice-group/owlapy/blob/develop/examples/ontology_reasoning.py) 
+for the following examples inside `examples/example_reasoner`
+(note that the naming of the classes/relations/individuals may change from the table below). 
+We constructed an ontology for testing purposes. On the table we show for
+each **method** of the reasoner _SyncReasoner_ the results
+depending on a given **TBox** and **Abox**. The level of complexity of the TBox-es is low compared
+to real world scenarios, but it's just to show the capabilities of the reasoner.
+
+> **Note:** not every method of the reasoner is used in this example. You can check all the methods at the 
+> [API documentation](owlapy.owl_reasoner).
+
+
+| Method                                    | TBox                      | ABox                  | Returns<br>(T = Thing) |
+|-------------------------------------------|---------------------------| --------------------- |------------------------|
+| Equivalent_classes(A)                     | A ≡ B                     | \-                    | [B]                    |
+| Equivalent_classes(B)                     | A ≡ B                     | \-                    | [A]                    |
+| Instances(A)                              | A ≡ B                     | A(a),B(b)             | [a,b]                  |
+| Instances(B)                              | A ≡ B                     | A(a),B(b)             | [a,b]                  |
+| Types(a)                                  | A ≡ B                     | A(a),B(b)             | [T, A,B]               |
+| Types(b)                                  | A ≡ B                     | A(a),B(b)             | [T, A,B]               |
+| Sub_classes(A)                            | A ≡ B                     | \-                    | [B]                    |
+| Sub_classes(B)                            | A ≡ B                     | \-                    | [A]                    |
+| Super_classes(A)                          | A ≡ B                     | \-                    | [B,T]                  |
+| Super_classes(B)                          | A ≡ B                     | \-                    | [A,T]                  |
+| Equivalent_object_properties(r1)          | r1 ≡ r2                   | \-                    | [r2]                   |
+| Equivalent_object_properties(r2)          | r1 ≡ r2                   | \-                    | [r1]                   |
+| sub_object_properties(r1)                 | r1 ≡ r2                   | \-                    | [r2]                   |
+| sub_object_properties(r2)                 | r1 ≡ r2                   | \-                    | [r1]                   |
+| object_property_values(a, r1, direct=False) | r1 ≡ r2                   | r1(a,b) r2(a,c)       | [c]                    |
+| object_property_values(a, r2, direct=False) | r1 ≡ r2                   | r1(a,b) r2(a,c)       | [c]                    |
+| Sub_classes(B)                            | A ⊑ B                     | \-                    | [A]                    |
+| Super_classes(A)                          | A ⊑ B                     | \-                    | [T, B]                 |
+| Types(a)                                  | A ⊑ B                     | A(a),B(b)             | [A,B,T]                |
+| Types(b)                                  | A ⊑ B                     | A(a),B(b)             | [B,T]                  |
+| Instances(A)                              | A ⊑ B                     | A(a),B(b)             | [a]                    |
+| Instances(B)                              | A ⊑ B                     | A(a),B(b)             | [a,b]                  |
+| sub_object_properties(r1)                 | r2 ⊑ r1                   | \-                    | [r2]                   |
+| object_property_values(a, r2)             | r2 ⊑ r1                   | r2(a,b)               | [b]                    |
+| object_property_values(a, r1, direct=False) | r2 ⊑ r1                   | r2(a,b)               | [b]                    |
+| Sub_classes(r1.T)                         | r2 ⊑ r1                   | \-                    | [r2.T]                 |
+| Super_classes(D, only_named=False)        | D ⊑ ∃r.E                  | \-                    | [T, ∃r.E]               |
+| Sub_classes(∃r.E)                          | D ⊑ ∃r.E                  | \-                    | [D]                    |
+| Instances(D)                              | D ⊑ ∃r.E                  | D(d) r(i,e) E(e)      | [d]                    |
+| Instances(∃r.E)                            | D ⊑ ∃r.E                  | D(d) r(i,e) E(e)      | [i, d]                 |
+| types(d)                                  | D ⊑ ∃r.E                  | D(d) r(i,e) E(e)      | [D,T]                  |
+| types(i)                                  | D ⊑ ∃r.E                  | D(d) r(i,e) E(e)      | [T]                    |
+| object_property_values(i, r)              | D ⊑ ∃r.E                  | r(i,e) E(e)           | [e]                    |
+| Sub_classes(D, only_named=False)          | ∃r.E ⊑ D                  | \-                    | [ ∃r.E]                 |
+| Super_classes( ∃r.E)                       | ∃r.E ⊑ D                  | \-                    | [D, T]                 |
+| Instances(D)                              | ∃r.E ⊑ D                  | D(d) r(i,e) E(e)      | [i, d]                 |
+| Instances(∃r.E)                            | ∃r.E ⊑ D                  | D(d) r(i,e) E(e)      | [i]                    |
+| types(d)                                  | ∃r.E ⊑ D                  | D(d) r(i,e) E(e)      | [D, T]                 |
+| types(i)                                  | ∃r.E ⊑ D                  | D(d) r(i,e) E(e)      | [D, T]                 |
+| object_property_values(i, r)              | ∃r.E ⊑ D                  | r(i,e) E(e)           | [e]                    |
+| Sub_classes(A)                            | A ⊑ B, B ⊑ A              | \-                    | [A,B]                  |
+| Sub_classes(B)                            | A ⊑ B, B ⊑ A              | \-                    | [A,B]                  |
+| Super_classes(A)                          | A ⊑ B, B ⊑ A              | \-                    | [T, B]                 |
+| Super_classes(B)                          | A ⊑ B, B ⊑ A              | \-                    | [T, A]                 |
+| Types(a)                                  | A ⊑ B, B ⊑ A              | A(a),B(b)             | [A,B,T]                |
+| Types(b)                                  | A ⊑ B, B ⊑ A              | A(a),B(b)             | [A,B,T]                |
+| Instances(A)                              | A ⊑ B, B ⊑ A              | A(a),B(b)             | [a,b]                  |
+| Instances(B)                              | A ⊑ B, B ⊑ A              | A(a),B(b)             | [a,b]                  |
+| Equivalent_classes(A,only_named=False)    | A ⊑ B, B ⊑ A              | \-                    | [B]                    |
+| Equivalent_classes(B,only_named=False)    | A ⊑ B, B ⊑ A              | \-                    | [A]                    |
+| sub_object_properties(r1)                 | r2⊑ r1, r1⊑ r2            | \-                    | [r2,r1]                |
+| sub_object_properties(r2)                 | r2⊑ r1, r1⊑ r2            | \-                    | [r1,r2]                |
+| Equivalent_object_properties(r1)          | r2⊑ r1, r1⊑ r2            | \-                    | [r2]                   |
+| Equivalent_object_properties(r2)          | r2⊑ r1, r1⊑ r2            | \-                    | [r1]                   |
+| object_property_values(a, r1, direct=False) | r2 ⊑ r1, r1 ⊑ r2          | r1(a,b) r2(a,c)       | [b,c]                  |
+| object_property_values(a, r2, direct=False) | r2 ⊑ r1, r1 ⊑ r2          | r1(a,b) r2(a,c)       | [b,c]                  |
+| Sub_classes(J ⊓ K)                        | I ⊑ J ⊓ K                 | \-                    | [I]                    |
+| Super_classes(I, only_named=False)        | I ⊑ J ⊓ K                 | \-                    | [J ⊓ K, J, K, T]       |
+| Instances(J ⊓ K)                          | I ⊑ J ⊓ K                 | I(c)                  | [c]                    |
+| types(c)                                  | I ⊑ J ⊓ K                 | I(c)                  | [J, K, I, T]           |
+| Super_classes(J ⊓ K)                      | J ⊓ K ⊑ I                 | \-                    | [I, T]                 |
+| Sub_classes(I, only_named=False)          | J ⊓ K ⊑ I                 | \-                    | [J ⊓ K]                |
+| Instances(I)                              | J ⊓ K ⊑ I                 | J(s),K(s)             | [s]                    |
+| Instances(J ⊓ K)                          | J ⊓ K ⊑ I                 | J(s),K(s)             | [s]                    |
+| types(s)                                  | J ⊓ K ⊑ I                 | J(s),K(s)             | [J, K, I, T]           |
+| Sub_classes( ∃r.E ⊓ B )                    | D ⊑ ∃r.E ⊓ B              | \-                    | [D]                    |
+| Super_classes(D, only_named=False)        | D ⊑ ∃r.E ⊓ B              | \-                    | [T, ∃r.E ⊓ B, B]        |
+| Instances(∃r.E ⊓ B)                        | D ⊑ ∃r.E ⊓ B              | D(d) r(b,f) E(f) B(b) | [d,b]                  |
+| Sub_classes(H, only_named= False)         | F ≡ ∃r.G, F ⊑ H           | \-                    | [F, ∃r.G]               |
+| Super_classes(F)                          | F ≡ ∃r.G, F ⊑ H           | \-                    | [H,∃r.G,T]              |
+| Super_classes(∃r.G)                        | F ≡ ∃r.G, F ⊑ H           | \-                    | [F,H,T]                |
+| Equivalent_classes(F, only_named=False)   | F ≡ ∃r.G, F ⊑ H           | \-                    | [∃r.G]                  |
+| Equivalent_classes(∃r.G)                   | F ≡ ∃r.G, F ⊑ H           | \-                    | [F]                    |
+| Instances(∃r.G)                            | F ≡ ∃r.G, F ⊑ H           | r(i,g) G(g)           | [i]                    |
+| Instances(F)                              | F ≡ ∃r.G, F ⊑ H           | r(i,g) G(g)           | [i]                    |
+| Instances(H)                              | F ≡ ∃r.G, F ⊑ H           | r(i,g) G(g)           | [i]                    |
+| types(i)                                  | F ≡ ∃r.G, F ⊑ H           | r(i,g) G(g)           | [H,F,T]                |
+| Sub_classes(C, only_named=False)          | A ⊓ B ≡ R, R ⊑ C          | \-                    | [R, A ⊓ B]             |
+| Super_classes(A ⊓ B)                      | A ⊓ B ≡ R, R ⊑ C          | \-                    | [R, C,A,B,T]           |
+| Equivalent_classes(R,<br>only_named=False) | A ⊓ B ≡ R, R ⊑ C          | \-                    | [A ⊓ B]                |
+| Equivalent_classes(A ⊓ B)                 | A ⊓ B ≡ R, R ⊑ C          | \-                    | [R]                    |
+| Instances(A ⊓ B)                          | A ⊓ B ≡ R, R ⊑ C          | R(e) A(a) B(a)        | [e,a]                  |
+| Instances(R)                              | A ⊓ B ≡ R, R ⊑ C          | R(e) A(a) B(a)        | [a, e]                 |
+| Instances(C)                              | A ⊓ B ≡ R, R ⊑ C          | R(e) A(a) B(a)        | [a, e]                 |
+| Types(a)                                  | A ⊓ B ≡ R, R ⊑ C          | R(e) A(a) B(a)        | [A,B,R,C,T]            |
+| Types(e)                                  | A ⊓ B ≡ R, R ⊑ C          | R(e) A(a) B(a)        | [A,B,R,C,T]            |
+| Sub_classes(D, only_named=False)          | ∃r.P ⊓ C ≡ E, E ⊑ D       | \-                    | [E, ∃r.P  ⊓ C]          |
+| Super_classes(∃r.P ⊓ C)                    | ∃r.P ⊓ C ≡ E, E ⊑ D       | \-                    | [E, D, T]              |
+| Equivalent_classes(∃r.P ⊓ C)               | ∃r.P ⊓ C ≡ E, E ⊑ D       | \-                    | [E]                    |
+| Equivalent_classes(E,<br>only_named=False) | ∃r.P ⊓ C ≡ E, E ⊑ D       | \-                    | [∃r.P ⊓ C]              |
+| Instances(∃r.P ⊓ C)                        | ∃r.P ⊓ C ≡ E, E ⊑ D       | r(x,y) C(x) P(y)      | [x]                    |
+| Instances(E)                              | ∃r.P ⊓ C ≡ E, E ⊑ D       | r(x,y) C(x) P(y)      | [x]                    |
+| Instances(D)                              | ∃r.P ⊓ C ≡ E, E ⊑ D       | r(x,y) C(x) P(y)      | [x]                    |
+| Types(x)                                  | ∃r.P ⊓ C ≡ E, E ⊑ D       | r(x,y) C(x) P(y)      | [C]                    |
+| disjoint_classes(A)                       | A ⊔ B                     | \-                    | [B]                    |
+| disjoint_classes(B)                       | A ⊔ B                     | \-                    | [A]                    |
+| disjoint_classes(A)                       | A ⊔ B, B ≡ C              | \-                    | [B, C]                 |
+| disjoint_classes(B)                       | A ⊔ B, B ≡ C              | \-                    | [A]                    |
+| disjoint_classes(C)                       | A ⊔ B, B ≡ C              | \-                    | [A]                    |
+| object_property_domains(r)                | Domain(r) = A             | \-                    | [A,T]                  |
+| object_property_domains(r)                | Domain(r) = A<br>A ≡ B    | \-                    | [A,T]                  |
+| object_property_domains(r2)               | Domain(r1) = A<br>r2 ⊑ r1 | \-                    | [A,T]                  |
+
+
diff --git a/docs/usage/usage_examples.md b/docs/usage/usage_examples.md
index 41413dc8..1b4ece75 100644
--- a/docs/usage/usage_examples.md
+++ b/docs/usage/usage_examples.md
@@ -1,4 +1,4 @@
-# Usage
+# Basic Usage
 
 The main usage for owlapy is to use it for class expression construction. Class 
 expression learning algorithms require such basic structure to work upon. Let's walk 
@@ -123,4 +123,5 @@ print(manchester_to_owl_expression("female and (hasChild max 2 person)", namespa
 ```
 
 In these examples we showed a fraction of **owlapy**. You can explore the
-[api documentation](owlapy) to learn more about all classes in owlapy.
\ No newline at end of file
+[api documentation](owlapy) to learn more about all classes in owlapy and check more 
+examples in the [examples](https://github.com/dice-group/owlapy/tree/develop/examples) directory.
\ No newline at end of file
diff --git a/examples/comparing_adaptor_reasoners.py b/examples/comparing_adaptor_reasoners.py
new file mode 100644
index 00000000..d0c1c50a
--- /dev/null
+++ b/examples/comparing_adaptor_reasoners.py
@@ -0,0 +1,46 @@
+from owlapy.owl_property import OWLObjectProperty
+from owlapy.owlapi_adaptor import OWLAPIAdaptor
+from owlapy.iri import IRI
+from owlapy.class_expression import OWLClass, OWLObjectIntersectionOf, OWLObjectAllValuesFrom, OWLObjectComplementOf
+from owlapy.providers import owl_datatype_min_exclusive_restriction
+import time
+ontology_location = "../KGs/Carcinogenesis/carcinogenesis.owl"
+
+i1 = set()
+i2 = set()
+i3 = set()
+i4 = set()
+
+for reasoner in ["HermiT", "Pellet", "JFact", "Openllet"]:
+    adaptor = OWLAPIAdaptor(ontology_location, reasoner)
+
+    ce = OWLObjectAllValuesFrom(property=OWLObjectProperty(IRI('http://dl-learner.org/carcinogenesis#','hasAtom')),
+                                filler=OWLObjectComplementOf(OWLClass(IRI('http://dl-learner.org/carcinogenesis#',
+                                                                          'Sulfur-75'))))
+
+    if reasoner == "HermiT":
+        i1 = set(adaptor.instances(ce))
+    elif reasoner == "Pellet":
+        i2 = set(adaptor.instances(ce))
+    elif reasoner == "JFact":
+        i3 = set(adaptor.instances(ce))
+    elif reasoner == "Openllet":
+        i4 = set(adaptor.instances(ce))
+
+print("Hermit-Pellet:")
+[print(_) for _ in i1-i2]
+time.sleep(10)
+print("Hermit-JFact:")
+[print(_) for _ in i1-i3]
+time.sleep(10)
+print("Hermit-Openllet:")
+[print(_) for _ in i1-i4]
+time.sleep(10)
+print("Pellet-JFact:")
+[print(_) for _ in i2-i3]
+time.sleep(10)
+print("Pellet-Openllet:")
+[print(_) for _ in i2-i4]
+time.sleep(10)
+print("JFact-Openllet:")
+[print(_) for _ in i3-i4]
\ No newline at end of file
diff --git a/examples/create_and_convert_expressions.py b/examples/create_and_convert_expressions.py
new file mode 100644
index 00000000..d6973bf2
--- /dev/null
+++ b/examples/create_and_convert_expressions.py
@@ -0,0 +1,43 @@
+from owlapy.static_funcs import download_external_files
+from owlapy.class_expression import OWLClass, OWLObjectIntersectionOf
+from owlapy.iri import IRI
+from owlapy.owl_property import OWLObjectProperty
+from owlapy.class_expression import OWLObjectMinCardinality
+from owlapy import owl_expression_to_sparql, owl_expression_to_dl, owl_expression_to_manchester
+
+"""Full documentation on this example: https://dice-group.github.io/owlapy/usage/usage_examples.html#"""
+
+# Download the ontologies from our ftp server if not already:
+# - this is just for you to see the structure of KGs/Family/father.owl ontology
+# - the datasets are not required in the script (you can comment the following line out if you want)
+download_external_files("https://files.dice-research.org/projects/Ontolearn/KGs.zip")
+
+# Create instance of atomic concepts:
+namespace = "http://example.com/family#"
+male = OWLClass(IRI(namespace, "male"))
+female = OWLClass(IRI(namespace, "female"))
+person = OWLClass(IRI(namespace, "person"))
+
+# Create instance of an object property:
+hasChild = OWLObjectProperty("http://example.com/family#hasChild")
+
+# Create a complex class expression:
+# - here a minimum cardinality restriction is created (https://www.w3.org/TR/owl2-syntax/#Minimum_Cardinality)
+has_at_least_one_child = OWLObjectMinCardinality(
+    cardinality=1,
+    property=hasChild,
+    filler=person
+)
+
+# Create an even more complex class expression:
+# - here an intersection of 2 class expressions (https://www.w3.org/TR/owl2-syntax/#Intersection_of_Class_Expressions)
+ce = OWLObjectIntersectionOf([male, has_at_least_one_child])
+
+# Convert to Description Logics expression
+print(f"----DL---- \n{owl_expression_to_dl(ce)}\n")
+
+# Convert to SPARQL query
+print(f"----SPARQL---- \n{owl_expression_to_sparql(ce)}\n")
+
+# Convert to Manchester expression
+print(f"----Manchester---- \n{owl_expression_to_manchester(ce)}\n")
diff --git a/examples/ontology_modification.py b/examples/ontology_modification.py
new file mode 100644
index 00000000..87d6e1ce
--- /dev/null
+++ b/examples/ontology_modification.py
@@ -0,0 +1,46 @@
+from owlapy.class_expression import OWLClass
+from owlapy.owl_axiom import OWLDeclarationAxiom, OWLClassAxiom, OWLClassAssertionAxiom
+from owlapy.owl_individual import OWLNamedIndividual
+from owlapy.owl_ontology_manager import OntologyManager
+from owlapy.iri import IRI
+from owlapy.static_funcs import download_external_files
+
+# Download the datasets if KGs does not exist.
+download_external_files("https://files.dice-research.org/projects/Ontolearn/KGs.zip")
+
+# Load the 'father' ontology using a new ontology manager.
+manager = OntologyManager()
+onto = manager.load_ontology(IRI.create(f'file://../KGs/Family/father.owl'))
+
+# Let's see what classes does this ontology has
+[print(_) for _ in onto.classes_in_signature()]
+
+# Create a new class
+new_class = OWLClass(IRI.create('http://example.com/father#child'))
+
+# Add a declaration axiom for this class using ontology manager
+manager.add_axiom(ontology=onto, axiom=OWLDeclarationAxiom(new_class))
+
+# Check whether the new class is added in the signature of the ontology
+print("------------------------")
+[print(_) for _ in onto.classes_in_signature()]
+
+# Add an individual of type child in the ontology
+new_ind = OWLNamedIndividual('http://example.com/father#lulu')
+manager.add_axiom(onto, OWLClassAssertionAxiom(new_ind, new_class))
+
+# Check if Lulu is added
+
+print("----------------------")
+[print(_) for _ in onto.individuals_in_signature()]
+
+# Save the modified ontology locally (otherwise the changes will be lost)
+manager.save_ontology(ontology=onto, document_iri=IRI.create("file:/../KGs/Family/father_modified.owl"))
+# NOTE: using the same name will overwrite the current file with the new one.
+
+
+"""
+You can also remove axioms by using manage.remove_axiom.
+There are countless axioms which you can add or remove from an ontology. 
+Check them here: https://dice-group.github.io/owlapy/autoapi/owlapy/owl_axiom/index.html
+"""
\ No newline at end of file
diff --git a/examples/ontology_reasoning.py b/examples/ontology_reasoning.py
new file mode 100644
index 00000000..03959372
--- /dev/null
+++ b/examples/ontology_reasoning.py
@@ -0,0 +1,304 @@
+from owlapy.class_expression import OWLObjectSomeValuesFrom, OWLObjectIntersectionOf, OWLClass
+from owlapy.iri import IRI
+from owlapy.owl_axiom import OWLSubClassOfAxiom, OWLObjectPropertyDomainAxiom, OWLEquivalentObjectPropertiesAxiom
+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
+
+data_file = '../KGs/Test/test_ontology.owl'
+NS = 'http://www.semanticweb.org/stefan/ontologies/2023/1/untitled-ontology-11#'
+
+"""
+---------Object Properties---------
+Domain(r1) = S ⊓ T, Range(r1) = G
+r2 ⊑ r1
+r3 ⊑ r4
+r7
+r5 ⊓ r1 = ∅
+r5 ≡ r6
+
+---------Data Properties---------
+dp2 ⊑ dp1
+dp3 ⊓ dp1 = ∅
+
+---------Classes-----------
+AB ≡ (A ⊓ B), AB ⊑ C
+D ⊑ (r7.E ⊓ B)
+F ≡ r2.G, F ⊑ H
+I ⊑ (J ⊓ K)
+L ⊓ M = ∅
+N ≡ Q
+O ⊑ P
+R ⊑ r5.Q
+(S ⊓ T) ⊑ U
+
+---------Individuals-----------
+o is O
+p is P
+a is A ^ B
+b is B, b has r1.f
+c is I
+d is D
+e is AB
+f is E
+g is G
+n is N, n has r3.q, r4.l, r6.s, r5.Q
+m is M
+l is L
+l ≠ m
+q is Q
+ind1 has r5.q, r2.g, r6.(S ⊓ T)
+r is R
+s is S ^ T
+"""
+
+# ---------------------------------------- Mapping to owlapy classes ----------------------------------------
+a = OWLNamedIndividual(IRI(NS, "a"))
+b = OWLNamedIndividual(IRI(NS, "b"))
+c = OWLNamedIndividual(IRI(NS, "c"))
+d = OWLNamedIndividual(IRI(NS, "d"))
+e = OWLNamedIndividual(IRI(NS, "e"))
+g = OWLNamedIndividual(IRI(NS, "g"))
+m = OWLNamedIndividual(IRI(NS, "m"))
+l = OWLNamedIndividual(IRI(NS, "l"))
+n = OWLNamedIndividual(IRI(NS, "n"))
+o = OWLNamedIndividual(IRI(NS, "o"))
+p = OWLNamedIndividual(IRI(NS, "p"))
+q = OWLNamedIndividual(IRI(NS, "q"))
+r = OWLNamedIndividual(IRI(NS, "r"))
+s = OWLNamedIndividual(IRI(NS, "s"))
+ind1 = OWLNamedIndividual(IRI(NS, "ind1"))
+
+r1 = OWLObjectProperty(IRI(NS, "r1"))
+r2 = OWLObjectProperty(IRI(NS, "r2"))
+r3 = OWLObjectProperty(IRI(NS, "r3"))
+r4 = OWLObjectProperty(IRI(NS, "r4"))
+r5 = OWLObjectProperty(IRI(NS, "r5"))
+r6 = OWLObjectProperty(IRI(NS, "r6"))
+r7 = OWLObjectProperty(IRI(NS, "r7"))
+
+dp1 = OWLDataProperty(IRI(NS, "dp1"))
+dp2 = OWLDataProperty(IRI(NS, "dp2"))
+dp3 = OWLDataProperty(IRI(NS, "dp3"))
+
+A = OWLClass(IRI(NS, 'A'))
+B = OWLClass(IRI(NS, 'B'))
+C = OWLClass(IRI(NS, 'C'))
+AB = OWLClass(IRI(NS, 'AB'))
+D = OWLClass(IRI(NS, 'D'))
+E = OWLClass(IRI(NS, 'E'))
+F = OWLClass(IRI(NS, 'F'))
+G = OWLClass(IRI(NS, 'G'))
+J = OWLClass(IRI(NS, 'J'))
+K = OWLClass(IRI(NS, 'K'))
+H = OWLClass(IRI(NS, 'H'))
+I = OWLClass(IRI(NS, 'I'))
+L = OWLClass(IRI(NS, 'L'))
+M = OWLClass(IRI(NS, 'M'))
+N = OWLClass(IRI(NS, 'N'))
+O = OWLClass(IRI(NS, 'O'))
+P = OWLClass(IRI(NS, 'P'))
+Q = OWLClass(IRI(NS, 'Q'))
+R = OWLClass(IRI(NS, 'R'))
+S = OWLClass(IRI(NS, 'S'))
+T = OWLClass(IRI(NS, 'T'))
+U = OWLClass(IRI(NS, 'U'))
+
+# ---------------------------------------- Adding some custom axioms ----------------------------------------
+
+r2G = OWLObjectSomeValuesFrom(property=r2, filler=G)
+r5Q = OWLObjectSomeValuesFrom(property=r5, filler=Q)
+ST = OWLObjectIntersectionOf([S, T])
+ABint = OWLObjectIntersectionOf([A, B])
+r7E = OWLObjectSomeValuesFrom(property=r7, filler=E)
+r7EB = OWLObjectIntersectionOf([r7E, B])
+JK = OWLObjectIntersectionOf([J, K])
+r1T = OWLObjectSomeValuesFrom(property=r1, filler=OWLClass(IRI('http://www.w3.org/2002/07/owl#', 'Thing')))
+
+manager = OntologyManager()
+onto = manager.load_ontology(IRI.create(f'file://' + data_file))
+
+manager.add_axiom(onto, OWLEquivalentObjectPropertiesAxiom([r6, r5]))
+manager.add_axiom(onto, OWLEquivalentObjectPropertiesAxiom([r5, r6]))
+manager.add_axiom(onto, OWLObjectPropertyDomainAxiom(r1, ST))
+
+manager.add_axiom(onto, OWLSubClassOfAxiom(R, r5Q))
+manager.add_axiom(onto, OWLSubClassOfAxiom(ST, U))
+
+base_reasoner = OntologyReasoner(onto)
+
+# ---------------------------------------- Reasoning ----------------------------------------
+
+reasoner = SyncReasoner(onto, BaseReasoner.HERMIT)
+
+# Instances
+t1 = list(reasoner.instances(N))
+t2 = list(reasoner.instances(r7E))
+t3 = list(reasoner.instances(D))
+t4 = list(reasoner.instances(H))
+t5 = list(reasoner.instances(JK))
+t6 = list(reasoner.instances(C))
+t7 = list(reasoner.instances(r2G))
+t8 = list(reasoner.instances(F))
+t9 = list(reasoner.instances(ABint))
+t10 = list(reasoner.instances(AB))
+t11 = list(reasoner.instances(r7EB))
+t12 = list(reasoner.instances(P))
+t13 = list(reasoner.instances(O))
+t14 = list(reasoner.instances(N))
+t15 = list(reasoner.instances(Q))
+t16 = list(reasoner.instances(r5Q))
+t17 = list(reasoner.instances(R))
+t18 = list(reasoner.instances(ST))
+t19 = list(reasoner.instances(U))
+t20 = list(reasoner.instances(r2G))
+
+# Equivalent classes
+t21 = list(reasoner.equivalent_classes(A))
+t22 = list(reasoner.equivalent_classes(L))
+t23 = list(reasoner.equivalent_classes(AB, only_named=False))
+t24 = list(reasoner.equivalent_classes(ABint))
+t25 = list(reasoner.equivalent_classes(F, only_named=False))
+t26 = list(reasoner.equivalent_classes(r2G))
+t27 = list(reasoner.equivalent_classes(O))
+t28 = list(reasoner.equivalent_classes(P, only_named=False))
+t29 = list(reasoner.equivalent_classes(N, only_named=False))
+t30 = list(reasoner.equivalent_classes(Q))
+
+# Equivalent Object Properties
+t31 = list(reasoner.equivalent_object_properties(r1))
+t32 = list(reasoner.equivalent_object_properties(r2))
+t33 = list(reasoner.equivalent_object_properties(r5))
+t34 = list(reasoner.equivalent_object_properties(r6))
+t35 = list(reasoner.equivalent_object_properties(r3))
+t36 = list(reasoner.equivalent_object_properties(r4))
+
+# Sup Classes
+t37 = list(reasoner.sub_classes(r7E))
+t38 = list(reasoner.sub_classes(r7EB))
+t39 = list(reasoner.sub_classes(H, only_named=False))
+t40 = list(reasoner.sub_classes(JK))
+t41 = list(reasoner.sub_classes(C, only_named=False))
+t42 = list(reasoner.sub_classes(A, only_named=False))
+t43 = list(reasoner.sub_classes(P))
+t44 = list(reasoner.sub_classes(O))
+t45 = list(reasoner.sub_classes(N, only_named=False))
+t46 = list(reasoner.sub_classes(Q, only_named=False))
+t47 = list(reasoner.sub_classes(r5Q, only_named=False))
+t48 = list(reasoner.sub_classes(R, only_named=False))
+t49 = list(reasoner.sub_classes(U, only_named=False))
+t50 = list(reasoner.sub_classes(r1T, only_named=False))
+
+# Sub Object Properties
+t51 = list(reasoner.sub_object_properties(r1))
+t52 = list(reasoner.sub_object_properties(r2))
+t59 = list(reasoner.sub_object_properties(r3))
+t60 = list(reasoner.sub_object_properties(r4))
+t85 = list(reasoner.sub_object_properties(r5))
+t86 = list(reasoner.sub_object_properties(r6))
+
+# Super Classes
+t87 = list(reasoner.super_classes(D, only_named=False))
+t88 = list(reasoner.super_classes(r2G))
+t89 = list(reasoner.super_classes(I, only_named=False))
+t90 = list(reasoner.super_classes(AB))
+t91 = list(reasoner.super_classes(ABint))
+t92 = list(reasoner.super_classes(P))
+t93 = list(reasoner.super_classes(O))
+t94 = list(reasoner.super_classes(N, only_named=False))
+t95 = list(reasoner.super_classes(Q, only_named=False))
+t96 = list(reasoner.super_classes(r5Q, only_named=False))
+t97 = list(reasoner.super_classes(R, only_named=False))
+t98 = list(reasoner.super_classes(ST, only_named=False))
+t99 = list(reasoner.super_classes(F))
+
+# Types
+t100 = list(reasoner.types(a))
+t101 = list(reasoner.types(d))
+t102 = list(reasoner.types(g))
+t103 = list(reasoner.types(p))
+t104 = list(reasoner.types(o))
+t105 = list(reasoner.types(n))
+t106 = list(reasoner.types(q))
+t107 = list(reasoner.types(r))
+t108 = list(reasoner.types(ind1))
+t109 = list(reasoner.types(e))
+t110 = list(reasoner.types(c))
+t111 = list(reasoner.types(s))
+
+# Different Individuals
+t112 = list(reasoner.different_individuals(a))
+t113 = list(reasoner.different_individuals(b))
+t114 = list(reasoner.different_individuals(l))
+t115 = list(reasoner.different_individuals(n))
+
+# Same Individuals
+t116 = list(reasoner.same_individuals(o))
+t117 = list(reasoner.same_individuals(p))
+t118 = list(reasoner.same_individuals(r))
+t119 = list(reasoner.same_individuals(q))
+t120 = list(reasoner.same_individuals(s))
+
+# Disjoint Classes
+t121 = list(reasoner.disjoint_classes(M, only_named=False))
+t122 = list(reasoner.disjoint_classes(N, only_named=False))
+t123 = list(reasoner.disjoint_classes(L, only_named=False))
+t124 = list(reasoner.disjoint_classes(O, only_named=False))
+t125 = list(reasoner.disjoint_classes(P, only_named=False))
+t126 = list(reasoner.disjoint_classes(Q, only_named=False))
+
+# Disjoint Object Properties
+t127 = list(reasoner.disjoint_object_properties(r3))
+t128 = list(reasoner.disjoint_object_properties(r4))
+t129 = list(reasoner.disjoint_object_properties(r1))
+t130 = list(reasoner.disjoint_object_properties(r2))
+t131 = list(reasoner.disjoint_object_properties(r5))
+t132 = list(reasoner.disjoint_object_properties(r6))
+
+# Disjoint Data Properties
+t133 = list(reasoner.disjoint_data_properties(dp1))
+t134 = list(reasoner.disjoint_data_properties(dp2))
+t135 = list(reasoner.disjoint_data_properties(dp3))
+
+# Object Properties Domains and Ranges
+t136 = list(reasoner.object_property_domains(r1))
+t137 = list(reasoner.object_property_domains(r2))
+t138 = list(reasoner.object_property_ranges(r1))
+t139 = list(reasoner.object_property_ranges(r2))
+
+# Object Properties Values
+t140 = list(reasoner.object_property_values(n, r3, direct=False))
+t141 = list(reasoner.object_property_values(n, r4, direct=False))
+t142 = list(reasoner.object_property_values(n, r5))
+t143 = list(reasoner.object_property_values(n, r6))
+t144 = list(reasoner.object_property_values(ind1, r6))
+t145 = list(reasoner.object_property_values(ind1, r5))
+
+# Super Object Properties
+t146 = list(reasoner.super_object_properties(r1))
+t147 = list(reasoner.super_object_properties(r2))
+t148 = list(reasoner.super_object_properties(r5))
+t149 = list(reasoner.super_object_properties(r6))
+
+# Sub Object Properties
+t150 = list(reasoner.sub_object_properties(r1))
+t151 = list(reasoner.sub_object_properties(r2))
+t152 = list(reasoner.sub_object_properties(r5))
+t153 = list(reasoner.sub_object_properties(r6))
+
+# Super Data Properties
+t154 = list(reasoner.super_data_properties(dp1))
+t155 = list(reasoner.super_data_properties(dp2))
+
+# Sub Data Properties
+t156 = list(reasoner.sub_data_properties(dp1))
+t157 = list(reasoner.sub_data_properties(dp2))
+
+# Display the results
+for x in range(1, 157):
+    var_name = f"t{x}"
+    try:
+        print(f" {var_name} = {globals()[var_name]}")
+    except KeyError:
+        continue
diff --git a/examples/parse_expressions.py b/examples/parse_expressions.py
new file mode 100644
index 00000000..2a205b64
--- /dev/null
+++ b/examples/parse_expressions.py
@@ -0,0 +1,11 @@
+from owlapy import dl_to_owl_expression, manchester_to_owl_expression
+
+# Define the namespace of your ontology
+namespace = "http://example.com/family#"
+
+# Convert dl or manchester expressions (as string) to owl expressions.
+print(dl_to_owl_expression("∃ hasChild.male", namespace))
+
+print(manchester_to_owl_expression("female and (hasChild max 2 person)", namespace))
+
+# It's that simple :)
diff --git a/examples/using_owlapi_adaptor.py b/examples/using_owlapi_adaptor.py
new file mode 100644
index 00000000..7c9dbaec
--- /dev/null
+++ b/examples/using_owlapi_adaptor.py
@@ -0,0 +1,33 @@
+from owlapy.owlapi_adaptor import OWLAPIAdaptor
+from owlapy.iri import IRI
+from owlapy.class_expression import OWLClass, OWLObjectIntersectionOf
+
+ontology_location = "../KGs/Family/family-benchmark_rich_background.owl"
+
+# Start an adaptor session and perform your operations.
+adaptor = OWLAPIAdaptor(ontology_location, "HermiT")
+# Check ontology consistency
+print(f"Is the given ontology consistent? --> {adaptor.has_consistent_ontology()}")
+
+# Construct an owlapy class expression
+brother = OWLClass(IRI.create("http://www.benchmark.org/family#Brother"))
+father = OWLClass(IRI.create("http://www.benchmark.org/family#Father"))
+brother_and_father = OWLObjectIntersectionOf([brother, father])
+
+# Find individual belonging to that class expression
+instances = adaptor.instances(brother_and_father)
+print("----------------------")
+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)
+
+# Convert from owlapi to owlapy
+pi_to_py = adaptor.convert_from_owlapi(py_to_pi, "http://www.benchmark.org/family#")
+print("----------------------")
+print(f"Owlapy ce: {pi_to_py}")
+
+# Stop the JVM to free the associated resources.
+adaptor.stopJVM()  # or jpype.shutdownJVM()
+
diff --git a/owlapy/__init__.py b/owlapy/__init__.py
index 124f7215..4b50bc94 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.0.2'
+__version__ = '1.1.1'
diff --git a/owlapy/class_expression/class_expression.py b/owlapy/class_expression/class_expression.py
index 375ae9d5..8e7bab6a 100644
--- a/owlapy/class_expression/class_expression.py
+++ b/owlapy/class_expression/class_expression.py
@@ -67,7 +67,7 @@ def get_object_complement_of(self) -> 'OWLObjectComplementOf':
 
     def get_nnf(self) -> 'OWLClassExpression':
         # documented in parent
-        from owlapy.util import NNF
+        from owlapy.utils import NNF
         return NNF().get_class_nnf(self)
 
 
diff --git a/owlapy/converter.py b/owlapy/converter.py
index c83dbe50..fb7ee3f4 100644
--- a/owlapy/converter.py
+++ b/owlapy/converter.py
@@ -85,7 +85,7 @@ def __getitem__(self, item: OWLEntity) -> str:
 class Owl2SparqlConverter:
     """Convert owl (owlapy model class expressions) to SPARQL."""
     __slots__ = 'ce', 'sparql', 'variables', 'parent', 'parent_var', 'properties', 'variable_entities', 'cnt', \
-                'mapping', 'grouping_vars', 'having_conditions', '_intersection'
+                'mapping', 'grouping_vars', 'having_conditions', 'for_all_de_morgan', 'named_individuals', '_intersection'
     # @TODO:CD: We need to document this class. The computation behind the mapping is not clear.
 
     ce: OWLClassExpression
@@ -100,8 +100,13 @@ class Owl2SparqlConverter:
     grouping_vars: Dict[OWLClassExpression, Set[str]]
     having_conditions: Dict[OWLClassExpression, Set[str]]
     cnt: int
+    for_all_de_morgan: bool
+    named_individuals: bool
 
-    def convert(self, root_variable: str, ce: OWLClassExpression, named_individuals: bool = False):
+    def convert(self, root_variable: str,
+                ce: OWLClassExpression,
+                for_all_de_morgan: bool = True,
+                named_individuals: bool = False):
         """Used to convert owl class expression to SPARQL syntax.
 
         Args:
@@ -124,9 +129,11 @@ def convert(self, root_variable: str, ce: OWLClassExpression, named_individuals:
         self.mapping = VariablesMapping()
         self.grouping_vars = defaultdict(set)
         self.having_conditions = defaultdict(set)
-        # if named_individuals is True, we return only entities that are instances of owl:NamedIndividual
-        if named_individuals:
-            self.append_triple(root_variable, 'a', f"<{OWLRDFVocabulary.OWL_NAMED_INDIVIDUAL.as_str()}>")
+        self.for_all_de_morgan = for_all_de_morgan
+        self.named_individuals = named_individuals
+        # # if named_individuals is True, we return only entities that are instances of owl:NamedIndividual
+        # if named_individuals:
+        #     self.append_triple(root_variable, 'a', f"<{OWLRDFVocabulary.OWL_NAMED_INDIVIDUAL.as_str()}>")
         with self.stack_variable(root_variable):
             with self.stack_parent(ce):
                 self.process(ce)
@@ -255,7 +262,19 @@ def _(self, ce: OWLObjectUnionOf):
     @process.register
     def _(self, ce: OWLObjectComplementOf):
         subject = self.current_variable
-        self.append_triple(subject, self.mapping.new_individual_variable(), self.mapping.new_individual_variable())
+        # the conversion was trying here to optimize the query
+        # but the proposed optimization alters the semantics of some queries
+        # example: ( A ⊓ ( B ⊔ ( ¬C ) ) )
+        # with the proposed optimization, the group graph pattern for (¬C) will be { FILTER NOT EXISTS { ?x a C } }
+        # however, the expected pattern is { ?x ?p ?o . FILTER NOT EXISTS { ?x a C } }
+        # the exclusion of "?x ?p ?o" results in the group graph pattern to just return true or false (not bindings)
+        # as a result, we need to comment out the if-clause of the following line
+        # if not self.in_intersection and self.modal_depth == 1:
+        # if namedIndividual is set to True, do not use variables --> restrict the subject to instances of NamedIndividual
+        if self.named_individuals:
+            self.append_triple(subject, "a", f"<{OWLRDFVocabulary.OWL_NAMED_INDIVIDUAL.as_str()}>")
+        else:
+            self.append_triple(subject, self.mapping.new_individual_variable(), self.mapping.new_individual_variable())
 
         self.append("FILTER NOT EXISTS { ")
         # process the concept after the ¬
@@ -280,11 +299,17 @@ def _(self, ce: OWLObjectSomeValuesFrom):
         with self.stack_variable(object_variable):
             self.process(filler)
 
+    @process.register
+    def _(self, ce: OWLObjectAllValuesFrom):
+        if self.for_all_de_morgan is True:
+            self.forAllDeMorgan(ce)
+        else:
+            self.forAll(ce)
+
     # an overload of process function
     # this overload is responsible for handling the forAll operator (e.g., ∀hasChild.Male)
     # general case: ∀r.C
-    @process.register
-    def _(self, ce: OWLObjectAllValuesFrom):
+    def forAll(self, ce: OWLObjectAllValuesFrom):
         subject = self.current_variable
         object_variable = self.mapping.new_individual_variable()
         # property expression holds the role of the class expression (hasChild in our example)
@@ -333,6 +358,34 @@ def _(self, ce: OWLObjectAllValuesFrom):
             self.append_triple(self.current_variable, predicate, self.mapping.new_individual_variable())
         self.append(" } }")
 
+    # an overload of process function
+    # this overload is responsible for handling the forAll operator but as if the DeMorgan law was applied
+    # (e.g., ∀hasChild.Male == ¬(∃hasChild.¬Male))
+    # general case: ∀r.C == ¬(∃r.¬C)
+    def forAllDeMorgan(self, ce: OWLObjectAllValuesFrom):
+        subject = self.current_variable
+        # here, we need to apply the complement rule twice
+        # the first filter not exists covers the outer ¬
+        self.append_triple(subject, self.mapping.new_individual_variable(), self.mapping.new_individual_variable())
+        self.append("FILTER NOT EXISTS { ")
+        object_variable = self.mapping.new_individual_variable()
+        # property expression holds the role of the class expression (hasChild in our example)
+        property_expression = ce.get_property()
+        if property_expression.is_anonymous():
+            # property expression is inverse of a property
+            self.append_triple(object_variable, property_expression.get_named_property(), self.current_variable)
+        else:
+            self.append_triple(self.current_variable, property_expression.get_named_property(), object_variable)
+
+        # the second filter not exists covers the inner ¬
+        # filler holds the concept of the expression (Male in our example) and is processed recursively
+        self.append("FILTER NOT EXISTS { ")
+        filler = ce.get_filler()
+        with self.stack_variable(object_variable):
+            self.process(filler)
+        self.append(" }")
+        self.append(" }")
+
     # an overload of process function
     # this overload is responsible for handling the exists operator combined with an individual (e.g., ∃hasChild.{john})
     # general case: ∃r.{a}
@@ -550,21 +603,19 @@ def triple(self, subject, predicate, object_):
     def as_query(self,
                  root_variable: str,
                  ce: OWLClassExpression,
+                 for_all_de_morgan: bool = True,
                  count: bool = False,
                  values: Optional[Iterable[OWLNamedIndividual]] = None,
-                 named_individuals: bool = False)->str:
-        """
-        root variable: the variable that will be projected
-        ce: the class expression to be transformed to a SPARQL query
-        count: True, counts the results ; False, projects the individuals
-        values: positive or negative examples from a class expression problem
-        named_individuals: if set to True, the generated SPARQL query will return only entities that are instances
-        of owl:NamedIndividual
-
-        """
-
+                 named_individuals: bool = False) -> str:
+        # root variable: the variable that will be projected
+        # ce: the class expression to be transformed to a SPARQL query
+        # for_all_de_morgan: true -> ¬(∃r.¬C), false -> (∀r.C)
+        # count: True, counts the results ; False, projects the individuals
+        # values: positive or negative examples from a class expression problem
+        # named_individuals: if set to True, the generated SPARQL query will return only entities that are instances
+        #                    of owl:NamedIndividual
         qs = ["SELECT"]
-        tp = self.convert(root_variable, ce, named_individuals)
+        tp = self.convert(root_variable, ce, for_all_de_morgan=for_all_de_morgan, named_individuals=named_individuals)
         if count:
             qs.append(f" ( COUNT ( DISTINCT {root_variable} ) AS ?cnt ) WHERE {{ ")
         else:
@@ -590,14 +641,18 @@ def as_query(self,
 def owl_expression_to_sparql(expression: OWLClassExpression = None,
                              root_variable: str = "?x",
                              values: Optional[Iterable[OWLNamedIndividual]] = None,
+                             for_all_de_morgan: bool = True,
                              named_individuals: bool = False) -> str:
     """Convert an OWL Class Expression (https://www.w3.org/TR/owl2-syntax/#Class_Expressions) into a SPARQL query
      root variable: the variable that will be projected
      expression: the class expression to be transformed to a SPARQL query
 
      values: positive or negative examples from a class expression problem. Unclear
+     for_all_de_morgan: if set to True, the SPARQL mapping will use the mapping containing the nested FILTER NOT EXISTS
+     patterns for the universal quantifier (¬(∃r.¬C)), instead of the counting query
      named_individuals: if set to True, the generated SPARQL query will return only entities
      that are instances of owl:NamedIndividual
     """
     assert expression is not None, "expression cannot be None"
-    return converter.as_query(root_variable, expression, False, values, named_individuals)
+    return converter.as_query(root_variable, expression, count=False, values=values,
+                              named_individuals=named_individuals, for_all_de_morgan=for_all_de_morgan)
diff --git a/owlapy/jar_dependencies/RoaringBitmap-0.6.32.jar b/owlapy/jar_dependencies/RoaringBitmap-0.6.32.jar
new file mode 100644
index 00000000..a92f8630
Binary files /dev/null and b/owlapy/jar_dependencies/RoaringBitmap-0.6.32.jar differ
diff --git a/owlapy/jar_dependencies/animal-sniffer-annotations-1.14.jar b/owlapy/jar_dependencies/animal-sniffer-annotations-1.14.jar
new file mode 100644
index 00000000..fb76acf7
Binary files /dev/null and b/owlapy/jar_dependencies/animal-sniffer-annotations-1.14.jar differ
diff --git a/owlapy/jar_dependencies/automaton-1.11-8.jar b/owlapy/jar_dependencies/automaton-1.11-8.jar
new file mode 100644
index 00000000..60f6ce01
Binary files /dev/null and b/owlapy/jar_dependencies/automaton-1.11-8.jar differ
diff --git a/owlapy/jar_dependencies/caffeine-2.6.1.jar b/owlapy/jar_dependencies/caffeine-2.6.1.jar
new file mode 100644
index 00000000..5c227dd7
Binary files /dev/null and b/owlapy/jar_dependencies/caffeine-2.6.1.jar differ
diff --git a/owlapy/jar_dependencies/commons-codec-1.10.jar b/owlapy/jar_dependencies/commons-codec-1.10.jar
new file mode 100644
index 00000000..1d7417c4
Binary files /dev/null and b/owlapy/jar_dependencies/commons-codec-1.10.jar differ
diff --git a/owlapy/jar_dependencies/commons-io-2.5.jar b/owlapy/jar_dependencies/commons-io-2.5.jar
new file mode 100644
index 00000000..107b061f
Binary files /dev/null and b/owlapy/jar_dependencies/commons-io-2.5.jar differ
diff --git a/owlapy/jar_dependencies/commons-rdf-api-0.5.0.jar b/owlapy/jar_dependencies/commons-rdf-api-0.5.0.jar
new file mode 100644
index 00000000..554b8d92
Binary files /dev/null and b/owlapy/jar_dependencies/commons-rdf-api-0.5.0.jar differ
diff --git a/owlapy/jar_dependencies/error_prone_annotations-2.0.18.jar b/owlapy/jar_dependencies/error_prone_annotations-2.0.18.jar
new file mode 100644
index 00000000..fa549b4d
Binary files /dev/null and b/owlapy/jar_dependencies/error_prone_annotations-2.0.18.jar differ
diff --git a/owlapy/jar_dependencies/fluent-hc-4.5.5.jar b/owlapy/jar_dependencies/fluent-hc-4.5.5.jar
new file mode 100644
index 00000000..f1495c76
Binary files /dev/null and b/owlapy/jar_dependencies/fluent-hc-4.5.5.jar differ
diff --git a/owlapy/jar_dependencies/guava-22.0.jar b/owlapy/jar_dependencies/guava-22.0.jar
new file mode 100644
index 00000000..fc7f3165
Binary files /dev/null and b/owlapy/jar_dependencies/guava-22.0.jar differ
diff --git a/owlapy/jar_dependencies/hppcrt-0.7.5.jar b/owlapy/jar_dependencies/hppcrt-0.7.5.jar
new file mode 100644
index 00000000..8debba90
Binary files /dev/null and b/owlapy/jar_dependencies/hppcrt-0.7.5.jar differ
diff --git a/owlapy/jar_dependencies/httpclient-4.5.2.jar b/owlapy/jar_dependencies/httpclient-4.5.2.jar
new file mode 100644
index 00000000..701609fc
Binary files /dev/null and b/owlapy/jar_dependencies/httpclient-4.5.2.jar differ
diff --git a/owlapy/jar_dependencies/httpclient-cache-4.5.2.jar b/owlapy/jar_dependencies/httpclient-cache-4.5.2.jar
new file mode 100644
index 00000000..a6f5d317
Binary files /dev/null and b/owlapy/jar_dependencies/httpclient-cache-4.5.2.jar differ
diff --git a/owlapy/jar_dependencies/httpclient-osgi-4.5.5.jar b/owlapy/jar_dependencies/httpclient-osgi-4.5.5.jar
new file mode 100644
index 00000000..9d28c6e1
Binary files /dev/null and b/owlapy/jar_dependencies/httpclient-osgi-4.5.5.jar differ
diff --git a/owlapy/jar_dependencies/httpcore-4.4.4.jar b/owlapy/jar_dependencies/httpcore-4.4.4.jar
new file mode 100644
index 00000000..ac4a8773
Binary files /dev/null and b/owlapy/jar_dependencies/httpcore-4.4.4.jar differ
diff --git a/owlapy/jar_dependencies/httpcore-nio-4.4.5.jar b/owlapy/jar_dependencies/httpcore-nio-4.4.5.jar
new file mode 100644
index 00000000..18452862
Binary files /dev/null and b/owlapy/jar_dependencies/httpcore-nio-4.4.5.jar differ
diff --git a/owlapy/jar_dependencies/httpcore-osgi-4.4.5.jar b/owlapy/jar_dependencies/httpcore-osgi-4.4.5.jar
new file mode 100644
index 00000000..98bd96d9
Binary files /dev/null and b/owlapy/jar_dependencies/httpcore-osgi-4.4.5.jar differ
diff --git a/owlapy/jar_dependencies/httpmime-4.5.5.jar b/owlapy/jar_dependencies/httpmime-4.5.5.jar
new file mode 100644
index 00000000..4d3ee66f
Binary files /dev/null and b/owlapy/jar_dependencies/httpmime-4.5.5.jar differ
diff --git a/owlapy/jar_dependencies/j2objc-annotations-1.1.jar b/owlapy/jar_dependencies/j2objc-annotations-1.1.jar
new file mode 100644
index 00000000..4b6f1274
Binary files /dev/null and b/owlapy/jar_dependencies/j2objc-annotations-1.1.jar differ
diff --git a/owlapy/jar_dependencies/jackson-annotations-2.9.7.jar b/owlapy/jar_dependencies/jackson-annotations-2.9.7.jar
new file mode 100644
index 00000000..46f56b26
Binary files /dev/null and b/owlapy/jar_dependencies/jackson-annotations-2.9.7.jar differ
diff --git a/owlapy/jar_dependencies/jackson-core-2.9.7.jar b/owlapy/jar_dependencies/jackson-core-2.9.7.jar
new file mode 100644
index 00000000..fa46a7f6
Binary files /dev/null and b/owlapy/jar_dependencies/jackson-core-2.9.7.jar differ
diff --git a/owlapy/jar_dependencies/jackson-databind-2.9.7.jar b/owlapy/jar_dependencies/jackson-databind-2.9.7.jar
new file mode 100644
index 00000000..76d50b4f
Binary files /dev/null and b/owlapy/jar_dependencies/jackson-databind-2.9.7.jar differ
diff --git a/owlapy/jar_dependencies/javax.inject-1.jar b/owlapy/jar_dependencies/javax.inject-1.jar
new file mode 100644
index 00000000..b2a9d0bf
Binary files /dev/null and b/owlapy/jar_dependencies/javax.inject-1.jar differ
diff --git a/owlapy/jar_dependencies/jaxb-api-2.3.0.jar b/owlapy/jar_dependencies/jaxb-api-2.3.0.jar
new file mode 100644
index 00000000..0817c083
Binary files /dev/null and b/owlapy/jar_dependencies/jaxb-api-2.3.0.jar differ
diff --git a/owlapy/jar_dependencies/jcl-over-slf4j-1.7.22.jar b/owlapy/jar_dependencies/jcl-over-slf4j-1.7.22.jar
new file mode 100644
index 00000000..df7c43a1
Binary files /dev/null and b/owlapy/jar_dependencies/jcl-over-slf4j-1.7.22.jar differ
diff --git a/owlapy/jar_dependencies/jfact-5.0.3.jar b/owlapy/jar_dependencies/jfact-5.0.3.jar
new file mode 100644
index 00000000..84c7844a
Binary files /dev/null and b/owlapy/jar_dependencies/jfact-5.0.3.jar differ
diff --git a/owlapy/jar_dependencies/jsonld-java-0.12.0.jar b/owlapy/jar_dependencies/jsonld-java-0.12.0.jar
new file mode 100644
index 00000000..6e01fdbc
Binary files /dev/null and b/owlapy/jar_dependencies/jsonld-java-0.12.0.jar differ
diff --git a/owlapy/jar_dependencies/jsr305-3.0.2.jar b/owlapy/jar_dependencies/jsr305-3.0.2.jar
new file mode 100644
index 00000000..59222d9c
Binary files /dev/null and b/owlapy/jar_dependencies/jsr305-3.0.2.jar differ
diff --git a/owlapy/jar_dependencies/openllet-core-2.6.5.jar b/owlapy/jar_dependencies/openllet-core-2.6.5.jar
new file mode 100644
index 00000000..51cd51cf
Binary files /dev/null and b/owlapy/jar_dependencies/openllet-core-2.6.5.jar differ
diff --git a/owlapy/jar_dependencies/openllet-functions-2.6.5.jar b/owlapy/jar_dependencies/openllet-functions-2.6.5.jar
new file mode 100644
index 00000000..b5fc7636
Binary files /dev/null and b/owlapy/jar_dependencies/openllet-functions-2.6.5.jar differ
diff --git a/owlapy/jar_dependencies/openllet-owlapi-2.6.5.jar b/owlapy/jar_dependencies/openllet-owlapi-2.6.5.jar
new file mode 100644
index 00000000..e3359f43
Binary files /dev/null and b/owlapy/jar_dependencies/openllet-owlapi-2.6.5.jar differ
diff --git a/owlapy/jar_dependencies/openllet-query-2.6.5.jar b/owlapy/jar_dependencies/openllet-query-2.6.5.jar
new file mode 100644
index 00000000..aa3c60d4
Binary files /dev/null and b/owlapy/jar_dependencies/openllet-query-2.6.5.jar differ
diff --git a/owlapy/jar_dependencies/org.semanticweb.hermit-1.4.3.517.jar b/owlapy/jar_dependencies/org.semanticweb.hermit-1.4.3.517.jar
new file mode 100644
index 00000000..a2a09169
Binary files /dev/null and b/owlapy/jar_dependencies/org.semanticweb.hermit-1.4.3.517.jar differ
diff --git a/owlapy/jar_dependencies/owlapi-distribution-5.1.9.jar b/owlapy/jar_dependencies/owlapi-distribution-5.1.9.jar
new file mode 100644
index 00000000..4309c7c5
Binary files /dev/null and b/owlapy/jar_dependencies/owlapi-distribution-5.1.9.jar differ
diff --git a/owlapy/jar_dependencies/rdf4j-model-2.3.2.jar b/owlapy/jar_dependencies/rdf4j-model-2.3.2.jar
new file mode 100644
index 00000000..367fa419
Binary files /dev/null and b/owlapy/jar_dependencies/rdf4j-model-2.3.2.jar differ
diff --git a/owlapy/jar_dependencies/rdf4j-rio-api-2.3.2.jar b/owlapy/jar_dependencies/rdf4j-rio-api-2.3.2.jar
new file mode 100644
index 00000000..a6047b66
Binary files /dev/null and b/owlapy/jar_dependencies/rdf4j-rio-api-2.3.2.jar differ
diff --git a/owlapy/jar_dependencies/rdf4j-rio-binary-2.3.2.jar b/owlapy/jar_dependencies/rdf4j-rio-binary-2.3.2.jar
new file mode 100644
index 00000000..41627364
Binary files /dev/null and b/owlapy/jar_dependencies/rdf4j-rio-binary-2.3.2.jar differ
diff --git a/owlapy/jar_dependencies/rdf4j-rio-datatypes-2.3.2.jar b/owlapy/jar_dependencies/rdf4j-rio-datatypes-2.3.2.jar
new file mode 100644
index 00000000..a62ee6a0
Binary files /dev/null and b/owlapy/jar_dependencies/rdf4j-rio-datatypes-2.3.2.jar differ
diff --git a/owlapy/jar_dependencies/rdf4j-rio-jsonld-2.3.2.jar b/owlapy/jar_dependencies/rdf4j-rio-jsonld-2.3.2.jar
new file mode 100644
index 00000000..2097340d
Binary files /dev/null and b/owlapy/jar_dependencies/rdf4j-rio-jsonld-2.3.2.jar differ
diff --git a/owlapy/jar_dependencies/rdf4j-rio-languages-2.3.2.jar b/owlapy/jar_dependencies/rdf4j-rio-languages-2.3.2.jar
new file mode 100644
index 00000000..0930d578
Binary files /dev/null and b/owlapy/jar_dependencies/rdf4j-rio-languages-2.3.2.jar differ
diff --git a/owlapy/jar_dependencies/rdf4j-rio-n3-2.3.2.jar b/owlapy/jar_dependencies/rdf4j-rio-n3-2.3.2.jar
new file mode 100644
index 00000000..f16d6e7a
Binary files /dev/null and b/owlapy/jar_dependencies/rdf4j-rio-n3-2.3.2.jar differ
diff --git a/owlapy/jar_dependencies/rdf4j-rio-nquads-2.3.2.jar b/owlapy/jar_dependencies/rdf4j-rio-nquads-2.3.2.jar
new file mode 100644
index 00000000..79e69e83
Binary files /dev/null and b/owlapy/jar_dependencies/rdf4j-rio-nquads-2.3.2.jar differ
diff --git a/owlapy/jar_dependencies/rdf4j-rio-ntriples-2.3.2.jar b/owlapy/jar_dependencies/rdf4j-rio-ntriples-2.3.2.jar
new file mode 100644
index 00000000..94c7b3c0
Binary files /dev/null and b/owlapy/jar_dependencies/rdf4j-rio-ntriples-2.3.2.jar differ
diff --git a/owlapy/jar_dependencies/rdf4j-rio-rdfjson-2.3.2.jar b/owlapy/jar_dependencies/rdf4j-rio-rdfjson-2.3.2.jar
new file mode 100644
index 00000000..8965f1ec
Binary files /dev/null and b/owlapy/jar_dependencies/rdf4j-rio-rdfjson-2.3.2.jar differ
diff --git a/owlapy/jar_dependencies/rdf4j-rio-rdfxml-2.3.2.jar b/owlapy/jar_dependencies/rdf4j-rio-rdfxml-2.3.2.jar
new file mode 100644
index 00000000..e8f48b74
Binary files /dev/null and b/owlapy/jar_dependencies/rdf4j-rio-rdfxml-2.3.2.jar differ
diff --git a/owlapy/jar_dependencies/rdf4j-rio-trig-2.3.2.jar b/owlapy/jar_dependencies/rdf4j-rio-trig-2.3.2.jar
new file mode 100644
index 00000000..9751b3bc
Binary files /dev/null and b/owlapy/jar_dependencies/rdf4j-rio-trig-2.3.2.jar differ
diff --git a/owlapy/jar_dependencies/rdf4j-rio-trix-2.3.2.jar b/owlapy/jar_dependencies/rdf4j-rio-trix-2.3.2.jar
new file mode 100644
index 00000000..4a3b921e
Binary files /dev/null and b/owlapy/jar_dependencies/rdf4j-rio-trix-2.3.2.jar differ
diff --git a/owlapy/jar_dependencies/rdf4j-rio-turtle-2.3.2.jar b/owlapy/jar_dependencies/rdf4j-rio-turtle-2.3.2.jar
new file mode 100644
index 00000000..14d03302
Binary files /dev/null and b/owlapy/jar_dependencies/rdf4j-rio-turtle-2.3.2.jar differ
diff --git a/owlapy/jar_dependencies/rdf4j-util-2.3.2.jar b/owlapy/jar_dependencies/rdf4j-util-2.3.2.jar
new file mode 100644
index 00000000..30a8fc3d
Binary files /dev/null and b/owlapy/jar_dependencies/rdf4j-util-2.3.2.jar differ
diff --git a/owlapy/jar_dependencies/slf4j-api-1.7.22.jar b/owlapy/jar_dependencies/slf4j-api-1.7.22.jar
new file mode 100644
index 00000000..3c3d9531
Binary files /dev/null and b/owlapy/jar_dependencies/slf4j-api-1.7.22.jar differ
diff --git a/owlapy/jar_dependencies/slf4j-simple-1.7.22.jar b/owlapy/jar_dependencies/slf4j-simple-1.7.22.jar
new file mode 100644
index 00000000..3cea5e9e
Binary files /dev/null and b/owlapy/jar_dependencies/slf4j-simple-1.7.22.jar differ
diff --git a/owlapy/jar_dependencies/trove-3.0.3.jar b/owlapy/jar_dependencies/trove-3.0.3.jar
new file mode 100644
index 00000000..cd00f93e
Binary files /dev/null and b/owlapy/jar_dependencies/trove-3.0.3.jar differ
diff --git a/owlapy/jar_dependencies/xz-1.6.jar b/owlapy/jar_dependencies/xz-1.6.jar
new file mode 100644
index 00000000..140b981f
Binary files /dev/null and b/owlapy/jar_dependencies/xz-1.6.jar differ
diff --git a/owlapy/owl_hierarchy.py b/owlapy/owl_hierarchy.py
new file mode 100644
index 00000000..108e35db
--- /dev/null
+++ b/owlapy/owl_hierarchy.py
@@ -0,0 +1,471 @@
+"""Classes representing hierarchy in OWL."""
+
+import operator
+from abc import ABCMeta, abstractmethod
+from functools import reduce
+from typing import Dict, Iterable, Tuple, overload, TypeVar, Generic, Type, cast, Optional, FrozenSet, Set
+
+from owlapy.class_expression import OWLClass, OWLThing, OWLNothing
+from owlapy.meta_classes import HasIRI
+from owlapy.owl_literal import OWLTopObjectProperty, OWLBottomObjectProperty, OWLTopDataProperty, OWLBottomDataProperty
+from owlapy.owl_property import OWLObjectProperty, OWLDataProperty
+from owlapy.owl_reasoner import OWLReasoner
+
+_S = TypeVar('_S', bound=HasIRI)  #:
+_U = TypeVar('_U', bound='AbstractHierarchy')  #:
+
+
+class AbstractHierarchy(Generic[_S], metaclass=ABCMeta):
+    """Representation of an abstract hierarchy which can be used for classes or properties.
+
+    Args:
+        hierarchy_down: A downwards hierarchy given as a mapping of Entities to sub-entities.
+        reasoner: Alternatively, a reasoner whose root_ontology is queried for entities.
+        """
+    __slots__ = '_Type', '_ent_set', '_parents_map', '_parents_map_trans', '_children_map', '_children_map_trans', \
+                '_leaf_set', '_root_set', \
+        # '_eq_set'
+
+    _ent_set: FrozenSet[_S]
+    _parents_map: Dict[_S, Set[_S]]  # Entity => parent entities
+    _parents_map_trans: Dict[_S, Set[_S]]  # Entity => parent entities
+    _children_map: Dict[_S, Set[_S]]  # Entity => child entities
+    _children_map_trans: Dict[_S, Set[_S]]  # Entity => child entities
+    # _eq_set: Dict[_S, Set[_S]]  # Entity => equivalent entities  # TODO
+    _root_set: Set[_S]  # root entities
+    _leaf_set: Set[_S]  # leaf entities
+
+    @overload
+    def __init__(self, factory: Type[_S], hierarchy_down: Iterable[Tuple[_S, Iterable[_S]]]):
+        ...
+
+    @overload
+    def __init__(self, factory: Type[_S], reasoner: OWLReasoner):
+        ...
+
+    @abstractmethod
+    def __init__(self, factory: Type[_S], arg):
+        self._Type = factory
+        if isinstance(arg, OWLReasoner):
+            hier_down_gen = self._hierarchy_down_generator(arg)
+            self._init(hier_down_gen)
+        else:
+            self._init(arg)
+
+    @abstractmethod
+    def _hierarchy_down_generator(self, reasoner: OWLReasoner) -> Iterable[Tuple[_S, Iterable[_S]]]:
+        """Generate the suitable downwards hierarchy based on the reasoner."""
+        pass
+
+    @classmethod
+    @abstractmethod
+    def get_top_entity(cls) -> _S:
+        """The most general entity in this hierarchy, which contains all the entities."""
+        pass
+
+    @classmethod
+    @abstractmethod
+    def get_bottom_entity(cls) -> _S:
+        """The most specific entity in this hierarchy, which contains none of the entities."""
+        pass
+
+    @staticmethod
+    def restrict(hierarchy: _U, *, remove: Iterable[_S] = None, allow: Iterable[_S] = None) \
+            -> _U:
+        """Restrict a given hierarchy to a set of allowed/removed entities.
+
+        Args:
+            hierarchy: An existing Entity hierarchy to restrict.
+            remove: Set of entities which should be ignored.
+            allow: Set of entities which should be used.
+
+        Returns:
+            The restricted hierarchy.
+
+        """
+        remove_set = frozenset(remove) if remove is not None else None
+        allow_set = frozenset(allow) if allow is not None else None
+
+        def _filter(_: _S):
+            if remove_set is None or _ not in remove_set:
+                if allow_set is None or _ in allow_set:
+                    return True
+            return False
+
+        _gen = ((_, filter(_filter, hierarchy.children(_, direct=False)))
+                for _ in filter(_filter, hierarchy.items()))
+
+        return type(hierarchy)(_gen)
+
+    def restrict_and_copy(self: _U, *, remove: Iterable[_S] = None, allow: Iterable[_S] = None) \
+            -> _U:
+        """Restrict this hierarchy.
+
+        See restrict for more info.
+        """
+        return type(self).restrict(self, remove=remove, allow=allow)
+
+    def _init(self, hierarchy_down: Iterable[Tuple[_S, Iterable[_S]]]) -> None:
+        self._parents_map_trans = dict()
+        self._children_map_trans = dict()
+        # self._eq_set = dict()
+
+        ent_to_sub_entities = dict(hierarchy_down)
+        self._ent_set = frozenset(ent_to_sub_entities.keys())
+
+        for ent, sub_it in ent_to_sub_entities.items():
+            self._children_map_trans[ent] = set(sub_it)
+            self._parents_map_trans[ent] = set()  # create empty parent entry for all classes
+
+        del ent_to_sub_entities  # exhausted
+
+        # calculate transitive children
+        for ent in self._children_map_trans:
+            _children_transitive(self._children_map_trans, ent=ent, seen_set=set())
+
+        # TODO handling of eq_sets
+        # sccs = list(_strongly_connected_components(self._children_map_trans))
+        # for scc in sccs:
+        #     sub_entities = 0
+        #     for ent_enc in iter_bits(scc):
+        #         self._eq_set[ent_enc] = scc
+        #         sub_entities |= self._children_map_trans[ent_enc]
+        #         del self._children_map_trans[ent_enc]
+        #         del self._parents_map_trans[ent_enc]
+        #     self._children_map_trans[scc] = sub_entities
+        #     self._parents_map_trans[scc] = 0
+
+        # fill transitive parents
+        for ent, sub_entities in self._children_map_trans.items():
+            for sub in sub_entities:
+                self._parents_map_trans[sub] |= {ent}
+
+        self._children_map, self._leaf_set = _reduce_transitive(self._children_map_trans, self._parents_map_trans)
+        self._parents_map, self._root_set = _reduce_transitive(self._parents_map_trans, self._children_map_trans)
+
+    def parents(self, entity: _S, direct: bool = True) -> Iterable[_S]:
+        """Parents of an entity.
+
+        Args:
+            entity: Entity for which to query parent entities.
+            direct: False to return transitive parents.
+
+        Returns:
+            Super-entities.
+
+        """
+        if entity == type(self).get_bottom_entity():
+            if not direct:
+                yield from self.items()
+            else:
+                yield from self.leaves()
+        elif entity == type(self).get_top_entity():
+            yield from {}
+        else:
+            if not direct:
+                yield from self._parents_map_trans[entity]
+            else:
+                yield from self._parents_map[entity]
+
+    def is_parent_of(self, a: _S, b: _S) -> bool:
+        """if A is a parent of B.
+
+        Note:
+              A is always a parent of A."""
+        if a == b:
+            return True
+        if a == type(self).get_top_entity():
+            return True
+        if {a} & self._parents_map_trans[b]:
+            return True
+        return False
+
+    def is_child_of(self, a: _S, b: _S) -> bool:
+        """If A is a child of B.
+
+        Note:
+              A is always a child of A."""
+        if a == b:
+            return True
+        if a == type(self).get_bottom_entity():
+            return True
+        if {a} & self._children_map_trans[b]:
+            return True
+        return False
+
+    def children(self, entity: _S, direct: bool = True) -> Iterable[_S]:
+        """Children of an entity.
+
+        Args:
+            entity: Entity for which to query child entities.
+            direct: False to return transitive children.
+
+        Returns:
+            Sub-entities.
+
+        """
+        if entity == type(self).get_top_entity():
+            if not direct:
+                yield from self.items()
+            else:
+                yield from self.roots()
+        elif entity == type(self).get_bottom_entity():
+            yield from {}
+        else:
+            if not direct:
+                yield from self._children_map_trans[entity]
+            else:
+                yield from self._children_map[entity]
+
+    def siblings(self, entity: _S) -> Iterable[_S]:
+        seen_set = {entity}
+        for parent in self.parents(entity, direct=True):
+            for sibling in self.children(parent, direct=True):
+                if sibling not in seen_set:
+                    yield sibling
+                    seen_set.add(sibling)
+
+    def items(self) -> Iterable[_S]:
+        yield from self._ent_set
+
+    def roots(self, of: Optional[_S] = None) -> Iterable[_S]:
+        if of is not None and of != type(self).get_bottom_entity():
+            yield from self._root_set & self._parents_map_trans[of]
+        else:
+            yield from self._root_set
+
+    def leaves(self, of: Optional[_S] = None) -> Iterable[_S]:
+        if of is not None and of != type(self).get_top_entity():
+            yield from self._leaf_set & self._children_map_trans[of]
+        else:
+            yield from self._leaf_set
+
+    def __contains__(self, item: _S) -> bool:
+        return item in self._ent_set
+
+    def __len__(self):
+        return len(self._ent_set)
+
+
+class ClassHierarchy(AbstractHierarchy[OWLClass]):
+    """Representation of a class hierarchy.
+
+    Args:
+        hierarchy_down: A downwards hierarchy given as a mapping of Class to sub-classes.
+        reasoner: Alternatively, a reasoner whose root_ontology is queried for classes and sub-classes.
+        """
+
+    @classmethod
+    def get_top_entity(cls) -> OWLClass:
+        return OWLThing
+
+    @classmethod
+    def get_bottom_entity(cls) -> OWLClass:
+        return OWLNothing
+
+    def _hierarchy_down_generator(self, reasoner: OWLReasoner) -> Iterable[Tuple[OWLClass, Iterable[OWLClass]]]:
+        yield from ((_, reasoner.sub_classes(_, direct=True))
+                    for _ in reasoner.get_root_ontology().classes_in_signature())
+
+    def sub_classes(self, entity: OWLClass, direct: bool = True) -> Iterable[OWLClass]:
+        yield from self.children(entity, direct)
+
+    def super_classes(self, entity: OWLClass, direct: bool = True) -> Iterable[OWLClass]:
+        yield from self.parents(entity, direct)
+
+    def is_subclass_of(self, subclass: OWLClass, superclass: OWLClass) -> bool:
+        return self.is_child_of(subclass, superclass)
+
+    @overload
+    def __init__(self, hierarchy_down: Iterable[Tuple[OWLClass, Iterable[OWLClass]]]): ...
+
+    @overload
+    def __init__(self, reasoner: OWLReasoner): ...
+
+    def __init__(self, arg):
+        super().__init__(OWLClass, arg)
+
+
+class ObjectPropertyHierarchy(AbstractHierarchy[OWLObjectProperty]):
+    """Representation of an objet property hierarchy."""
+    @classmethod
+    def get_top_entity(cls) -> OWLObjectProperty:
+        return OWLTopObjectProperty
+
+    @classmethod
+    def get_bottom_entity(cls) -> OWLObjectProperty:
+        return OWLBottomObjectProperty
+
+    def _hierarchy_down_generator(self, reasoner: OWLReasoner) \
+            -> Iterable[Tuple[OWLObjectProperty, Iterable[OWLObjectProperty]]]:
+        return ((_, map(lambda _: cast(OWLObjectProperty, _),
+                        filter(lambda _: isinstance(_, OWLObjectProperty),
+                               reasoner.sub_object_properties(_, direct=True))))
+                for _ in reasoner.get_root_ontology().object_properties_in_signature())
+
+    def sub_object_properties(self, entity: OWLObjectProperty, direct: bool = True) -> Iterable[OWLObjectProperty]:
+        yield from self.children(entity, direct)
+
+    def super_object_properties(self, entity: OWLObjectProperty, direct: bool = True) -> Iterable[OWLObjectProperty]:
+        yield from self.parents(entity, direct)
+
+    def more_general_roles(self, role: OWLObjectProperty, direct: bool = True) -> Iterable[OWLObjectProperty]:
+        yield from self.parents(role, direct=direct)
+
+    def more_special_roles(self, role: OWLObjectProperty, direct: bool = True) -> Iterable[OWLObjectProperty]:
+        yield from self.children(role, direct=direct)
+
+    def is_sub_property_of(self, sub_property: OWLObjectProperty, super_property: OWLObjectProperty) -> bool:
+        return self.is_child_of(sub_property, super_property)
+
+    def most_general_roles(self) -> Iterable[OWLObjectProperty]:
+        yield from self.roots()
+
+    def most_special_roles(self) -> Iterable[OWLObjectProperty]:
+        yield from self.leaves()
+
+    @overload
+    def __init__(self, hierarchy_down: Iterable[Tuple[OWLObjectProperty, Iterable[OWLObjectProperty]]]): ...
+
+    @overload
+    def __init__(self, reasoner: OWLReasoner): ...
+
+    def __init__(self, arg):
+        super().__init__(OWLObjectProperty, arg)
+
+
+class DatatypePropertyHierarchy(AbstractHierarchy[OWLDataProperty]):
+    """Representation of a data property hierarchy."""
+    @classmethod
+    def get_top_entity(cls) -> OWLDataProperty:
+        return OWLTopDataProperty
+
+    @classmethod
+    def get_bottom_entity(cls) -> OWLDataProperty:
+        return OWLBottomDataProperty
+
+    def _hierarchy_down_generator(self, reasoner: OWLReasoner) \
+            -> Iterable[Tuple[OWLDataProperty, Iterable[OWLDataProperty]]]:
+        return ((_, reasoner.sub_data_properties(_, direct=True))
+                for _ in reasoner.get_root_ontology().data_properties_in_signature())
+
+    def sub_data_properties(self, entity: OWLDataProperty, direct: bool = True):
+        yield from self.children(entity, direct)
+
+    def super_data_properties(self, entity: OWLDataProperty, direct: bool = True):
+        yield from self.parents(entity, direct)
+
+    def more_general_roles(self, role: OWLDataProperty, direct: bool = True) -> Iterable[OWLDataProperty]:
+        yield from self.parents(role, direct=direct)
+
+    def more_special_roles(self, role: OWLDataProperty, direct: bool = True) -> Iterable[OWLDataProperty]:
+        yield from self.children(role, direct=direct)
+
+    def is_sub_property_of(self, sub_property: OWLDataProperty, super_property: OWLDataProperty) -> bool:
+        return self.is_child_of(sub_property, super_property)
+
+    def most_general_roles(self) -> Iterable[OWLDataProperty]:
+        yield from self.roots()
+
+    def most_special_roles(self) -> Iterable[OWLDataProperty]:
+        yield from self.leaves()
+
+    @overload
+    def __init__(self, hierarchy_down: Iterable[Tuple[OWLDataProperty, Iterable[OWLDataProperty]]]): ...
+
+    @overload
+    def __init__(self, reasoner: OWLReasoner): ...
+
+    def __init__(self, arg):
+        super().__init__(OWLDataProperty, arg)
+
+
+def _children_transitive(hier_trans: Dict[_S, Set[_S]], ent: _S, seen_set: Set[_S]):
+    """Add transitive links to map_trans.
+
+    Note:
+        Changes map_trans.
+
+    Args:
+        hier_trans: Map to which transitive links are added.
+        ent: Class in map_trans for which to add transitive sub-classes.
+
+    """
+    sub_classes_ent = frozenset(hier_trans[ent])
+    for sub_ent in sub_classes_ent:
+        if not {sub_ent} & seen_set:
+            _children_transitive(hier_trans, sub_ent, seen_set | {ent})
+            seen_set = seen_set | {sub_ent} | hier_trans[sub_ent]
+            hier_trans[ent] |= hier_trans[sub_ent]
+
+
+def _reduce_transitive(hier: Dict[_S, Set[_S]], hier_inverse: Dict[_S, Set[_S]]) \
+        -> Tuple[Dict[_S, Set[_S]], FrozenSet[_S]]:
+    """Remove all transitive links.
+
+    Takes a downward hierarchy and an upward hierarchy with transitive links, and removes all links that can be
+    implicitly detected since they are transitive.
+
+    Args:
+         hier: downward hierarchy with all transitive links, from Class => sub-classes.
+         hier_inverse: upward hierarchy with all transitive links, from Class => super-classes.
+
+    Returns:
+        Thin map with only direct sub-classes.
+        Set of classes without sub-classes.
+
+    """
+    result_hier: Dict[_S, Set[_S]] = dict()
+    leaf_set = set()
+    for ent, set_ent in hier.items():
+        direct_set = set()
+        for item_ent in set_ent:
+            if not hier_inverse[item_ent] & (set_ent ^ {item_ent}):
+                direct_set |= {item_ent}
+        result_hier[ent] = direct_set
+        if not direct_set:
+            leaf_set |= {ent}
+    return result_hier, frozenset(leaf_set)
+
+
+def _strongly_connected_components(graph: Dict[_S, Set[_S]]) -> Iterable[FrozenSet[_S]]:
+    """Strongly connected component algorithm.
+
+    Args:
+        graph: Directed Graph dictionary, vertex => set of vertices (there is an edge from v to each V).
+
+    Returns:
+        The strongly connected components.
+
+    Author: Mario Alviano
+    Source: https://github.com/alviano/python/blob/master/rewrite_aggregates/scc.py
+    Licence: GPL-3.0
+    """
+    identified = set()
+    stack = []
+    index = {}
+    boundaries = []
+
+    def dfs(v):
+        nonlocal identified
+        index[v] = len(stack)
+        stack.append({v})
+        boundaries.append(index[v])
+
+        for w in graph[v]:
+            if w not in index:
+                yield from dfs(w)
+            elif not {w} & identified:
+                while index[w] < boundaries[-1]:
+                    boundaries.pop()
+
+        if boundaries[-1] == index[v]:
+            boundaries.pop()
+            scc = reduce(operator.or_, stack[index[v]:])
+            del stack[index[v]:]
+            identified |= scc
+            yield frozenset(scc)
+
+    for v in graph:
+        if v not in index:
+            yield from dfs(v)
diff --git a/owlapy/owl_ontology.py b/owlapy/owl_ontology.py
index 2d628233..9e9d2f6f 100644
--- a/owlapy/owl_ontology.py
+++ b/owlapy/owl_ontology.py
@@ -1,14 +1,49 @@
 """OWL Ontology"""
 from abc import ABCMeta, abstractmethod
-from typing import Iterable, TypeVar, Final, Optional
+from functools import singledispatchmethod
+from itertools import chain
+from types import MappingProxyType
+from typing import Iterable, TypeVar, Final, Optional, Union
+import logging
+import owlready2
+from pandas import Timedelta
+from owlapy import namespaces
 from owlapy.owl_axiom import OWLEquivalentClassesAxiom, OWLClassAxiom, OWLDataPropertyDomainAxiom, \
-    OWLDataPropertyRangeAxiom, OWLObjectPropertyDomainAxiom, OWLObjectPropertyRangeAxiom
-from owlapy.owl_individual import OWLNamedIndividual
+    OWLDataPropertyRangeAxiom, OWLObjectPropertyDomainAxiom, OWLObjectPropertyRangeAxiom, OWLSubClassOfAxiom, \
+    OWLAnnotationProperty
+from owlapy.owl_data_ranges import OWLDataRange, OWLDataComplementOf, OWLDataUnionOf, OWLDataIntersectionOf
+from owlapy.owl_datatype import OWLDatatype
+from owlapy.owl_individual import OWLNamedIndividual, OWLIndividual
+from owlapy.owl_literal import IntegerOWLDatatype, DoubleOWLDatatype, BooleanOWLDatatype, StringOWLDatatype, \
+    DateOWLDatatype, DateTimeOWLDatatype, DurationOWLDatatype, OWLLiteral
 from owlapy.owl_object import OWLObject
 from owlapy.iri import IRI
-from owlapy.class_expression import OWLClass
-from owlapy.owl_property import OWLDataProperty, OWLObjectProperty
+from owlapy.class_expression import OWLClass, OWLThing, OWLClassExpression, OWLObjectComplementOf, OWLObjectUnionOf, \
+    OWLObjectIntersectionOf, OWLObjectSomeValuesFrom, OWLObjectAllValuesFrom, OWLObjectOneOf, OWLObjectExactCardinality, \
+    OWLObjectMaxCardinality, OWLObjectMinCardinality, OWLObjectHasValue, OWLDataSomeValuesFrom, OWLDataAllValuesFrom, \
+    OWLDataExactCardinality, OWLDataMaxCardinality, OWLDataMinCardinality, OWLDataHasValue, OWLDataOneOf, \
+    OWLDatatypeRestriction, OWLRestriction, OWLObjectRestriction, OWLDataRestriction, OWLFacetRestriction
+from owlapy.owl_property import OWLDataProperty, OWLObjectProperty, OWLPropertyExpression, OWLObjectInverseOf, \
+    OWLObjectPropertyExpression, OWLDataPropertyExpression
+from datetime import date, datetime
 
+from owlapy.vocab import OWLFacet
+
+logger = logging.getLogger(__name__)
+
+_Datatype_map: Final = MappingProxyType({
+    int: IntegerOWLDatatype,
+    float: DoubleOWLDatatype,
+    bool: BooleanOWLDatatype,
+    str: StringOWLDatatype,
+    date: DateOWLDatatype,
+    datetime: DateTimeOWLDatatype,
+    Timedelta: DurationOWLDatatype,
+})
+
+
+
+_VERSION_IRI: Final = IRI.create(namespaces.OWL, "versionIRI")
 
 _M = TypeVar('_M', bound='OWLOntologyManager')
 
@@ -214,3 +249,506 @@ def is_anonymous(self) -> bool:
         return self.get_ontology_id().is_anonymous()
 
 
+class Ontology(OWLOntology):
+    __slots__ = '_manager', '_iri', '_world', '_onto'
+
+    _manager: 'OntologyManager'
+    _onto: owlready2.Ontology
+    _world: owlready2.World
+
+    def __init__(self, manager: 'OntologyManager', ontology_iri: IRI, load: bool):
+        """Represents an Ontology in Ontolearn.
+
+        Args:
+            manager: Ontology manager.
+            ontology_iri: IRI of the ontology.
+            load: Whether to load the ontology or not.
+        """
+        self._manager = manager
+        self._iri = ontology_iri
+        self._world = manager._world
+        onto = self._world.get_ontology(ontology_iri.as_str())
+        if load:
+            onto = onto.load()
+        self._onto = onto
+
+    def classes_in_signature(self) -> Iterable[OWLClass]:
+        for c in self._onto.classes():
+            yield OWLClass(IRI.create(c.iri))
+
+    def data_properties_in_signature(self) -> Iterable[OWLDataProperty]:
+        for dp in self._onto.data_properties():
+            yield OWLDataProperty(IRI.create(dp.iri))
+
+    def object_properties_in_signature(self) -> Iterable[OWLObjectProperty]:
+        for op in self._onto.object_properties():
+            yield OWLObjectProperty(IRI.create(op.iri))
+
+    def individuals_in_signature(self) -> Iterable[OWLNamedIndividual]:
+        for i in self._onto.individuals():
+            yield OWLNamedIndividual(IRI.create(i.iri))
+
+    def equivalent_classes_axioms(self, c: OWLClass) -> Iterable[OWLEquivalentClassesAxiom]:
+        c_x: owlready2.ThingClass = self._world[c.str]
+        # TODO: Should this also return EquivalentClasses general class axioms? Compare to java owlapi
+        for ec_x in c_x.equivalent_to:
+            yield OWLEquivalentClassesAxiom([c, _parse_concept_to_owlapy(ec_x)])
+
+    def general_class_axioms(self) -> Iterable[OWLClassAxiom]:
+        # TODO: At the moment owlready2 only supports SubClassOf general class axioms. (18.02.2023)
+        for ca in self._onto.general_class_axioms():
+            yield from (OWLSubClassOfAxiom(_parse_concept_to_owlapy(ca.left_side), _parse_concept_to_owlapy(c))
+                        for c in ca.is_a)
+
+    def get_owl_ontology_manager(self) -> 'OntologyManager':
+        return self._manager
+
+    def get_ontology_id(self) -> OWLOntologyID:
+        onto_iri = self._world._unabbreviate(self._onto.storid)
+        look_version = self._world._get_obj_triple_sp_o(
+            self._onto.storid,
+            self._world._abbreviate(_VERSION_IRI.as_str()))
+        if look_version is not None:
+            version_iri = self._world._unabbreviate(look_version)
+        else:
+            version_iri = None
+
+        return OWLOntologyID(IRI.create(onto_iri) if onto_iri is not None else None,
+                             IRI.create(version_iri) if version_iri is not None else None)
+
+    def data_property_domain_axioms(self, pe: OWLDataProperty) -> Iterable[OWLDataPropertyDomainAxiom]:
+        p_x: owlready2.DataPropertyClass = self._world[pe.str]
+        domains = set(p_x.domains_indirect())
+        if len(domains) == 0:
+            yield OWLDataPropertyDomainAxiom(pe, OWLThing)
+        else:
+            for dom in domains:
+                if isinstance(dom, (owlready2.ThingClass, owlready2.ClassConstruct)):
+                    yield OWLDataPropertyDomainAxiom(pe, _parse_concept_to_owlapy(dom))
+                else:
+                    logger.warning("Construct %s not implemented at %s", dom, pe)
+                    pass  # XXX TODO
+
+    def data_property_range_axioms(self, pe: OWLDataProperty) -> Iterable[OWLDataPropertyRangeAxiom]:
+        p_x: owlready2.DataPropertyClass = self._world[pe.str]
+        ranges = set(chain.from_iterable(super_prop.range for super_prop in p_x.ancestors()))
+        if len(ranges) == 0:
+            pass
+            # TODO
+        else:
+            for rng in ranges:
+                if rng in _Datatype_map:
+                    yield OWLDataPropertyRangeAxiom(pe, _Datatype_map[rng])
+                elif isinstance(rng, owlready2.ClassConstruct):
+                    yield OWLDataPropertyRangeAxiom(pe, _parse_datarange_to_owlapy(rng))
+                else:
+                    logger.warning("Datatype %s not implemented at %s", rng, pe)
+                    pass  # XXX TODO
+
+    def object_property_domain_axioms(self, pe: OWLObjectProperty) -> Iterable[OWLObjectPropertyDomainAxiom]:
+        p_x: owlready2.ObjectPropertyClass = self._world[pe.str]
+        domains = set(p_x.domains_indirect())
+        if len(domains) == 0:
+            yield OWLObjectPropertyDomainAxiom(pe, OWLThing)
+        else:
+            for dom in domains:
+                if isinstance(dom, (owlready2.ThingClass, owlready2.ClassConstruct)):
+                    yield OWLObjectPropertyDomainAxiom(pe, _parse_concept_to_owlapy(dom))
+                else:
+                    logger.warning("Construct %s not implemented at %s", dom, pe)
+                    pass  # XXX TODO
+
+    def object_property_range_axioms(self, pe: OWLObjectProperty) -> Iterable[OWLObjectPropertyRangeAxiom]:
+        p_x: owlready2.ObjectPropertyClass = self._world[pe.str]
+        ranges = set(chain.from_iterable(super_prop.range for super_prop in p_x.ancestors()))
+        if len(ranges) == 0:
+            yield OWLObjectPropertyRangeAxiom(pe, OWLThing)
+        else:
+            for rng in ranges:
+                if isinstance(rng, (owlready2.ThingClass, owlready2.ClassConstruct)):
+                    yield OWLObjectPropertyRangeAxiom(pe, _parse_concept_to_owlapy(rng))
+                else:
+                    logger.warning("Construct %s not implemented at %s", rng, pe)
+                    pass  # XXX TODO
+
+    def get_original_iri(self):
+        """Get the IRI argument that was used to create this ontology."""
+        return self._iri
+
+    def __eq__(self, other):
+        if type(other) == type(self):
+            return self._onto.loaded == other._onto.loaded and self._onto.base_iri == other._onto.base_iri
+        return NotImplemented
+
+    def __hash__(self):
+        return hash(self._onto.base_iri)
+
+    def __repr__(self):
+        return f'Ontology({IRI.create(self._onto.base_iri)}, {self._onto.loaded})'
+
+
+OWLREADY2_FACET_KEYS = MappingProxyType({
+    OWLFacet.MIN_INCLUSIVE: "min_inclusive",
+    OWLFacet.MIN_EXCLUSIVE: "min_exclusive",
+    OWLFacet.MAX_INCLUSIVE: "max_inclusive",
+    OWLFacet.MAX_EXCLUSIVE: "max_exclusive",
+    OWLFacet.LENGTH: "length",
+    OWLFacet.MIN_LENGTH: "min_length",
+    OWLFacet.MAX_LENGTH: "max_length",
+    OWLFacet.PATTERN: "pattern",
+    OWLFacet.TOTAL_DIGITS: "total_digits",
+    OWLFacet.FRACTION_DIGITS: "fraction_digits"
+})
+
+class ToOwlready2:
+    __slots__ = '_world'
+
+    _world: owlready2.World
+
+    def __init__(self, world: owlready2.World):
+        """Map owlapy model classes to owlready2.
+
+        Args:
+            world: Owlready2 World to use for mapping.
+        """
+        self._world = world
+
+    @singledispatchmethod
+    def map_object(self, o: OWLObject):
+        """Map owlapy object classes."""
+        raise NotImplementedError(f'don\'t know how to map {o}')
+
+    @map_object.register
+    def _(self, ce: OWLClassExpression) -> Union[owlready2.ClassConstruct, owlready2.ThingClass]:
+        return self.map_concept(ce)
+
+    @map_object.register
+    def _(self, ont: OWLOntology) -> owlready2.namespace.Ontology:
+        return self._world.get_ontology(
+            ont.get_ontology_id().get_ontology_iri().as_str()
+        )
+
+    @map_object.register
+    def _(self, ap: OWLAnnotationProperty) -> owlready2.annotation.AnnotationPropertyClass:
+        return self._world[ap.str]
+
+    # @TODO CD: map_object is buggy. and it can return None
+    # single dispatch is still not implemented in mypy, see https://github.com/python/mypy/issues/2904
+    @singledispatchmethod
+    def map_concept(self, o: OWLClassExpression) \
+            -> Union[owlready2.ClassConstruct, owlready2.ThingClass]:
+        """Map owlapy concept classes."""
+        raise NotImplementedError(o)
+
+    @singledispatchmethod
+    def _to_owlready2_property(self, p: OWLPropertyExpression) -> owlready2.Property:
+        raise NotImplementedError(p)
+
+    @_to_owlready2_property.register
+    def _(self, p: OWLObjectInverseOf):
+        p_x = self._to_owlready2_property(p.get_named_property())
+        return owlready2.Inverse(p_x)
+
+    @_to_owlready2_property.register
+    def _(self, p: OWLObjectProperty) -> owlready2.prop.ObjectPropertyClass:
+        return self._world[p.str]
+
+    @_to_owlready2_property.register
+    def _(self, p: OWLDataProperty) -> owlready2.prop.DataPropertyClass:
+        return self._world[p.str]
+
+    @singledispatchmethod
+    def _to_owlready2_individual(self, i: OWLIndividual) -> owlready2.Thing:
+        raise NotImplementedError(i)
+
+    @_to_owlready2_individual.register
+    def _(self, i: OWLNamedIndividual):
+        return self._world[i.str]
+
+    @map_concept.register
+    def _(self, c: OWLClass) -> owlready2.ThingClass:
+        x = self._world[c.str]
+        try:
+            assert x is not None
+        except AssertionError:
+            print(f"The world attribute{self._world} maps {c} into None")
+
+        return x
+
+    @map_concept.register
+    def _(self, c: OWLObjectComplementOf) -> owlready2.class_construct.Not:
+        return owlready2.Not(self.map_concept(c.get_operand()))
+
+    @map_concept.register
+    def _(self, ce: OWLObjectUnionOf) -> owlready2.class_construct.Or:
+        return owlready2.Or(map(self.map_concept, ce.operands()))
+
+    @map_concept.register
+    def _(self, ce: OWLObjectIntersectionOf) -> owlready2.class_construct.And:
+        return owlready2.And(map(self.map_concept, ce.operands()))
+
+    @map_concept.register
+    def _(self, ce: OWLObjectSomeValuesFrom) -> owlready2.class_construct.Restriction:
+        prop = self._to_owlready2_property(ce.get_property())
+        assert isinstance(ce.get_filler(),
+                          OWLClassExpression), f"{ce.get_filler()} is not an OWL Class expression and cannot be serialized at the moment"
+        return prop.some(self.map_concept(ce.get_filler()))
+
+    @map_concept.register
+    def _(self, ce: OWLObjectAllValuesFrom) -> owlready2.class_construct.Restriction:
+        prop = self._to_owlready2_property(ce.get_property())
+        return prop.only(self.map_concept(ce.get_filler()))
+
+    @map_concept.register
+    def _(self, ce: OWLObjectOneOf) -> owlready2.class_construct.OneOf:
+        return owlready2.OneOf(list(map(self._to_owlready2_individual, ce.individuals())))
+
+    @map_concept.register
+    def _(self, ce: OWLObjectExactCardinality) -> owlready2.class_construct.Restriction:
+        prop = self._to_owlready2_property(ce.get_property())
+        return prop.exactly(ce.get_cardinality(), self.map_concept(ce.get_filler()))
+
+    @map_concept.register
+    def _(self, ce: OWLObjectMaxCardinality) -> owlready2.class_construct.Restriction:
+        prop = self._to_owlready2_property(ce.get_property())
+        return prop.max(ce.get_cardinality(), self.map_concept(ce.get_filler()))
+
+    @map_concept.register
+    def _(self, ce: OWLObjectMinCardinality) -> owlready2.class_construct.Restriction:
+        prop = self._to_owlready2_property(ce.get_property())
+        return prop.min(ce.get_cardinality(), self.map_concept(ce.get_filler()))
+
+    @map_concept.register
+    def _(self, ce: OWLObjectHasValue) -> owlready2.class_construct.Restriction:
+        prop = self._to_owlready2_property(ce.get_property())
+        return prop.value(self._to_owlready2_individual(ce.get_filler()))
+
+    @map_concept.register
+    def _(self, ce: OWLDataSomeValuesFrom) -> owlready2.class_construct.Restriction:
+        prop = self._to_owlready2_property(ce.get_property())
+        return prop.some(self.map_datarange(ce.get_filler()))
+
+    @map_concept.register
+    def _(self, ce: OWLDataAllValuesFrom) -> owlready2.class_construct.Restriction:
+        prop = self._to_owlready2_property(ce.get_property())
+        return prop.only(self.map_datarange(ce.get_filler()))
+
+    @map_concept.register
+    def _(self, ce: OWLDataExactCardinality) -> owlready2.class_construct.Restriction:
+        prop = self._to_owlready2_property(ce.get_property())
+        return prop.exactly(ce.get_cardinality(), self.map_datarange(ce.get_filler()))
+
+    @map_concept.register
+    def _(self, ce: OWLDataMaxCardinality) -> owlready2.class_construct.Restriction:
+        prop = self._to_owlready2_property(ce.get_property())
+        return prop.max(ce.get_cardinality(), self.map_datarange(ce.get_filler()))
+
+    @map_concept.register
+    def _(self, ce: OWLDataMinCardinality) -> owlready2.class_construct.Restriction:
+        prop = self._to_owlready2_property(ce.get_property())
+        return prop.min(ce.get_cardinality(), self.map_datarange(ce.get_filler()))
+
+    @map_concept.register
+    def _(self, ce: OWLDataHasValue) -> owlready2.class_construct.Restriction:
+        prop = self._to_owlready2_property(ce.get_property())
+        return prop.value(ce.get_filler().to_python())
+
+    @singledispatchmethod
+    def map_datarange(self, p: OWLDataRange) -> Union[owlready2.ClassConstruct, type]:
+        """Map owlapy data range classes."""
+        raise NotImplementedError(p)
+
+    @map_datarange.register
+    def _(self, p: OWLDataComplementOf) -> owlready2.class_construct.Not:
+        return owlready2.Not(self.map_datarange(p.get_data_range()))
+
+    @map_datarange.register
+    def _(self, p: OWLDataUnionOf) -> owlready2.class_construct.Or:
+        return owlready2.Or(map(self.map_datarange, p.operands()))
+
+    @map_datarange.register
+    def _(self, p: OWLDataIntersectionOf) -> owlready2.class_construct.And:
+        return owlready2.And(map(self.map_datarange, p.operands()))
+
+    @map_datarange.register
+    def _(self, p: OWLDataOneOf) -> owlready2.class_construct.OneOf:
+        return owlready2.OneOf([lit.to_python() for lit in p.operands()])
+
+    @map_datarange.register
+    def _(self, p: OWLDatatypeRestriction) -> owlready2.class_construct.ConstrainedDatatype:
+        facet_args = dict()
+        for facet_res in p.get_facet_restrictions():
+            value = facet_res.get_facet_value().to_python()
+            facet_key = OWLREADY2_FACET_KEYS[facet_res.get_facet()]
+            facet_args[facet_key] = value
+        return owlready2.ConstrainedDatatype(self.map_datarange(p.get_datatype()), **facet_args)
+
+    @map_datarange.register
+    def _(self, type_: OWLDatatype) -> type:
+        if type_ == BooleanOWLDatatype:
+            return bool
+        elif type_ == DoubleOWLDatatype:
+            return float
+        elif type_ == IntegerOWLDatatype:
+            return int
+        elif type_ == StringOWLDatatype:
+            return str
+        elif type_ == DateOWLDatatype:
+            return date
+        elif type_ == DateTimeOWLDatatype:
+            return datetime
+        elif type_ == DurationOWLDatatype:
+            return Timedelta
+        else:
+            raise ValueError(type_)
+
+
+class FromOwlready2:
+    """Map owlready2 classes to owlapy model classes."""
+    __slots__ = ()
+
+    @singledispatchmethod
+    def map_concept(self, c: Union[owlready2.ClassConstruct, owlready2.ThingClass]) -> OWLClassExpression:
+        """Map concept classes."""
+        raise NotImplementedError(c)
+
+    @singledispatchmethod
+    def _from_owlready2_property(self, c: Union[owlready2.PropertyClass, owlready2.Inverse]) -> OWLPropertyExpression:
+        raise NotImplementedError(c)
+
+    @_from_owlready2_property.register
+    def _(self, p: owlready2.ObjectPropertyClass) -> OWLObjectProperty:
+        return OWLObjectProperty(IRI.create(p.iri))
+
+    @_from_owlready2_property.register
+    def _(self, p: owlready2.DataPropertyClass) -> OWLDataProperty:
+        return OWLDataProperty(IRI.create(p.iri))
+
+    @_from_owlready2_property.register
+    def _(self, i: owlready2.Inverse) -> OWLObjectInverseOf:
+        return OWLObjectInverseOf(self._from_owlready2_property(i.property))
+
+    @map_concept.register
+    def _(self, c: owlready2.ThingClass) -> OWLClass:
+        return OWLClass(IRI.create(c.iri))
+
+    @map_concept.register
+    def _(self, c: owlready2.Not) -> OWLObjectComplementOf:
+        return OWLObjectComplementOf(self.map_concept(c.Class))
+
+    @map_concept.register
+    def _(self, c: owlready2.And) -> OWLObjectIntersectionOf:
+        return OWLObjectIntersectionOf(map(self.map_concept, c.Classes))
+
+    @map_concept.register
+    def _(self, c: owlready2.Or) -> OWLObjectUnionOf:
+        return OWLObjectUnionOf(map(self.map_concept, c.Classes))
+
+    @map_concept.register
+    def _(self, c: owlready2.OneOf) -> OWLObjectOneOf:
+        return OWLObjectOneOf([OWLNamedIndividual(IRI.create(ind.iri)) for ind in c.instances])
+
+    @map_concept.register
+    def _(self, c: owlready2.Restriction) -> OWLRestriction:
+        if isinstance(c.property, owlready2.ObjectPropertyClass):
+            return self._to_object_property(c)
+        elif isinstance(c.property, owlready2.DataPropertyClass):
+            return self._to_data_property(c)
+        else:
+            raise NotImplementedError(c)
+
+    def _to_object_property(self, c: owlready2.Restriction) -> OWLObjectRestriction:
+        p = self._from_owlready2_property(c.property)
+        assert isinstance(p, OWLObjectPropertyExpression)
+
+        if c.type == owlready2.VALUE:
+            ind = OWLNamedIndividual(IRI.create(c.value.iri))
+            return OWLObjectHasValue(p, ind)
+        else:
+            f = self.map_concept(c.value)
+            if c.type == owlready2.SOME:
+                return OWLObjectSomeValuesFrom(p, f)
+            elif c.type == owlready2.ONLY:
+                return OWLObjectAllValuesFrom(p, f)
+            elif c.type == owlready2.EXACTLY:
+                return OWLObjectExactCardinality(c.cardinality, p, f)
+            elif c.type == owlready2.MIN:
+                return OWLObjectMinCardinality(c.cardinality, p, f)
+            elif c.type == owlready2.MAX:
+                return OWLObjectMaxCardinality(c.cardinality, p, f)
+            else:
+                raise NotImplementedError(c)
+
+    def _to_data_property(self, c: owlready2.Restriction) -> OWLDataRestriction:
+        p = self._from_owlready2_property(c.property)
+        assert isinstance(p, OWLDataPropertyExpression)
+
+        if c.type == owlready2.VALUE:
+            return OWLDataHasValue(p, OWLLiteral(c.value))
+        else:
+            f = self.map_datarange(c.value)
+            if c.type == owlready2.SOME:
+                return OWLDataSomeValuesFrom(p, f)
+            elif c.type == owlready2.ONLY:
+                return OWLDataAllValuesFrom(p, f)
+            elif c.type == owlready2.EXACTLY:
+                return OWLDataExactCardinality(c.cardinality, p, f)
+            elif c.type == owlready2.MIN:
+                return OWLDataMinCardinality(c.cardinality, p, f)
+            elif c.type == owlready2.MAX:
+                return OWLDataMaxCardinality(c.cardinality, p, f)
+            else:
+                raise NotImplementedError(c)
+
+    @singledispatchmethod
+    def map_datarange(self, p: owlready2.ClassConstruct) -> OWLDataRange:
+        """Map data range classes."""
+        raise NotImplementedError(p)
+
+    @map_datarange.register
+    def _(self, p: owlready2.Not) -> OWLDataComplementOf:
+        return OWLDataComplementOf(self.map_datarange(p.Class))
+
+    @map_datarange.register
+    def _(self, p: owlready2.Or) -> OWLDataUnionOf:
+        return OWLDataUnionOf(map(self.map_datarange, p.Classes))
+
+    @map_datarange.register
+    def _(self, p: owlready2.And) -> OWLDataIntersectionOf:
+        return OWLDataIntersectionOf(map(self.map_datarange, p.Classes))
+
+    @map_datarange.register
+    def _(self, p: owlready2.OneOf) -> OWLDataOneOf:
+        return OWLDataOneOf([OWLLiteral(i) for i in p.instances])
+
+    @map_datarange.register
+    def _(self, p: owlready2.ConstrainedDatatype) -> OWLDatatypeRestriction:
+        restrictions = []
+        for facet in OWLFacet:
+            value = getattr(p, OWLREADY2_FACET_KEYS[facet], None)
+            if value is not None:
+                restrictions.append(OWLFacetRestriction(facet, OWLLiteral(value)))
+        return OWLDatatypeRestriction(self.map_datarange(p.base_datatype), restrictions)
+
+    @map_datarange.register
+    def _(self, type_: type) -> OWLDatatype:
+        if type_ == bool:
+            return BooleanOWLDatatype
+        elif type_ == float:
+            return DoubleOWLDatatype
+        elif type_ == int:
+            return IntegerOWLDatatype
+        elif type_ == str:
+            return StringOWLDatatype
+        elif type_ == date:
+            return DateOWLDatatype
+        elif type_ == datetime:
+            return DateTimeOWLDatatype
+        elif type_ == Timedelta:
+            return DurationOWLDatatype
+        else:
+            raise ValueError(type_)
+
+
+_parse_concept_to_owlapy = FromOwlready2().map_concept
+_parse_datarange_to_owlapy = FromOwlready2().map_datarange
\ No newline at end of file
diff --git a/owlapy/owl_ontology_manager.py b/owlapy/owl_ontology_manager.py
index 17d86c80..2d5ff52e 100644
--- a/owlapy/owl_ontology_manager.py
+++ b/owlapy/owl_ontology_manager.py
@@ -1,8 +1,32 @@
 from abc import ABCMeta, abstractmethod
-from owlapy.owl_ontology import OWLOntology
+from functools import singledispatch
+from itertools import islice, combinations
+import types
+from typing import cast
+
+import owlready2
+
 from owlapy.iri import IRI
 from owlapy.meta_classes import HasIRI
-from owlapy.owl_axiom import OWLAxiom
+from owlapy.owl_object import OWLObject
+from owlready2 import destroy_entity, AllDisjoint, AllDifferent, GeneralClassAxiom
+from owlapy.class_expression import OWLThing, OWLClass, \
+    OWLQuantifiedDataRestriction, OWLDataHasValue, OWLNaryBooleanClassExpression, OWLObjectOneOf, OWLObjectComplementOf, \
+    OWLObjectHasValue, OWLQuantifiedObjectRestriction
+from owlapy.owl_axiom import OWLObjectPropertyRangeAxiom, OWLAxiom, OWLSubClassOfAxiom, OWLEquivalentClassesAxiom, \
+    OWLDisjointUnionAxiom, OWLAnnotationAssertionAxiom, OWLAnnotationProperty, OWLSubPropertyAxiom, \
+    OWLPropertyRangeAxiom, OWLClassAssertionAxiom, OWLDeclarationAxiom, OWLObjectPropertyAssertionAxiom, \
+    OWLSymmetricObjectPropertyAxiom, OWLTransitiveObjectPropertyAxiom, OWLPropertyDomainAxiom, \
+    OWLAsymmetricObjectPropertyAxiom, OWLDataPropertyCharacteristicAxiom, OWLFunctionalDataPropertyAxiom, \
+    OWLReflexiveObjectPropertyAxiom, OWLDataPropertyAssertionAxiom, OWLFunctionalObjectPropertyAxiom, \
+    OWLObjectPropertyCharacteristicAxiom, OWLIrreflexiveObjectPropertyAxiom, OWLInverseFunctionalObjectPropertyAxiom, \
+    OWLDisjointDataPropertiesAxiom, OWLDisjointObjectPropertiesAxiom, OWLEquivalentDataPropertiesAxiom, \
+    OWLEquivalentObjectPropertiesAxiom, OWLInverseObjectPropertiesAxiom, OWLNaryPropertyAxiom, OWLNaryIndividualAxiom, \
+    OWLDifferentIndividualsAxiom, OWLDisjointClassesAxiom, OWLSameIndividualAxiom
+from owlapy.owl_individual import OWLNamedIndividual, OWLIndividual
+from owlapy.owl_ontology import OWLOntology, Ontology, ToOwlready2
+from owlapy.owl_property import OWLDataProperty, OWLObjectInverseOf, OWLObjectProperty, \
+    OWLProperty
 
 
 class OWLOntologyChange(metaclass=ABCMeta):
@@ -151,3 +175,715 @@ def get_import_declaration(self) -> OWLImportsDeclaration:
             The import declaration.
         """
         return self._declaration
+
+
+@singledispatch
+def _add_axiom(axiom: OWLAxiom):
+    raise NotImplementedError(f'Axiom type {axiom} is not implemented yet.')
+
+
+@_add_axiom.register
+def _(axiom: OWLDeclarationAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.namespace.Ontology = conv.map_object(ontology)
+
+    entity = axiom.get_entity()
+    with ont_x:
+        entity_x = world[entity.to_string_id()]
+        # Entity already exists
+        if entity_x is not None:
+            return
+
+        thing_x: owlready2.entity.ThingClass = conv.map_concept(OWLThing)
+        if isinstance(entity, OWLClass):
+            if entity.is_owl_thing() or entity.is_owl_nothing():
+                return
+            entity_x = types.new_class(name=entity.iri.get_remainder(), bases=(thing_x,))
+        elif isinstance(entity, OWLIndividual):
+            entity_x = thing_x(entity.iri.get_remainder())
+        elif isinstance(entity, OWLObjectProperty):
+            entity_x = types.new_class(name=entity.iri.get_remainder(), bases=(owlready2.ObjectProperty,))
+        elif isinstance(entity, OWLDataProperty):
+            entity_x = types.new_class(name=entity.iri.get_remainder(), bases=(owlready2.DatatypeProperty,))
+        elif isinstance(entity, OWLAnnotationProperty):
+            entity_x = types.new_class(name=entity.iri.get_remainder(), bases=(owlready2.AnnotationProperty,))
+        else:
+            raise ValueError(f'Cannot add ({entity}). Not an atomic class, property, or individual.')
+        entity_x.namespace = ont_x.get_namespace(entity.iri.get_namespace())
+        entity_x.namespace.world._refactor(entity_x.storid, entity_x.iri)
+
+
+@_add_axiom.register
+def _(axiom: OWLClassAssertionAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.namespace.Ontology = conv.map_object(ontology)
+
+    individual = axiom.get_individual()
+    cls_ = axiom.get_class_expression()
+    _check_expression(cls_, ontology, world)
+    _add_axiom(OWLDeclarationAxiom(individual), ontology, world)
+    with ont_x:
+        cls_x = conv.map_concept(cls_)
+        ind_x = conv._to_owlready2_individual(individual)
+        thing_x = conv.map_concept(OWLThing)
+        if thing_x in ind_x.is_a:
+            ind_x.is_a.remove(thing_x)
+        ind_x.is_a.append(cls_x)
+
+
+@_add_axiom.register
+def _(axiom: OWLObjectPropertyAssertionAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.namespace.Ontology = conv.map_object(ontology)
+
+    subject = axiom.get_subject()
+    property_ = axiom.get_property()
+    object_ = axiom.get_object()
+    _add_axiom(OWLDeclarationAxiom(subject), ontology, world)
+    _add_axiom(OWLDeclarationAxiom(property_), ontology, world)
+    _add_axiom(OWLDeclarationAxiom(object_), ontology, world)
+    with ont_x:
+        subject_x = conv._to_owlready2_individual(subject)
+        property_x = conv._to_owlready2_property(property_)
+        object_x = conv._to_owlready2_individual(object_)
+        property_x[subject_x].append(object_x)
+
+
+@_add_axiom.register
+def _(axiom: OWLDataPropertyAssertionAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.namespace.Ontology = conv.map_object(ontology)
+
+    subject = axiom.get_subject()
+    property_ = axiom.get_property()
+    _add_axiom(OWLDeclarationAxiom(subject), ontology, world)
+    _add_axiom(OWLDeclarationAxiom(property_), ontology, world)
+    with ont_x:
+        subject_x = conv._to_owlready2_individual(subject)
+        property_x = conv._to_owlready2_property(property_)
+        property_x[subject_x].append(axiom.get_object().to_python())
+
+
+@_add_axiom.register
+def _(axiom: OWLSubClassOfAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.namespace.Ontology = conv.map_object(ontology)
+
+    sub_class = axiom.get_sub_class()
+    super_class = axiom.get_super_class()
+
+    _check_expression(sub_class, ontology, world)
+    _check_expression(super_class, ontology, world)
+    with ont_x:
+        thing_x = conv.map_concept(OWLThing)
+        sub_class_x = conv.map_concept(sub_class)
+        super_class_x = conv.map_concept(super_class)
+        if isinstance(sub_class, OWLClass):
+            if thing_x in sub_class_x.is_a:
+                sub_class_x.is_a.remove(thing_x)
+        else:
+            # Currently owlready2 seems to expect that we make a new GeneralClassAxiom object each time.
+            # Another option would be to check whether a GeneralClassAxiom with the sub_class_x already exists and just
+            # add the super_class_x to its is_a attribute
+            sub_class_x = GeneralClassAxiom(sub_class_x)
+        sub_class_x.is_a.append(super_class_x)
+
+
+# TODO: Update as soon as owlready2 adds support for EquivalentClasses general class axioms
+@_add_axiom.register
+def _(axiom: OWLEquivalentClassesAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x = conv.map_object(ontology)
+
+    assert axiom.contains_named_equivalent_class(), 'Owlready2 does not support general' \
+                                                    'class axioms for equivalent classes.'
+    for ce in axiom.class_expressions():
+        _check_expression(ce, ontology, world)
+    with ont_x:
+        for ce_1, ce_2 in combinations(axiom.class_expressions(), 2):
+            assert ce_1 is not None, f"ce_1 cannot be None: {ce_1}, {type(ce_1)}"
+            assert ce_2 is not None, f"ce_2_x cannot be None: {ce_2}, {type(ce_2)}"
+
+            ce_1_x = conv.map_concept(ce_1)
+            ce_2_x = conv.map_concept(ce_2)
+            try:
+                assert ce_1_x is not None, f"ce_1_x cannot be None: {ce_1_x}, {type(ce_1_x)}"
+                assert ce_2_x is not None, f"ce_2_x cannot be None: {ce_2_x}, {type(ce_2_x)}"
+            except AssertionError:
+                print("function of ToOwlready2.map_concept() returns None")
+                print(ce_1, ce_1_x)
+                print(ce_2, ce_2_x)
+                print("Axiom:", axiom)
+                print("Temporary solution is reinitializing ce_1_x=ce_2_x\n\n")
+                ce_1_x=ce_2_x
+
+            if isinstance(ce_1_x, owlready2.ThingClass):
+                ce_1_x.equivalent_to.append(ce_2_x)
+            if isinstance(ce_2_x, owlready2.ThingClass):
+                ce_2_x.equivalent_to.append(ce_1_x)
+
+
+@_add_axiom.register
+def _(axiom: OWLDisjointClassesAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.Ontology = conv.map_object(ontology)
+
+    for cls_ in axiom.class_expressions():
+        _check_expression(cls_, ontology, world)
+    with ont_x:
+        # TODO: If the first element in the list is a complex class expression owlready2 is bugged
+        # and creates an AllDifferent axiom
+        AllDisjoint(list(map(conv.map_concept, axiom.class_expressions())))
+
+
+@_add_axiom.register
+def _(axiom: OWLDisjointUnionAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.Ontology = conv.map_object(ontology)
+
+    assert isinstance(axiom.get_owl_class(), OWLClass), f'({axiom.get_owl_class()}) is not a named class.'
+    _add_axiom(OWLDeclarationAxiom(axiom.get_owl_class()), ontology, world)
+    for cls_ in axiom.get_class_expressions():
+        _check_expression(cls_, ontology, world)
+    with ont_x:
+        cls_x = conv.map_concept(axiom.get_owl_class())
+        cls_x.disjoint_unions.append(list(map(conv.map_concept, axiom.get_class_expressions())))
+
+
+@_add_axiom.register
+def _(axiom: OWLAnnotationAssertionAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.Ontology = conv.map_object(ontology)
+
+    prop_x = conv.map_object(axiom.get_property())
+    if prop_x is None:
+        with ont_x:
+            prop_x: owlready2.annotation.AnnotationPropertyClass = cast(
+                owlready2.AnnotationProperty,
+                types.new_class(
+                    name=axiom.get_property().iri.get_remainder(),
+                    bases=(owlready2.AnnotationProperty,)))
+            prop_x.namespace = ont_x.get_namespace(axiom.get_property().iri.get_namespace())
+    sub_x = world[axiom.get_subject().as_iri().as_str()]
+    assert sub_x is not None, f'{axiom.get_subject} not found in {ontology}'
+    with ont_x:
+        if axiom.get_value().is_literal():
+            literal = axiom.get_value().as_literal()
+            setattr(sub_x, prop_x.python_name, literal.to_python())
+        else:
+            o_x = world[axiom.get_value().as_iri().as_str()]
+            assert o_x is not None, f'{axiom.get_value()} not found in {ontology}'
+            setattr(sub_x, prop_x.python_name, o_x)
+
+
+@_add_axiom.register
+def _(axiom: OWLNaryIndividualAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.Ontology = conv.map_object(ontology)
+
+    for ind in axiom.individuals():
+        _add_axiom(OWLDeclarationAxiom(ind), ontology, world)
+    with ont_x:
+        if isinstance(axiom, OWLSameIndividualAxiom):
+            for idx, ind in enumerate(axiom.individuals()):
+                ind_x = conv._to_owlready2_individual(ind)
+                for ind_2 in islice(axiom.individuals(), idx + 1, None):
+                    ind_2_x = conv._to_owlready2_individual(ind_2)
+                    ind_x.equivalent_to.append(ind_2_x)
+        elif isinstance(axiom, OWLDifferentIndividualsAxiom):
+            AllDifferent(list(map(conv._to_owlready2_individual, axiom.individuals())))
+        else:
+            raise ValueError(f'OWLNaryIndividualAxiom ({axiom}) is not defined.')
+
+
+@_add_axiom.register
+def _(axiom: OWLSubPropertyAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.Ontology = conv.map_object(ontology)
+
+    sub_property = axiom.get_sub_property()
+    super_property = axiom.get_super_property()
+    _add_axiom(OWLDeclarationAxiom(sub_property), ontology, world)
+    _add_axiom(OWLDeclarationAxiom(super_property), ontology, world)
+    with ont_x:
+        sub_property_x = conv._to_owlready2_property(sub_property)
+        super_property_x = conv._to_owlready2_property(super_property)
+        sub_property_x.is_a.append(super_property_x)
+
+
+@_add_axiom.register
+def _(axiom: OWLPropertyDomainAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.Ontology = conv.map_object(ontology)
+
+    property_ = axiom.get_property()
+    domain = axiom.get_domain()
+    _add_axiom(OWLDeclarationAxiom(property_), ontology, world)
+    _check_expression(domain, ontology, world)
+    with ont_x:
+        property_x = conv._to_owlready2_property(property_)
+        domain_x = conv.map_concept(domain)
+        property_x.domain.append(domain_x)
+
+
+@_add_axiom.register
+def _(axiom: OWLPropertyRangeAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.Ontology = conv.map_object(ontology)
+
+    property_ = axiom.get_property()
+    range_ = axiom.get_range()
+    _add_axiom(OWLDeclarationAxiom(property_), ontology, world)
+    if isinstance(axiom, OWLObjectPropertyRangeAxiom):
+        _check_expression(range_, ontology, world)
+    with ont_x:
+        property_x = conv._to_owlready2_property(property_)
+        range_x = conv.map_concept(range_) if isinstance(axiom, OWLObjectPropertyRangeAxiom) \
+            else conv.map_datarange(range_)
+        property_x.range.append(range_x)
+
+
+@_add_axiom.register
+def _(axiom: OWLNaryPropertyAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.Ontology = conv.map_object(ontology)
+
+    for property_ in axiom.properties():
+        _add_axiom(OWLDeclarationAxiom(property_), ontology, world)
+    with ont_x:
+        if isinstance(axiom, (OWLEquivalentObjectPropertiesAxiom, OWLEquivalentDataPropertiesAxiom,)):
+            for idx, property_ in enumerate(axiom.properties()):
+                property_x = conv._to_owlready2_property(property_)
+                for property_2 in islice(axiom.properties(), idx + 1, None):
+                    property_2_x = conv._to_owlready2_property(property_2)
+                    property_x.equivalent_to.append(property_2_x)
+        elif isinstance(axiom, (OWLDisjointObjectPropertiesAxiom, OWLDisjointDataPropertiesAxiom,)):
+            AllDisjoint(list(map(conv._to_owlready2_property, axiom.properties())))
+        elif isinstance(axiom, OWLInverseObjectPropertiesAxiom):
+            property_first_x = conv._to_owlready2_property(axiom.get_first_property())
+            property_second_x = conv._to_owlready2_property(axiom.get_second_property())
+            if property_second_x.inverse_property is not None:
+                property_second_x.inverse_property = None
+            property_first_x.inverse_property = property_second_x
+        else:
+            raise ValueError(f'OWLNaryPropertyAxiom ({axiom}) is not defined.')
+
+
+@_add_axiom.register
+def _(axiom: OWLObjectPropertyCharacteristicAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.Ontology = conv.map_object(ontology)
+
+    property_ = axiom.get_property()
+    _add_axiom(OWLDeclarationAxiom(property_), ontology, world)
+    with ont_x:
+        property_x = conv._to_owlready2_property(property_)
+        if isinstance(axiom, OWLFunctionalObjectPropertyAxiom):
+            property_x.is_a.append(owlready2.FunctionalProperty)
+        elif isinstance(axiom, OWLAsymmetricObjectPropertyAxiom):
+            property_x.is_a.append(owlready2.AsymmetricProperty)
+        elif isinstance(axiom, OWLInverseFunctionalObjectPropertyAxiom):
+            property_x.is_a.append(owlready2.InverseFunctionalProperty)
+        elif isinstance(axiom, OWLIrreflexiveObjectPropertyAxiom):
+            property_x.is_a.append(owlready2.IrreflexiveProperty)
+        elif isinstance(axiom, OWLReflexiveObjectPropertyAxiom):
+            property_x.is_a.append(owlready2.ReflexiveProperty)
+        elif isinstance(axiom, OWLSymmetricObjectPropertyAxiom):
+            property_x.is_a.append(owlready2.SymmetricProperty)
+        elif isinstance(axiom, OWLTransitiveObjectPropertyAxiom):
+            property_x.is_a.append(owlready2.TransitiveProperty)
+        else:
+            raise ValueError(f'ObjectPropertyCharacteristicAxiom ({axiom}) is not defined.')
+
+
+@_add_axiom.register
+def _(axiom: OWLDataPropertyCharacteristicAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.Ontology = conv.map_object(ontology)
+
+    property_ = axiom.get_property()
+    _add_axiom(OWLDeclarationAxiom(property_), ontology, world)
+    with ont_x:
+        property_x = conv._to_owlready2_property(property_)
+        if isinstance(axiom, OWLFunctionalDataPropertyAxiom):
+            property_x.is_a.append(owlready2.FunctionalProperty)
+        else:
+            raise ValueError(f'DataPropertyCharacteristicAxiom ({axiom}) is not defined.')
+
+
+@singledispatch
+def _remove_axiom(axiom: OWLAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    raise NotImplementedError(f'Axiom type {axiom} is not implemented yet.')
+
+
+@_remove_axiom.register
+def _(axiom: OWLDeclarationAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.namespace.Ontology = conv.map_object(ontology)
+    with ont_x:
+        entity_x = world[axiom.get_entity().to_string_id()]
+        if entity_x is not None:
+            # TODO: owlready2 seems to be bugged for properties here
+            destroy_entity(entity_x)
+
+
+@_remove_axiom.register
+def _(axiom: OWLClassAssertionAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.namespace.Ontology = conv.map_object(ontology)
+
+    with ont_x:
+        cls_x = conv.map_concept(axiom.get_class_expression())
+        ind_x = conv._to_owlready2_individual(axiom.get_individual())
+        if cls_x is None or ind_x is None:
+            return
+        if cls_x in ind_x.is_a:
+            ind_x.is_a.remove(cls_x)
+        elif isinstance(axiom.get_class_expression(), OWLClass):
+            ont_x._del_obj_triple_spo(ind_x.storid, owlready2.rdf_type, cls_x.storid)
+
+
+@_remove_axiom.register
+def _(axiom: OWLObjectPropertyAssertionAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.namespace.Ontology = conv.map_object(ontology)
+
+    with ont_x:
+        subject_x = conv._to_owlready2_individual(axiom.get_subject())
+        property_x = conv._to_owlready2_property(axiom.get_property())
+        object_x = conv._to_owlready2_individual(axiom.get_object())
+        if all([subject_x, property_x, object_x]) and object_x in property_x[subject_x]:
+            property_x[subject_x].remove(object_x)
+
+
+@_remove_axiom.register
+def _(axiom: OWLDataPropertyAssertionAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.namespace.Ontology = conv.map_object(ontology)
+
+    with ont_x:
+        subject_x = conv._to_owlready2_individual(axiom.get_subject())
+        property_x = conv._to_owlready2_property(axiom.get_property())
+        object_ = axiom.get_object().to_python()
+        if subject_x is not None and property_x is not None and object_ in property_x[subject_x]:
+            property_x[subject_x].remove(object_)
+
+
+@_remove_axiom.register
+def _(axiom: OWLSubClassOfAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.namespace.Ontology = conv.map_object(ontology)
+    sub_class = axiom.get_sub_class()
+    super_class = axiom.get_super_class()
+
+    with ont_x:
+        sub_class_x = conv.map_concept(sub_class)
+        super_class_x = conv.map_concept(super_class)
+        if sub_class_x is None or super_class_x is None:
+            return
+
+        if isinstance(sub_class, OWLClass):
+            if super_class_x in sub_class_x.is_a:
+                sub_class_x.is_a.remove(super_class_x)
+            elif isinstance(axiom.get_sub_class(), OWLClass) and isinstance(axiom.get_super_class(), OWLClass):
+                ont_x._del_obj_triple_spo(sub_class_x.storid, owlready2.rdfs_subclassof, super_class_x.storid)
+        else:
+            for ca in ont_x.general_class_axioms():
+                if ca.left_side == sub_class_x and super_class_x in ca.is_a:
+                    ca.is_a.remove(super_class_x)
+
+
+# TODO: Update as soons as owlready2 adds support for EquivalentClasses general class axioms
+@_remove_axiom.register
+def _(axiom: OWLEquivalentClassesAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x = conv.map_object(ontology)
+
+    if not axiom.contains_named_equivalent_class():
+        return
+
+    with ont_x:
+        ces_x = list(map(conv.map_concept, axiom.class_expressions()))
+        if len(ces_x) < 2 or not all(ces_x):
+            return
+
+        for ce_1_x, ce_2_x in combinations(ces_x, 2):
+            if isinstance(ce_2_x, owlready2.ThingClass) and ce_1_x in ce_2_x.equivalent_to:
+                ce_2_x.equivalent_to.remove(ce_1_x)
+            if isinstance(ce_1_x, owlready2.ThingClass) and ce_2_x in ce_1_x.equivalent_to:
+                ce_1_x.equivalent_to.remove(ce_2_x)
+
+
+@_remove_axiom.register
+def _(axiom: OWLDisjointClassesAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.Ontology = conv.map_object(ontology)
+
+    with ont_x:
+        class_expressions_x = set(map(conv.map_concept, axiom.class_expressions()))
+        if len(class_expressions_x) < 2 or not all(class_expressions_x):
+            return
+        for disjoints_x in ont_x.disjoint_classes():
+            if set(disjoints_x.entities) == class_expressions_x:
+                del disjoints_x.entities[:-1]
+                break
+
+
+@_remove_axiom.register
+def _(axiom: OWLDisjointUnionAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.Ontology = conv.map_object(ontology)
+    assert isinstance(axiom.get_owl_class(), OWLClass), f'({axiom.get_owl_class()}) is not a named class.'
+
+    with ont_x:
+        cls_x = conv.map_concept(axiom.get_owl_class())
+        union_expressions_x = set(map(conv.map_concept, axiom.get_class_expressions()))
+        if cls_x is not None and all(union_expressions_x):
+            for union_x in cls_x.disjoint_unions:
+                if union_expressions_x == set(union_x):
+                    cls_x.disjoint_unions.remove(union_x)
+                    break
+
+
+@_remove_axiom.register
+def _(axiom: OWLAnnotationAssertionAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.Ontology = conv.map_object(ontology)
+
+    sub_x = world[axiom.get_subject().as_iri().as_str()]
+    if sub_x is None:
+        return
+    name = axiom.get_property().iri.get_remainder()
+    with ont_x:
+        if axiom.get_value().is_literal():
+            o_x = axiom.get_value().as_literal().to_python()
+        else:
+            o_x = world[axiom.get_value().as_iri().as_str()]
+
+        value = getattr(sub_x, name, None)
+        if value is not None and o_x in value:
+            value.remove(o_x)
+
+
+@_remove_axiom.register
+def _(axiom: OWLNaryIndividualAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.Ontology = conv.map_object(ontology)
+
+    with ont_x:
+        individuals_x = list(map(conv._to_owlready2_individual, axiom.individuals()))
+        if len(individuals_x) < 2 or not all(individuals_x):
+            return
+        if isinstance(axiom, OWLSameIndividualAxiom):
+            if set(individuals_x[1:-1]) <= set(individuals_x[0].INDIRECT_equivalent_to):
+                for individual_1_x, individual_2_x in combinations(individuals_x, 2):
+                    if individual_1_x in individual_2_x.equivalent_to:
+                        individual_2_x.equivalent_to.remove(individual_1_x)
+                    if individual_2_x in individual_1_x.equivalent_to:
+                        individual_1_x.equivalent_to.remove(individual_2_x)
+        elif isinstance(axiom, OWLDifferentIndividualsAxiom):
+            individuals_x = set(individuals_x)
+            for different_x in ont_x.different_individuals():
+                if set(different_x.entities) == individuals_x:
+                    del different_x.entities[:-1]
+                    break
+        else:
+            raise ValueError(f'OWLNaryIndividualAxiom ({axiom}) is not defined.')
+
+
+@_remove_axiom.register
+def _(axiom: OWLSubPropertyAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.namespace.Ontology = conv.map_object(ontology)
+
+    with ont_x:
+        sub_property_x = conv._to_owlready2_property(axiom.get_sub_property())
+        super_property_x = conv._to_owlready2_property(axiom.get_super_property())
+        if sub_property_x is None or super_property_x is None:
+            return
+        if super_property_x in sub_property_x.is_a:
+            sub_property_x.is_a.remove(super_property_x)
+        else:
+            ont_x._del_obj_triple_spo(sub_property_x.storid, owlready2.rdfs_subpropertyof, super_property_x.storid)
+
+
+@_remove_axiom.register
+def _(axiom: OWLPropertyDomainAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.Ontology = conv.map_object(ontology)
+
+    with ont_x:
+        property_x = conv._to_owlready2_property(axiom.get_property())
+        domain_x = conv.map_concept(axiom.get_domain())
+        if domain_x is not None and property_x is not None and domain_x in property_x.domain:
+            property_x.domain.remove(domain_x)
+
+
+@_remove_axiom.register
+def _(axiom: OWLPropertyRangeAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.Ontology = conv.map_object(ontology)
+
+    with ont_x:
+        property_x = conv._to_owlready2_property(axiom.get_property())
+        range_x = conv.map_concept(axiom.get_range()) \
+            if isinstance(axiom, OWLObjectPropertyRangeAxiom) else conv.map_datarange(axiom.get_range())
+        if range_x is not None and property_x is not None and range_x in property_x.range:
+            property_x.range.remove(range_x)
+
+
+@_remove_axiom.register
+def _(axiom: OWLNaryPropertyAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.Ontology = conv.map_object(ontology)
+
+    with ont_x:
+        properties_x = list(map(conv._to_owlready2_property, axiom.properties()))
+        if len(properties_x) < 2 or not all(properties_x):
+            return
+        if isinstance(axiom, (OWLEquivalentObjectPropertiesAxiom, OWLEquivalentDataPropertiesAxiom,)):
+            # Check if all equivalent properties are defined in the ontology
+            if set(properties_x[1:-1]) <= set(properties_x[0].INDIRECT_equivalent_to):
+                for property_1_x, property_2_x in combinations(properties_x, 2):
+                    if property_1_x in property_2_x.equivalent_to:
+                        property_2_x.equivalent_to.remove(property_1_x)
+                    if property_2_x in property_1_x.equivalent_to:
+                        property_1_x.equivalent_to.remove(property_2_x)
+        elif isinstance(axiom, (OWLDisjointObjectPropertiesAxiom, OWLDisjointDataPropertiesAxiom,)):
+            properties_x = set(properties_x)
+            for disjoints_x in ont_x.disjoint_properties():
+                if set(disjoints_x.entities) == properties_x:
+                    del disjoints_x.entities[:-1]
+                    break
+        elif isinstance(axiom, OWLInverseObjectPropertiesAxiom):
+            if len(properties_x) != 2:
+                return
+            first = properties_x[0]
+            second = properties_x[1]
+            if first.inverse_property == second and second.inverse_property == first:
+                first.inverse_property = None
+        else:
+            raise ValueError(f'OWLNaryPropertyAxiom ({axiom}) is not defined.')
+
+
+@_remove_axiom.register
+def _(axiom: OWLObjectPropertyCharacteristicAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.Ontology = conv.map_object(ontology)
+
+    with ont_x:
+        property_x = conv._to_owlready2_property(axiom.get_property())
+        if property_x is None:
+            return
+
+        if isinstance(axiom, OWLFunctionalObjectPropertyAxiom) and owlready2.FunctionalProperty in property_x.is_a:
+            property_x.is_a.remove(owlready2.FunctionalProperty)
+        elif isinstance(axiom, OWLAsymmetricObjectPropertyAxiom) and owlready2.AsymmetricProperty in property_x.is_a:
+            property_x.is_a.remove(owlready2.AsymmetricProperty)
+        elif isinstance(axiom, OWLInverseFunctionalObjectPropertyAxiom) \
+                and owlready2.InverseFunctionalProperty in property_x.is_a:
+            property_x.is_a.remove(owlready2.InverseFunctionalProperty)
+        elif isinstance(axiom, OWLIrreflexiveObjectPropertyAxiom) and owlready2.IrreflexiveProperty in property_x.is_a:
+            property_x.is_a.remove(owlready2.IrreflexiveProperty)
+        elif isinstance(axiom, OWLReflexiveObjectPropertyAxiom) and owlready2.ReflexiveProperty in property_x.is_a:
+            property_x.is_a.remove(owlready2.ReflexiveProperty)
+        elif isinstance(axiom, OWLSymmetricObjectPropertyAxiom) and owlready2.SymmetricProperty in property_x.is_a:
+            property_x.is_a.remove(owlready2.SymmetricProperty)
+        elif isinstance(axiom, OWLTransitiveObjectPropertyAxiom) and owlready2.TransitiveProperty in property_x.is_a:
+            property_x.is_a.remove(owlready2.TransitiveProperty)
+        else:
+            raise ValueError(f'OWLObjectPropertyCharacteristicAxiom ({axiom}) is not defined.')
+
+
+@_remove_axiom.register
+def _(axiom: OWLDataPropertyCharacteristicAxiom, ontology: OWLOntology, world: owlready2.namespace.World):
+    conv = ToOwlready2(world)
+    ont_x: owlready2.Ontology = conv.map_object(ontology)
+
+    with ont_x:
+        property_x = conv._to_owlready2_property(axiom.get_property())
+        if property_x is not None and isinstance(axiom, OWLFunctionalDataPropertyAxiom) \
+                and owlready2.FunctionalProperty in property_x.is_a:
+            property_x.is_a.remove(owlready2.FunctionalProperty)
+
+
+def _check_expression(expr: OWLObject, ontology: OWLOntology, world: owlready2.namespace.World):
+    """
+    @TODO:CD: Documentation
+    Creates all entities (individuals, classes, properties) that appear in the given (complex) class expression
+    and do not exist in the given ontology yet
+
+    """
+    if isinstance(expr, (OWLClass, OWLProperty, OWLNamedIndividual,)):
+        _add_axiom(OWLDeclarationAxiom(expr), ontology, world)
+    elif isinstance(expr, (OWLNaryBooleanClassExpression, OWLObjectComplementOf, OWLObjectOneOf,)):
+        for op in expr.operands():
+            _check_expression(op, ontology, world)
+    elif isinstance(expr, (OWLQuantifiedObjectRestriction, OWLObjectHasValue,)):
+        _check_expression(expr.get_property(), ontology, world)
+        _check_expression(expr.get_filler(), ontology, world)
+    elif isinstance(expr, OWLObjectInverseOf):
+        _check_expression(expr.get_named_property(), ontology, world)
+        _check_expression(expr.get_inverse_property(), ontology, world)
+    elif isinstance(expr, (OWLQuantifiedDataRestriction, OWLDataHasValue,)):
+        _check_expression(expr.get_property(), ontology, world)
+    elif not isinstance(expr, OWLObject):
+        raise ValueError(f'({expr}) is not an OWLObject.')
+
+
+class OntologyManager(OWLOntologyManager):
+    __slots__ = '_world'
+
+    _world: owlready2.namespace.World
+
+    def __init__(self, world_store=None):
+        """Ontology manager in Ontolearn.
+        Creates a world where ontology is loaded.
+        Used to make changes in the ontology.
+
+        Args:
+            world_store: The file name of the world store. Leave to default value to create a new world.
+        """
+        if world_store is None:
+            self._world = owlready2.World()
+        else:
+            self._world = owlready2.World(filename=world_store)
+
+    def create_ontology(self, iri: IRI) -> 'Ontology':
+        return Ontology(self, iri, load=False)
+
+    def load_ontology(self, iri: IRI) -> 'Ontology':
+        return Ontology(self, iri, load=True)
+
+    def apply_change(self, change: OWLOntologyChange):
+        if isinstance(change, AddImport):
+            ont_x: owlready2.namespace.Ontology = self._world.get_ontology(
+                change.get_ontology().get_ontology_id().get_ontology_iri().as_str())
+            ont_x.imported_ontologies.append(
+                self._world.get_ontology(change.get_import_declaration().str))
+        else:
+            # TODO XXX
+            raise NotImplementedError
+
+    def add_axiom(self, ontology: OWLOntology, axiom: OWLAxiom):
+        _add_axiom(axiom, ontology, self._world)
+
+    def remove_axiom(self, ontology: OWLOntology, axiom: OWLAxiom):
+        _remove_axiom(axiom, ontology, self._world)
+
+    def save_ontology(self, ontology: OWLOntology, document_iri: IRI):
+        ont_x: owlready2.namespace.Ontology = self._world.get_ontology(
+            ontology.get_ontology_id().get_ontology_iri().as_str()
+        )
+        if document_iri.get_namespace().startswith('file:/'):
+            filename = document_iri.as_str()[len('file:/'):]
+            ont_x.save(file=filename)
+        else:
+            # TODO XXX
+            raise NotImplementedError
+
+    def save_world(self):
+        """Saves the actual state of the quadstore in the SQLite3 file.
+        """
+        self._world.save()
+
diff --git a/owlapy/owl_reasoner.py b/owlapy/owl_reasoner.py
index 387f0251..db5ba271 100644
--- a/owlapy/owl_reasoner.py
+++ b/owlapy/owl_reasoner.py
@@ -1,16 +1,37 @@
 """OWL Reasoner"""
+import operator
+import os
+import types
 from abc import ABCMeta, abstractmethod
-from typing import Iterable
-
-from owlapy.class_expression import OWLClassExpression
+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
+import logging
+
+import owlready2
+
+from owlapy.class_expression import OWLClassExpression, OWLObjectSomeValuesFrom, OWLObjectUnionOf, \
+    OWLObjectIntersectionOf, OWLObjectComplementOf, OWLObjectAllValuesFrom, OWLObjectOneOf, OWLObjectHasValue, \
+    OWLObjectMinCardinality, OWLObjectMaxCardinality, OWLObjectExactCardinality, OWLObjectCardinalityRestriction, \
+    OWLDataSomeValuesFrom, OWLDataOneOf, OWLDatatypeRestriction, OWLFacetRestriction, OWLDataHasValue, \
+    OWLDataAllValuesFrom
 from owlapy.class_expression import OWLClass
-from owlapy.owl_ontology import OWLOntology
-
-from owlapy.owl_property import OWLObjectPropertyExpression, OWLDataProperty, OWLObjectProperty
-
-
+from owlapy.iri import IRI
+from owlapy.owl_axiom import OWLAxiom, 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_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.utils import LRUCache
+
+logger = logging.getLogger(__name__)
 
 
 class OWLReasoner(metaclass=ABCMeta):
@@ -370,11 +391,6 @@ def is_isolated(self):
         """Return True if this reasoner is using an isolated ontology."""
         pass
 
-    @abstractmethod
-    def is_using_triplestore(self):
-        """Return True if this reasoner is using a triplestore to retrieve instances."""
-        pass
-
     @abstractmethod
     def super_classes(self, ce: OWLClassExpression, direct: bool = False, only_named: bool = True) \
             -> Iterable[OWLClassExpression]:
@@ -392,4 +408,1361 @@ class expression with respect to the imports closure of the root ontology.
             If direct is False, each class C where  set of reasoner axioms entails StrictSubClassOf(ce, C).
             If ce is equivalent to owl:Thing then nothing will be returned.
         """
-        pass
\ No newline at end of file
+        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"""
+
+    # default
+    def data_property_ranges(self, pe: OWLDataProperty, direct: bool = False) -> Iterable[OWLDataRange]:
+        """Gets the data ranges that are the direct or indirect ranges of this property with respect to the imports
+        closure of the root ontology.
+
+        Args:
+            pe: 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:
+        """
+        for ax in self.get_root_ontology().data_property_range_axioms(pe):
+            yield ax.get_range()
+            if not direct:
+                logger.warning("indirect not implemented")
+                # TODO:
+
+    # default
+    def all_data_property_values(self, pe: OWLDataProperty, direct: bool = True) -> Iterable[OWLLiteral]:
+        """Gets all values for the given data property expression that appear in the knowledge base.
+
+        Args:
+            pe: The data property expression whose values are to be retrieved
+            direct: Specifies if only the direct values of the data property pe should be retrieved (True), or if
+                    the values of sub properties of pe should be taken into account (False).
+
+        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) for any ind.
+        """
+        onto = self.get_root_ontology()
+        for ind in onto.individuals_in_signature():
+            for lit in self.data_property_values(ind, pe, direct):
+                yield lit
+
+    # default
+    def ind_data_properties(self, ind: OWLNamedIndividual, direct: bool = True) -> Iterable[OWLDataProperty]:
+        """Gets all data properties for the given individual that appear in the knowledge base.
+
+        Args:
+            ind: The named individual whose data properties are to be retrieved
+            direct: Specifies if the direct data properties should be retrieved (True), or if all
+                data properties should be retrieved (False), so that sub properties are taken into account.
+
+        Returns:
+            All data properties pe where the set of reasoner axioms entails DataPropertyAssertion(pe ind l)
+            for atleast one l.
+        """
+        onto = self.get_root_ontology()
+        for dp in onto.data_properties_in_signature():
+            try:
+                next(iter(self.data_property_values(ind, dp, direct)))
+                yield dp
+            except StopIteration:
+                pass
+
+    # default
+    def ind_object_properties(self, ind: OWLNamedIndividual, direct: bool = True) -> Iterable[OWLObjectProperty]:
+        """Gets all object properties for the given individual that appear in the knowledge base.
+
+        Args:
+            ind: The named individual whose object properties are to be retrieved
+            direct: Specifies if the direct object properties should be retrieved (True), or if all
+                object properties should be retrieved (False), so that sub properties are taken into account.
+
+        Returns:
+            All data properties pe where the set of reasoner axioms entails ObjectPropertyAssertion(pe ind ind2)
+            for atleast one ind2.
+        """
+        onto = self.get_root_ontology()
+        for op in onto.object_properties_in_signature():
+            try:
+                next(iter(self.object_property_values(ind, op, direct)))
+                yield op
+            except StopIteration:
+                pass
+
+
+class OntologyReasoner(OWLReasonerEx):
+    __slots__ = '_ontology', '_world'
+
+    _ontology: Ontology
+    _world: owlready2.World
+
+    def __init__(self, ontology: Ontology, isolate: bool = False):
+        """
+        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.")
+
+    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)}
+        super_domains = set(chain.from_iterable([self.super_classes(d) for d in domains]))
+        yield from domains - super_domains
+        if not direct:
+            yield from super_domains
+
+    def object_property_domains(self, pe: OWLObjectProperty, direct: bool = False) -> Iterable[OWLClassExpression]:
+        domains = {d.get_domain() for d in self.get_root_ontology().object_property_domain_axioms(pe)}
+        super_domains = set(chain.from_iterable([self.super_classes(d) for d in domains]))
+        yield from domains - super_domains
+        if not direct:
+            yield from super_domains
+
+    def object_property_ranges(self, pe: OWLObjectProperty, direct: bool = False) -> Iterable[OWLClassExpression]:
+        ranges = {r.get_range() for r in self.get_root_ontology().object_property_range_axioms(pe)}
+        super_ranges = set(chain.from_iterable([self.super_classes(d) for d in ranges]))
+        yield from ranges - super_ranges
+        if not direct:
+            yield from super_ranges
+
+    def equivalent_classes(self, ce: OWLClassExpression, only_named: bool = True) -> Iterable[OWLClassExpression]:
+        seen_set = {ce}
+        if isinstance(ce, OWLClass):
+            c_x: owlready2.ThingClass = self._world[ce.str]
+            for eq_x in c_x.INDIRECT_equivalent_to:
+                eq = _parse_concept_to_owlapy(eq_x)
+                if (isinstance(eq, OWLClass) or
+                    (isinstance(eq, OWLClassExpression) and not only_named)) and eq not in seen_set:
+                    seen_set.add(eq)
+                    yield eq
+                # Workaround for problems in owlready2. It does not always recognize equivalent complex class
+                # expressions through INDIRECT_equivalent_to. Maybe it will work as soon as owlready2 adds support for
+                # EquivalentClasses general class axioms.
+                if not only_named and isinstance(eq_x, owlready2.ThingClass):
+                    for eq_2_x in eq_x.equivalent_to:
+                        eq_2 = _parse_concept_to_owlapy(eq_2_x)
+                        if eq_2 not in seen_set:
+                            seen_set.add(eq_2)
+                            yield eq_2
+        elif isinstance(ce, OWLClassExpression):
+            # Extend as soon as owlready2 supports EquivalentClasses general class axioms
+            # Slow but works. No better way to do this in owlready2 without using the reasoners at the moment.
+            # Might be able to change this when owlready2 supports general class axioms for EquivalentClasses.
+            for c in self._ontology.classes_in_signature():
+                if ce in self.equivalent_classes(c, only_named=False) and c not in seen_set:
+                    seen_set.add(c)
+                    yield c
+                    for e_c in self.equivalent_classes(c, only_named=False):
+                        if e_c not in seen_set and (not only_named or isinstance(e_c, OWLClass)):
+                            seen_set.add(e_c)
+                            yield e_c
+        else:
+            raise ValueError(f'Equivalent classes not implemented for: {ce}')
+
+    def _find_disjoint_classes(self, ce: OWLClassExpression, only_named: bool = True, seen_set=None):
+        if isinstance(ce, OWLClass):
+            c_x: owlready2.ThingClass = self._world[ce.str]
+            for d_x in chain.from_iterable(map(lambda d: d.entities, c_x.disjoints())):
+                if d_x != c_x and (isinstance(d_x, owlready2.ThingClass) or
+                                   (isinstance(d_x, owlready2.ClassConstruct) and not only_named)):
+                    d_owlapy = _parse_concept_to_owlapy(d_x)
+                    seen_set.add(d_owlapy)
+                    yield d_owlapy
+                    for c in self.equivalent_classes(d_owlapy, only_named=only_named):
+                        if c not in seen_set:
+                            seen_set.add(c)
+                            yield c
+                    for c in self.sub_classes(d_owlapy, only_named=only_named):
+                        if c not in seen_set:
+                            seen_set.add(c)
+                            yield c
+        elif isinstance(ce, OWLClassExpression):
+            # Extend as soon as owlready2 supports DisjointClasses general class axioms
+            # Slow but works. No better way to do this in owlready2 without using the reasoners at the moment.
+            # Might be able to change this when owlready2 supports general class axioms for DjsjointClasses
+            yield from (c for c in self._ontology.classes_in_signature() if ce in self.disjoint_classes(c, False))
+        else:
+            raise ValueError(f'Equivalent classes not implemented for: {ce}')
+
+    def disjoint_classes(self, ce: OWLClassExpression, only_named: bool = True) -> Iterable[OWLClassExpression]:
+        seen_set = set()
+        yield from self._find_disjoint_classes(ce, only_named, seen_set)
+        for c in self.super_classes(ce, only_named=only_named):
+            if c != OWLClass(IRI('http://www.w3.org/2002/07/owl#', 'Thing')):
+                yield from self._find_disjoint_classes(c, only_named=only_named, seen_set=seen_set)
+
+    def different_individuals(self, ind: OWLNamedIndividual) -> Iterable[OWLNamedIndividual]:
+        i: owlready2.Thing = self._world[ind.str]
+        yield from (OWLNamedIndividual(IRI.create(d_i.iri))
+                    for d_i in chain.from_iterable(map(lambda x: x.entities, i.differents()))
+                    if isinstance(d_i, owlready2.Thing) and i != d_i)
+
+    def same_individuals(self, ind: OWLNamedIndividual) -> Iterable[OWLNamedIndividual]:
+        i: owlready2.Thing = self._world[ind.str]
+        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) \
+            -> Iterable[OWLLiteral]:
+        i: owlready2.Thing = self._world[ind.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):
+            yield OWLLiteral(val)
+
+    def all_data_property_values(self, pe: OWLDataProperty, direct: bool = True) -> Iterable[OWLLiteral]:
+        p: owlready2.DataPropertyClass = self._world[pe.str]
+        relations = p.get_relations()
+        if not direct:
+            indirect_relations = chain.from_iterable(
+                map(lambda x: self._world[x.str].get_relations(),
+                    self.sub_data_properties(pe, direct=False)))
+            relations = chain(relations, indirect_relations)
+        for _, val in relations:
+            yield OWLLiteral(val)
+
+    def object_property_values(self, ind: OWLNamedIndividual, pe: OWLObjectPropertyExpression, direct: bool = False) \
+            -> Iterable[OWLNamedIndividual]:
+        if isinstance(pe, OWLObjectProperty):
+            i: owlready2.Thing = self._world[ind.str]
+            p: owlready2.ObjectPropertyClass = self._world[pe.str]
+            # Recommended to use direct=False because _get_values_for_individual does not give consistent result
+            # for the case when there are equivalent object properties. At least until this is fixed on owlready2.
+            retieval_func = p._get_values_for_individual if direct else p._get_indirect_values_for_individual
+            for val in retieval_func(i):
+                yield OWLNamedIndividual(IRI.create(val.iri))
+        elif isinstance(pe, OWLObjectInverseOf):
+            p: owlready2.ObjectPropertyClass = self._world[pe.get_named_property().str]
+            inverse_p = p.inverse_property
+            # If the inverse property is explicitly defined we can take shortcut
+            if inverse_p is not None:
+                yield from self.object_property_values(ind, OWLObjectProperty(IRI.create(inverse_p.iri)), direct)
+            else:
+                if not direct:
+                    raise NotImplementedError('Indirect values of inverse properties are only implemented if the '
+                                              'inverse property is explicitly defined in the ontology.'
+                                              f'Property: {pe}')
+                i: owlready2.Thing = self._world[ind.str]
+                for val in p._get_inverse_values_for_individual(i):
+                    yield OWLNamedIndividual(IRI.create(val.iri))
+        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):
+                c_x: owlready2.ThingClass = self._world[ce.str]
+                for i in self._ontology._onto.get_instances_of(c_x):
+                    if isinstance(i, owlready2.Thing):
+                        yield OWLNamedIndividual(IRI.create(i.iri))
+            else:
+                raise NotImplementedError("instances for complex class expressions not implemented", ce)
+        else:
+            if ce.is_owl_thing():
+                yield from self._ontology.individuals_in_signature()
+            elif isinstance(ce, OWLClass):
+                c_x: owlready2.ThingClass = self._world[ce.str]
+                for i in c_x.instances(world=self._world):
+                    if isinstance(i, owlready2.Thing):
+                        yield OWLNamedIndividual(IRI.create(i.iri))
+            # elif isinstance(ce, OWLObjectSomeValuesFrom) and ce.get_filler().is_owl_thing()\
+            #         and isinstance(ce.get_property(), OWLProperty):
+            #     seen_set = set()
+            #     p_x: owlready2.ObjectProperty = self._world[ce.get_property().get_named_property().str]
+            #     for i, _ in p_x.get_relations():
+            #         if isinstance(i, owlready2.Thing) and i not in seen_set:
+            #             seen_set.add(i)
+            #             yield OWLNamedIndividual(IRI.create(i.iri))
+            else:
+                raise NotImplementedError("instances for complex class expressions not implemented", ce)
+
+    def _sub_classes_recursive(self, ce: OWLClassExpression, seen_set: Set, only_named: bool = True) \
+            -> Iterable[OWLClassExpression]:
+
+        # work around issue in class equivalence detection in Owlready2
+        for c in [ce, *self.equivalent_classes(ce, only_named=False)]:
+            if c not in seen_set:
+                seen_set.add(c)
+                yield c
+            # First go through all general class axioms, they should only have complex classes as sub_classes.
+            # Done for OWLClass and OWLClassExpression.
+            for axiom in self._ontology.general_class_axioms():
+                if (isinstance(axiom, OWLSubClassOfAxiom) and axiom.get_super_class() == c
+                        and axiom.get_sub_class() not in seen_set):
+                    seen_set.add(axiom.get_sub_class())
+                    if not only_named:
+                        yield axiom.get_sub_class()
+                    yield from self._sub_classes_recursive(axiom.get_sub_class(), seen_set, only_named)
+
+            if isinstance(c, OWLClass):
+                c_x: owlready2.EntityClass = self._world[c.str]
+                # Subclasses will only return named classes
+                for sc_x in c_x.subclasses(world=self._world):
+                    sc = _parse_concept_to_owlapy(sc_x)
+                    if isinstance(sc, OWLClass) and sc not in seen_set:
+                        seen_set.add(sc)
+                        yield sc
+                        yield from self._sub_classes_recursive(sc, seen_set, only_named=only_named)
+            elif isinstance(c, OWLClassExpression):
+                # Slow but works. No better way to do this in owlready2 without using the reasoners at the moment.
+                for atomic_c in self._ontology.classes_in_signature():
+                    if c in self.super_classes(atomic_c, direct=True, only_named=False) and atomic_c not in seen_set:
+                        seen_set.add(atomic_c)
+                        yield atomic_c
+                        yield from self._sub_classes_recursive(atomic_c, seen_set, only_named=only_named)
+                if isinstance(ce, OWLObjectSomeValuesFrom):
+                    for r in self.sub_object_properties(ce.get_property()):
+                        osvf = OWLObjectSomeValuesFrom(property=r,
+                                                       filler=ce.get_filler())
+                        if osvf not in seen_set:
+                            seen_set.add(osvf)
+                            yield osvf
+                            # yield from self._sub_classes_recursive(osvf, seen_set, only_named=only_named)
+            else:
+                raise ValueError(f'Sub classes retrieval not implemented for: {ce}')
+
+    def sub_classes(self, ce: OWLClassExpression, direct: bool = False, only_named: bool = True) \
+            -> Iterable[OWLClassExpression]:
+        if not direct:
+            seen_set = {ce}
+            yield from self._sub_classes_recursive(ce, seen_set, only_named=only_named)
+        else:
+            # First go through all general class axioms, they should only have complex classes as sub_classes.
+            # Done for OWLClass and OWLClassExpression.
+            if not only_named:
+                for axiom in self._ontology.general_class_axioms():
+                    if isinstance(axiom, OWLSubClassOfAxiom) and axiom.get_super_class() == ce:
+                        yield axiom.get_sub_class()
+            if isinstance(ce, OWLClass):
+                c_x: owlready2.ThingClass = self._world[ce.str]
+                # Subclasses will only return named classes
+                for sc in c_x.subclasses(world=self._world):
+                    if isinstance(sc, owlready2.ThingClass):
+                        yield OWLClass(IRI.create(sc.iri))
+            elif isinstance(ce, OWLClassExpression):
+                # Slow but works. No better way to do this in owlready2 without using the reasoners at the moment.
+                for c in self._ontology.classes_in_signature():
+                    if ce in self.super_classes(c, direct=True, only_named=False):
+                        yield c
+            else:
+                raise ValueError(f'Sub classes retrieval not implemented for: {ce}')
+
+    def _super_classes_recursive(self, ce: OWLClassExpression, seen_set: Set, only_named: bool = True) \
+            -> Iterable[OWLClassExpression]:
+        # work around issue in class equivalence detection in Owlready2
+        for c in [ce, *self.equivalent_classes(ce, only_named=False)]:
+            if c not in seen_set:
+                seen_set.add(c)
+                yield c
+            if isinstance(c, OWLClass):
+                c_x: owlready2.EntityClass = self._world[c.str]
+                for sc_x in c_x.is_a:
+                    sc = _parse_concept_to_owlapy(sc_x)
+                    if (isinstance(sc, OWLClass) or isinstance(sc, OWLClassExpression)) and sc not in seen_set:
+                        seen_set.add(sc)
+                        # Return class expression if it is a named class or complex class expressions should be
+                        # included
+                        if isinstance(sc, OWLClass) or not only_named:
+                            yield sc
+                        yield from self._super_classes_recursive(sc, seen_set, only_named=only_named)
+            elif isinstance(c, OWLClassExpression):
+                for axiom in self._ontology.general_class_axioms():
+                    if (isinstance(axiom, OWLSubClassOfAxiom) and axiom.get_sub_class() == c
+                            and (axiom.get_super_class() not in seen_set)):
+                        super_class = axiom.get_super_class()
+                        seen_set.add(super_class)
+                        # Return class expression if it is a named class or complex class expressions should be
+                        # included
+                        if isinstance(super_class, OWLClass) or not only_named:
+                            yield super_class
+                        yield from self._super_classes_recursive(super_class, seen_set, only_named=only_named)
+
+                # Slow but works. No better way to do this in owlready2 without using the reasoners at the moment.
+                for atomic_c in self._ontology.classes_in_signature():
+                    if c in self.sub_classes(atomic_c, direct=True, only_named=False) and atomic_c not in seen_set:
+                        seen_set.add(atomic_c)
+                        yield atomic_c
+                        yield from self._super_classes_recursive(atomic_c, seen_set, only_named=only_named)
+            else:
+                raise ValueError(f'Super classes retrieval not supported for: {ce}')
+
+    def super_classes(self, ce: OWLClassExpression, direct: bool = False, only_named: bool = True) \
+            -> Iterable[OWLClassExpression]:
+        if not direct:
+            seen_set = {ce}
+            yield from self._super_classes_recursive(ce, seen_set, only_named=only_named)
+        else:
+            if isinstance(ce, OWLClass):
+                c_x: owlready2.ThingClass = self._world[ce.str]
+                for sc in c_x.is_a:
+                    if (isinstance(sc, owlready2.ThingClass) or
+                            (not only_named and isinstance(sc, owlready2.ClassConstruct))):
+                        yield _parse_concept_to_owlapy(sc)
+            elif isinstance(ce, OWLClassExpression):
+                seen_set = set()
+                for axiom in self._ontology.general_class_axioms():
+                    if (isinstance(axiom, OWLSubClassOfAxiom) and axiom.get_sub_class() == ce
+                            and (not only_named or isinstance(axiom.get_super_class(), OWLClass))):
+                        seen_set.add(axiom.get_super_class())
+                        yield axiom.get_super_class()
+                # Slow but works. No better way to do this in owlready2 without using the reasoners at the moment.
+                # TODO: Might not be needed, in theory the general class axioms above should cover all classes
+                # that can be found here
+                for c in self._ontology.classes_in_signature():
+                    if ce in self.sub_classes(c, direct=True, only_named=False) and c not in seen_set:
+                        seen_set.add(c)
+                        yield c
+            else:
+                raise ValueError(f'Super classes retrieval not supported for {ce}')
+
+    def equivalent_object_properties(self, op: OWLObjectPropertyExpression) -> Iterable[OWLObjectPropertyExpression]:
+        if isinstance(op, OWLObjectProperty):
+            p_x: owlready2.ObjectPropertyClass = self._world[op.str]
+            yield from (OWLObjectProperty(IRI.create(ep_x.iri)) for ep_x in p_x.INDIRECT_equivalent_to
+                        if isinstance(ep_x, owlready2.ObjectPropertyClass))
+        else:
+            raise NotImplementedError("equivalent properties of inverse properties not yet implemented", op)
+
+    def equivalent_data_properties(self, dp: OWLDataProperty) -> Iterable[OWLDataProperty]:
+        p_x: owlready2.DataPropertyClass = self._world[dp.str]
+        yield from (OWLDataProperty(IRI.create(ep_x.iri)) for ep_x in p_x.INDIRECT_equivalent_to
+                    if isinstance(ep_x, owlready2.DataPropertyClass))
+
+    def _find_disjoint_object_properties(self, op: OWLObjectPropertyExpression, seen_set=None) \
+            -> Iterable[OWLObjectPropertyExpression]:
+        if isinstance(op, OWLObjectProperty):
+            p_x: owlready2.ObjectPropertyClass = self._world[op.str]
+            ont_x: owlready2.Ontology = self.get_root_ontology()._onto
+            for disjoint in ont_x.disjoint_properties():
+                if p_x in disjoint.entities:
+                    for o_p in disjoint.entities:
+                        if isinstance(o_p, owlready2.ObjectPropertyClass) and o_p != p_x:
+                            op_owlapy = OWLObjectProperty(IRI.create(o_p.iri))
+                            seen_set.add(op_owlapy)
+                            yield op_owlapy
+                            for o in self.equivalent_object_properties(op_owlapy):
+                                if o not in seen_set:
+                                    seen_set.add(o)
+                                    yield o
+                            for o in self.sub_object_properties(op_owlapy):
+                                if o not in seen_set:
+                                    seen_set.add(o)
+                                    yield o
+        else:
+            raise NotImplementedError("disjoint object properties of inverse properties not yet implemented", op)
+
+    def disjoint_object_properties(self, op: OWLObjectPropertyExpression) -> Iterable[OWLObjectPropertyExpression]:
+        seen_set = set()
+        yield from self._find_disjoint_object_properties(op, seen_set)
+        for o in self.super_object_properties(op):
+            if o != OWLObjectProperty(IRI('http://www.w3.org/2002/07/owl#', 'ObjectProperty')):
+                yield from self._find_disjoint_object_properties(o, seen_set=seen_set)
+
+    def _find_disjoint_data_properties(self, dp: OWLDataProperty, seen_set=None) -> Iterable[OWLDataProperty]:
+        p_x: owlready2.DataPropertyClass = self._world[dp.str]
+        ont_x: owlready2.Ontology = self.get_root_ontology()._onto
+        for disjoint in ont_x.disjoint_properties():
+            if p_x in disjoint.entities:
+                for d_p in disjoint.entities:
+                    if isinstance(d_p, owlready2.DataPropertyClass) and d_p != p_x:
+                        dp_owlapy = OWLDataProperty(IRI.create(d_p.iri))
+                        seen_set.add(dp_owlapy)
+                        yield dp_owlapy
+                        for d in self.equivalent_data_properties(dp_owlapy):
+                            if d not in seen_set:
+                                seen_set.add(d)
+                                yield d
+                        for d in self.sub_data_properties(dp_owlapy):
+                            if d not in seen_set:
+                                seen_set.add(d)
+                                yield d
+
+    def disjoint_data_properties(self, dp: OWLDataProperty) -> Iterable[OWLDataProperty]:
+        seen_set = set()
+        yield from self._find_disjoint_data_properties(dp, seen_set)
+        for d in self.super_data_properties(dp):
+            if d != OWLDataProperty(IRI('http://www.w3.org/2002/07/owl#', 'DatatypeProperty')):
+                yield from self._find_disjoint_data_properties(d, seen_set=seen_set)
+
+    def _sup_or_sub_data_properties_recursive(self, dp: OWLDataProperty, seen_set: Set, super_or_sub="") \
+            -> Iterable[OWLDataProperty]:
+        for d in self.equivalent_data_properties(dp):
+            if d not in seen_set:
+                seen_set.add(d)
+                yield d
+        p_x: owlready2.DataPropertyClass = self._world[dp.str]
+        assert isinstance(p_x, owlready2.DataPropertyClass)
+        if super_or_sub == "super":
+            dps = set(p_x.is_a)
+        else:
+            dps = set(p_x.subclasses(world=self._world))
+        for sp_x in dps:
+            if isinstance(sp_x, owlready2.DataPropertyClass):
+                sp = OWLDataProperty(IRI.create(sp_x.iri))
+                if sp not in seen_set:
+                    seen_set.add(sp)
+                    yield sp
+                    yield from self._sup_or_sub_data_properties_recursive(sp, seen_set, super_or_sub)
+
+    def _sup_or_sub_data_properties(self, dp: OWLDataProperty, direct: bool = False, super_or_sub=""):
+        assert isinstance(dp, OWLDataProperty)
+        if direct:
+            p_x: owlready2.DataPropertyClass = self._world[dp.str]
+            if super_or_sub == "super":
+                dps = set(p_x.is_a)
+            else:
+                dps = set(p_x.subclasses(world=self._world))
+            for sp in dps:
+                if isinstance(sp, owlready2.DataPropertyClass):
+                    yield OWLDataProperty(IRI.create(sp.iri))
+        else:
+            seen_set = set()
+            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]:
+        yield from self._sup_or_sub_data_properties(dp, direct, "sub")
+
+    def _sup_or_sub_object_properties_recursive(self, op: OWLObjectProperty, seen_set: Set, super_or_sub=""):
+        for o in self.equivalent_object_properties(op):
+            if o not in seen_set:
+                seen_set.add(o)
+                yield o
+        p_x: owlready2.ObjectPropertyClass = self._world[op.str]
+        assert isinstance(p_x, owlready2.ObjectPropertyClass)
+        if super_or_sub == "super":
+            dps = set(p_x.is_a)
+        else:
+            dps = set(p_x.subclasses(world=self._world))
+        for sp_x in dps:
+            if isinstance(sp_x, owlready2.ObjectPropertyClass):
+                sp = OWLObjectProperty(IRI.create(sp_x.iri))
+                if sp not in seen_set:
+                    seen_set.add(sp)
+                    yield sp
+                    yield from self._sup_or_sub_object_properties_recursive(sp, seen_set, super_or_sub)
+
+    def _sup_or_sub_object_properties(self, op: OWLObjectPropertyExpression, direct: bool = False, super_or_sub="") \
+            -> Iterable[OWLObjectPropertyExpression]:
+        if isinstance(op, OWLObjectProperty):
+            if direct:
+                p_x: owlready2.ObjectPropertyClass = self._world[op.str]
+                if super_or_sub == "super":
+                    dps = set(p_x.is_a)
+                else:
+                    dps = set(p_x.subclasses(world=self._world))
+                for sp in dps:
+                    if isinstance(sp, owlready2.ObjectPropertyClass):
+                        yield OWLObjectProperty(IRI.create(sp.iri))
+            else:
+                seen_set = set()
+                yield from self._sup_or_sub_object_properties_recursive(op, seen_set, super_or_sub)
+        elif isinstance(op, OWLObjectInverseOf):
+            p: owlready2.ObjectPropertyClass = self._world[op.get_named_property().str]
+            inverse_p = p.inverse_property
+            if inverse_p is not None:
+                yield from self._sup_or_sub_object_properties(OWLObjectProperty(IRI.create(inverse_p.iri)), direct,
+                                                              super_or_sub)
+            else:
+                raise NotImplementedError(f'{super_or_sub} properties of inverse properties are only implemented if the'
+                                          ' inverse property is explicitly defined in the ontology. '
+                                          f'Property: {op}')
+        else:
+            raise NotImplementedError(op)
+
+    def super_object_properties(self, op: OWLObjectPropertyExpression, direct: bool = False) \
+            -> Iterable[OWLObjectPropertyExpression]:
+        """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:
+             op (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._sup_or_sub_object_properties(op, direct, "super")
+
+    def sub_object_properties(self, op: OWLObjectPropertyExpression, direct: bool = False) \
+            -> Iterable[OWLObjectPropertyExpression]:
+        yield from self._sup_or_sub_object_properties(op, direct, "sub")
+
+    def types(self, ind: OWLNamedIndividual, direct: bool = False) -> Iterable[OWLClass]:
+        i: owlready2.Thing = self._world[ind.str]
+        if direct:
+            for c in i.is_a:
+                if isinstance(c, owlready2.ThingClass):
+                    yield OWLClass(IRI.create(c.iri))
+                # Anonymous classes are ignored
+        else:
+            for c in i.INDIRECT_is_a:
+                if isinstance(c, owlready2.ThingClass):
+                    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)
+
+    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)."""
+    __slots__ = '_ontology', '_base_reasoner', \
+                '_ind_set', '_cls_to_ind', \
+                '_has_prop', \
+                '_objectsomevalues_cache', '_datasomevalues_cache', '_objectcardinality_cache', \
+                '_property_cache', \
+                '_obj_prop', '_obj_prop_inv', '_data_prop', \
+                '_negation_default', \
+                '__warned'
+
+    _ontology: OWLOntology
+    _base_reasoner: OWLReasoner
+    _cls_to_ind: Dict[OWLClass, FrozenSet[OWLNamedIndividual]]  # Class => individuals
+    _has_prop: Mapping[Type[_P], LRUCache[_P, FrozenSet[OWLNamedIndividual]]]  # Type => Property => individuals
+    _ind_set: FrozenSet[OWLNamedIndividual]
+    # ObjectSomeValuesFrom => individuals
+    _objectsomevalues_cache: LRUCache[OWLClassExpression, FrozenSet[OWLNamedIndividual]]
+    # DataSomeValuesFrom => individuals
+    _datasomevalues_cache: LRUCache[OWLClassExpression, FrozenSet[OWLNamedIndividual]]
+    # ObjectCardinalityRestriction => individuals
+    _objectcardinality_cache: LRUCache[OWLClassExpression, FrozenSet[OWLNamedIndividual]]
+    # ObjectProperty => { individual => individuals }
+    _obj_prop: Dict[OWLObjectProperty, Mapping[OWLNamedIndividual, Set[OWLNamedIndividual]]]
+    # ObjectProperty => { individual => individuals }
+    _obj_prop_inv: Dict[OWLObjectProperty, Mapping[OWLNamedIndividual, Set[OWLNamedIndividual]]]
+    # DataProperty => { individual => literals }
+    _data_prop: Dict[OWLDataProperty, Mapping[OWLNamedIndividual, Set[OWLLiteral]]]
+    _property_cache: bool
+    _negation_default: bool
+    _sub_properties: bool
+
+    def __init__(self, ontology: OWLOntology, base_reasoner: OWLReasoner, *,
+                 property_cache: bool = True, negation_default: bool = True, sub_properties: bool = False):
+        """Fast instance checker.
+
+        Args:
+            ontology: Ontology to use.
+            base_reasoner: Reasoner to get instances/types from.
+            property_cache: Whether to cache property values.
+            negation_default: Whether to assume a missing fact means it is false ("closed world view").
+            sub_properties: Whether to take sub properties into account for the
+                :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._base_reasoner = base_reasoner
+        self._property_cache = property_cache
+        self._negation_default = negation_default
+        self._sub_properties = sub_properties
+        self.__warned = 0
+        self._init()
+
+    def _init(self, cache_size=128):
+        self._cls_to_ind = dict()
+        individuals = self._ontology.individuals_in_signature()
+        self._ind_set = frozenset(individuals)
+        self._objectsomevalues_cache = LRUCache(maxsize=cache_size)
+        self._datasomevalues_cache = LRUCache(maxsize=cache_size)
+        self._objectcardinality_cache = LRUCache(maxsize=cache_size)
+        if self._property_cache:
+            self._obj_prop = dict()
+            self._obj_prop_inv = dict()
+            self._data_prop = dict()
+        else:
+            self._has_prop = MappingProxyType({
+                OWLDataProperty: LRUCache(maxsize=cache_size),
+                OWLObjectProperty: LRUCache(maxsize=cache_size),
+                OWLObjectInverseOf: LRUCache(maxsize=cache_size),
+            })
+
+    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)
+
+    def data_property_ranges(self, pe: OWLDataProperty, direct: bool = False) -> Iterable[OWLDataRange]:
+        yield from self._base_reasoner.data_property_ranges(pe, direct=direct)
+
+    def object_property_domains(self, pe: OWLObjectProperty, direct: bool = False) -> Iterable[OWLClassExpression]:
+        yield from self._base_reasoner.object_property_domains(pe, direct=direct)
+
+    def object_property_ranges(self, pe: OWLObjectProperty, direct: bool = False) -> Iterable[OWLClassExpression]:
+        yield from self._base_reasoner.object_property_ranges(pe, direct=direct)
+
+    def equivalent_classes(self, ce: OWLClassExpression, only_named: bool = True) -> Iterable[OWLClassExpression]:
+        yield from self._base_reasoner.equivalent_classes(ce, only_named=only_named)
+
+    def disjoint_classes(self, ce: OWLClassExpression, only_named: bool = True) -> Iterable[OWLClassExpression]:
+        yield from self._base_reasoner.disjoint_classes(ce, only_named=only_named)
+
+    def different_individuals(self, ce: OWLNamedIndividual) -> Iterable[OWLNamedIndividual]:
+        yield from self._base_reasoner.different_individuals(ce)
+
+    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) \
+            -> Iterable[OWLLiteral]:
+        yield from self._base_reasoner.data_property_values(ind, 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)
+
+    def object_property_values(self, ind: OWLNamedIndividual, pe: OWLObjectPropertyExpression, direct: bool = True) \
+            -> 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:
+                logger.warning("direct not implemented")
+                self.__warned |= 2
+        temp = self._find_instances(ce)
+        yield from temp
+
+    def sub_classes(self, ce: OWLClassExpression, direct: bool = False, only_named: bool = True) \
+            -> Iterable[OWLClassExpression]:
+        yield from self._base_reasoner.sub_classes(ce, direct=direct, only_named=only_named)
+
+    def super_classes(self, ce: OWLClassExpression, direct: bool = False, only_named: bool = True) \
+            -> Iterable[OWLClassExpression]:
+        yield from self._base_reasoner.super_classes(ce, direct=direct, only_named=only_named)
+
+    def types(self, ind: OWLNamedIndividual, direct: bool = False) -> Iterable[OWLClass]:
+        yield from self._base_reasoner.types(ind, direct=direct)
+
+    def equivalent_object_properties(self, dp: OWLObjectPropertyExpression) -> Iterable[OWLObjectPropertyExpression]:
+        yield from self._base_reasoner.equivalent_object_properties(dp)
+
+    def equivalent_data_properties(self, dp: OWLDataProperty) -> Iterable[OWLDataProperty]:
+        yield from self._base_reasoner.equivalent_data_properties(dp)
+
+    def disjoint_object_properties(self, dp: OWLObjectPropertyExpression) -> Iterable[OWLObjectPropertyExpression]:
+        yield from self._base_reasoner.disjoint_object_properties(dp)
+
+    def disjoint_data_properties(self, dp: OWLDataProperty) -> Iterable[OWLDataProperty]:
+        yield from self._base_reasoner.disjoint_data_properties(dp)
+
+    def sub_data_properties(self, dp: OWLDataProperty, direct: bool = False) -> Iterable[OWLDataProperty]:
+        yield from self._base_reasoner.sub_data_properties(dp=dp, direct=direct)
+
+    def super_data_properties(self, dp: OWLDataProperty, direct: bool = False) -> Iterable[OWLDataProperty]:
+        yield from self._base_reasoner.super_data_properties(dp=dp, direct=direct)
+
+    def super_object_properties(self, op: OWLObjectProperty, direct: bool = False) -> Iterable[OWLDataProperty]:
+        yield from self._base_reasoner.super_object_properties(op=op, direct=direct)
+
+    def sub_object_properties(self, op: OWLObjectPropertyExpression, direct: bool = False) \
+            -> Iterable[OWLObjectPropertyExpression]:
+        yield from self._base_reasoner.sub_object_properties(op=op, direct=direct)
+
+    def get_root_ontology(self) -> OWLOntology:
+        return self._ontology
+
+    def _lazy_cache_obj_prop(self, pe: OWLObjectPropertyExpression) -> None:
+        """Get all individuals involved in this object property and put them in a Dict."""
+        if isinstance(pe, OWLObjectInverseOf):
+            inverse = True
+            if pe.get_named_property() in self._obj_prop_inv:
+                return
+        elif isinstance(pe, OWLObjectProperty):
+            inverse = False
+            if pe in self._obj_prop:
+                return
+        else:
+            raise NotImplementedError
+
+        # Dict with Individual => Set[Individual]
+        opc: DefaultDict[OWLNamedIndividual, Set[OWLNamedIndividual]] = defaultdict(set)
+
+        # shortcut for owlready2
+        from owlapy.owl_ontology import Ontology
+        if isinstance(self._ontology, Ontology):
+            import owlready2
+            # _x => owlready2 objects
+            for l_x, r_x in self._retrieve_triples(pe):
+                if inverse:
+                    o_x = l_x
+                    s_x = r_x
+                else:
+                    s_x = l_x
+                    o_x = r_x
+                if isinstance(s_x, owlready2.Thing) and isinstance(o_x, owlready2.Thing):
+                    s = OWLNamedIndividual(IRI.create(s_x.iri))
+                    o = OWLNamedIndividual(IRI.create(o_x.iri))
+                    if s not in opc:
+                        opc[s] = set()
+                    opc[s] |= {o}
+        else:
+            for s in self._ind_set:
+                individuals = set(self._base_reasoner.object_property_values(s, pe, not self._sub_properties))
+                if individuals:
+                    opc[s] = individuals
+
+        if inverse:
+            self._obj_prop_inv[pe.get_named_property()] = MappingProxyType(opc)
+        else:
+            self._obj_prop[pe] = MappingProxyType(opc)
+
+    def _some_values_subject_index(self, pe: OWLPropertyExpression) -> FrozenSet[OWLNamedIndividual]:
+        if isinstance(pe, OWLDataProperty):
+            typ = OWLDataProperty
+        elif isinstance(pe, OWLObjectProperty):
+            typ = OWLObjectProperty
+        elif isinstance(pe, OWLObjectInverseOf):
+            typ = OWLObjectInverseOf
+        else:
+            raise NotImplementedError
+
+        if pe not in self._has_prop[typ]:
+            subs = set()
+
+            # shortcut for owlready2
+            from owlapy.owl_ontology import Ontology
+            if isinstance(self._ontology, Ontology):
+                import owlready2
+                # _x => owlready2 objects
+                for s_x, o_x in self._retrieve_triples(pe):
+                    if isinstance(pe, OWLObjectInverseOf):
+                        l_x = o_x
+                    else:
+                        l_x = s_x
+                    if isinstance(l_x, owlready2.Thing):
+                        subs |= {OWLNamedIndividual(IRI.create(l_x.iri))}
+            else:
+                if isinstance(pe, OWLDataProperty):
+                    func = self._base_reasoner.data_property_values
+                else:
+                    func = self._base_reasoner.object_property_values
+
+                for s in self._ind_set:
+                    try:
+                        next(iter(func(s, pe, not self._sub_properties)))
+                        subs |= {s}
+                    except StopIteration:
+                        pass
+
+            self._has_prop[typ][pe] = frozenset(subs)
+
+        return self._has_prop[typ][pe]
+
+    def _find_some_values(self, pe: OWLObjectPropertyExpression, filler_inds: Set[OWLNamedIndividual],
+                          min_count: int = 1, max_count: Optional[int] = None) -> FrozenSet[OWLNamedIndividual]:
+        """Get all individuals that have one of filler_inds as their object property value."""
+        ret = set()
+
+        if self._property_cache:
+            self._lazy_cache_obj_prop(pe)
+
+            if isinstance(pe, OWLObjectInverseOf):
+                ops = self._obj_prop_inv[pe.get_named_property()]
+            elif isinstance(pe, OWLObjectProperty):
+                ops = self._obj_prop[pe]
+            else:
+                raise ValueError
+
+            exists_p = min_count == 1 and max_count is None
+
+            for s, o_set in ops.items():
+                if exists_p:
+                    if o_set & filler_inds:
+                        ret |= {s}
+                else:
+                    count = len(o_set & filler_inds)
+                    if count >= min_count and (max_count is None or count <= max_count):
+                        ret |= {s}
+        else:
+            subs = self._some_values_subject_index(pe)
+
+            for s in subs:
+                count = 0
+                for o in self._base_reasoner.object_property_values(s, pe, not self._sub_properties):
+                    if {o} & filler_inds:
+                        count = count + 1
+                        if max_count is None and count >= min_count:
+                            break
+                if count >= min_count and (max_count is None or count <= max_count):
+                    ret |= {s}
+
+        return frozenset(ret)
+
+    def _lazy_cache_data_prop(self, pe: OWLDataPropertyExpression) -> None:
+        """Get all individuals and values involved in this data property and put them in a Dict."""
+        assert (isinstance(pe, OWLDataProperty))
+        if pe in self._data_prop:
+            return
+
+        opc: Dict[OWLNamedIndividual, Set[OWLLiteral]] = dict()
+
+        # shortcut for owlready2
+        from owlapy.owl_ontology  import Ontology
+        if isinstance(self._ontology, Ontology):
+            import owlready2
+            # _x => owlready2 objects
+            for s_x, o_x in self._retrieve_triples(pe):
+                if isinstance(s_x, owlready2.Thing):
+                    o_literal = OWLLiteral(o_x)
+                    s = OWLNamedIndividual(IRI.create(s_x.iri))
+                    if s not in opc:
+                        opc[s] = set()
+                    opc[s].add(o_literal)
+        else:
+            for s in self._ind_set:
+                values = set(self._base_reasoner.data_property_values(s, pe))
+                if len(values) > 0:
+                    opc[s] = values
+
+        self._data_prop[pe] = MappingProxyType(opc)
+
+    # single dispatch is still not implemented in mypy, see https://github.com/python/mypy/issues/2904
+    @singledispatchmethod
+    def _find_instances(self, ce: OWLClassExpression) -> FrozenSet[OWLNamedIndividual]:
+        raise NotImplementedError(ce)
+
+    @_find_instances.register
+    def _(self, c: OWLClass) -> FrozenSet[OWLNamedIndividual]:
+        self._lazy_cache_class(c)
+        return self._cls_to_ind[c]
+
+    @_find_instances.register
+    def _(self, ce: OWLObjectUnionOf) -> FrozenSet[OWLNamedIndividual]:
+        return reduce(operator.or_, map(self._find_instances, ce.operands()))
+
+    @_find_instances.register
+    def _(self, ce: OWLObjectIntersectionOf) -> FrozenSet[OWLNamedIndividual]:
+        return reduce(operator.and_, map(self._find_instances, ce.operands()))
+
+    @_find_instances.register
+    def _(self, ce: OWLObjectSomeValuesFrom) -> FrozenSet[OWLNamedIndividual]:
+        if ce in self._objectsomevalues_cache:
+            return self._objectsomevalues_cache[ce]
+
+        p = ce.get_property()
+        assert isinstance(p, OWLObjectPropertyExpression)
+        if not self._property_cache and ce.get_filler().is_owl_thing():
+            return self._some_values_subject_index(p)
+
+        filler_ind = self._find_instances(ce.get_filler())
+
+        ind = self._find_some_values(p, filler_ind)
+
+        self._objectsomevalues_cache[ce] = ind
+        return ind
+
+    @_find_instances.register
+    def _(self, ce: OWLObjectComplementOf) -> FrozenSet[OWLNamedIndividual]:
+        if self._negation_default:
+            all_ = self._ind_set
+            complement_ind = self._find_instances(ce.get_operand())
+            return all_ ^ complement_ind
+        else:
+            # TODO! XXX
+            if not self.__warned & 1:
+                logger.warning("Object Complement Of not implemented at %s", ce)
+                self.__warned |= 1
+            return frozenset()
+            # if self.complement_as_negation:
+            #     ...
+            # else:
+            #     self._lazy_cache_negation
+
+    @_find_instances.register
+    def _(self, ce: OWLObjectAllValuesFrom) -> FrozenSet[OWLNamedIndividual]:
+        return self._find_instances(
+            OWLObjectSomeValuesFrom(
+                property=ce.get_property(),
+                filler=ce.get_filler().get_object_complement_of().get_nnf()
+            ).get_object_complement_of())
+
+    @_find_instances.register
+    def _(self, ce: OWLObjectOneOf) -> FrozenSet[OWLNamedIndividual]:
+        return frozenset(ce.individuals())
+
+    @_find_instances.register
+    def _(self, ce: OWLObjectHasValue) -> FrozenSet[OWLNamedIndividual]:
+        return self._find_instances(ce.as_some_values_from())
+
+    @_find_instances.register
+    def _(self, ce: OWLObjectMinCardinality) -> FrozenSet[OWLNamedIndividual]:
+        return self._get_instances_object_card_restriction(ce)
+
+    @_find_instances.register
+    def _(self, ce: OWLObjectMaxCardinality) -> FrozenSet[OWLNamedIndividual]:
+        all_ = self._ind_set
+        min_ind = self._find_instances(OWLObjectMinCardinality(cardinality=ce.get_cardinality() + 1,
+                                                               property=ce.get_property(),
+                                                               filler=ce.get_filler()))
+        return all_ ^ min_ind
+
+    @_find_instances.register
+    def _(self, ce: OWLObjectExactCardinality) -> FrozenSet[OWLNamedIndividual]:
+        return self._get_instances_object_card_restriction(ce)
+
+    def _get_instances_object_card_restriction(self, ce: OWLObjectCardinalityRestriction):
+        if ce in self._objectcardinality_cache:
+            return self._objectcardinality_cache[ce]
+
+        p = ce.get_property()
+        assert isinstance(p, OWLObjectPropertyExpression)
+
+        if isinstance(ce, OWLObjectMinCardinality):
+            min_count = ce.get_cardinality()
+            max_count = None
+        elif isinstance(ce, OWLObjectExactCardinality):
+            min_count = max_count = ce.get_cardinality()
+        elif isinstance(ce, OWLObjectMaxCardinality):
+            min_count = 0
+            max_count = ce.get_cardinality()
+        else:
+            assert isinstance(ce, OWLObjectCardinalityRestriction)
+            raise NotImplementedError
+        assert min_count >= 0
+        assert max_count is None or max_count >= 0
+
+        filler_ind = self._find_instances(ce.get_filler())
+
+        ind = self._find_some_values(p, filler_ind, min_count=min_count, max_count=max_count)
+
+        self._objectcardinality_cache[ce] = ind
+        return ind
+
+    @_find_instances.register
+    def _(self, ce: OWLDataSomeValuesFrom) -> FrozenSet[OWLNamedIndividual]:
+        if ce in self._datasomevalues_cache:
+            return self._datasomevalues_cache[ce]
+
+        pe = ce.get_property()
+        filler = ce.get_filler()
+        assert isinstance(pe, OWLDataProperty)
+        #
+
+        property_cache = self._property_cache
+
+        if property_cache:
+            self._lazy_cache_data_prop(pe)
+            dps = self._data_prop[pe]
+        else:
+            subs = self._some_values_subject_index(pe)
+
+        ind = set()
+
+        if isinstance(filler, OWLDatatype):
+            if property_cache:
+                # TODO: Currently we just assume that the values are of the given type (also done in DLLearner)
+                for s in dps.keys():
+                    ind |= {s}
+            else:
+                for s in subs:
+                    for lit in self._base_reasoner.data_property_values(s, pe):
+                        if lit.get_datatype() == filler:
+                            ind |= {s}
+                            break
+        elif isinstance(filler, OWLDataOneOf):
+            values = set(filler.values())
+            if property_cache:
+                for s, literals in dps.items():
+                    if literals & values:
+                        ind |= {s}
+            else:
+                for s in subs:
+                    for lit in self._base_reasoner.data_property_values(s, pe):
+                        if lit in values:
+                            ind |= {s}
+                            break
+        elif isinstance(filler, OWLDataComplementOf):
+            temp = self._find_instances(
+                OWLDataSomeValuesFrom(property=pe, filler=filler.get_data_range()))
+            if property_cache:
+                subs = set()
+                for s in dps.keys():
+                    subs |= {s}
+
+            ind = subs.difference(temp)
+        elif isinstance(filler, OWLDataUnionOf):
+            operands = [OWLDataSomeValuesFrom(pe, op) for op in filler.operands()]
+            ind = reduce(operator.or_, map(self._find_instances, operands))
+        elif isinstance(filler, OWLDataIntersectionOf):
+            operands = [OWLDataSomeValuesFrom(pe, op) for op in filler.operands()]
+            ind = reduce(operator.and_, map(self._find_instances, operands))
+        elif isinstance(filler, OWLDatatypeRestriction):
+            def res_to_callable(res: OWLFacetRestriction):
+                op = res.get_facet().operator
+                v = res.get_facet_value()
+
+                def inner(lv: OWLLiteral):
+                    return op(lv, v)
+
+                return inner
+
+            apply = FunctionType.__call__
+
+            facet_restrictions = tuple(map(res_to_callable, filler.get_facet_restrictions()))
+
+            def include(lv: OWLLiteral):
+                return lv.get_datatype() == filler.get_datatype() and \
+                       all(map(apply, facet_restrictions, repeat(lv)))
+
+            if property_cache:
+                for s, literals in dps.items():
+                    for lit in literals:
+                        if include(lit):
+                            ind |= {s}
+                            break
+            else:
+                for s in subs:
+                    for lit in self._base_reasoner.data_property_values(s, pe):
+                        if include(lit):
+                            ind |= {s}
+                            break
+        else:
+            raise ValueError
+
+        r = frozenset(ind)
+        self._datasomevalues_cache[ce] = r
+        return r
+
+    @_find_instances.register
+    def _(self, ce: OWLDataAllValuesFrom) -> FrozenSet[OWLNamedIndividual]:
+        filler = ce.get_filler()
+        if isinstance(filler, OWLDataComplementOf):
+            filler = filler.get_data_range()
+        else:
+            filler = OWLDataComplementOf(filler)
+        return self._find_instances(
+            OWLDataSomeValuesFrom(
+                property=ce.get_property(),
+                filler=filler
+            ).get_object_complement_of())
+
+    @_find_instances.register
+    def _(self, ce: OWLDataHasValue) -> FrozenSet[OWLNamedIndividual]:
+        return self._find_instances(ce.as_some_values_from())
+
+    def _lazy_cache_class(self, c: OWLClass) -> None:
+        if c in self._cls_to_ind:
+            return
+        temp = self._base_reasoner.instances(c)
+        self._cls_to_ind[c] = frozenset(temp)
+
+    def _retrieve_triples(self, pe: OWLPropertyExpression) -> Iterable:
+        """Retrieve all subject/object pairs for the given property."""
+
+        if isinstance(pe, OWLObjectPropertyExpression):
+            retrieval_func = self.sub_object_properties
+            p_x: owlready2.ObjectProperty = self._ontology._world[pe.get_named_property().str]
+        else:
+            retrieval_func = self.sub_data_properties
+            p_x: owlready2.DataProperty = self._ontology._world[pe.str]
+
+        relations = p_x.get_relations()
+        if self._sub_properties:
+            # Retrieve the subject/object pairs for all sub properties of pe
+            indirect_relations = chain.from_iterable(
+                map(lambda x: self._ontology._world[x.str].get_relations(),
+                    retrieval_func(pe, direct=False)))
+            # If pe is an OWLObjectInverseOf we need to swap the pairs
+            if isinstance(pe, OWLObjectInverseOf):
+                indirect_relations = ((r[1], r[0]) for r in indirect_relations)
+            relations = chain(relations, indirect_relations)
+        yield from relations
+
+
+class SyncReasoner(OntologyReasoner):
+    __slots__ = '_cnt', '_conv', '_base_reasoner'
+
+    _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):
+        """
+        OWL Reasoner with support for Complex Class Expression Instances + sync_reasoner.
+
+        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.
+        """
+
+        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)
+
+        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 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}")
diff --git a/owlapy/owlapi_adaptor.py b/owlapy/owlapi_adaptor.py
new file mode 100644
index 00000000..78299eeb
--- /dev/null
+++ b/owlapy/owlapi_adaptor.py
@@ -0,0 +1,166 @@
+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_individual import OWLNamedIndividual
+from owlapy.render import owl_expression_to_manchester
+from typing import List
+
+
+class OWLAPIAdaptor:
+    """
+    A class to interface with the OWL API using the HermiT reasoner, enabling ontology management,
+    reasoning, and parsing class expressions in Manchester OWL Syntax.
+
+    Attributes:
+        path (str): The file path to the ontology.
+        name_reasoner (str): The reasoner to be used, default is "HermiT".
+        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.
+
+        Args:
+            path (str): The file path to the ontology.
+            name_reasoner (str, optional): The reasoner to be used.
+                    Available options are: ['HermiT' (default), 'Pellet', 'JFact', 'Openllet'].
+
+        Raises:
+            AssertionError: If the provided reasoner name is not implemented.
+        """
+        self.path = path
+        assert name_reasoner in ["HermiT", "Pellet", "JFact", "Openllet"], \
+            f"'{name_reasoner}' is not implemented. Available reasoners: ['HermiT', 'Pellet', 'JFact', 'Openllet']"
+        self.name_reasoner = name_reasoner
+        # Attributes are initialized as JVM is started
+        # () Manager is needed to load an ontology
+        self.manager = None
+        # () Load a local ontology using the manager
+        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
+        # () Set up the necessary attributes by making use of the java packages
+        self._setup()
+
+    def _startJVM(self):
+        """Start the JVM with jar dependencies. This method is called automatically on object initialization, if the
+        JVM is not started yet."""
+        # Start a java virtual machine using the dependencies in the respective folder:
+        jar_folder = pkg_resources.resource_filename('owlapy', 'jar_dependencies')
+        jar_files = [os.path.join(jar_folder, f) for f in os.listdir(jar_folder) if f.endswith('.jar')]
+        # Starting JVM.
+        jpype.startJVM(classpath=jar_files)
+
+    def stopJVM(self, *args, **kwargs) -> None:
+        """Detaches the thread from Java packages and shuts down the java virtual machine hosted by jpype."""
+        if jpype.isJVMStarted():
+            jpype.detachThreadFromJVM()
+            jpype.shutdownJVM()
+
+    def _setup(self):
+        """
+        Start the JVM if not already, import necessary OWL API dependencies, and initialize attributes.
+        """
+        if not jpype.isJVMStarted():
+            self._startJVM()
+
+        # Import Java classes
+        from org.semanticweb.owlapi.apibinding import OWLManager
+        from java.io import File
+        if self.name_reasoner == "HermiT":
+            from org.semanticweb.HermiT import ReasonerFactory
+        elif self.name_reasoner == "Pellet":
+            from openllet.owlapi import PelletReasonerFactory
+        elif self.name_reasoner == "JFact":
+            from uk.ac.manchester.cs.jfact import JFactFactory
+        elif self.name_reasoner == "Openllet":
+            from openllet.owlapi import OpenlletReasonerFactory
+        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))
+        # () Create a reasoner for the loaded ontology
+        if self.name_reasoner == "HermiT":
+            self.reasoner = ReasonerFactory().createReasoner(self.ontology)
+        elif self.name_reasoner == "JFact":
+            self.reasoner = JFactFactory().createReasoner(self.ontology)
+        elif self.name_reasoner == "Pellet":
+            self.reasoner = PelletReasonerFactory().createReasoner(self.ontology)
+        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 convert_to_owlapi(self, ce: OWLClassExpression):
+        """
+        Converts an OWLAPY class expression to an OWLAPI class expression.
+
+        Args:
+            ce (OWLClassExpression): The class expression in OWLAPY format to be converted.
+
+        Returns:
+            The class expression in OWLAPI format.
+        """
+        return self.parser.parse(owl_expression_to_manchester(ce))
+
+    def convert_from_owlapi(self, ce, namespace: str) -> OWLClassExpression:
+        """
+        Converts an OWLAPI class expression to an OWLAPY class expression.
+
+        Args:
+            ce: The class expression in OWLAPI format.
+            namespace (str): The ontology's namespace where the class expression belongs.
+
+        Returns:
+            OWLClassExpression: The class expression in OWLAPY format.
+        """
+        return manchester_to_owl_expression(str(self.renderer.render(ce)), namespace)
+
+    def instances(self, ce: OWLClassExpression)->List[OWLNamedIndividual]:
+        """
+        Get the instances for a given class expression using HermiT.
+
+        Args:
+            ce (OWLClassExpression): The class expression in OWLAPY format.
+
+        Returns:
+            list: A list of individuals classified by 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]
+
+    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()
diff --git a/owlapy/static_funcs.py b/owlapy/static_funcs.py
new file mode 100644
index 00000000..4fd0a764
--- /dev/null
+++ b/owlapy/static_funcs.py
@@ -0,0 +1,39 @@
+"""Static functions for general purposes."""
+import os
+import subprocess
+import platform
+import shutil
+
+# NOTE: Static functions closely related with owl classes should be placed in utils.py not here
+
+
+def move(*args):
+    """"Move" an imported class to the current module by setting the classes __module__ attribute.
+
+    This is useful for documentation purposes to hide internal packages in sphinx.
+
+    Args:
+        args: List of classes to move.
+    """
+    from inspect import currentframe
+    f = currentframe()
+    f = f.f_back
+    mod = f.f_globals['__name__']
+    for cls in args:
+        cls.__module__ = mod
+
+
+def download_external_files(ftp_link: str):
+
+    file_name = ftp_link.split("/")[-1]
+    root_dir = os.path.abspath(os.path.join(os.getcwd(), '..'))
+    current_dir = os.path.join(os.getcwd(), file_name[:-4])
+    if not os.path.exists(os.path.join(root_dir, file_name[:-4])):
+        subprocess.run(['curl', '-O', ftp_link])
+
+        if platform.system() == "Windows":
+            subprocess.run(['tar', '-xf', file_name])
+        else:
+            subprocess.run(['unzip', file_name])
+        os.remove(os.path.join(os.getcwd(), file_name))
+        shutil.move(current_dir, root_dir)
diff --git a/owlapy/util.py b/owlapy/util.py
deleted file mode 100644
index d72023ec..00000000
--- a/owlapy/util.py
+++ /dev/null
@@ -1,555 +0,0 @@
-"""Owlapy utils."""
-from functools import singledispatchmethod, total_ordering
-from typing import Iterable, List, Type, TypeVar, Generic, Tuple, cast, Optional, Union, overload, Protocol, ClassVar
-from .meta_classes import HasIRI, HasFiller, HasCardinality, HasOperands
-from .owl_literal import OWLLiteral
-from .owl_property import OWLObjectInverseOf
-from owlapy.class_expression import OWLClassExpression, OWLClass, OWLObjectCardinalityRestriction, \
-    OWLObjectComplementOf, OWLNothing, OWLRestriction, OWLThing, OWLObjectSomeValuesFrom, \
-    OWLObjectMinCardinality, OWLObjectMaxCardinality, OWLObjectExactCardinality, OWLObjectHasSelf, \
-    OWLDataMaxCardinality, OWLDataMinCardinality, OWLDataExactCardinality, OWLDataHasValue, \
-    OWLDataAllValuesFrom, OWLDataSomeValuesFrom, OWLObjectAllValuesFrom, \
-    OWLDataOneOf, OWLObjectIntersectionOf, \
-    OWLDataCardinalityRestriction, OWLNaryBooleanClassExpression, OWLObjectUnionOf, \
-    OWLObjectHasValue, OWLDatatypeRestriction, OWLFacetRestriction, OWLObjectOneOf
-from .owl_data_ranges import OWLDataComplementOf, OWLDataUnionOf, OWLDataIntersectionOf, OWLNaryDataRange, OWLDataRange, \
-    OWLPropertyRange
-from .owl_object import OWLObject
-from .owl_datatype import OWLDatatype
-
-
-class HasIndex(Protocol):
-    """Interface for types with an index; this is used to group objects by type when sorting."""
-    type_index: ClassVar[int]  #: index for this type. This is a sorting index for the types.
-
-    def __eq__(self, other): ...
-
-
-_HasIRI = TypeVar('_HasIRI', bound=HasIRI)  #:
-_HasIndex = TypeVar('_HasIndex', bound=HasIndex)  #:
-_O = TypeVar('_O')  #:
-_Enc = TypeVar('_Enc')
-_Con = TypeVar('_Con')
-_K = TypeVar('_K')
-_V = TypeVar('_V')
-
-
-@total_ordering
-class OrderedOWLObject:
-    """Holder of OWL Objects that can be used for Python sorted.
-
-    The Ordering is dependent on the type_index of the impl. classes recursively followed by all components of the
-    OWL Object.
-
-    Attributes:
-        o: OWL object.
-    """
-    __slots__ = 'o', '_chain'
-
-    o: _HasIndex  # o: Intersection[OWLObject, HasIndex]
-    _chain: Optional[Tuple]
-
-    # we are limited by https://github.com/python/typing/issues/213 # o: Intersection[OWLObject, HasIndex]
-    def __init__(self, o: _HasIndex):
-        """OWL Object holder with a defined sort order.
-
-        Args:
-            o: OWL Object.
-        """
-        self.o = o
-        self._chain = None
-
-    def _comparison_chain(self):
-        if self._chain is None:
-            c = [self.o.type_index]
-
-            if isinstance(self.o, OWLRestriction):
-                c.append(OrderedOWLObject(as_index(self.o.get_property())))
-            if isinstance(self.o, OWLObjectInverseOf):
-                c.append(self.o.get_named_property().str)
-            if isinstance(self.o, HasFiller):
-                c.append(OrderedOWLObject(self.o.get_filler()))
-            if isinstance(self.o, HasCardinality):
-                c.append(self.o.get_cardinality())
-            if isinstance(self.o, HasOperands):
-                c.append(tuple(map(OrderedOWLObject, self.o.operands())))
-            if isinstance(self.o, HasIRI):
-                c.append(self.o.str)
-            if isinstance(self.o, OWLDataComplementOf):
-                c.append(OrderedOWLObject(self.o.get_data_range()))
-            if isinstance(self.o, OWLDatatypeRestriction):
-                c.append((OrderedOWLObject(self.o.get_datatype()),
-                          tuple(map(OrderedOWLObject, self.o.get_facet_restrictions()))))
-            if isinstance(self.o, OWLFacetRestriction):
-                c.append((self.o.get_facet().str, self.o.get_facet_value().get_literal()))
-            if isinstance(self.o, OWLLiteral):
-                c.append(self.o.get_literal())
-            if len(c) == 1:
-                raise NotImplementedError(type(self.o))
-
-            self._chain = tuple(c)
-
-        return self._chain
-
-    def __lt__(self, other):
-        if self.o.type_index < other.o.type_index:
-            return True
-        elif self.o.type_index > other.o.type_index:
-            return False
-        else:
-            return self._comparison_chain() < other._comparison_chain()
-
-    def __eq__(self, other):
-        return self.o == other.o
-
-
-def _sort_by_ordered_owl_object(i: Iterable[_O]) -> Iterable[_O]:
-    return sorted(i, key=OrderedOWLObject)
-
-
-class NNF:
-    """This class contains functions to transform a Class Expression into Negation Normal Form."""
-    @singledispatchmethod
-    def get_class_nnf(self, ce: OWLClassExpression, negated: bool = False) -> OWLClassExpression:
-        """Convert a Class Expression to Negation Normal Form. Operands will be sorted.
-
-        Args:
-            ce: Class Expression.
-            negated: Whether the result should be negated.
-
-        Returns:
-            Class Expression in Negation Normal Form.
-            """
-        raise NotImplementedError
-
-    @get_class_nnf.register
-    def _(self, ce: OWLClass, negated: bool = False):
-        if negated:
-            if ce.is_owl_thing():
-                return OWLNothing
-            if ce.is_owl_nothing():
-                return OWLThing
-            return OWLObjectComplementOf(ce)
-        return ce
-
-    @get_class_nnf.register
-    def _(self, ce: OWLObjectIntersectionOf, negated: bool = False):
-        ops = map(lambda _: self.get_class_nnf(_, negated),
-                  _sort_by_ordered_owl_object(ce.operands()))
-        if negated:
-            return OWLObjectUnionOf(ops)
-        return OWLObjectIntersectionOf(ops)
-
-    @get_class_nnf.register
-    def _(self, ce: OWLObjectUnionOf, negated: bool = False):
-        ops = map(lambda _: self.get_class_nnf(_, negated),
-                  _sort_by_ordered_owl_object(ce.operands()))
-        if negated:
-            return OWLObjectIntersectionOf(ops)
-        return OWLObjectUnionOf(ops)
-
-    @get_class_nnf.register
-    def _(self, ce: OWLObjectComplementOf, negated: bool = False):
-        return self.get_class_nnf(ce.get_operand(), not negated)
-
-    @get_class_nnf.register
-    def _(self, ce: OWLObjectSomeValuesFrom, negated: bool = False):
-        filler = self.get_class_nnf(ce.get_filler(), negated)
-        if negated:
-            return OWLObjectAllValuesFrom(ce.get_property(), filler)
-        return OWLObjectSomeValuesFrom(ce.get_property(), filler)
-
-    @get_class_nnf.register
-    def _(self, ce: OWLObjectAllValuesFrom, negated: bool = False):
-        filler = self.get_class_nnf(ce.get_filler(), negated)
-        if negated:
-            return OWLObjectSomeValuesFrom(ce.get_property(), filler)
-        return OWLObjectAllValuesFrom(ce.get_property(), filler)
-
-    @get_class_nnf.register
-    def _(self, ce: OWLObjectHasValue, negated: bool = False):
-        return self.get_class_nnf(ce.as_some_values_from(), negated)
-
-    @get_class_nnf.register
-    def _(self, ce: OWLObjectMinCardinality, negated: bool = False):
-        card = ce.get_cardinality()
-        if negated:
-            card = max(0, card - 1)
-        filler = self.get_class_nnf(ce.get_filler(), negated=False)
-        if negated:
-            return OWLObjectMaxCardinality(card, ce.get_property(), filler)
-        return OWLObjectMinCardinality(card, ce.get_property(), filler)
-
-    @get_class_nnf.register
-    def _(self, ce: OWLObjectExactCardinality, negated: bool = False):
-        return self.get_class_nnf(ce.as_intersection_of_min_max(), negated)
-
-    @get_class_nnf.register
-    def _(self, ce: OWLObjectMaxCardinality, negated: bool = False):
-        card = ce.get_cardinality()
-        if negated:
-            card = card + 1
-        filler = self.get_class_nnf(ce.get_filler(), negated=False)
-        if negated:
-            return OWLObjectMinCardinality(card, ce.get_property(), filler)
-        return OWLObjectMaxCardinality(card, ce.get_property(), filler)
-
-    @get_class_nnf.register
-    def _(self, ce: OWLObjectHasSelf, negated: bool = False):
-        if negated:
-            return ce.get_object_complement_of()
-        return ce
-
-    @get_class_nnf.register
-    def _(self, ce: OWLObjectOneOf, negated: bool = False):
-        union = ce.as_object_union_of()
-        if isinstance(union, OWLObjectOneOf):
-            if negated:
-                return ce.get_object_complement_of()
-            return ce
-        return self.get_class_nnf(union, negated)
-
-    @get_class_nnf.register
-    def _(self, ce: OWLDataSomeValuesFrom, negated: bool = False):
-        filler = self.get_class_nnf(ce.get_filler(), negated)
-        if negated:
-            return OWLDataAllValuesFrom(ce.get_property(), filler)
-        return OWLDataSomeValuesFrom(ce.get_property(), filler)
-
-    @get_class_nnf.register
-    def _(self, ce: OWLDataAllValuesFrom, negated: bool = False):
-        filler = self.get_class_nnf(ce.get_filler(), negated)
-        if negated:
-            return OWLDataSomeValuesFrom(ce.get_property(), filler)
-        return OWLDataAllValuesFrom(ce.get_property(), filler)
-
-    @get_class_nnf.register
-    def _(self, ce: OWLDatatypeRestriction, negated: bool = False):
-        if negated:
-            return OWLDataComplementOf(ce)
-        return ce
-
-    @get_class_nnf.register
-    def _(self, ce: OWLDatatype, negated: bool = False):
-        if negated:
-            return OWLDataComplementOf(ce)
-        return ce
-
-    @get_class_nnf.register
-    def _(self, ce: OWLDataComplementOf, negated: bool = False):
-        return self.get_class_nnf(ce.get_data_range(), not negated)
-
-    @get_class_nnf.register
-    def _(self, ce: OWLDataHasValue, negated: bool = False):
-        return self.get_class_nnf(ce.as_some_values_from(), negated)
-
-    @get_class_nnf.register
-    def _(self, ce: OWLDataOneOf, negated: bool = False):
-        if len(list(ce.values())) == 1:
-            if negated:
-                return OWLDataComplementOf(ce)
-            return ce
-        union = OWLDataUnionOf([OWLDataOneOf(v) for v in ce.values()])
-        return self.get_class_nnf(union, negated)
-
-    @get_class_nnf.register
-    def _(self, ce: OWLDataIntersectionOf, negated: bool = False):
-        ops = map(lambda _: self.get_class_nnf(_, negated),
-                  _sort_by_ordered_owl_object(ce.operands()))
-        if negated:
-            return OWLDataUnionOf(ops)
-        return OWLDataIntersectionOf(ops)
-
-    @get_class_nnf.register
-    def _(self, ce: OWLDataUnionOf, negated: bool = False):
-        ops = map(lambda _: self.get_class_nnf(_, negated),
-                  _sort_by_ordered_owl_object(ce.operands()))
-        if negated:
-            return OWLDataIntersectionOf(ops)
-        return OWLDataUnionOf(ops)
-
-    @get_class_nnf.register
-    def _(self, ce: OWLDataExactCardinality, negated: bool = False):
-        return self.get_class_nnf(ce.as_intersection_of_min_max(), negated)
-
-    @get_class_nnf.register
-    def _(self, ce: OWLDataMinCardinality, negated: bool = False):
-        card = ce.get_cardinality()
-        if negated:
-            card = max(0, card - 1)
-        filler = self.get_class_nnf(ce.get_filler(), negated=False)
-        if negated:
-            return OWLDataMaxCardinality(card, ce.get_property(), filler)
-        return OWLDataMinCardinality(card, ce.get_property(), filler)
-
-    @get_class_nnf.register
-    def _(self, ce: OWLDataMaxCardinality, negated: bool = False):
-        card = ce.get_cardinality()
-        if negated:
-            card = card + 1
-        filler = self.get_class_nnf(ce.get_filler(), negated=False)
-        if negated:
-            return OWLDataMinCardinality(card, ce.get_property(), filler)
-        return OWLDataMaxCardinality(card, ce.get_property(), filler)
-
-
-# OWL-APy custom util start
-
-class TopLevelCNF:
-    """This class contains functions to transform a class expression into Top-Level Conjunctive Normal Form."""
-
-    def get_top_level_cnf(self, ce: OWLClassExpression) -> OWLClassExpression:
-        """Convert a class expression into Top-Level Conjunctive Normal Form. Operands will be sorted.
-
-        Args:
-            ce: Class Expression.
-
-        Returns:
-            Class Expression in Top-Level Conjunctive Normal Form.
-            """
-        c = _get_top_level_form(ce.get_nnf(), OWLObjectUnionOf, OWLObjectIntersectionOf)
-        return combine_nary_expressions(c)
-
-
-class TopLevelDNF:
-    """This class contains functions to transform a class expression into Top-Level Disjunctive Normal Form."""
-
-    def get_top_level_dnf(self, ce: OWLClassExpression) -> OWLClassExpression:
-        """Convert a class expression into Top-Level Disjunctive Normal Form. Operands will be sorted.
-
-        Args:
-            ce: Class Expression.
-
-        Returns:
-            Class Expression in Top-Level Disjunctive Normal Form.
-            """
-        c = _get_top_level_form(ce.get_nnf(), OWLObjectIntersectionOf, OWLObjectUnionOf)
-        return combine_nary_expressions(c)
-
-
-def _get_top_level_form(ce: OWLClassExpression,
-                        type_a: Type[OWLNaryBooleanClassExpression],
-                        type_b: Type[OWLNaryBooleanClassExpression]) -> OWLClassExpression:
-    """ Transforms a class expression (that's already in NNF) into Top-Level Conjunctive/Disjunctive Normal Form.
-    Here type_a specifies the operand which should be distributed inwards over type_b.
-
-    Conjunctive Normal form:
-        type_a = OWLObjectUnionOf
-        type_b = OWLObjectIntersectionOf
-    Disjunctive Normal form:
-        type_a = OWLObjectIntersectionOf
-        type_b = OWLObjectUnionOf
-    """
-
-    def distributive_law(a: OWLClassExpression, b: OWLNaryBooleanClassExpression) -> OWLNaryBooleanClassExpression:
-        return type_b(type_a([a, op]) for op in b.operands())
-
-    if isinstance(ce, type_a):
-        ce = cast(OWLNaryBooleanClassExpression, combine_nary_expressions(ce))
-        type_b_exprs = [op for op in ce.operands() if isinstance(op, type_b)]
-        non_type_b_exprs = [op for op in ce.operands() if not isinstance(op, type_b)]
-        if not len(type_b_exprs):
-            return ce
-
-        if len(non_type_b_exprs):
-            expr = non_type_b_exprs[0] if len(non_type_b_exprs) == 1 \
-                else type_a(non_type_b_exprs)
-            expr = distributive_law(expr, type_b_exprs[0])
-        else:
-            expr = type_b_exprs[0]
-
-        if len(type_b_exprs) == 1:
-            return _get_top_level_form(expr, type_a, type_b)
-
-        for type_b_expr in type_b_exprs[1:]:
-            expr = distributive_law(type_b_expr, expr)
-        return _get_top_level_form(expr, type_a, type_b)
-    elif isinstance(ce, type_b):
-        return type_b(_get_top_level_form(op, type_a, type_b) for op in ce.operands())
-    elif isinstance(ce, OWLClassExpression):
-        return ce
-    else:
-        raise ValueError('Top-Level CNF/DNF only applicable on class expressions', ce)
-
-
-@overload
-def combine_nary_expressions(ce: OWLClassExpression) -> OWLClassExpression:
-    ...
-
-
-@overload
-def combine_nary_expressions(ce: OWLDataRange) -> OWLDataRange:
-    ...
-
-
-def combine_nary_expressions(ce: OWLPropertyRange) -> OWLPropertyRange:
-    """ Shortens an OWLClassExpression or OWLDataRange by combining all nested nary expressions of the same type.
-    Operands will be sorted.
-
-    E.g. OWLObjectUnionOf(A, OWLObjectUnionOf(C, B)) -> OWLObjectUnionOf(A, B, C).
-    """
-    if isinstance(ce, (OWLNaryBooleanClassExpression, OWLNaryDataRange)):
-        expressions: List[OWLPropertyRange] = []
-        for op in ce.operands():
-            expr = combine_nary_expressions(op)
-            if type(expr) is type(ce):
-                expr = cast(Union[OWLNaryBooleanClassExpression, OWLNaryDataRange], expr)
-                expressions.extend(expr.operands())
-            else:
-                expressions.append(expr)
-        return type(ce)(_sort_by_ordered_owl_object(expressions))  # type: ignore
-    elif isinstance(ce, OWLObjectComplementOf):
-        return OWLObjectComplementOf(combine_nary_expressions(ce.get_operand()))
-    elif isinstance(ce, OWLDataComplementOf):
-        return OWLDataComplementOf(combine_nary_expressions(ce.get_data_range()))
-    elif isinstance(ce, OWLObjectCardinalityRestriction):
-        return type(ce)(ce.get_cardinality(), ce.get_property(), combine_nary_expressions(ce.get_filler()))
-    elif isinstance(ce, OWLDataCardinalityRestriction):
-        return type(ce)(ce.get_cardinality(), ce.get_property(), combine_nary_expressions(ce.get_filler()))
-    elif isinstance(ce, (OWLObjectSomeValuesFrom, OWLObjectAllValuesFrom)):
-        return type(ce)(ce.get_property(), combine_nary_expressions(ce.get_filler()))
-    elif isinstance(ce, (OWLDataSomeValuesFrom, OWLDataAllValuesFrom)):
-        return type(ce)(ce.get_property(), combine_nary_expressions(ce.get_filler()))
-    elif isinstance(ce, OWLObjectOneOf):
-        return OWLObjectOneOf(_sort_by_ordered_owl_object(ce.operands()))
-    elif isinstance(ce, OWLDataOneOf):
-        return OWLDataOneOf(_sort_by_ordered_owl_object(ce.operands()))
-    elif isinstance(ce, OWLPropertyRange):
-        return ce
-    else:
-        raise ValueError(f'({ce}) is not an OWLObject.')
-
-
-def iter_count(i: Iterable) -> int:
-    """Count the number of elements in an iterable."""
-    return sum(1 for _ in i)
-
-
-def as_index(o: OWLObject) -> HasIndex:
-    """Cast OWL Object to HasIndex."""
-    i = cast(HasIndex, o)
-    assert type(i).type_index
-    return i
-
-
-class LRUCache(Generic[_K, _V]):
-    """Constants shares by all lru cache instances.
-
-    Adapted from functools.lru_cache.
-
-    Attributes:
-        sentinel: Unique object used to signal cache misses.
-        PREV: Name for the link field 0.
-        NEXT: Name for the link field 1.
-        KEY: Name for the link field 2.
-        RESULT: Name for the link field 3.
-    """
-
-    sentinel = object()
-    PREV, NEXT, KEY, RESULT = 0, 1, 2, 3  # names for the link fields
-
-    def __init__(self, maxsize: Optional[int] = None):
-        from _thread import RLock
-
-        self.cache = {}
-        self.hits = self.misses = 0
-        self.full = False
-        self.cache_get = self.cache.get  # bound method to lookup a key or return None
-        self.cache_len = self.cache.__len__  # get cache size without calling len()
-        self.lock = RLock()  # because linkedlist updates aren't threadsafe
-        self.root = []  # root of the circular doubly linked list
-        self.root[:] = [self.root, self.root, None, None]  # initialize by pointing to self
-        self.maxsize = maxsize
-
-    def __contains__(self, item: _K) -> bool:
-        with self.lock:
-            link = self.cache_get(item)
-            if link is not None:
-                self.hits += 1
-                return True
-            self.misses += 1
-            return False
-
-    def __getitem__(self, item: _K) -> _V:
-        with self.lock:
-            link = self.cache_get(item)
-            if link is not None:
-                # Move the link to the front of the circular queue
-                link_prev, link_next, _key, result = link
-                link_prev[LRUCache.NEXT] = link_next
-                link_next[LRUCache.PREV] = link_prev
-                last = self.root[LRUCache.PREV]
-                last[LRUCache.NEXT] = self.root[LRUCache.PREV] = link
-                link[LRUCache.PREV] = last
-                link[LRUCache.NEXT] = self.root
-                return result
-
-    def __setitem__(self, key: _K, value: _V):
-        with self.lock:
-            if key in self.cache:
-                # Getting here means that this same key was added to the
-                # cache while the lock was released.  Since the link
-                # update is already done, we need only return the
-                # computed result and update the count of misses.
-                pass
-            elif self.full:
-                # Use the old root to store the new key and result.
-                oldroot = self.root
-                oldroot[LRUCache.KEY] = key
-                oldroot[LRUCache.RESULT] = value
-                # Empty the oldest link and make it the new root.
-                # Keep a reference to the old key and old result to
-                # prevent their ref counts from going to zero during the
-                # update. That will prevent potentially arbitrary object
-                # clean-up code (i.e. __del__) from running while we're
-                # still adjusting the links.
-                self.root = oldroot[LRUCache.NEXT]
-                oldkey = self.root[LRUCache.KEY]
-                _oldresult = self.root[LRUCache.RESULT]  # noqa: F841
-                self.root[LRUCache.KEY] = self.root[LRUCache.RESULT] = None
-                # Now update the cache dictionary.
-                del self.cache[oldkey]
-                # Save the potentially reentrant cache[key] assignment
-                # for last, after the root and links have been put in
-                # a consistent state.
-                self.cache[key] = oldroot
-            else:
-                # Put result in a new link at the front of the queue.
-                last = self.root[LRUCache.PREV]
-                link = [last, self.root, key, value]
-                last[LRUCache.NEXT] = self.root[LRUCache.PREV] = self.cache[key] = link
-                # Use the cache_len bound method instead of the len() function
-                # which could potentially be wrapped in an lru_cache itself.
-                if self.maxsize is not None:
-                    self.full = (self.cache_len() >= self.maxsize)
-
-    def cache_info(self):
-        """Report cache statistics."""
-        with self.lock:
-            from collections import namedtuple
-            return namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])(
-                self.hits, self.misses, self.maxsize, self.cache_len())
-
-    def cache_clear(self):
-        """Clear the cache and cache statistics."""
-        with self.lock:
-            self.cache.clear()
-            self.root[:] = [self.root, self.root, None, None]
-            self.hits = self.misses = 0
-            self.full = False
-
-
-def move(*args):
-    """"Move" an imported class to the current module by setting the classes __module__ attribute.
-
-    This is useful for documentation purposes to hide internal packages in sphinx.
-
-    Args:
-        args: List of classes to move.
-    """
-    from inspect import currentframe
-    f = currentframe()
-    f = f.f_back
-    mod = f.f_globals['__name__']
-    for cls in args:
-        cls.__module__ = mod
diff --git a/owlapy/utils.py b/owlapy/utils.py
new file mode 100644
index 00000000..945da589
--- /dev/null
+++ b/owlapy/utils.py
@@ -0,0 +1,1206 @@
+"""Owlapy utils."""
+from collections import Counter
+from owlapy.owl_individual import OWLNamedIndividual
+from sortedcontainers import SortedSet
+from functools import singledispatchmethod, total_ordering
+from typing import Iterable, List, Type,Callable, TypeVar, Generic, Tuple, cast, Optional, Union, overload, Protocol, ClassVar
+from .meta_classes import HasIRI, HasFiller, HasCardinality, HasOperands
+from .owl_literal import OWLLiteral
+from .owl_property import OWLObjectInverseOf, OWLObjectProperty, OWLDataProperty
+from owlapy.class_expression import OWLClassExpression, OWLClass, OWLObjectCardinalityRestriction, \
+    OWLObjectComplementOf, OWLNothing, OWLRestriction, OWLThing, OWLObjectSomeValuesFrom, \
+    OWLObjectMinCardinality, OWLObjectMaxCardinality, OWLObjectExactCardinality, OWLObjectHasSelf, \
+    OWLDataMaxCardinality, OWLDataMinCardinality, OWLDataExactCardinality, OWLDataHasValue, \
+    OWLDataAllValuesFrom, OWLDataSomeValuesFrom, OWLObjectAllValuesFrom, \
+    OWLDataOneOf, OWLObjectIntersectionOf, \
+    OWLDataCardinalityRestriction, OWLNaryBooleanClassExpression, OWLObjectUnionOf, \
+    OWLObjectHasValue, OWLDatatypeRestriction, OWLFacetRestriction, OWLObjectOneOf
+from .owl_data_ranges import OWLDataComplementOf, OWLDataUnionOf, OWLDataIntersectionOf, OWLNaryDataRange, OWLDataRange, \
+    OWLPropertyRange
+from .owl_object import OWLObject
+from .owl_datatype import OWLDatatype
+
+
+class OWLClassExpressionLengthMetric:
+    """Length calculation of OWLClassExpression
+
+    Args:
+        class_length: Class: "C"
+        object_intersection_length: Intersection: A ⨅ B
+        object_union_length: Union: A ⨆ B
+        object_complement_length: Complement: ¬ C
+        object_some_values_length: Obj. Some Values: ∃ r.C
+        object_all_values_length: Obj. All Values: ∀ r.C
+        object_has_value_length: Obj. Has Value: ∃ r.{I}
+        object_cardinality_length: Obj. Cardinality restriction: ≤n r.C
+        object_has_self_length: Obj. Self restriction: ∃ r.Self
+        object_one_of_length: Obj. One of: ∃ r.{X,Y,Z}
+        data_some_values_length: Data Some Values: ∃ p.t
+        data_all_values_length: Data All Values: ∀ p.t
+        data_has_value_length: Data Has Value: ∃ p.{V}
+        data_cardinality_length: Data Cardinality restriction: ≤n r.t
+        object_property_length: Obj. Property: ∃ r.C
+        object_inverse_length: Inverse property: ∃ r⁻.C
+        data_property_length: Data Property: ∃ p.t
+        datatype_length: Datatype: ^^datatype
+        data_one_of_length: Data One of: ∃ p.{U,V,W}
+        data_complement_length: Data Complement: ¬datatype
+        data_intersection_length: Data Intersection: datatype ⨅ datatype
+        data_union_length: Data Union: datatype ⨆ datatype
+    """
+
+    __slots__ = 'class_length', 'object_intersection_length', 'object_union_length', 'object_complement_length', \
+                'object_some_values_length', 'object_all_values_length', 'object_has_value_length', \
+                'object_cardinality_length', 'object_has_self_length', 'object_one_of_length', \
+                'data_some_values_length', 'data_all_values_length', 'data_has_value_length', \
+                'data_cardinality_length', 'object_property_length', 'object_inverse_length', 'data_property_length', \
+                'datatype_length', 'data_one_of_length', 'data_complement_length', 'data_intersection_length', \
+                'data_union_length'
+
+    class_length: int
+    object_intersection_length: int
+    object_union_length: int
+    object_complement_length: int
+    object_some_values_length: int
+    object_all_values_length: int
+    object_has_value_length: int
+    object_cardinality_length: int
+    object_has_self_length: int
+    object_one_of_length: int
+    data_some_values_length: int
+    data_all_values_length: int
+    data_has_value_length: int
+    data_cardinality_length: int
+    object_property_length: int
+    object_inverse_length: int
+    data_property_length: int
+    datatype_length: int
+    data_one_of_length: int
+    data_complement_length: int
+    data_intersection_length: int
+    data_union_length: int
+
+    def __init__(self, *,
+                 class_length: int,
+                 object_intersection_length: int,
+                 object_union_length: int,
+                 object_complement_length: int,
+                 object_some_values_length: int,
+                 object_all_values_length: int,
+                 object_has_value_length: int,
+                 object_cardinality_length: int,
+                 object_has_self_length: int,
+                 object_one_of_length: int,
+                 data_some_values_length: int,
+                 data_all_values_length: int,
+                 data_has_value_length: int,
+                 data_cardinality_length: int,
+                 object_property_length: int,
+                 object_inverse_length: int,
+                 data_property_length: int,
+                 datatype_length: int,
+                 data_one_of_length: int,
+                 data_complement_length: int,
+                 data_intersection_length: int,
+                 data_union_length: int,
+                 ):
+        self.class_length = class_length
+        self.object_intersection_length = object_intersection_length
+        self.object_union_length = object_union_length
+        self.object_complement_length = object_complement_length
+        self.object_some_values_length = object_some_values_length
+        self.object_all_values_length = object_all_values_length
+        self.object_has_value_length = object_has_value_length
+        self.object_cardinality_length = object_cardinality_length
+        self.object_has_self_length = object_has_self_length
+        self.object_one_of_length = object_one_of_length
+        self.data_some_values_length = data_some_values_length
+        self.data_all_values_length = data_all_values_length
+        self.data_has_value_length = data_has_value_length
+        self.data_cardinality_length = data_cardinality_length
+        self.object_property_length = object_property_length
+        self.object_inverse_length = object_inverse_length
+        self.data_property_length = data_property_length
+        self.datatype_length = datatype_length
+        self.data_one_of_length = data_one_of_length
+        self.data_complement_length = data_complement_length
+        self.data_intersection_length = data_intersection_length
+        self.data_union_length = data_union_length
+
+    @staticmethod
+    def get_default() -> 'OWLClassExpressionLengthMetric':
+        return OWLClassExpressionLengthMetric(
+            class_length=1,
+            object_intersection_length=1,
+            object_union_length=1,
+            object_complement_length=1,
+            object_some_values_length=1,
+            object_all_values_length=1,
+            object_has_value_length=2,
+            object_cardinality_length=2,
+            object_has_self_length=1,
+            object_one_of_length=1,
+            data_some_values_length=1,
+            data_all_values_length=1,
+            data_has_value_length=2,
+            data_cardinality_length=2,
+            object_property_length=1,
+            object_inverse_length=2,
+            data_property_length=1,
+            datatype_length=1,
+            data_one_of_length=1,
+            data_complement_length=1,
+            data_intersection_length=1,
+            data_union_length=1,
+        )
+
+    # single dispatch is still not implemented in mypy, see https://github.com/python/mypy/issues/2904
+    @singledispatchmethod
+    def length(self, o: OWLObject) -> int:
+        raise NotImplementedError
+
+    @length.register
+    def _(self, o: OWLClass) -> int:
+        return self.class_length
+
+    @length.register
+    def _(self, p: OWLObjectProperty) -> int:
+        return self.object_property_length
+
+    @length.register
+    def _(self, e: OWLObjectSomeValuesFrom) -> int:
+        return self.object_some_values_length \
+               + self.length(e.get_property()) \
+               + self.length(e.get_filler())
+
+    @length.register
+    def _(self, e: OWLObjectAllValuesFrom) -> int:
+        return self.object_all_values_length \
+               + self.length(e.get_property()) \
+               + self.length(e.get_filler())
+
+    @length.register
+    def _(self, c: OWLObjectUnionOf) -> int:
+        length = -self.object_union_length
+        for op in c.operands():
+            length += self.length(op) + self.object_union_length
+
+        return length
+
+    @length.register
+    def _(self, c: OWLObjectIntersectionOf) -> int:
+        length = -self.object_intersection_length
+        for op in c.operands():
+            length += self.length(op) + self.object_intersection_length
+
+        return length
+
+    @length.register
+    def _(self, n: OWLObjectComplementOf) -> int:
+        return self.length(n.get_operand()) + self.object_complement_length
+
+    @length.register
+    def _(self, p: OWLObjectInverseOf) -> int:
+        return self.object_inverse_length
+
+    @length.register
+    def _(self, e: OWLObjectCardinalityRestriction) -> int:
+        return self.object_cardinality_length \
+               + self.length(e.get_property()) \
+               + self.length(e.get_filler())
+
+    @length.register
+    def _(self, s: OWLObjectHasSelf) -> int:
+        return self.object_has_self_length + self.length(s.get_property())
+
+    @length.register
+    def _(self, v: OWLObjectHasValue) -> int:
+        return self.object_has_value_length + self.length(v.get_property())
+
+    @length.register
+    def _(self, o: OWLObjectOneOf) -> int:
+        return self.object_one_of_length
+
+    @length.register
+    def _(self, p: OWLDataProperty) -> int:
+        return self.data_property_length
+
+    @length.register
+    def _(self, e: OWLDataSomeValuesFrom) -> int:
+        return self.data_some_values_length \
+               + self.length(e.get_property()) \
+               + self.length(e.get_filler())
+
+    @length.register
+    def _(self, e: OWLDataAllValuesFrom) -> int:
+        return self.data_all_values_length \
+               + self.length(e.get_property()) \
+               + self.length(e.get_filler())
+
+    @length.register
+    def _(self, e: OWLDataCardinalityRestriction) -> int:
+        return self.data_cardinality_length \
+               + self.length(e.get_property()) \
+               + self.length(e.get_filler())
+
+    @length.register
+    def _(self, v: OWLDataHasValue) -> int:
+        return self.data_has_value_length + self.length(v.get_property())
+
+    @length.register
+    def _(self, o: OWLDataOneOf) -> int:
+        return self.data_one_of_length
+
+    @length.register
+    def _(self, n: OWLDatatypeRestriction) -> int:
+        return iter_count(n.get_facet_restrictions())
+
+    @length.register
+    def _(self, n: OWLDataComplementOf) -> int:
+        return self.data_complement_length + self.length(n.get_data_range())
+
+    @length.register
+    def _(self, c: OWLDataUnionOf) -> int:
+        length = -self.data_union_length
+        for op in c.operands():
+            length += self.length(op) + self.data_union_length
+        return length
+
+    @length.register
+    def _(self, c: OWLDataIntersectionOf) -> int:
+        length = -self.data_intersection_length
+        for op in c.operands():
+            length += self.length(op) + self.data_intersection_length
+        return length
+
+    @length.register
+    def _(self, t: OWLDatatype) -> int:
+        return self.datatype_length
+
+
+measurer = OWLClassExpressionLengthMetric.get_default()
+
+
+def get_expression_length(ce: OWLClassExpression) -> int:
+    return measurer.length(ce)
+
+
+_N = TypeVar('_N')  #:
+_O = TypeVar('_O')  #:
+
+
+class EvaluatedDescriptionSet(Generic[_N, _O]):
+    __slots__ = 'items', '_max_size', '_Ordering'
+
+    items: 'SortedSet[_N]'
+    _max_size: int
+    _Ordering: Callable[[_N], _O]
+
+    def __init__(self, ordering: Callable[[_N], _O], max_size: int = 10):
+        self._max_size = max_size
+        self._Ordering = ordering
+        self.items = SortedSet(key=self._Ordering)
+
+    def maybe_add(self, node: _N):
+        if len(self.items) == self._max_size:
+            worst = self.items[0]
+            if self._Ordering(node) > self._Ordering(worst):
+                self.items.pop(0)
+                self.items.add(node)
+                return True
+        else:
+            self.items.add(node)
+            return True
+        return False
+
+    def clean(self):
+        self.items.clear()
+
+    def worst(self):
+        return self.items[0]
+
+    def best(self):
+        return self.items[-1]
+
+    def best_quality_value(self) -> float:
+        return self.items[-1].quality
+
+    def __iter__(self) -> Iterable[_N]:
+        yield from reversed(self.items)
+
+
+def _avoid_overly_redundand_operands(operands: List[_O], max_count: int = 2) -> List[_O]:
+    _max_count = max_count
+    r = []
+    counts = Counter(operands)
+    for op in sorted(operands, key=OrderedOWLObject):
+        for _ in range(min(_max_count, counts[op])):
+            r.append(op)
+    return r
+
+
+def _sort_by_ordered_owl_object(i: Iterable[_O]) -> Iterable[_O]:
+    return sorted(i, key=OrderedOWLObject)
+
+
+class ConceptOperandSorter:
+    # single dispatch is still not implemented in mypy, see https://github.com/python/mypy/issues/2904
+    @singledispatchmethod
+    def sort(self, o: _O) -> _O:
+        raise NotImplementedError(o)
+
+    @sort.register
+    def _(self, o: OWLClass) -> OWLClass:
+        return o
+
+    @sort.register
+    def _(self, p: OWLObjectProperty) -> OWLObjectProperty:
+        return p
+
+    @sort.register
+    def _(self, p: OWLDataProperty) -> OWLDataProperty:
+        return p
+
+    @sort.register
+    def _(self, i: OWLNamedIndividual) -> OWLNamedIndividual:
+        return i
+
+    @sort.register
+    def _(self, i: OWLLiteral) -> OWLLiteral:
+        return i
+
+    @sort.register
+    def _(self, e: OWLObjectSomeValuesFrom) -> OWLObjectSomeValuesFrom:
+        t = OWLObjectSomeValuesFrom(property=e.get_property(), filler=self.sort(e.get_filler()))
+        if t == e:
+            return e
+        else:
+            return t
+
+    @sort.register
+    def _(self, e: OWLObjectAllValuesFrom) -> OWLObjectAllValuesFrom:
+        t = OWLObjectAllValuesFrom(property=e.get_property(), filler=self.sort(e.get_filler()))
+        if t == e:
+            return e
+        else:
+            return t
+
+    @sort.register
+    def _(self, c: OWLObjectUnionOf) -> OWLObjectUnionOf:
+        t = OWLObjectUnionOf(_sort_by_ordered_owl_object(c.operands()))
+        if t == c:
+            return c
+        else:
+            return t
+
+    @sort.register
+    def _(self, c: OWLObjectIntersectionOf) -> OWLObjectIntersectionOf:
+        t = OWLObjectIntersectionOf(_sort_by_ordered_owl_object(c.operands()))
+        if t == c:
+            return c
+        else:
+            return t
+
+    @sort.register
+    def _(self, n: OWLObjectComplementOf) -> OWLObjectComplementOf:
+        return n
+
+    @sort.register
+    def _(self, p: OWLObjectInverseOf) -> OWLObjectInverseOf:
+        return p
+
+    @sort.register
+    def _(self, r: OWLObjectMinCardinality) -> OWLObjectMinCardinality:
+        t = OWLObjectMinCardinality(cardinality=r.get_cardinality(), property=r.get_property(),
+                                    filler=self.sort(r.get_filler()))
+        if t == r:
+            return r
+        else:
+            return t
+
+    @sort.register
+    def _(self, r: OWLObjectExactCardinality) -> OWLObjectExactCardinality:
+        t = OWLObjectExactCardinality(cardinality=r.get_cardinality(), property=r.get_property(),
+                                      filler=self.sort(r.get_filler()))
+        if t == r:
+            return r
+        else:
+            return t
+
+    @sort.register
+    def _(self, r: OWLObjectMaxCardinality) -> OWLObjectMaxCardinality:
+        t = OWLObjectMaxCardinality(cardinality=r.get_cardinality(), property=r.get_property(),
+                                    filler=self.sort(r.get_filler()))
+        if t == r:
+            return r
+        else:
+            return t
+
+    @sort.register
+    def _(self, r: OWLObjectHasSelf) -> OWLObjectHasSelf:
+        return r
+
+    @sort.register
+    def _(self, r: OWLObjectHasValue) -> OWLObjectHasValue:
+        return r
+
+    @sort.register
+    def _(self, r: OWLObjectOneOf) -> OWLObjectOneOf:
+        t = OWLObjectOneOf(_sort_by_ordered_owl_object(r.individuals()))
+        if t == r:
+            return r
+        else:
+            return t
+
+    @sort.register
+    def _(self, e: OWLDataSomeValuesFrom) -> OWLDataSomeValuesFrom:
+        t = OWLDataSomeValuesFrom(property=e.get_property(), filler=self.sort(e.get_filler()))
+        if t == e:
+            return e
+        else:
+            return t
+
+    @sort.register
+    def _(self, e: OWLDataAllValuesFrom) -> OWLDataAllValuesFrom:
+        t = OWLDataAllValuesFrom(property=e.get_property(), filler=self.sort(e.get_filler()))
+        if t == e:
+            return e
+        else:
+            return t
+
+    @sort.register
+    def _(self, c: OWLDataUnionOf) -> OWLDataUnionOf:
+        t = OWLDataUnionOf(_sort_by_ordered_owl_object(c.operands()))
+        if t == c:
+            return c
+        else:
+            return t
+
+    @sort.register
+    def _(self, c: OWLDataIntersectionOf) -> OWLDataIntersectionOf:
+        t = OWLDataIntersectionOf(_sort_by_ordered_owl_object(c.operands()))
+        if t == c:
+            return c
+        else:
+            return t
+
+    @sort.register
+    def _(self, n: OWLDataComplementOf) -> OWLDataComplementOf:
+        return n
+
+    @sort.register
+    def _(self, n: OWLDatatypeRestriction) -> OWLDatatypeRestriction:
+        t = OWLDatatypeRestriction(n.get_datatype(), _sort_by_ordered_owl_object(n.get_facet_restrictions()))
+        if t == n:
+            return n
+        else:
+            return t
+
+    @sort.register
+    def _(self, d: OWLDatatype) -> OWLDatatype:
+        return d
+
+    @sort.register
+    def _(self, r: OWLDataMinCardinality) -> OWLDataMinCardinality:
+        t = OWLDataMinCardinality(cardinality=r.get_cardinality(), property=r.get_property(),
+                                  filler=self.sort(r.get_filler()))
+        if t == r:
+            return r
+        else:
+            return t
+
+    @sort.register
+    def _(self, r: OWLDataExactCardinality) -> OWLDataExactCardinality:
+        t = OWLDataExactCardinality(cardinality=r.get_cardinality(), property=r.get_property(),
+                                    filler=self.sort(r.get_filler()))
+        if t == r:
+            return r
+        else:
+            return t
+
+    @sort.register
+    def _(self, r: OWLDataMaxCardinality) -> OWLDataMaxCardinality:
+        t = OWLDataMaxCardinality(cardinality=r.get_cardinality(), property=r.get_property(),
+                                  filler=self.sort(r.get_filler()))
+        if t == r:
+            return r
+        else:
+            return t
+
+    @sort.register
+    def _(self, r: OWLDataHasValue) -> OWLDataHasValue:
+        return r
+
+    @sort.register
+    def _(self, n: OWLDataOneOf) -> OWLDataOneOf:
+        t = OWLDataOneOf(_sort_by_ordered_owl_object(n.values()))
+        if t == n:
+            return n
+        else:
+            return t
+
+
+class OperandSetTransform:
+    def simplify(self, o: OWLClassExpression) -> OWLClassExpression:
+        return self._simplify(o).get_nnf()
+
+    # single dispatch is still not implemented in mypy, see https://github.com/python/mypy/issues/2904
+    @singledispatchmethod
+    def _simplify(self, o: _O) -> _O:
+        raise NotImplementedError(o)
+
+    @_simplify.register
+    def _(self, o: OWLClass) -> OWLClass:
+        return o
+
+    @_simplify.register
+    def _(self, p: OWLObjectProperty) -> OWLObjectProperty:
+        return p
+
+    @_simplify.register
+    def _(self, p: OWLDataProperty) -> OWLDataProperty:
+        return p
+
+    @_simplify.register
+    def _(self, i: OWLNamedIndividual) -> OWLNamedIndividual:
+        return i
+
+    @_simplify.register
+    def _(self, i: OWLLiteral) -> OWLLiteral:
+        return i
+
+    @_simplify.register
+    def _(self, e: OWLObjectSomeValuesFrom) -> OWLObjectSomeValuesFrom:
+        return OWLObjectSomeValuesFrom(property=e.get_property(), filler=self._simplify(e.get_filler()))
+
+    @_simplify.register
+    def _(self, e: OWLObjectAllValuesFrom) -> OWLObjectAllValuesFrom:
+        return OWLObjectAllValuesFrom(property=e.get_property(), filler=self._simplify(e.get_filler()))
+
+    @_simplify.register
+    def _(self, c: OWLObjectUnionOf) -> OWLClassExpression:
+        s = set(map(self._simplify, set(c.operands())))
+        if OWLThing in s:
+            return OWLThing
+        elif len(s) == 1:
+            return s.pop()
+        return OWLObjectUnionOf(_sort_by_ordered_owl_object(s))
+
+    @_simplify.register
+    def _(self, c: OWLObjectIntersectionOf) -> OWLClassExpression:
+        s = set(map(self._simplify, set(c.operands())))
+        s.discard(OWLThing)
+        if not s:
+            return OWLThing
+        elif len(s) == 1:
+            return s.pop()
+        return OWLObjectIntersectionOf(_sort_by_ordered_owl_object(s))
+
+    @_simplify.register
+    def _(self, n: OWLObjectComplementOf) -> OWLObjectComplementOf:
+        return n
+
+    @_simplify.register
+    def _(self, p: OWLObjectInverseOf) -> OWLObjectInverseOf:
+        return p
+
+    @_simplify.register
+    def _(self, r: OWLObjectMinCardinality) -> OWLObjectMinCardinality:
+        return OWLObjectMinCardinality(cardinality=r.get_cardinality(), property=r.get_property(),
+                                       filler=self._simplify(r.get_filler()))
+
+    @_simplify.register
+    def _(self, r: OWLObjectExactCardinality) -> OWLObjectExactCardinality:
+        return OWLObjectExactCardinality(cardinality=r.get_cardinality(), property=r.get_property(),
+                                         filler=self._simplify(r.get_filler()))
+
+    @_simplify.register
+    def _(self, r: OWLObjectMaxCardinality) -> OWLObjectMaxCardinality:
+        return OWLObjectMaxCardinality(cardinality=r.get_cardinality(), property=r.get_property(),
+                                       filler=self._simplify(r.get_filler()))
+
+    @_simplify.register
+    def _(self, r: OWLObjectHasSelf) -> OWLObjectHasSelf:
+        return r
+
+    @_simplify.register
+    def _(self, r: OWLObjectHasValue) -> OWLObjectHasValue:
+        return r
+
+    @_simplify.register
+    def _(self, r: OWLObjectOneOf) -> OWLObjectOneOf:
+        return OWLObjectOneOf(_sort_by_ordered_owl_object(set(r.individuals())))
+
+    @_simplify.register
+    def _(self, e: OWLDataSomeValuesFrom) -> OWLDataSomeValuesFrom:
+        return OWLDataSomeValuesFrom(property=e.get_property(), filler=self._simplify(e.get_filler()))
+
+    @_simplify.register
+    def _(self, e: OWLDataAllValuesFrom) -> OWLDataAllValuesFrom:
+        return OWLDataAllValuesFrom(property=e.get_property(), filler=self._simplify(e.get_filler()))
+
+    @_simplify.register
+    def _(self, c: OWLDataUnionOf) -> OWLDataRange:
+        s = set(map(self._simplify, set(c.operands())))
+        if len(s) == 1:
+            return s.pop()
+        return OWLDataUnionOf(_sort_by_ordered_owl_object(s))
+
+    @_simplify.register
+    def _(self, c: OWLDataIntersectionOf) -> OWLDataRange:
+        s = set(map(self._simplify, set(c.operands())))
+        if len(s) == 1:
+            return s.pop()
+        return OWLDataIntersectionOf(_sort_by_ordered_owl_object(s))
+
+    @_simplify.register
+    def _(self, n: OWLDatatypeRestriction) -> OWLDatatypeRestriction:
+        return n
+
+    @_simplify.register
+    def _(self, n: OWLDataComplementOf) -> OWLDataComplementOf:
+        return n
+
+    @_simplify.register
+    def _(self, r: OWLDataMinCardinality) -> OWLDataMinCardinality:
+        return OWLDataMinCardinality(cardinality=r.get_cardinality(), property=r.get_property(),
+                                     filler=self._simplify(r.get_filler()))
+
+    @_simplify.register
+    def _(self, r: OWLDataExactCardinality) -> OWLDataExactCardinality:
+        return OWLDataExactCardinality(cardinality=r.get_cardinality(), property=r.get_property(),
+                                       filler=self._simplify(r.get_filler()))
+
+    @_simplify.register
+    def _(self, r: OWLDataMaxCardinality) -> OWLDataMaxCardinality:
+        return OWLDataMaxCardinality(cardinality=r.get_cardinality(), property=r.get_property(),
+                                     filler=self._simplify(r.get_filler()))
+
+    @_simplify.register
+    def _(self, r: OWLDataHasValue) -> OWLDataHasValue:
+        return r
+
+    @_simplify.register
+    def _(self, r: OWLDataOneOf) -> OWLDataOneOf:
+        return OWLDataOneOf(_sort_by_ordered_owl_object(set(r.values())))
+
+
+class HasIndex(Protocol):
+    """Interface for types with an index; this is used to group objects by type when sorting."""
+    type_index: ClassVar[int]  #: index for this type. This is a sorting index for the types.
+
+    def __eq__(self, other): ...
+
+
+_HasIRI = TypeVar('_HasIRI', bound=HasIRI)  #:
+_HasIndex = TypeVar('_HasIndex', bound=HasIndex)  #:
+_O = TypeVar('_O')  #:
+_Enc = TypeVar('_Enc')
+_Con = TypeVar('_Con')
+_K = TypeVar('_K')
+_V = TypeVar('_V')
+
+
+@total_ordering
+class OrderedOWLObject:
+    """Holder of OWL Objects that can be used for Python sorted.
+
+    The Ordering is dependent on the type_index of the impl. classes recursively followed by all components of the
+    OWL Object.
+
+    Attributes:
+        o: OWL object.
+    """
+    __slots__ = 'o', '_chain'
+
+    o: _HasIndex  # o: Intersection[OWLObject, HasIndex]
+    _chain: Optional[Tuple]
+
+    # we are limited by https://github.com/python/typing/issues/213 # o: Intersection[OWLObject, HasIndex]
+    def __init__(self, o: _HasIndex):
+        """OWL Object holder with a defined sort order.
+
+        Args:
+            o: OWL Object.
+        """
+        self.o = o
+        self._chain = None
+
+    def _comparison_chain(self):
+        if self._chain is None:
+            c = [self.o.type_index]
+
+            if isinstance(self.o, OWLRestriction):
+                c.append(OrderedOWLObject(as_index(self.o.get_property())))
+            if isinstance(self.o, OWLObjectInverseOf):
+                c.append(self.o.get_named_property().str)
+            if isinstance(self.o, HasFiller):
+                c.append(OrderedOWLObject(self.o.get_filler()))
+            if isinstance(self.o, HasCardinality):
+                c.append(self.o.get_cardinality())
+            if isinstance(self.o, HasOperands):
+                c.append(tuple(map(OrderedOWLObject, self.o.operands())))
+            if isinstance(self.o, HasIRI):
+                c.append(self.o.str)
+            if isinstance(self.o, OWLDataComplementOf):
+                c.append(OrderedOWLObject(self.o.get_data_range()))
+            if isinstance(self.o, OWLDatatypeRestriction):
+                c.append((OrderedOWLObject(self.o.get_datatype()),
+                          tuple(map(OrderedOWLObject, self.o.get_facet_restrictions()))))
+            if isinstance(self.o, OWLFacetRestriction):
+                c.append((self.o.get_facet().str, self.o.get_facet_value().get_literal()))
+            if isinstance(self.o, OWLLiteral):
+                c.append(self.o.get_literal())
+            if len(c) == 1:
+                raise NotImplementedError(type(self.o))
+
+            self._chain = tuple(c)
+
+        return self._chain
+
+    def __lt__(self, other):
+        if self.o.type_index < other.o.type_index:
+            return True
+        elif self.o.type_index > other.o.type_index:
+            return False
+        else:
+            return self._comparison_chain() < other._comparison_chain()
+
+    def __eq__(self, other):
+        return self.o == other.o
+
+
+def _sort_by_ordered_owl_object(i: Iterable[_O]) -> Iterable[_O]:
+    return sorted(i, key=OrderedOWLObject)
+
+
+class NNF:
+    """This class contains functions to transform a Class Expression into Negation Normal Form."""
+    @singledispatchmethod
+    def get_class_nnf(self, ce: OWLClassExpression, negated: bool = False) -> OWLClassExpression:
+        """Convert a Class Expression to Negation Normal Form. Operands will be sorted.
+
+        Args:
+            ce: Class Expression.
+            negated: Whether the result should be negated.
+
+        Returns:
+            Class Expression in Negation Normal Form.
+            """
+        raise NotImplementedError
+
+    @get_class_nnf.register
+    def _(self, ce: OWLClass, negated: bool = False):
+        if negated:
+            if ce.is_owl_thing():
+                return OWLNothing
+            if ce.is_owl_nothing():
+                return OWLThing
+            return OWLObjectComplementOf(ce)
+        return ce
+
+    @get_class_nnf.register
+    def _(self, ce: OWLObjectIntersectionOf, negated: bool = False):
+        ops = map(lambda _: self.get_class_nnf(_, negated),
+                  _sort_by_ordered_owl_object(ce.operands()))
+        if negated:
+            return OWLObjectUnionOf(ops)
+        return OWLObjectIntersectionOf(ops)
+
+    @get_class_nnf.register
+    def _(self, ce: OWLObjectUnionOf, negated: bool = False):
+        ops = map(lambda _: self.get_class_nnf(_, negated),
+                  _sort_by_ordered_owl_object(ce.operands()))
+        if negated:
+            return OWLObjectIntersectionOf(ops)
+        return OWLObjectUnionOf(ops)
+
+    @get_class_nnf.register
+    def _(self, ce: OWLObjectComplementOf, negated: bool = False):
+        return self.get_class_nnf(ce.get_operand(), not negated)
+
+    @get_class_nnf.register
+    def _(self, ce: OWLObjectSomeValuesFrom, negated: bool = False):
+        filler = self.get_class_nnf(ce.get_filler(), negated)
+        if negated:
+            return OWLObjectAllValuesFrom(ce.get_property(), filler)
+        return OWLObjectSomeValuesFrom(ce.get_property(), filler)
+
+    @get_class_nnf.register
+    def _(self, ce: OWLObjectAllValuesFrom, negated: bool = False):
+        filler = self.get_class_nnf(ce.get_filler(), negated)
+        if negated:
+            return OWLObjectSomeValuesFrom(ce.get_property(), filler)
+        return OWLObjectAllValuesFrom(ce.get_property(), filler)
+
+    @get_class_nnf.register
+    def _(self, ce: OWLObjectHasValue, negated: bool = False):
+        return self.get_class_nnf(ce.as_some_values_from(), negated)
+
+    @get_class_nnf.register
+    def _(self, ce: OWLObjectMinCardinality, negated: bool = False):
+        card = ce.get_cardinality()
+        if negated:
+            card = max(0, card - 1)
+        filler = self.get_class_nnf(ce.get_filler(), negated=False)
+        if negated:
+            return OWLObjectMaxCardinality(card, ce.get_property(), filler)
+        return OWLObjectMinCardinality(card, ce.get_property(), filler)
+
+    @get_class_nnf.register
+    def _(self, ce: OWLObjectExactCardinality, negated: bool = False):
+        return self.get_class_nnf(ce.as_intersection_of_min_max(), negated)
+
+    @get_class_nnf.register
+    def _(self, ce: OWLObjectMaxCardinality, negated: bool = False):
+        card = ce.get_cardinality()
+        if negated:
+            card = card + 1
+        filler = self.get_class_nnf(ce.get_filler(), negated=False)
+        if negated:
+            return OWLObjectMinCardinality(card, ce.get_property(), filler)
+        return OWLObjectMaxCardinality(card, ce.get_property(), filler)
+
+    @get_class_nnf.register
+    def _(self, ce: OWLObjectHasSelf, negated: bool = False):
+        if negated:
+            return ce.get_object_complement_of()
+        return ce
+
+    @get_class_nnf.register
+    def _(self, ce: OWLObjectOneOf, negated: bool = False):
+        union = ce.as_object_union_of()
+        if isinstance(union, OWLObjectOneOf):
+            if negated:
+                return ce.get_object_complement_of()
+            return ce
+        return self.get_class_nnf(union, negated)
+
+    @get_class_nnf.register
+    def _(self, ce: OWLDataSomeValuesFrom, negated: bool = False):
+        filler = self.get_class_nnf(ce.get_filler(), negated)
+        if negated:
+            return OWLDataAllValuesFrom(ce.get_property(), filler)
+        return OWLDataSomeValuesFrom(ce.get_property(), filler)
+
+    @get_class_nnf.register
+    def _(self, ce: OWLDataAllValuesFrom, negated: bool = False):
+        filler = self.get_class_nnf(ce.get_filler(), negated)
+        if negated:
+            return OWLDataSomeValuesFrom(ce.get_property(), filler)
+        return OWLDataAllValuesFrom(ce.get_property(), filler)
+
+    @get_class_nnf.register
+    def _(self, ce: OWLDatatypeRestriction, negated: bool = False):
+        if negated:
+            return OWLDataComplementOf(ce)
+        return ce
+
+    @get_class_nnf.register
+    def _(self, ce: OWLDatatype, negated: bool = False):
+        if negated:
+            return OWLDataComplementOf(ce)
+        return ce
+
+    @get_class_nnf.register
+    def _(self, ce: OWLDataComplementOf, negated: bool = False):
+        return self.get_class_nnf(ce.get_data_range(), not negated)
+
+    @get_class_nnf.register
+    def _(self, ce: OWLDataHasValue, negated: bool = False):
+        return self.get_class_nnf(ce.as_some_values_from(), negated)
+
+    @get_class_nnf.register
+    def _(self, ce: OWLDataOneOf, negated: bool = False):
+        if len(list(ce.values())) == 1:
+            if negated:
+                return OWLDataComplementOf(ce)
+            return ce
+        union = OWLDataUnionOf([OWLDataOneOf(v) for v in ce.values()])
+        return self.get_class_nnf(union, negated)
+
+    @get_class_nnf.register
+    def _(self, ce: OWLDataIntersectionOf, negated: bool = False):
+        ops = map(lambda _: self.get_class_nnf(_, negated),
+                  _sort_by_ordered_owl_object(ce.operands()))
+        if negated:
+            return OWLDataUnionOf(ops)
+        return OWLDataIntersectionOf(ops)
+
+    @get_class_nnf.register
+    def _(self, ce: OWLDataUnionOf, negated: bool = False):
+        ops = map(lambda _: self.get_class_nnf(_, negated),
+                  _sort_by_ordered_owl_object(ce.operands()))
+        if negated:
+            return OWLDataIntersectionOf(ops)
+        return OWLDataUnionOf(ops)
+
+    @get_class_nnf.register
+    def _(self, ce: OWLDataExactCardinality, negated: bool = False):
+        return self.get_class_nnf(ce.as_intersection_of_min_max(), negated)
+
+    @get_class_nnf.register
+    def _(self, ce: OWLDataMinCardinality, negated: bool = False):
+        card = ce.get_cardinality()
+        if negated:
+            card = max(0, card - 1)
+        filler = self.get_class_nnf(ce.get_filler(), negated=False)
+        if negated:
+            return OWLDataMaxCardinality(card, ce.get_property(), filler)
+        return OWLDataMinCardinality(card, ce.get_property(), filler)
+
+    @get_class_nnf.register
+    def _(self, ce: OWLDataMaxCardinality, negated: bool = False):
+        card = ce.get_cardinality()
+        if negated:
+            card = card + 1
+        filler = self.get_class_nnf(ce.get_filler(), negated=False)
+        if negated:
+            return OWLDataMinCardinality(card, ce.get_property(), filler)
+        return OWLDataMaxCardinality(card, ce.get_property(), filler)
+
+
+# OWL-APy custom util start
+
+class TopLevelCNF:
+    """This class contains functions to transform a class expression into Top-Level Conjunctive Normal Form."""
+
+    def get_top_level_cnf(self, ce: OWLClassExpression) -> OWLClassExpression:
+        """Convert a class expression into Top-Level Conjunctive Normal Form. Operands will be sorted.
+
+        Args:
+            ce: Class Expression.
+
+        Returns:
+            Class Expression in Top-Level Conjunctive Normal Form.
+            """
+        c = _get_top_level_form(ce.get_nnf(), OWLObjectUnionOf, OWLObjectIntersectionOf)
+        return combine_nary_expressions(c)
+
+
+class TopLevelDNF:
+    """This class contains functions to transform a class expression into Top-Level Disjunctive Normal Form."""
+
+    def get_top_level_dnf(self, ce: OWLClassExpression) -> OWLClassExpression:
+        """Convert a class expression into Top-Level Disjunctive Normal Form. Operands will be sorted.
+
+        Args:
+            ce: Class Expression.
+
+        Returns:
+            Class Expression in Top-Level Disjunctive Normal Form.
+            """
+        c = _get_top_level_form(ce.get_nnf(), OWLObjectIntersectionOf, OWLObjectUnionOf)
+        return combine_nary_expressions(c)
+
+
+def _get_top_level_form(ce: OWLClassExpression,
+                        type_a: Type[OWLNaryBooleanClassExpression],
+                        type_b: Type[OWLNaryBooleanClassExpression]) -> OWLClassExpression:
+    """ Transforms a class expression (that's already in NNF) into Top-Level Conjunctive/Disjunctive Normal Form.
+    Here type_a specifies the operand which should be distributed inwards over type_b.
+
+    Conjunctive Normal form:
+        type_a = OWLObjectUnionOf
+        type_b = OWLObjectIntersectionOf
+    Disjunctive Normal form:
+        type_a = OWLObjectIntersectionOf
+        type_b = OWLObjectUnionOf
+    """
+
+    def distributive_law(a: OWLClassExpression, b: OWLNaryBooleanClassExpression) -> OWLNaryBooleanClassExpression:
+        return type_b(type_a([a, op]) for op in b.operands())
+
+    if isinstance(ce, type_a):
+        ce = cast(OWLNaryBooleanClassExpression, combine_nary_expressions(ce))
+        type_b_exprs = [op for op in ce.operands() if isinstance(op, type_b)]
+        non_type_b_exprs = [op for op in ce.operands() if not isinstance(op, type_b)]
+        if not len(type_b_exprs):
+            return ce
+
+        if len(non_type_b_exprs):
+            expr = non_type_b_exprs[0] if len(non_type_b_exprs) == 1 \
+                else type_a(non_type_b_exprs)
+            expr = distributive_law(expr, type_b_exprs[0])
+        else:
+            expr = type_b_exprs[0]
+
+        if len(type_b_exprs) == 1:
+            return _get_top_level_form(expr, type_a, type_b)
+
+        for type_b_expr in type_b_exprs[1:]:
+            expr = distributive_law(type_b_expr, expr)
+        return _get_top_level_form(expr, type_a, type_b)
+    elif isinstance(ce, type_b):
+        return type_b(_get_top_level_form(op, type_a, type_b) for op in ce.operands())
+    elif isinstance(ce, OWLClassExpression):
+        return ce
+    else:
+        raise ValueError('Top-Level CNF/DNF only applicable on class expressions', ce)
+
+
+@overload
+def combine_nary_expressions(ce: OWLClassExpression) -> OWLClassExpression:
+    ...
+
+
+@overload
+def combine_nary_expressions(ce: OWLDataRange) -> OWLDataRange:
+    ...
+
+
+def combine_nary_expressions(ce: OWLPropertyRange) -> OWLPropertyRange:
+    """ Shortens an OWLClassExpression or OWLDataRange by combining all nested nary expressions of the same type.
+    Operands will be sorted.
+
+    E.g. OWLObjectUnionOf(A, OWLObjectUnionOf(C, B)) -> OWLObjectUnionOf(A, B, C).
+    """
+    if isinstance(ce, (OWLNaryBooleanClassExpression, OWLNaryDataRange)):
+        expressions: List[OWLPropertyRange] = []
+        for op in ce.operands():
+            expr = combine_nary_expressions(op)
+            if type(expr) is type(ce):
+                expr = cast(Union[OWLNaryBooleanClassExpression, OWLNaryDataRange], expr)
+                expressions.extend(expr.operands())
+            else:
+                expressions.append(expr)
+        return type(ce)(_sort_by_ordered_owl_object(expressions))  # type: ignore
+    elif isinstance(ce, OWLObjectComplementOf):
+        return OWLObjectComplementOf(combine_nary_expressions(ce.get_operand()))
+    elif isinstance(ce, OWLDataComplementOf):
+        return OWLDataComplementOf(combine_nary_expressions(ce.get_data_range()))
+    elif isinstance(ce, OWLObjectCardinalityRestriction):
+        return type(ce)(ce.get_cardinality(), ce.get_property(), combine_nary_expressions(ce.get_filler()))
+    elif isinstance(ce, OWLDataCardinalityRestriction):
+        return type(ce)(ce.get_cardinality(), ce.get_property(), combine_nary_expressions(ce.get_filler()))
+    elif isinstance(ce, (OWLObjectSomeValuesFrom, OWLObjectAllValuesFrom)):
+        return type(ce)(ce.get_property(), combine_nary_expressions(ce.get_filler()))
+    elif isinstance(ce, (OWLDataSomeValuesFrom, OWLDataAllValuesFrom)):
+        return type(ce)(ce.get_property(), combine_nary_expressions(ce.get_filler()))
+    elif isinstance(ce, OWLObjectOneOf):
+        return OWLObjectOneOf(_sort_by_ordered_owl_object(ce.operands()))
+    elif isinstance(ce, OWLDataOneOf):
+        return OWLDataOneOf(_sort_by_ordered_owl_object(ce.operands()))
+    elif isinstance(ce, OWLPropertyRange):
+        return ce
+    else:
+        raise ValueError(f'({ce}) is not an OWLObject.')
+
+
+def iter_count(i: Iterable) -> int:
+    """Count the number of elements in an iterable."""
+    return sum(1 for _ in i)
+
+
+def as_index(o: OWLObject) -> HasIndex:
+    """Cast OWL Object to HasIndex."""
+    i = cast(HasIndex, o)
+    assert type(i).type_index
+    return i
+
+
+class LRUCache(Generic[_K, _V]):
+    """Constants shares by all lru cache instances.
+
+    Adapted from functools.lru_cache.
+
+    Attributes:
+        sentinel: Unique object used to signal cache misses.
+        PREV: Name for the link field 0.
+        NEXT: Name for the link field 1.
+        KEY: Name for the link field 2.
+        RESULT: Name for the link field 3.
+    """
+
+    sentinel = object()
+    PREV, NEXT, KEY, RESULT = 0, 1, 2, 3  # names for the link fields
+
+    def __init__(self, maxsize: Optional[int] = None):
+        from _thread import RLock
+
+        self.cache = {}
+        self.hits = self.misses = 0
+        self.full = False
+        self.cache_get = self.cache.get  # bound method to lookup a key or return None
+        self.cache_len = self.cache.__len__  # get cache size without calling len()
+        self.lock = RLock()  # because linkedlist updates aren't threadsafe
+        self.root = []  # root of the circular doubly linked list
+        self.root[:] = [self.root, self.root, None, None]  # initialize by pointing to self
+        self.maxsize = maxsize
+
+    def __contains__(self, item: _K) -> bool:
+        with self.lock:
+            link = self.cache_get(item)
+            if link is not None:
+                self.hits += 1
+                return True
+            self.misses += 1
+            return False
+
+    def __getitem__(self, item: _K) -> _V:
+        with self.lock:
+            link = self.cache_get(item)
+            if link is not None:
+                # Move the link to the front of the circular queue
+                link_prev, link_next, _key, result = link
+                link_prev[LRUCache.NEXT] = link_next
+                link_next[LRUCache.PREV] = link_prev
+                last = self.root[LRUCache.PREV]
+                last[LRUCache.NEXT] = self.root[LRUCache.PREV] = link
+                link[LRUCache.PREV] = last
+                link[LRUCache.NEXT] = self.root
+                return result
+
+    def __setitem__(self, key: _K, value: _V):
+        with self.lock:
+            if key in self.cache:
+                # Getting here means that this same key was added to the
+                # cache while the lock was released.  Since the link
+                # update is already done, we need only return the
+                # computed result and update the count of misses.
+                pass
+            elif self.full:
+                # Use the old root to store the new key and result.
+                oldroot = self.root
+                oldroot[LRUCache.KEY] = key
+                oldroot[LRUCache.RESULT] = value
+                # Empty the oldest link and make it the new root.
+                # Keep a reference to the old key and old result to
+                # prevent their ref counts from going to zero during the
+                # update. That will prevent potentially arbitrary object
+                # clean-up code (i.e. __del__) from running while we're
+                # still adjusting the links.
+                self.root = oldroot[LRUCache.NEXT]
+                oldkey = self.root[LRUCache.KEY]
+                _oldresult = self.root[LRUCache.RESULT]  # noqa: F841
+                self.root[LRUCache.KEY] = self.root[LRUCache.RESULT] = None
+                # Now update the cache dictionary.
+                del self.cache[oldkey]
+                # Save the potentially reentrant cache[key] assignment
+                # for last, after the root and links have been put in
+                # a consistent state.
+                self.cache[key] = oldroot
+            else:
+                # Put result in a new link at the front of the queue.
+                last = self.root[LRUCache.PREV]
+                link = [last, self.root, key, value]
+                last[LRUCache.NEXT] = self.root[LRUCache.PREV] = self.cache[key] = link
+                # Use the cache_len bound method instead of the len() function
+                # which could potentially be wrapped in an lru_cache itself.
+                if self.maxsize is not None:
+                    self.full = (self.cache_len() >= self.maxsize)
+
+    def cache_info(self):
+        """Report cache statistics."""
+        with self.lock:
+            from collections import namedtuple
+            return namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])(
+                self.hits, self.misses, self.maxsize, self.cache_len())
+
+    def cache_clear(self):
+        """Clear the cache and cache statistics."""
+        with self.lock:
+            self.cache.clear()
+            self.root[:] = [self.root, self.root, None, None]
+            self.hits = self.misses = 0
+            self.full = False
diff --git a/setup.py b/setup.py
index b5304a6a..10587f1e 100644
--- a/setup.py
+++ b/setup.py
@@ -5,19 +5,26 @@
 setup(
     name="owlapy",
     description="OWLAPY is a Python Framework for creating and manipulating OWL Ontologies.",
-    version="1.0.2",
+    version="1.1.1",
     packages=find_packages(),
+    include_package_data=True,
+    package_data={
+        'owlapy': ['jar_dependencies/*.jar'],
+    },
     install_requires=[
         "pandas>=1.5.0",
         "rdflib>=6.0.2",
         "parsimonious>=0.8.1",
-        "pytest>=8.1.1"],
+        "pytest>=8.1.1",
+        "sortedcontainers>=2.4.0",
+        "owlready2>=0.40",
+        "JPype1>=1.5.0"],
     author='Caglar Demir',
     author_email='caglardemir8@gmail.com',
     url='https://github.com/dice-group/owlapy',
     classifiers=[
         "Programming Language :: Python :: 3.10",
-        "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
+        "License :: OSI Approved :: MIT License",
         "Topic :: Scientific/Engineering"],
     python_requires='>=3.10.13',
     long_description=long_description,


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index ccd89d03..be929a87 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -21,4 +21,6 @@ jobs:
 
       - name: Test with pytest
         run: |
+          wget https://files.dice-research.org/projects/Ontolearn/KGs.zip
+          unzip KGs.zip
           python -m pytest -p no:warnings -x
diff --git a/tests/test_owlapi_adaptor.py b/tests/test_owlapi_adaptor.py
new file mode 100644
index 00000000..5864d5a2
--- /dev/null
+++ b/tests/test_owlapi_adaptor.py
@@ -0,0 +1,90 @@
+import os
+import unittest
+
+from jpype import JDouble
+
+from owlapy.class_expression import OWLClass, OWLDataSomeValuesFrom, OWLObjectIntersectionOf
+from owlapy.iri import IRI
+from owlapy.owl_axiom import OWLDisjointClassesAxiom, OWLDeclarationAxiom, OWLClassAssertionAxiom
+from owlapy.owl_individual import OWLNamedIndividual
+from owlapy.owl_ontology_manager import OntologyManager
+from owlapy.owl_property import OWLDataProperty
+from owlapy.owlapi_adaptor import OWLAPIAdaptor
+from owlapy.providers import owl_datatype_min_inclusive_restriction
+
+
+class TestOwlapiAdaptor(unittest.TestCase):
+    ns = "http://dl-learner.org/mutagenesis#"
+    ontology_path = "KGs/Mutagenesis/mutagenesis.owl"
+    nitrogen38 = OWLClass(IRI.create(ns, "Nitrogen-38"))
+    charge = OWLDataProperty(IRI.create(ns, "charge"))
+    has_charge_more_than_0_85 = OWLDataSomeValuesFrom(charge, owl_datatype_min_inclusive_restriction(0.85))
+    ce = OWLObjectIntersectionOf([nitrogen38, has_charge_more_than_0_85])
+    adaptor = OWLAPIAdaptor(ontology_path)
+
+    def test_consistency_check(self):
+        self.assertEqual(self.adaptor.has_consistent_ontology(), True)
+
+    def test_inconsistency_check(self):
+        manager = OntologyManager()
+        onto = manager.load_ontology(IRI.create(self.ontology_path))
+
+        carbon230 = OWLClass(IRI.create(self.ns, "Carbon-230"))
+        axiom = OWLDisjointClassesAxiom([self.nitrogen38, carbon230])
+        manager.add_axiom(onto, axiom)
+        new_individual = OWLNamedIndividual(IRI.create(self.ns, "testIndividual"))
+        manager.add_axiom(onto, OWLDeclarationAxiom(new_individual))
+        manager.add_axiom(onto, OWLClassAssertionAxiom(new_individual, self.nitrogen38))
+        manager.add_axiom(onto, OWLClassAssertionAxiom(new_individual, carbon230))
+
+        manager.save_ontology(onto, IRI.create("file:/test.owl"))
+        adaptor1 = OWLAPIAdaptor("test.owl")
+        self.assertEqual(adaptor1.has_consistent_ontology(), False)
+        os.remove("test.owl")
+
+    def test_instances_retrieval(self):
+        instances = self.adaptor.instances(self.ce)
+        expected = [OWLNamedIndividual(IRI('http://dl-learner.org/mutagenesis#', 'd141_10')),
+                    OWLNamedIndividual(IRI('http://dl-learner.org/mutagenesis#', 'd195_12')),
+                    OWLNamedIndividual(IRI('http://dl-learner.org/mutagenesis#', 'd144_10')),
+                    OWLNamedIndividual(IRI('http://dl-learner.org/mutagenesis#', 'd147_11')),
+                    OWLNamedIndividual(IRI('http://dl-learner.org/mutagenesis#', 'e18_9')),
+                    OWLNamedIndividual(IRI('http://dl-learner.org/mutagenesis#', 'd175_17')),
+                    OWLNamedIndividual(IRI('http://dl-learner.org/mutagenesis#', 'e16_9'))]
+        # Assert equal without considering the order
+        for instance in instances:
+            self.assertIn(instance, expected)
+        self.assertEqual(len(list(instances)), len(expected))
+
+    def test_conversion(self):
+        # construct the class expression in owlapi
+        from org.semanticweb.owlapi.model import IRI as IRIowlapi, OWLClass, OWLObjectProperty
+        from org.semanticweb.owlapi.vocab import OWLFacet
+
+        nitrogenIRI = IRIowlapi.create(self.ns + "Nitrogen-38")
+        charge_iri = IRIowlapi.create(self.ns + "charge")
+
+        data_factory = self.adaptor.manager.getOWLDataFactory()
+        nitrogen_class = data_factory.getOWLClass(nitrogenIRI)
+
+        charge_property = data_factory.getOWLDataProperty(charge_iri)
+        double_datatype = data_factory.getDoubleOWLDatatype()
+        facet_restriction = data_factory.getOWLFacetRestriction(OWLFacet.MIN_INCLUSIVE, JDouble(0.85))
+        datatype_restriction = data_factory.getOWLDatatypeRestriction(double_datatype, facet_restriction)
+        some_values_from = data_factory.getOWLDataSomeValuesFrom(charge_property, datatype_restriction)
+
+        class_expression = data_factory.getOWLObjectIntersectionOf(nitrogen_class, some_values_from)
+
+        # compare them with the adaptor converted expression
+        ce_converted = self.adaptor.convert_to_owlapi(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)
+
+        self.assertEqual(ce_1, ce_2)
+        self.assertEqual(ce_1, self.ce)
+        self.assertEqual(ce_2, self.ce)
diff --git a/tests/test_owlapy_cnf_dnf.py b/tests/test_owlapy_cnf_dnf.py
index df33ea04..c145da25 100644
--- a/tests/test_owlapy_cnf_dnf.py
+++ b/tests/test_owlapy_cnf_dnf.py
@@ -7,7 +7,7 @@
 from owlapy.owl_individual import OWLNamedIndividual
 from owlapy.owl_property import OWLObjectProperty, OWLDataProperty
 from owlapy.providers import owl_datatype_min_exclusive_restriction
-from owlapy.util import TopLevelCNF, TopLevelDNF
+from owlapy.utils import TopLevelCNF, TopLevelDNF
 
 
 class TopLevelNFTest(unittest.TestCase):
diff --git a/tests/test_owlapy_fastinstancechecker.py b/tests/test_owlapy_fastinstancechecker.py
new file mode 100644
index 00000000..48fd6647
--- /dev/null
+++ b/tests/test_owlapy_fastinstancechecker.py
@@ -0,0 +1,417 @@
+from datetime import date, datetime
+import unittest
+
+from owlapy.class_expression import OWLObjectOneOf, OWLObjectSomeValuesFrom, OWLThing, OWLObjectComplementOf, \
+    OWLObjectAllValuesFrom, OWLNothing, OWLObjectHasValue, OWLClass, OWLDataAllValuesFrom, OWLDataHasValue, \
+    OWLDataOneOf, OWLDataSomeValuesFrom, OWLObjectExactCardinality, OWLObjectMaxCardinality, OWLObjectMinCardinality, \
+    OWLObjectIntersectionOf
+from owlapy.iri import IRI
+from owlapy.owl_axiom import OWLSubDataPropertyOfAxiom, OWLInverseObjectPropertiesAxiom, OWLSubObjectPropertyOfAxiom
+from owlapy.owl_data_ranges import OWLDataComplementOf, OWLDataIntersectionOf, OWLDataUnionOf
+from owlapy.owl_individual import OWLNamedIndividual
+from owlapy.owl_literal import DoubleOWLDatatype, OWLLiteral, DurationOWLDatatype
+from owlapy.owl_ontology_manager import OntologyManager
+from owlapy.owl_property import OWLObjectInverseOf, OWLObjectProperty, OWLDataProperty
+from owlready2.prop import DataProperty
+from pandas import Timedelta
+
+from owlapy.owl_reasoner import FastInstanceCheckerReasoner, OntologyReasoner
+
+from owlapy.providers import owl_datatype_min_exclusive_restriction, \
+                            owl_datatype_min_max_inclusive_restriction, owl_datatype_min_max_exclusive_restriction, \
+                            owl_datatype_max_exclusive_restriction, owl_datatype_max_inclusive_restriction
+
+
+class Owlapy_FastInstanceChecker_Test(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'))
+
+        base_reasoner = OntologyReasoner(onto)
+        reasoner = FastInstanceCheckerReasoner(onto, base_reasoner=base_reasoner)
+
+        self.assertEqual([], list(reasoner.sub_object_properties(has_child, direct=True)))
+
+        inst = frozenset(reasoner.instances(female))
+        target_inst = frozenset({OWLNamedIndividual(IRI(NS, 'anna')),
+                                 OWLNamedIndividual(IRI(NS, 'michelle'))})
+        self.assertEqual(inst, target_inst)
+
+        inst = frozenset(reasoner.instances(
+            OWLObjectIntersectionOf((male, OWLObjectSomeValuesFrom(property=has_child, filler=female)))))
+        target_inst = frozenset({OWLNamedIndividual(IRI(NS, 'markus'))})
+        self.assertEqual(inst, target_inst)
+
+        inst = frozenset(reasoner.instances(
+            OWLObjectIntersectionOf((female, OWLObjectSomeValuesFrom(property=has_child, filler=OWLThing)))))
+        target_inst = frozenset({OWLNamedIndividual(IRI(NS, 'anna'))})
+        self.assertEqual(inst, target_inst)
+
+        inst = frozenset(reasoner.instances(
+            OWLObjectSomeValuesFrom(property=has_child,
+                                    filler=OWLObjectSomeValuesFrom(property=has_child,
+                                                                   filler=OWLObjectSomeValuesFrom(property=has_child,
+                                                                                                  filler=OWLThing)))))
+        target_inst = frozenset({OWLNamedIndividual(IRI(NS, 'stefan'))})
+        self.assertEqual(inst, target_inst)
+
+        inst = frozenset(reasoner.instances(OWLObjectHasValue(property=has_child,
+                                                              individual=OWLNamedIndividual(IRI(NS, 'heinz')))))
+        target_inst = frozenset({OWLNamedIndividual(IRI(NS, 'anna')),
+                                 OWLNamedIndividual(IRI(NS, 'martin'))})
+        self.assertEqual(inst, target_inst)
+
+        inst = frozenset(reasoner.instances(OWLObjectOneOf((OWLNamedIndividual(IRI(NS, 'anna')),
+                                                            OWLNamedIndividual(IRI(NS, 'michelle')),
+                                                            OWLNamedIndividual(IRI(NS, 'markus'))))))
+        target_inst = frozenset({OWLNamedIndividual(IRI(NS, 'anna')),
+                                 OWLNamedIndividual(IRI(NS, 'michelle')),
+                                 OWLNamedIndividual(IRI(NS, 'markus'))})
+        self.assertEqual(inst, target_inst)
+
+    def test_complement(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'))
+
+        base_reasoner = OntologyReasoner(onto)
+        reasoner_nd = FastInstanceCheckerReasoner(onto, base_reasoner=base_reasoner, negation_default=True)
+        reasoner_open = FastInstanceCheckerReasoner(onto, base_reasoner=base_reasoner, negation_default=False)
+
+        self.assertEqual(set(reasoner_nd.instances(male)), set(reasoner_nd.instances(OWLObjectComplementOf(female))))
+        self.assertEqual(set(reasoner_nd.instances(female)), set(reasoner_nd.instances(OWLObjectComplementOf(male))))
+
+        self.assertEqual(set(), set(reasoner_open.instances(
+            OWLObjectComplementOf(
+                OWLObjectSomeValuesFrom(property=has_child, filler=OWLThing)))))
+
+        all_inds = set(onto.individuals_in_signature())
+        unknown_child = set(reasoner_nd.instances(
+            OWLObjectComplementOf(
+                OWLObjectSomeValuesFrom(property=has_child, filler=OWLThing))))
+        with_child = set(reasoner_open.instances(
+            OWLObjectSomeValuesFrom(property=has_child, filler=OWLThing)))
+        self.assertEqual(all_inds - unknown_child, with_child)
+
+    def test_all_values(self):
+        NS = "http://example.com/father#"
+        mgr = OntologyManager()
+        onto = mgr.load_ontology(IRI.create("file://KGs/Family/father.owl"))
+
+        has_child = OWLObjectProperty(IRI(NS, 'hasChild'))
+
+        base_reasoner = OntologyReasoner(onto)
+        reasoner_nd = FastInstanceCheckerReasoner(onto, base_reasoner=base_reasoner, negation_default=True)
+
+        # note, these answers are all wrong under OWA
+        no_child = frozenset(reasoner_nd.instances(OWLObjectAllValuesFrom(property=has_child, filler=OWLNothing)))
+        target_inst = frozenset({OWLNamedIndividual(IRI('http://example.com/father#', 'michelle')),
+                                 OWLNamedIndividual(IRI('http://example.com/father#', 'heinz'))})
+        self.assertEqual(no_child, target_inst)
+
+    def test_complement2(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'))
+
+        base_reasoner = OntologyReasoner(onto)
+        reasoner_open = FastInstanceCheckerReasoner(onto, base_reasoner=base_reasoner, negation_default=False)
+
+        # Should be empty under open world assumption
+        self.assertEqual(set(), set(reasoner_open.instances(OWLObjectComplementOf(female))))
+        self.assertEqual(set(), set(reasoner_open.instances(OWLObjectComplementOf(male))))
+
+    def test_cardinality_restrictions(self):
+        NS = "http://dl-learner.org/mutagenesis#"
+        mgr = OntologyManager()
+        onto = mgr.load_ontology(IRI.create("file://KGs/Mutagenesis/mutagenesis.owl"))
+
+        hydrogen_3 = OWLClass(IRI.create(NS, 'Hydrogen-3'))
+        atom = OWLClass(IRI.create(NS, 'Atom'))
+        has_atom = OWLObjectProperty(IRI(NS, 'hasAtom'))
+
+        base_reasoner = OntologyReasoner(onto)
+        reasoner = FastInstanceCheckerReasoner(onto, base_reasoner=base_reasoner, negation_default=True)
+
+        inst = frozenset(reasoner.instances(OWLObjectExactCardinality(cardinality=2,
+                                                                      property=has_atom,
+                                                                      filler=hydrogen_3)))
+        target_inst = frozenset({OWLNamedIndividual(IRI(NS, 'd160')),
+                                 OWLNamedIndividual(IRI(NS, 'd195')),
+                                 OWLNamedIndividual(IRI(NS, 'd175'))})
+        self.assertEqual(inst, target_inst)
+
+        inst = frozenset(reasoner.instances(OWLObjectMinCardinality(cardinality=40,
+                                                                    property=has_atom,
+                                                                    filler=atom)))
+        target_inst_min = frozenset({OWLNamedIndividual(IRI(NS, 'd52')),
+                                     OWLNamedIndividual(IRI(NS, 'd91')),
+                                     OWLNamedIndividual(IRI(NS, 'd71')),
+                                     OWLNamedIndividual(IRI(NS, 'd51'))})
+        self.assertEqual(inst, target_inst_min)
+
+        all_inds = set(onto.individuals_in_signature())
+        inst = frozenset(reasoner.instances(OWLObjectMaxCardinality(cardinality=39,
+                                                                    property=has_atom,
+                                                                    filler=atom)))
+        self.assertEqual(all_inds - target_inst_min, inst)
+
+    def test_data_properties(self):
+        NS = "http://dl-learner.org/mutagenesis#"
+        mgr = OntologyManager()
+        onto = mgr.load_ontology(IRI.create("file://KGs/Mutagenesis/mutagenesis.owl"))
+
+        act = OWLDataProperty(IRI(NS, 'act'))
+        fused_rings = OWLDataProperty(IRI(NS, 'hasThreeOrMoreFusedRings'))
+        lumo = OWLDataProperty(IRI(NS, 'lumo'))
+        logp = OWLDataProperty(IRI(NS, 'logp'))
+        charge = OWLDataProperty(IRI(NS, 'charge'))
+
+        base_reasoner = OntologyReasoner(onto)
+        reasoner = FastInstanceCheckerReasoner(onto, base_reasoner=base_reasoner, negation_default=True)
+
+        self.assertEqual([], list(reasoner.sub_data_properties(act, direct=True)))
+
+        # OWLDataHasValue
+        inst = frozenset(reasoner.instances(
+            OWLObjectIntersectionOf((OWLDataHasValue(property=fused_rings, value=OWLLiteral(True)),
+                                     OWLDataHasValue(property=act, value=OWLLiteral(2.11))))))
+        target_inst = frozenset({OWLNamedIndividual(IRI(NS, 'd1'))})
+        self.assertEqual(inst, target_inst)
+
+        # OWLDatatypeRestriction
+        restriction = owl_datatype_min_max_inclusive_restriction(-3.0, -2.8)
+        inst = frozenset(reasoner.instances(OWLDataSomeValuesFrom(property=lumo, filler=restriction)))
+        target_inst = frozenset({OWLNamedIndividual(IRI(NS, 'd149')),
+                                 OWLNamedIndividual(IRI(NS, 'd29')),
+                                 OWLNamedIndividual(IRI(NS, 'd49')),
+                                 OWLNamedIndividual(IRI(NS, 'd96'))})
+        self.assertEqual(inst, target_inst)
+
+        # OWLDataAllValuesFrom
+        inst2 = frozenset(reasoner.instances(
+            OWLObjectComplementOf(OWLDataAllValuesFrom(property=lumo, filler=OWLDataComplementOf(restriction)))))
+        self.assertEqual(inst, inst2)
+
+        # OWLDataComplementOf
+        restriction = owl_datatype_min_max_exclusive_restriction(-2.0, 0.88)
+        inst = frozenset(reasoner.instances(OWLDataSomeValuesFrom(property=charge,
+                                                                  filler=OWLDataComplementOf(restriction))))
+        target_inst = frozenset({OWLNamedIndividual(IRI(NS, 'd195_12')),
+                                 OWLNamedIndividual(IRI(NS, 'd33_27'))})
+        self.assertEqual(inst, target_inst)
+
+        # OWLDataOneOf, OWLDatatype, OWLDataIntersectionOf
+        inst = frozenset(reasoner.instances(
+            OWLDataSomeValuesFrom(property=logp,
+                                  filler=OWLDataIntersectionOf((
+                                      OWLDataOneOf((OWLLiteral(6.26), OWLLiteral(6.07))),
+                                      DoubleOWLDatatype
+                                  )))))
+        target_inst = frozenset({OWLNamedIndividual(IRI(NS, 'd101')),
+                                 OWLNamedIndividual(IRI(NS, 'd109')),
+                                 OWLNamedIndividual(IRI(NS, 'd104')),
+                                 OWLNamedIndividual(IRI(NS, 'd180'))})
+        self.assertEqual(inst, target_inst)
+
+        # OWLDataUnionOf
+        restriction = owl_datatype_min_max_exclusive_restriction(5.07, 5.3)
+        inst = frozenset(reasoner.instances(
+            OWLDataSomeValuesFrom(property=logp,
+                                  filler=OWLDataUnionOf((
+                                      OWLDataOneOf((OWLLiteral(6.26), OWLLiteral(6.07))), restriction)))))
+        target_inst = frozenset({OWLNamedIndividual(IRI(NS, 'd101')),
+                                 OWLNamedIndividual(IRI(NS, 'd109')),
+                                 OWLNamedIndividual(IRI(NS, 'd92')),
+                                 OWLNamedIndividual(IRI(NS, 'd22')),
+                                 OWLNamedIndividual(IRI(NS, 'd104')),
+                                 OWLNamedIndividual(IRI(NS, 'd180'))})
+        self.assertEqual(inst, target_inst)
+
+    # def test_data_properties_time(self):
+    #     NS = "http://example.com/father#"
+    #     mgr = OWLOntologyManager_Owlready2()
+    #     onto = mgr.load_ontology(IRI.create("file://KGs/Family/father.owl"))
+    #
+    #     with onto._onto:
+    #         class birthDate(DataProperty):
+    #             range = [date]
+    #
+    #         class birthDateTime(DataProperty):
+    #             range = [datetime]
+    #
+    #         class age(DataProperty):
+    #             range = [Timedelta]
+    #
+    #     onto._onto.markus.birthDate = [date(year=1990, month=10, day=2)]
+    #     onto._onto.markus.birthDateTime = [datetime(year=1990, month=10, day=2, hour=10, minute=20, second=5)]
+    #     onto._onto.markus.age = [Timedelta(days=11315, hours=10, minutes=2)]
+    #
+    #     onto._onto.anna.birthDate = [date(year=1995, month=6, day=10)]
+    #     onto._onto.anna.birthDateTime = [datetime(year=1995, month=6, day=10, hour=2, minute=10)]
+    #     onto._onto.anna.age = [Timedelta(days=9490, hours=4)]
+    #
+    #     onto._onto.heinz.birthDate = [date(year=1986, month=6, day=10)]
+    #     onto._onto.heinz.birthDateTime = [datetime(year=1986, month=6, day=10, hour=10, second=10)]
+    #     onto._onto.heinz.age = [Timedelta(days=12775, hours=14, seconds=40)]
+    #
+    #     onto._onto.michelle.birthDate = [date(year=2000, month=1, day=4)]
+    #     onto._onto.michelle.birthDateTime = [datetime(year=2000, month=1, day=4, minute=4, second=10)]
+    #     onto._onto.michelle.age = [Timedelta(days=7665, hours=1, milliseconds=11)]
+    #
+    #     onto._onto.martin.birthDate = [date(year=1999, month=3, day=1)]
+    #     onto._onto.martin.birthDateTime = [datetime(year=1999, month=3, day=2, hour=20, minute=2, second=30)]
+    #     onto._onto.martin.age = [Timedelta(days=8030, minutes=2)]
+    #
+    #     birth_date = OWLDataProperty(IRI(NS, 'birthDate'))
+    #     birth_date_time = OWLDataProperty(IRI(NS, 'birthDateTime'))
+    #     age_ = OWLDataProperty(IRI(NS, 'age'))
+    #     markus = OWLNamedIndividual(IRI(NS, 'markus'))
+    #     anna = OWLNamedIndividual(IRI(NS, 'anna'))
+    #     heinz = OWLNamedIndividual(IRI(NS, 'heinz'))
+    #     michelle = OWLNamedIndividual(IRI(NS, 'michelle'))
+    #     martin = OWLNamedIndividual(IRI(NS, 'martin'))
+    #
+    #     base_reasoner = OWLReasoner_Owlready2(onto)
+    #     reasoner = OWLReasoner_FastInstanceChecker(onto, base_reasoner=base_reasoner)
+    #
+    #     restriction = owl_datatype_min_max_exclusive_restriction(date(year=1995, month=6, day=12),
+    #                                                         date(year=1999, month=3, day=2))
+    #     inst = frozenset(reasoner.instances(OWLDataSomeValuesFrom(property=birth_date,
+    #                                                               filler=restriction)))
+    #     target_inst = frozenset({martin})
+    #     self.assertEqual(inst, target_inst)
+    #
+    #     inst = frozenset(reasoner.instances(OWLDataSomeValuesFrom(property=birth_date,
+    #                                                               filler=OWLDataComplementOf(restriction))))
+    #     target_inst = frozenset({michelle, anna, heinz, markus})
+    #     self.assertEqual(inst, target_inst)
+    #
+    #     restriction = owl_datatype_max_inclusive_restriction(datetime(year=1990, month=10, day=2, hour=10,
+    #                                                               minute=20, second=5))
+    #     inst = frozenset(reasoner.instances(OWLDataSomeValuesFrom(property=birth_date_time,
+    #                                                               filler=restriction)))
+    #     target_inst = frozenset({markus, heinz})
+    #     self.assertEqual(inst, target_inst)
+    #
+    #     restriction_min = owl_datatype_min_exclusive_restriction(Timedelta(days=8030, minutes=1))
+    #     restriction_max = owl_datatype_max_exclusive_restriction(Timedelta(days=9490, hours=4, nanoseconds=1))
+    #     filler = OWLDataIntersectionOf([restriction_min, restriction_max, DurationOWLDatatype])
+    #     inst = frozenset(reasoner.instances(OWLDataSomeValuesFrom(property=age_, filler=filler)))
+    #     target_inst = frozenset({anna, martin})
+    #     self.assertEqual(inst, target_inst)
+
+    def test_sub_property_inclusion(self):
+        ns = "http://dl-learner.org/mutagenesis#"
+        mgr = OntologyManager()
+        onto = mgr.load_ontology(IRI.create("file://KGs/Mutagenesis/mutagenesis.owl"))
+
+        carbon_22 = OWLClass(IRI(ns, 'Carbon-22'))
+        compound = OWLClass(IRI(ns, 'Compound'))
+        benzene = OWLClass(IRI(ns, 'Benzene'))
+        has_structure = OWLObjectProperty(IRI(ns, 'hasStructure'))
+        super_has_structure = OWLObjectProperty(IRI(ns, 'superHasStucture'))
+        charge = OWLDataProperty(IRI(ns, 'charge'))
+        super_charge = OWLDataProperty(IRI.create(ns, 'super_charge'))
+        mgr.add_axiom(onto, OWLSubObjectPropertyOfAxiom(has_structure, super_has_structure))
+        mgr.add_axiom(onto, OWLSubDataPropertyOfAxiom(charge, super_charge))
+
+        # sub_property = True
+        base_reasoner = OntologyReasoner(onto)
+        reasoner = FastInstanceCheckerReasoner(onto, base_reasoner=base_reasoner, sub_properties=True)
+
+        # object property
+        ce = OWLObjectIntersectionOf([compound, OWLObjectSomeValuesFrom(super_has_structure, benzene)])
+        individuals = frozenset(reasoner.instances(ce))
+        self.assertEqual(len(individuals), 222)
+
+        # data property
+        ce = OWLObjectIntersectionOf([carbon_22, OWLDataHasValue(super_charge, OWLLiteral(-0.128))])
+        individuals = frozenset(reasoner.instances(ce))
+        self.assertEqual(len(individuals), 75)
+
+        # sub_property = False
+        base_reasoner = OntologyReasoner(onto)
+        reasoner = FastInstanceCheckerReasoner(onto, base_reasoner=base_reasoner, sub_properties=False)
+
+        # object property
+        ce = OWLObjectIntersectionOf([compound, OWLObjectSomeValuesFrom(super_has_structure, benzene)])
+        individuals = frozenset(reasoner.instances(ce))
+        self.assertEqual(len(individuals), 0)
+
+        # data property
+        ce = OWLObjectIntersectionOf([carbon_22, OWLDataHasValue(super_charge, OWLLiteral(-0.128))])
+        individuals = frozenset(reasoner.instances(ce))
+        self.assertEqual(len(individuals), 0)
+
+        mgr.remove_axiom(onto, OWLSubObjectPropertyOfAxiom(has_structure, super_has_structure))
+        mgr.remove_axiom(onto, OWLSubDataPropertyOfAxiom(charge, super_charge))
+
+    def test_inverse(self):
+        ns = "http://example.com/father#"
+        mgr = OntologyManager()
+        onto = mgr.load_ontology(IRI.create("file://KGs/Family/father.owl"))
+
+        has_child = OWLObjectProperty(IRI(ns, 'hasChild'))
+        has_child_inverse = OWLObjectProperty(IRI.create(ns, 'hasChild_inverse'))
+        mgr.add_axiom(onto, OWLInverseObjectPropertiesAxiom(has_child, has_child_inverse))
+
+        parents = {OWLNamedIndividual(IRI.create(ns, 'anna')),
+                   OWLNamedIndividual(IRI.create(ns, 'martin')),
+                   OWLNamedIndividual(IRI.create(ns, 'stefan')),
+                   OWLNamedIndividual(IRI.create(ns, 'markus'))}
+
+        base_reasoner = OntologyReasoner(onto)
+        reasoner = FastInstanceCheckerReasoner(onto, base_reasoner=base_reasoner, sub_properties=False)
+
+        expr = OWLObjectSomeValuesFrom(has_child, OWLThing)
+        expr_inverse = OWLObjectSomeValuesFrom(OWLObjectInverseOf(has_child_inverse), OWLThing)
+        parents_expr = frozenset(reasoner.instances(expr))
+        parents_expr_inverse = frozenset(reasoner.instances(expr_inverse))
+        self.assertEqual(parents_expr, parents)
+        self.assertEqual(parents_expr_inverse, parents)
+        # Removal not needed, just for completeness
+        mgr.remove_axiom(onto, OWLInverseObjectPropertiesAxiom(has_child, has_child_inverse))
+
+        # test sub properties
+        super_has_child = OWLObjectProperty(IRI(ns, 'super_hasChild'))
+        mgr.add_axiom(onto, OWLSubObjectPropertyOfAxiom(has_child, super_has_child))
+        super_has_child_inverse = OWLObjectProperty(IRI(ns, 'super_hasChild_inverse'))
+        mgr.add_axiom(onto, OWLInverseObjectPropertiesAxiom(super_has_child, super_has_child_inverse))
+
+        # False (sub properties not taken into account)
+        expr = OWLObjectSomeValuesFrom(super_has_child, OWLThing)
+        expr_inverse = OWLObjectSomeValuesFrom(OWLObjectInverseOf(super_has_child_inverse), OWLThing)
+        parents_expr = frozenset(reasoner.instances(expr))
+        parents_expr_inverse = frozenset(reasoner.instances(expr_inverse))
+        self.assertEqual(parents_expr, frozenset())
+        self.assertEqual(parents_expr_inverse, frozenset())
+
+        # True (sub properties taken into account)
+        reasoner = FastInstanceCheckerReasoner(onto, base_reasoner=base_reasoner, sub_properties=True)
+        expr = OWLObjectSomeValuesFrom(super_has_child, OWLThing)
+        expr_inverse = OWLObjectSomeValuesFrom(OWLObjectInverseOf(super_has_child_inverse), OWLThing)
+        parents_expr = frozenset(reasoner.instances(expr))
+        parents_expr_inverse = frozenset(reasoner.instances(expr_inverse))
+        self.assertEqual(parents_expr, parents)
+        self.assertEqual(parents_expr_inverse, parents)
+
+        mgr.remove_axiom(onto, OWLSubObjectPropertyOfAxiom(has_child, super_has_child))
+        mgr.remove_axiom(onto, OWLInverseObjectPropertiesAxiom(super_has_child, super_has_child_inverse))
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/test_owlapy_nnf.py b/tests/test_owlapy_nnf.py
index f090b6da..4d1ea261 100644
--- a/tests/test_owlapy_nnf.py
+++ b/tests/test_owlapy_nnf.py
@@ -34,7 +34,7 @@
 from owlapy.owl_literal import IntegerOWLDatatype, BooleanOWLDatatype, DoubleOWLDatatype, OWLLiteral
 from owlapy.owl_property import OWLObjectProperty, OWLDataProperty
 from owlapy.providers import owl_datatype_min_max_exclusive_restriction
-from owlapy.util import NNF
+from owlapy.utils import NNF
 
 from owlapy.owl_data_ranges import OWLDataComplementOf, OWLDataIntersectionOf, OWLDataUnionOf
 from owlapy.class_expression import OWLObjectHasValue, OWLObjectOneOf
diff --git a/tests/test_owlapy_ontology_management.py b/tests/test_owlapy_ontology_management.py
new file mode 100644
index 00000000..6eb09c3b
--- /dev/null
+++ b/tests/test_owlapy_ontology_management.py
@@ -0,0 +1,862 @@
+from datetime import date, datetime
+import unittest
+
+from pandas import Timedelta
+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
+
+import owlready2
+
+from owlapy.class_expression import OWLObjectOneOf, OWLObjectSomeValuesFrom, OWLThing, OWLObjectComplementOf, \
+    OWLObjectAllValuesFrom, OWLObjectHasValue, OWLClass, OWLDataAllValuesFrom, OWLDataHasValue, \
+    OWLDataOneOf, OWLDataSomeValuesFrom, OWLObjectExactCardinality, OWLObjectMaxCardinality, OWLObjectMinCardinality, \
+    OWLObjectIntersectionOf, OWLDataMaxCardinality, OWLDataMinCardinality, OWLObjectUnionOf, \
+    OWLDataExactCardinality
+from owlapy.iri import IRI
+from owlapy.owl_axiom import OWLSubDataPropertyOfAxiom, OWLInverseObjectPropertiesAxiom, OWLSubObjectPropertyOfAxiom, \
+    OWLObjectPropertyRangeAxiom, OWLSameIndividualAxiom, OWLClassAssertionAxiom, OWLEquivalentClassesAxiom, \
+    OWLDifferentIndividualsAxiom, OWLDisjointClassesAxiom, OWLDisjointDataPropertiesAxiom, \
+    OWLDisjointObjectPropertiesAxiom, OWLEquivalentDataPropertiesAxiom, OWLEquivalentObjectPropertiesAxiom, \
+    OWLDataPropertyDomainAxiom, OWLDataPropertyRangeAxiom, OWLSubClassOfAxiom, OWLObjectPropertyDomainAxiom, \
+    OWLDataPropertyAssertionAxiom, OWLObjectPropertyAssertionAxiom, OWLDeclarationAxiom
+from owlapy.owl_data_ranges import OWLDataComplementOf, OWLDataIntersectionOf, OWLDataUnionOf
+from owlapy.owl_individual import OWLNamedIndividual
+from owlapy.owl_literal import DoubleOWLDatatype, OWLLiteral, DurationOWLDatatype, IntegerOWLDatatype, \
+    BooleanOWLDatatype, DateTimeOWLDatatype, DateOWLDatatype
+from owlapy.owl_property import OWLObjectInverseOf, OWLObjectProperty, OWLDataProperty
+
+from owlapy.owl_ontology_manager import OntologyManager
+from owlapy.owl_ontology import ToOwlready2, FromOwlready2
+
+
+class Owlapy_Owlready2_Test(unittest.TestCase):
+
+    def test_equivalent_classes(self):
+        ns = "http://dl-learner.org/mutagenesis#"
+        mgr = OntologyManager()
+        onto = mgr.load_ontology(IRI.create("file://KGs/Mutagenesis/mutagenesis.owl"))
+        reasoner = OntologyReasoner(onto)
+
+        atom = OWLClass(IRI(ns, 'Atom'))
+        bond = OWLClass(IRI(ns, 'Bond'))
+        ball3 = OWLClass(IRI(ns, 'Ball3'))
+        benzene = OWLClass(IRI(ns, 'Benzene'))
+        compound = OWLClass(IRI(ns, 'Compound'))
+        has_atom = OWLObjectProperty(IRI(ns, 'hasAtom'))
+
+        mgr.add_axiom(onto, OWLEquivalentClassesAxiom([OWLObjectUnionOf([atom, bond]), compound, atom, bond]))
+        mgr.add_axiom(onto, OWLEquivalentClassesAxiom([bond, benzene]))
+        mgr.add_axiom(onto, OWLEquivalentClassesAxiom([bond, OWLObjectSomeValuesFrom(has_atom, ball3)]))
+
+        classes = frozenset({atom, compound, bond})
+        target_classes = frozenset(reasoner.equivalent_classes(benzene, only_named=True))
+        self.assertEqual(classes, target_classes)
+
+        classes = frozenset({OWLObjectUnionOf([atom, bond]), atom, compound, bond,
+                             OWLObjectSomeValuesFrom(has_atom, ball3)})
+        target_classes = frozenset(reasoner.equivalent_classes(benzene, only_named=False))
+        self.assertEqual(classes, target_classes)
+
+        classes = frozenset({bond, atom, compound, benzene})
+        target_classes = frozenset(reasoner.equivalent_classes(OWLObjectUnionOf([atom, bond]), only_named=True))
+        self.assertEqual(classes, target_classes)
+
+        classes = frozenset({atom, compound, bond, benzene, OWLObjectSomeValuesFrom(has_atom, ball3)})
+        target_classes = frozenset(reasoner.equivalent_classes(OWLObjectUnionOf([atom, bond]), only_named=False))
+        self.assertEqual(classes, target_classes)
+
+        classes = frozenset({bond, atom, compound, benzene})
+        target_classes = frozenset(reasoner.equivalent_classes(OWLObjectSomeValuesFrom(has_atom, ball3),
+                                                               only_named=True))
+        self.assertEqual(classes, target_classes)
+
+        classes = frozenset({atom, compound, bond, benzene, OWLObjectUnionOf([atom, bond])})
+        target_classes = frozenset(reasoner.equivalent_classes(OWLObjectSomeValuesFrom(has_atom, ball3),
+                                                               only_named=False))
+        self.assertEqual(classes, target_classes)
+
+    def test_sub_classes(self):
+        ns = "http://dl-learner.org/mutagenesis#"
+        mgr = OntologyManager()
+        onto = mgr.load_ontology(IRI.create("file://KGs/Mutagenesis/mutagenesis.owl"))
+        reasoner = OntologyReasoner(onto)
+
+        bond = OWLClass(IRI(ns, 'Bond'))
+        ball3 = OWLClass(IRI(ns, 'Ball3'))
+        benzene = OWLClass(IRI(ns, 'Benzene'))
+        has_atom = OWLObjectProperty(IRI(ns, 'hasAtom'))
+
+        mgr.add_axiom(onto, OWLSubClassOfAxiom(benzene, OWLObjectUnionOf([bond, bond])))
+        mgr.add_axiom(onto, OWLSubClassOfAxiom(OWLObjectUnionOf([bond, bond]), ball3))
+        mgr.add_axiom(onto, OWLSubClassOfAxiom(ball3, OWLObjectSomeValuesFrom(has_atom, ball3)))
+
+        # Named class
+        # Direct, only named
+        classes = frozenset({})
+        target_classes = frozenset(reasoner.sub_classes(ball3, direct=True, only_named=True))
+        self.assertEqual(classes, target_classes)
+
+        # non Direct, only named
+        classes = frozenset({benzene})
+        target_classes = frozenset(reasoner.sub_classes(ball3, direct=False, only_named=True))
+        self.assertEqual(classes, target_classes)
+
+        # Direct, not only named
+        classes = frozenset({OWLObjectUnionOf([bond, bond])})
+        target_classes = frozenset(reasoner.sub_classes(ball3, direct=True, only_named=False))
+        self.assertEqual(classes, target_classes)
+
+        # non Direct, not only named
+        classes = frozenset({OWLObjectUnionOf([bond, bond]), benzene})
+        target_classes = frozenset(reasoner.sub_classes(ball3, direct=False, only_named=False))
+        self.assertEqual(classes, target_classes)
+
+        # Complex class expression
+        # Direct, only named
+        classes = frozenset({ball3})
+        target_classes = frozenset(reasoner.sub_classes(OWLObjectSomeValuesFrom(has_atom, ball3),
+                                                        direct=True, only_named=True))
+        self.assertEqual(classes, target_classes)
+
+        # non Direct, only named
+        classes = frozenset({ball3, benzene})
+        target_classes = frozenset(reasoner.sub_classes(OWLObjectSomeValuesFrom(has_atom, ball3),
+                                                        direct=False, only_named=True))
+        self.assertEqual(classes, target_classes)
+
+        # Direct, not only named
+        classes = frozenset({ball3})
+        target_classes = frozenset(reasoner.sub_classes(OWLObjectSomeValuesFrom(has_atom, ball3),
+                                                        direct=True, only_named=False))
+        self.assertEqual(classes, target_classes)
+
+        # non Direct, not only named
+        classes = frozenset({OWLObjectUnionOf([bond, bond]), benzene, ball3})
+        target_classes = frozenset(reasoner.sub_classes(OWLObjectSomeValuesFrom(has_atom, ball3),
+                                                        direct=False, only_named=False))
+        self.assertEqual(classes, target_classes)
+
+    def test_super_classes(self):
+        ns = "http://dl-learner.org/mutagenesis#"
+        mgr = OntologyManager()
+        onto = mgr.load_ontology(IRI.create("file://KGs/Mutagenesis/mutagenesis.owl"))
+        reasoner = OntologyReasoner(onto)
+
+        bond = OWLClass(IRI(ns, 'Bond'))
+        ball3 = OWLClass(IRI(ns, 'Ball3'))
+        benzene = OWLClass(IRI(ns, 'Benzene'))
+        ring_structure = OWLClass(IRI(ns, 'RingStructure'))
+        has_atom = OWLObjectProperty(IRI(ns, 'hasAtom'))
+
+        mgr.add_axiom(onto, OWLSubClassOfAxiom(OWLObjectSomeValuesFrom(has_atom, ball3), benzene))
+        mgr.add_axiom(onto, OWLSubClassOfAxiom(benzene, OWLObjectUnionOf([bond, bond])))
+        mgr.add_axiom(onto, OWLSubClassOfAxiom(OWLObjectUnionOf([bond, bond]), ball3))
+
+        # Named class
+        # Direct, only named
+        classes = frozenset({ring_structure})
+        target_classes = frozenset(reasoner.super_classes(benzene, direct=True, only_named=True))
+        self.assertEqual(classes, target_classes)
+
+        # non Direct, only named
+        classes = frozenset({ring_structure, OWLThing, ball3})
+        target_classes = frozenset(reasoner.super_classes(benzene, direct=False, only_named=True))
+        self.assertEqual(classes, target_classes)
+
+        # Direct, not only named
+        classes = frozenset({OWLObjectUnionOf([bond, bond]), ring_structure})
+        target_classes = frozenset(reasoner.super_classes(benzene, direct=True, only_named=False))
+        self.assertEqual(classes, target_classes)
+
+        # non Direct, not only named
+        classes = frozenset({OWLObjectUnionOf([bond, bond]), ball3, ring_structure, OWLThing})
+        target_classes = frozenset(reasoner.super_classes(benzene, direct=False, only_named=False))
+        self.assertEqual(classes, target_classes)
+
+        # Complex class expression
+        # Direct, only named
+        classes = frozenset({benzene})
+        target_classes = frozenset(reasoner.super_classes(OWLObjectSomeValuesFrom(has_atom, ball3),
+                                                          direct=True, only_named=True))
+        self.assertEqual(classes, target_classes)
+
+        # non Direct, only named
+        classes = frozenset({ring_structure, OWLThing, benzene, ball3})
+        target_classes = frozenset(reasoner.super_classes(OWLObjectSomeValuesFrom(has_atom, ball3),
+                                                          direct=False, only_named=True))
+        self.assertEqual(classes, target_classes)
+
+        # Direct, not only named
+        classes = frozenset({benzene})
+        target_classes = frozenset(reasoner.super_classes(OWLObjectSomeValuesFrom(has_atom, ball3),
+                                                          direct=True, only_named=False))
+        self.assertEqual(classes, target_classes)
+
+        # non Direct, not only named
+        classes = frozenset({OWLObjectUnionOf([bond, bond]), ball3, ring_structure, OWLThing, benzene})
+        target_classes = frozenset(reasoner.super_classes(OWLObjectSomeValuesFrom(has_atom, ball3),
+                                                          direct=False, only_named=False))
+        self.assertEqual(classes, target_classes)
+
+    def test_sub_object_properties(self):
+        ns = "http://www.biopax.org/examples/glycolysis#"
+        mgr = OntologyManager()
+        onto = mgr.load_ontology(IRI.create("file://KGs/Biopax/biopax.owl"))
+        reasoner = OntologyReasoner(onto)
+
+        participants = OWLObjectProperty(IRI.create(ns, 'PARTICIPANTS'))
+        target_props = frozenset({OWLObjectProperty(IRI(ns, 'COFACTOR')),
+                                  OWLObjectProperty(IRI(ns, 'CONTROLLED')),
+                                  OWLObjectProperty(IRI(ns, 'CONTROLLER')),
+                                  OWLObjectProperty(IRI(ns, 'LEFT')),
+                                  OWLObjectProperty(IRI(ns, 'RIGHT'))})
+        self.assertEqual(frozenset(reasoner.sub_object_properties(participants, direct=True)), target_props)
+        self.assertEqual(frozenset(reasoner.sub_object_properties(participants, direct=False)), target_props)
+
+    def test_instances(self):
+        ns = "http://example.com/father#"
+        mgr = OntologyManager()
+        onto = mgr.load_ontology(IRI.create("file://KGs/Family/father.owl"))
+        reasoner = OntologyReasoner(onto)
+
+        inst = frozenset(reasoner.instances(OWLThing))
+        target_inst = frozenset({OWLNamedIndividual(IRI(ns, 'anna')),
+                                 OWLNamedIndividual(IRI(ns, 'heinz')),
+                                 OWLNamedIndividual(IRI(ns, 'markus')),
+                                 OWLNamedIndividual(IRI(ns, 'martin')),
+                                 OWLNamedIndividual(IRI(ns, 'michelle')),
+                                 OWLNamedIndividual(IRI(ns, 'stefan'))})
+        self.assertEqual(inst, target_inst)
+
+        inst = frozenset(reasoner.instances(OWLClass(IRI.create(ns, "male"))))
+        target_inst = frozenset({OWLNamedIndividual(IRI(ns, 'heinz')), OWLNamedIndividual(IRI(ns, 'martin')),
+                                 OWLNamedIndividual(IRI(ns, 'markus')), OWLNamedIndividual(IRI(ns, 'stefan'))})
+        self.assertEqual(inst, target_inst)
+
+    def test_types(self):
+        ns = "http://example.com/father#"
+        mgr = OntologyManager()
+        onto = mgr.load_ontology(IRI.create("file://KGs/Family/father.owl"))
+        reasoner = OntologyReasoner(onto)
+
+        types = frozenset(reasoner.types(OWLNamedIndividual(IRI.create(ns, 'stefan'))))
+        target_types = frozenset({OWLThing, OWLClass(IRI(ns, 'male')), OWLClass(IRI(ns, 'person'))})
+        self.assertEqual(target_types, types)
+
+    def test_object_values(self):
+        ns = "http://example.com/father#"
+        mgr = OntologyManager()
+        onto = mgr.load_ontology(IRI.create("file://KGs/Family/father.owl"))
+        reasoner = OntologyReasoner(onto)
+
+        stefan = OWLNamedIndividual(IRI.create(ns, 'stefan'))
+        markus = OWLNamedIndividual(IRI.create(ns, 'markus'))
+        heinz = OWLNamedIndividual(IRI(ns, 'heinz'))
+        has_child = OWLObjectProperty(IRI.create(ns, 'hasChild'))
+        super_has_child = OWLObjectProperty(IRI.create(ns, 'super_hasChild'))
+        mgr.add_axiom(onto, OWLSubObjectPropertyOfAxiom(has_child, super_has_child))
+
+        kids = frozenset(reasoner.object_property_values(stefan, has_child))
+        target_kids = frozenset({markus})
+        self.assertEqual(target_kids, kids)
+
+        no_kids = frozenset(reasoner.object_property_values(heinz, has_child))
+        self.assertEqual(frozenset(), no_kids)
+
+        # test sub property
+        kids = frozenset(reasoner.object_property_values(stefan, super_has_child, direct=True))
+        target_kids = frozenset()
+        self.assertEqual(target_kids, kids)
+
+        kids = frozenset(reasoner.object_property_values(stefan, super_has_child, direct=False))
+        target_kids = frozenset({markus})
+        self.assertEqual(target_kids, kids)
+
+        # test inverse
+        has_child_inverse = OWLObjectProperty(IRI.create(ns, 'hasChild_inverse'))
+        mgr.add_axiom(onto, OWLInverseObjectPropertiesAxiom(has_child, has_child_inverse))
+        parents = frozenset(reasoner.object_property_values(markus, OWLObjectInverseOf(has_child), direct=True))
+        target_parents = frozenset({stefan})
+        self.assertEqual(target_parents, parents)
+        # Remove again for completeness, would not be necessary
+        mgr.remove_axiom(onto, OWLInverseObjectPropertiesAxiom(has_child, has_child_inverse))
+
+        # test inverse with sub property
+        # Setup:
+        # hasChild subPropertyOf super_hasChild
+        # hasChild inverseOf hasChild_inverse
+        # super_hasChild inverseOf super_hasChild_inverse
+        super_has_child_inverse = OWLObjectProperty(IRI.create(ns, 'super_hasChild_inverse'))
+        mgr.add_axiom(onto, OWLInverseObjectPropertiesAxiom(super_has_child, super_has_child_inverse))
+
+        parents = frozenset(reasoner.object_property_values(stefan,
+                                                            OWLObjectInverseOf(super_has_child_inverse),
+                                                            direct=True))
+        target_parents = frozenset()
+        self.assertEqual(target_parents, parents)
+
+        parents = frozenset(reasoner.object_property_values(stefan,
+                                                            OWLObjectInverseOf(super_has_child_inverse),
+                                                            direct=False))
+        target_parents = frozenset({markus})
+        self.assertEqual(target_parents, parents)
+        mgr.remove_axiom(onto, OWLInverseObjectPropertiesAxiom(super_has_child, super_has_child_inverse))
+        mgr.remove_axiom(onto, OWLSubObjectPropertyOfAxiom(has_child, super_has_child))
+
+    def test_data_values(self):
+        ns = "http://dl-learner.org/mutagenesis#"
+        mgr = OntologyManager()
+        onto = mgr.load_ontology(IRI.create("file://KGs/Mutagenesis/mutagenesis.owl"))
+        base_reasoner = OntologyReasoner(onto)
+        reasoner = FastInstanceCheckerReasoner(onto, base_reasoner)
+
+        d100_1 = OWLNamedIndividual(IRI.create(ns, 'd100_1'))
+        charge = OWLDataProperty(IRI.create(ns, 'charge'))
+        super_charge = OWLDataProperty(IRI.create(ns, 'super_charge'))
+        mgr.add_axiom(onto, OWLSubDataPropertyOfAxiom(charge, super_charge))
+
+        values = frozenset(reasoner.data_property_values(d100_1, charge))
+        targets = frozenset({OWLLiteral(-0.128)})
+        self.assertEqual(targets, values)
+
+        d100 = OWLNamedIndividual(IRI(ns, 'd100'))
+        values = frozenset(reasoner.data_property_values(d100, charge))
+        self.assertEqual(frozenset(), values)
+
+        # super_charge sub property
+        values = frozenset(reasoner.data_property_values(d100_1, super_charge, direct=False))
+        targets = frozenset({OWLLiteral(-0.128)})
+        self.assertEqual(targets, values)
+
+        values = frozenset(reasoner.data_property_values(d100_1, super_charge, direct=True))
+        targets = frozenset()
+        self.assertEqual(targets, values)
+        mgr.remove_axiom(onto, OWLSubDataPropertyOfAxiom(charge, super_charge))
+
+    def test_all_data_values(self):
+        ns = "http://dl-learner.org/mutagenesis#"
+        mgr = OntologyManager()
+        onto = mgr.load_ontology(IRI.create("file://KGs/Mutagenesis/mutagenesis.owl"))
+        base_reasoner = OntologyReasoner(onto)
+        reasoner = FastInstanceCheckerReasoner(onto, base_reasoner)
+
+        charge = OWLDataProperty(IRI.create(ns, 'charge'))
+        super_charge = OWLDataProperty(IRI.create(ns, 'super_charge'))
+        mgr.add_axiom(onto, OWLSubDataPropertyOfAxiom(charge, super_charge))
+
+        values = frozenset(reasoner.all_data_property_values(charge, direct=True))
+        self.assertEqual(529, len(values))
+
+        values = frozenset(reasoner.all_data_property_values(super_charge, direct=True))
+        self.assertEqual(0, len(values))
+
+        values = frozenset(reasoner.all_data_property_values(super_charge, direct=False))
+        self.assertEqual(529, len(values))
+        mgr.remove_axiom(onto, OWLSubDataPropertyOfAxiom(charge, super_charge))
+
+    def test_ind_object_properties(self):
+        ns = "http://example.com/father#"
+        mgr = OntologyManager()
+        onto = mgr.load_ontology(IRI.create("file://KGs/Family/father.owl"))
+        base_reasoner = OntologyReasoner(onto)
+        reasoner = FastInstanceCheckerReasoner(onto, base_reasoner)
+
+        stefan = OWLNamedIndividual(IRI.create(ns, 'stefan'))
+        has_child = OWLObjectProperty(IRI.create(ns, 'hasChild'))
+        super_has_child = OWLObjectProperty(IRI.create(ns, 'super_hasChild'))
+        mgr.add_axiom(onto, OWLSubObjectPropertyOfAxiom(has_child, super_has_child))
+
+        properties = frozenset(reasoner.ind_object_properties(stefan, direct=True))
+        target_properties = frozenset({has_child})
+        self.assertEqual(target_properties, properties)
+
+        properties = frozenset(reasoner.ind_object_properties(stefan, direct=False))
+        target_properties = frozenset({has_child, super_has_child})
+        self.assertEqual(target_properties, properties)
+        mgr.remove_axiom(onto, OWLSubObjectPropertyOfAxiom(has_child, super_has_child))
+
+    def test_ind_data_properties(self):
+        ns = "http://dl-learner.org/mutagenesis#"
+        mgr = OntologyManager()
+        onto = mgr.load_ontology(IRI.create("file://KGs/Mutagenesis/mutagenesis.owl"))
+        base_reasoner = OntologyReasoner(onto)
+        reasoner = FastInstanceCheckerReasoner(onto, base_reasoner)
+
+        d100_1 = OWLNamedIndividual(IRI.create(ns, 'd100_1'))
+        charge = OWLDataProperty(IRI.create(ns, 'charge'))
+        super_charge = OWLDataProperty(IRI.create(ns, 'super_charge'))
+        mgr.add_axiom(onto, OWLSubDataPropertyOfAxiom(charge, super_charge))
+
+        properties = frozenset(reasoner.ind_data_properties(d100_1, direct=True))
+        target_properties = frozenset({charge})
+        self.assertEqual(target_properties, properties)
+
+        properties = frozenset(reasoner.ind_data_properties(d100_1, direct=False))
+        target_properties = frozenset({charge, super_charge})
+        self.assertEqual(target_properties, properties)
+        mgr.remove_axiom(onto, OWLSubDataPropertyOfAxiom(charge, super_charge))
+
+    def test_add_remove_axiom(self):
+        ns = "http://example.com/father#"
+        mgr = OntologyManager()
+        onto = mgr.load_ontology(IRI.create("file://KGs/Family/father.owl"))
+        reasoner = OntologyReasoner(onto)
+
+        markus = OWLNamedIndividual(IRI.create(ns, 'markus'))
+        michelle = OWLNamedIndividual(IRI.create(ns, 'michelle'))
+        anna = OWLNamedIndividual(IRI.create(ns, 'anna'))
+        marius = OWLNamedIndividual(IRI.create(ns, 'marius'))
+        person1 = OWLNamedIndividual(IRI.create(ns, 'person1'))
+
+        has_child = OWLObjectProperty(IRI.create(ns, 'hasChild'))
+        has_sibling = OWLObjectProperty(IRI.create(ns, 'has_sibling'))
+        age = OWLDataProperty(IRI.create(ns, 'age'))
+        test1 = OWLDataProperty(IRI.create(ns, 'test1'))
+
+        male = OWLClass(IRI(ns, 'male'))
+        brother = OWLClass(IRI(ns, 'brother'))
+        sister = OWLClass(IRI(ns, 'sister'))
+        person = OWLClass(IRI(ns, 'person'))
+        person_sibling = OWLClass(IRI(ns, 'PersonWithASibling'))
+        animal = OWLClass(IRI(ns, 'animal'))
+        aerial_animal = OWLClass(IRI(ns, 'aerialAnimal'))
+
+        self.assertNotIn(sister, list(onto.classes_in_signature()))
+        mgr.add_axiom(onto, OWLClassAssertionAxiom(anna, sister))
+        self.assertIn(sister, list(onto.classes_in_signature()))
+        self.assertIn(anna, list(reasoner.instances(sister)))
+        self.assertIn(sister, list(reasoner.types(anna)))
+        mgr.remove_axiom(onto, OWLClassAssertionAxiom(anna, sister))
+        self.assertNotIn(anna, list(reasoner.instances(sister)))
+        self.assertNotIn(sister, list(reasoner.types(anna)))
+
+        self.assertNotIn(michelle, list(reasoner.instances(sister)))
+        mgr.add_axiom(onto, OWLClassAssertionAxiom(michelle, sister))
+        self.assertIn(michelle, list(reasoner.instances(sister)))
+        self.assertIn(sister, list(reasoner.types(michelle)))
+        mgr.remove_axiom(onto, OWLClassAssertionAxiom(michelle, sister))
+        self.assertNotIn(michelle, list(reasoner.instances(sister)))
+        self.assertNotIn(sister, list(reasoner.types(michelle)))
+
+        self.assertFalse(list(reasoner.object_property_values(michelle, has_child)))
+        mgr.add_axiom(onto, OWLObjectPropertyAssertionAxiom(michelle, has_child, anna))
+        self.assertIn(anna, list(reasoner.object_property_values(michelle, has_child)))
+        mgr.remove_axiom(onto, OWLObjectPropertyAssertionAxiom(michelle, has_child, anna))
+        self.assertNotIn(anna, list(reasoner.object_property_values(michelle, has_child)))
+        mgr.remove_axiom(onto, OWLObjectPropertyAssertionAxiom(michelle, has_child, anna))
+
+        self.assertNotIn(has_sibling, list(onto.object_properties_in_signature()))
+        self.assertNotIn(marius, list(onto.individuals_in_signature()))
+        mgr.add_axiom(onto, OWLObjectPropertyAssertionAxiom(marius, has_sibling, michelle))
+        self.assertIn(has_sibling, list(onto.object_properties_in_signature()))
+        self.assertIn(marius, list(onto.individuals_in_signature()))
+        self.assertIn(michelle, list(reasoner.object_property_values(marius, has_sibling)))
+        mgr.remove_axiom(onto, OWLObjectPropertyAssertionAxiom(marius, has_sibling, michelle))
+        self.assertNotIn(michelle, list(reasoner.object_property_values(marius, has_sibling)))
+
+        self.assertNotIn(age, list(onto.data_properties_in_signature()))
+        mgr.add_axiom(onto, OWLDataPropertyAssertionAxiom(markus, age, OWLLiteral(30)))
+        self.assertIn(age, list(onto.data_properties_in_signature()))
+        self.assertIn(OWLLiteral(30), list(reasoner.data_property_values(markus, age)))
+        mgr.remove_axiom(onto, OWLDataPropertyAssertionAxiom(markus, age, OWLLiteral(30)))
+        self.assertNotIn(OWLLiteral(30), list(reasoner.data_property_values(markus, age)))
+
+        self.assertNotIn(OWLLiteral(31), list(reasoner.data_property_values(anna, age)))
+        mgr.add_axiom(onto, OWLDataPropertyAssertionAxiom(anna, age, OWLLiteral(31)))
+        self.assertIn(OWLLiteral(31), list(reasoner.data_property_values(anna, age)))
+        mgr.remove_axiom(onto, OWLDataPropertyAssertionAxiom(anna, age, OWLLiteral(31)))
+        self.assertNotIn(OWLLiteral(31), list(reasoner.data_property_values(anna, age)))
+
+        self.assertNotIn(brother, list(onto.classes_in_signature()))
+        self.assertNotIn(animal, list(onto.classes_in_signature()))
+        self.assertNotIn(aerial_animal, list(onto.classes_in_signature()))
+        mgr.add_axiom(onto, OWLSubClassOfAxiom(brother, male))
+        mgr.add_axiom(onto, OWLSubClassOfAxiom(aerial_animal, animal))
+        self.assertIn(brother, list(reasoner.sub_classes(male)))
+        self.assertIn(aerial_animal, list(reasoner.sub_classes(animal)))
+        self.assertIn(male, list(reasoner.super_classes(brother)))
+        self.assertIn(animal, list(reasoner.super_classes(aerial_animal)))
+        mgr.remove_axiom(onto, OWLSubClassOfAxiom(brother, male))
+        self.assertNotIn(brother, list(reasoner.sub_classes(male)))
+        self.assertNotIn(male, list(reasoner.super_classes(brother)))
+
+        self.assertNotIn(has_sibling, list(reasoner.sub_object_properties(has_child)))
+        mgr.add_axiom(onto, OWLSubObjectPropertyOfAxiom(has_sibling, has_child))
+        self.assertIn(has_sibling, list(reasoner.sub_object_properties(has_child)))
+        mgr.remove_axiom(onto, OWLSubObjectPropertyOfAxiom(has_sibling, has_child))
+        self.assertNotIn(has_sibling, list(reasoner.sub_object_properties(has_child)))
+
+        self.assertNotIn(OWLObjectUnionOf([person, person_sibling]),
+                         list(reasoner.object_property_domains(has_sibling)))
+        mgr.add_axiom(onto, OWLObjectPropertyDomainAxiom(has_sibling, OWLObjectUnionOf([person, person_sibling])))
+        self.assertIn(OWLObjectUnionOf([person, person_sibling]),
+                      list(reasoner.object_property_domains(has_sibling, direct=True)))
+        mgr.remove_axiom(onto, OWLObjectPropertyDomainAxiom(has_sibling, OWLObjectUnionOf([person, person_sibling])))
+        self.assertNotIn(OWLObjectUnionOf([person, person_sibling]),
+                         list(reasoner.object_property_domains(has_sibling, direct=True)))
+
+        self.assertNotIn(sister, list(reasoner.object_property_ranges(has_sibling)))
+        mgr.add_axiom(onto, OWLObjectPropertyRangeAxiom(has_sibling, sister))
+        self.assertIn(sister, list(reasoner.object_property_ranges(has_sibling)))
+        mgr.remove_axiom(onto, OWLObjectPropertyRangeAxiom(has_sibling, sister))
+        self.assertNotIn(sister, list(reasoner.object_property_ranges(has_sibling)))
+
+        self.assertNotIn(person, list(reasoner.data_property_domains(age)))
+        mgr.add_axiom(onto, OWLDataPropertyDomainAxiom(age, person))
+        self.assertIn(person, list(reasoner.data_property_domains(age)))
+        mgr.remove_axiom(onto, OWLDataPropertyDomainAxiom(age, person))
+        self.assertNotIn(person, list(reasoner.data_property_domains(age)))
+
+        self.assertFalse(list(reasoner.data_property_ranges(age)))
+        mgr.add_axiom(onto, OWLDataPropertyRangeAxiom(age, OWLDataUnionOf([IntegerOWLDatatype, DateOWLDatatype])))
+        self.assertIn(OWLDataUnionOf([IntegerOWLDatatype, DateOWLDatatype]), list(reasoner.data_property_ranges(age)))
+        mgr.remove_axiom(onto, OWLDataPropertyRangeAxiom(age, OWLDataUnionOf([IntegerOWLDatatype, DateOWLDatatype])))
+        self.assertNotIn(OWLDataUnionOf([IntegerOWLDatatype, DateOWLDatatype]),
+                         list(reasoner.data_property_ranges(age)))
+
+        self.assertFalse(list(reasoner.equivalent_classes(brother)))
+        mgr.add_axiom(onto, OWLEquivalentClassesAxiom([brother, male]))
+        self.assertIn(male, list(reasoner.equivalent_classes(brother)))
+        mgr.remove_axiom(onto, OWLEquivalentClassesAxiom([brother, male]))
+        self.assertNotIn(male, list(reasoner.equivalent_classes(brother)))
+
+        self.assertFalse(list(reasoner.equivalent_object_properties(has_child)))
+        mgr.add_axiom(onto, OWLEquivalentObjectPropertiesAxiom([has_child, has_sibling]))
+        self.assertIn(has_sibling, list(reasoner.equivalent_object_properties(has_child)))
+        mgr.remove_axiom(onto, OWLEquivalentObjectPropertiesAxiom([has_child, has_sibling]))
+        self.assertNotIn(has_sibling, list(reasoner.equivalent_object_properties(has_child)))
+
+        self.assertFalse(list(reasoner.equivalent_data_properties(age)))
+        mgr.add_axiom(onto, OWLEquivalentDataPropertiesAxiom([age, test1]))
+        self.assertIn(test1, list(reasoner.equivalent_data_properties(age)))
+        mgr.remove_axiom(onto, OWLEquivalentDataPropertiesAxiom([age, test1]))
+        self.assertNotIn(test1, list(reasoner.equivalent_data_properties(age)))
+
+        self.assertFalse(list(reasoner.same_individuals(markus)))
+        mgr.add_axiom(onto, OWLSameIndividualAxiom([markus, anna, person1]))
+        self.assertEqual({anna, person1}, set(reasoner.same_individuals(markus)))
+        mgr.remove_axiom(onto, OWLSameIndividualAxiom([markus, anna, person1]))
+        self.assertFalse(set(reasoner.same_individuals(markus)))
+
+        self.assertFalse(list(reasoner.disjoint_classes(brother)))
+        self.assertFalse(list(reasoner.disjoint_classes(person)))
+        mgr.add_axiom(onto, OWLDisjointClassesAxiom([brother, sister, aerial_animal]))
+        self.assertEqual({sister, aerial_animal}, set(reasoner.disjoint_classes(brother)))
+        mgr.remove_axiom(onto, OWLDisjointClassesAxiom([brother, sister, aerial_animal]))
+        self.assertFalse(set(reasoner.disjoint_classes(brother)))
+        mgr.add_axiom(onto, OWLDisjointClassesAxiom([person, animal]))
+        self.assertEqual({animal, aerial_animal}, set(reasoner.disjoint_classes(person)))
+        mgr.remove_axiom(onto, OWLDisjointClassesAxiom([person, animal]))
+        self.assertFalse(set(reasoner.disjoint_classes(person)))
+
+        self.assertFalse(list(reasoner.disjoint_object_properties(has_sibling)))
+        self.assertFalse(list(reasoner.disjoint_object_properties(has_child)))
+        mgr.add_axiom(onto, OWLDisjointObjectPropertiesAxiom([has_child, has_sibling]))
+        self.assertIn(has_sibling, set(reasoner.disjoint_object_properties(has_child)))
+        self.assertIn(has_child, set(reasoner.disjoint_object_properties(has_sibling)))
+        mgr.remove_axiom(onto, OWLDisjointObjectPropertiesAxiom([has_child, has_sibling]))
+        self.assertNotIn(has_sibling, set(reasoner.disjoint_object_properties(has_child)))
+        self.assertNotIn(has_child, set(reasoner.disjoint_object_properties(has_sibling)))
+
+        self.assertFalse(list(reasoner.disjoint_data_properties(age)))
+        self.assertFalse(list(reasoner.disjoint_data_properties(test1)))
+        mgr.add_axiom(onto, OWLDisjointDataPropertiesAxiom([age, test1]))
+        self.assertIn(test1, set(reasoner.disjoint_data_properties(age)))
+        self.assertIn(age, set(reasoner.disjoint_data_properties(test1)))
+        mgr.remove_axiom(onto, OWLDisjointDataPropertiesAxiom([age, test1]))
+        self.assertNotIn(test1, set(reasoner.disjoint_data_properties(age)))
+        self.assertNotIn(age, set(reasoner.disjoint_data_properties(test1)))
+
+        self.assertFalse(list(reasoner.different_individuals(markus)))
+        self.assertFalse(list(reasoner.different_individuals(michelle)))
+        mgr.add_axiom(onto, OWLDifferentIndividualsAxiom([markus, michelle]))
+        mgr.add_axiom(onto, OWLDifferentIndividualsAxiom([markus, anna, marius]))
+        self.assertEqual({michelle, anna, marius}, set(reasoner.different_individuals(markus)))
+        self.assertEqual({markus}, set(reasoner.different_individuals(michelle)))
+        mgr.remove_axiom(onto, OWLDifferentIndividualsAxiom([markus, michelle]))
+        mgr.remove_axiom(onto, OWLDifferentIndividualsAxiom([markus, anna, marius]))
+        self.assertFalse(set(reasoner.different_individuals(markus)))
+        self.assertFalse(set(reasoner.different_individuals(michelle)))
+
+    def test_mapping(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'))
+
+        to_owlready = ToOwlready2(world=onto._world)
+
+        ce = male
+        owlready_ce = onto._onto.male
+        self.assertEqual(owlready_ce, to_owlready.map_concept(ce))
+
+        ce = OWLObjectUnionOf((male, female))
+        owlready_ce = onto._onto.male | onto._onto.female
+        self.assertEqual(owlready_ce, to_owlready.map_concept(ce))
+
+        ce = OWLObjectIntersectionOf((male, female))
+        owlready_ce = onto._onto.male & onto._onto.female
+        self.assertEqual(owlready_ce, to_owlready.map_concept(ce))
+
+        ce = OWLObjectSomeValuesFrom(has_child, OWLThing)
+        owlready_ce = onto._onto.hasChild.some(owlready2.owl.Thing)
+        self.assertEqual(owlready_ce, to_owlready.map_concept(ce))
+
+        ce = OWLObjectAllValuesFrom(has_child, male)
+        owlready_ce = onto._onto.hasChild.only(onto._onto.male)
+        self.assertEqual(owlready_ce, to_owlready.map_concept(ce))
+
+        ce = male.get_object_complement_of()
+        owlready_ce = owlready2.Not(onto._onto.male)
+        self.assertEqual(owlready_ce, to_owlready.map_concept(ce))
+
+        ce = OWLObjectOneOf([OWLNamedIndividual(IRI.create(ns, 'martin')),
+                             OWLNamedIndividual(IRI.create(ns, 'michelle'))])
+        owlready_ce = owlready2.OneOf([onto._onto.martin, onto._onto.michelle])
+        self.assertEqual(owlready_ce, to_owlready.map_concept(ce))
+
+        ce = OWLObjectMinCardinality(2, has_child, male)
+        owlready_ce = onto._onto.hasChild.min(2, onto._onto.male)
+        self.assertEqual(owlready_ce, to_owlready.map_concept(ce))
+
+        ce = OWLObjectMaxCardinality(5, has_child, female)
+        owlready_ce = onto._onto.hasChild.max(5, onto._onto.female)
+        self.assertEqual(owlready_ce, to_owlready.map_concept(ce))
+
+        ce = OWLObjectExactCardinality(3, has_child, OWLThing)
+        owlready_ce = onto._onto.hasChild.exactly(3, owlready2.owl.Thing)
+        self.assertEqual(owlready_ce, to_owlready.map_concept(ce))
+
+        ce = OWLObjectHasValue(has_child, OWLNamedIndividual(IRI.create(ns, 'markus')))
+        owlready_ce = onto._onto.hasChild.value(onto._onto.markus)
+        self.assertEqual(owlready_ce, to_owlready.map_concept(ce))
+
+    def test_mapping_data_properties(self):
+        ns = "http://dl-learner.org/mutagenesis#"
+        mgr = OntologyManager()
+        onto = mgr.load_ontology(IRI.create("file://KGs/Mutagenesis/mutagenesis.owl"))
+
+        act = OWLDataProperty(IRI(ns, 'act'))
+        charge = OWLDataProperty(IRI(ns, 'charge'))
+
+        to_owlready = ToOwlready2(world=onto._world)
+
+        # owlready2 defines no equal or hash method for ConstrainedDatatype, just using the __dict__ attribute
+        # should be sufficient for the purpose of these tests
+        def constraint_datatype_eq(self, other):
+            return isinstance(other, owlready2.ConstrainedDatatype) and self.__dict__ == other.__dict__
+        setattr(owlready2.ConstrainedDatatype, '__eq__', constraint_datatype_eq)
+        setattr(owlready2.ConstrainedDatatype, '__hash__', lambda self: hash(frozenset(self.__dict__.items())))
+
+        ce = OWLDataSomeValuesFrom(act, DateOWLDatatype)
+        owlready_ce = onto._onto.act.some(date)
+        self.assertEqual(owlready_ce, to_owlready.map_concept(ce))
+
+        res = owl_datatype_min_inclusive_restriction(20)
+        ce = OWLDataAllValuesFrom(charge, OWLDataComplementOf(res))
+        owlready_ce = onto._onto.charge.only(owlready2.Not(owlready2.ConstrainedDatatype(int, min_inclusive=20)))
+        self.assertEqual(owlready_ce, to_owlready.map_concept(ce))
+
+        res_both = owl_datatype_min_max_exclusive_restriction(0.5, 1)
+        ce = OWLDataAllValuesFrom(charge, OWLDataUnionOf([res, res_both]))
+        owlready_ce = onto._onto.charge.only(
+            owlready2.Or([owlready2.ConstrainedDatatype(int, min_inclusive=20),
+                          owlready2.ConstrainedDatatype(float, min_exclusive=0.5, max_exclusive=1.0)]))
+        self.assertEqual(owlready_ce, to_owlready.map_concept(ce))
+
+        res = owl_datatype_max_inclusive_restriction(1.2)
+        oneof = OWLDataOneOf([OWLLiteral(2.3), OWLLiteral(5.9), OWLLiteral(7.2)])
+        ce = OWLDataAllValuesFrom(charge, OWLDataIntersectionOf([res, oneof]))
+        owlready_ce = onto._onto.charge.only(owlready2.ConstrainedDatatype(float, max_inclusive=1.2) &
+                                             owlready2.OneOf([2.3, 5.9, 7.2]))
+        self.assertEqual(owlready_ce, to_owlready.map_concept(ce))
+
+        ce = OWLDataSomeValuesFrom(charge, OWLDataIntersectionOf([res, BooleanOWLDatatype]))
+        owlready_ce = onto._onto.charge.some(
+            owlready2.And([owlready2.ConstrainedDatatype(float, max_inclusive=1.2), bool]))
+        self.assertEqual(owlready_ce, to_owlready.map_concept(ce))
+
+        ce = OWLDataMinCardinality(2, act, res)
+        owlready_ce = onto._onto.act.min(2, owlready2.ConstrainedDatatype(float, max_inclusive=1.2))
+        self.assertEqual(owlready_ce, to_owlready.map_concept(ce))
+
+        ce = OWLDataMaxCardinality(4, charge, DurationOWLDatatype)
+        owlready_ce = onto._onto.charge.max(4, Timedelta)
+        self.assertEqual(owlready_ce, to_owlready.map_concept(ce))
+
+        ce = OWLDataExactCardinality(3, charge, OWLDataComplementOf(IntegerOWLDatatype))
+        owlready_ce = onto._onto.charge.exactly(3, owlready2.Not(int))
+        self.assertEqual(owlready_ce, to_owlready.map_concept(ce))
+
+    def test_mapping_rev(self):
+        ns = "http://example.com/father#"
+        mgr = OntologyManager()
+        onto = mgr.load_ontology(IRI.create("file://KGs/Family/father.owl"))
+
+        male = onto._onto.male
+        female = onto._onto.female
+        has_child = onto._onto.hasChild
+
+        from_owlready = FromOwlready2()
+
+        ce = male | female
+        owl_ce = OWLObjectUnionOf((OWLClass(IRI.create(ns, 'male')), OWLClass(IRI.create(ns, 'female'))))
+        self.assertEqual(owl_ce, from_owlready.map_concept(ce))
+
+        ce = male & female
+        owl_ce = OWLObjectIntersectionOf((OWLClass(IRI.create(ns, 'male')), OWLClass(IRI.create(ns, 'female'))))
+        self.assertEqual(owl_ce, from_owlready.map_concept(ce))
+
+        ce = has_child.some(owlready2.owl.Thing)
+        owl_ce = OWLObjectSomeValuesFrom(OWLObjectProperty(IRI(ns, 'hasChild')), OWLThing)
+        self.assertEqual(owl_ce, from_owlready.map_concept(ce))
+
+        ce = has_child.only(male)
+        owl_ce = OWLObjectAllValuesFrom(OWLObjectProperty(IRI(ns, 'hasChild')), OWLClass(IRI.create(ns, 'male')))
+        self.assertEqual(owl_ce, from_owlready.map_concept(ce))
+
+        ce = owlready2.Not(male)
+        owl_ce = OWLObjectComplementOf(OWLClass(IRI(ns, 'male')))
+        self.assertEqual(owl_ce, from_owlready.map_concept(ce))
+
+        ce = owlready2.OneOf([onto._onto.markus, onto._onto.anna])
+        owl_ce = OWLObjectOneOf([OWLNamedIndividual(IRI.create(ns, 'markus')),
+                                 OWLNamedIndividual(IRI.create(ns, 'anna'))])
+        self.assertEqual(owl_ce, from_owlready.map_concept(ce))
+
+        ce = onto._onto.hasChild.min(2, onto._onto.male)
+        owl_ce = OWLObjectMinCardinality(2, OWLObjectProperty(IRI(ns, 'hasChild')), OWLClass(IRI(ns, 'male')))
+        self.assertEqual(owl_ce, from_owlready.map_concept(ce))
+
+        ce = onto._onto.hasChild.max(5, onto._onto.female)
+        owl_ce = OWLObjectMaxCardinality(5, OWLObjectProperty(IRI(ns, 'hasChild')), OWLClass(IRI(ns, 'female')))
+        self.assertEqual(owl_ce, from_owlready.map_concept(ce))
+
+        ce = onto._onto.hasChild.exactly(3, owlready2.owl.Thing)
+        owl_ce = OWLObjectExactCardinality(3, OWLObjectProperty(IRI(ns, 'hasChild')), OWLThing)
+        self.assertEqual(owl_ce, from_owlready.map_concept(ce))
+
+        ce = has_child.value(onto._onto.markus)
+        owl_ce = OWLObjectHasValue(OWLObjectProperty(IRI(ns, 'hasChild')), OWLNamedIndividual(IRI.create(ns, 'markus')))
+        self.assertEqual(owl_ce, from_owlready.map_concept(ce))
+
+    def test_mapping_rev_data_properties(self):
+        ns = "http://dl-learner.org/mutagenesis#"
+        mgr = OntologyManager()
+        onto = mgr.load_ontology(IRI.create("file://KGs/Mutagenesis/mutagenesis.owl"))
+
+        act = onto._onto.act
+        charge = onto._onto.charge
+
+        from_owlready = FromOwlready2()
+
+        ce = act.some(float)
+        owl_ce = OWLDataSomeValuesFrom(OWLDataProperty(IRI(ns, 'act')), DoubleOWLDatatype)
+        self.assertEqual(owl_ce, from_owlready.map_concept(ce))
+
+        ce = charge.only(owlready2.Not(owlready2.ConstrainedDatatype(int, max_inclusive=2)))
+        res = owl_datatype_max_inclusive_restriction(2)
+        owl_ce = OWLDataAllValuesFrom(OWLDataProperty(IRI(ns, 'charge')), OWLDataComplementOf(res))
+        self.assertEqual(owl_ce, from_owlready.map_concept(ce))
+
+        ce = charge.some(owlready2.Not(owlready2.ConstrainedDatatype(int, max_inclusive=2)) |
+                         owlready2.ConstrainedDatatype(float, min_inclusive=2.1, max_inclusive=2.2))
+        res = owl_datatype_max_inclusive_restriction(2)
+        res2 = owl_datatype_min_max_inclusive_restriction(2.1, 2.2)
+        owl_ce = OWLDataSomeValuesFrom(OWLDataProperty(IRI(ns, 'charge')),
+                                       OWLDataUnionOf([OWLDataComplementOf(res), res2]))
+        self.assertEqual(owl_ce, from_owlready.map_concept(ce))
+
+        ce = act.only(owlready2.Not(owlready2.ConstrainedDatatype(int, max_inclusive=2)) & datetime)
+        owl_ce = OWLDataAllValuesFrom(OWLDataProperty(IRI(ns, 'act')),
+                                      OWLDataIntersectionOf([OWLDataComplementOf(res), DateTimeOWLDatatype]))
+        self.assertEqual(owl_ce, from_owlready.map_concept(ce))
+
+        ce = act.some(owlready2.Not(owlready2.OneOf([1, 2, 3])) & Timedelta)
+        values = OWLDataOneOf([OWLLiteral(1), OWLLiteral(2), OWLLiteral(3)])
+        owl_ce = OWLDataSomeValuesFrom(OWLDataProperty(IRI(ns, 'act')),
+                                       OWLDataIntersectionOf([OWLDataComplementOf(values), DurationOWLDatatype]))
+        self.assertEqual(owl_ce, from_owlready.map_concept(ce))
+
+        ce = onto._onto.act.value(19.5)
+        owl_ce = OWLDataHasValue(OWLDataProperty(IRI(ns, 'act')), OWLLiteral(19.5))
+        self.assertEqual(owl_ce, from_owlready.map_concept(ce))
+
+        ce = onto._onto.act.min(2, owlready2.ConstrainedDatatype(int, max_inclusive=2))
+        owl_ce = OWLDataMinCardinality(2, OWLDataProperty(IRI(ns, 'act')), res)
+        self.assertEqual(owl_ce, from_owlready.map_concept(ce))
+
+        ce = onto._onto.charge.max(5, date)
+        owl_ce = OWLDataMaxCardinality(5, OWLDataProperty(IRI(ns, 'charge')), DateOWLDatatype)
+        self.assertEqual(owl_ce, from_owlready.map_concept(ce))
+
+        ce = onto._onto.charge.exactly(3, owlready2.Not(float))
+        owl_ce = OWLDataExactCardinality(3, OWLDataProperty(IRI(ns, 'charge')), OWLDataComplementOf(DoubleOWLDatatype))
+        self.assertEqual(owl_ce, from_owlready.map_concept(ce))
+
+
+class Owlapy_Owlready2_ComplexCEInstances_Test(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)
+
+        inst = frozenset(reasoner.instances(female))
+        target_inst = frozenset({OWLNamedIndividual(IRI(ns, 'anna')),
+                                 OWLNamedIndividual(IRI(ns, 'michelle'))})
+        self.assertEqual(inst, target_inst)
+
+        inst = frozenset(reasoner.instances(
+            OWLObjectIntersectionOf((male, OWLObjectSomeValuesFrom(property=has_child, filler=female)))))
+        target_inst = frozenset({OWLNamedIndividual(IRI(ns, 'markus'))})
+        self.assertEqual(inst, target_inst)
+
+        inst = frozenset(reasoner.instances(
+            OWLObjectIntersectionOf((female, OWLObjectSomeValuesFrom(property=has_child, filler=OWLThing)))))
+        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
new file mode 100644
index 00000000..6b5dd21b
--- /dev/null
+++ b/tests/test_owlapy_owl2sparql_converter.py
@@ -0,0 +1,412 @@
+import unittest
+
+import rdflib.plugins.sparql.sparql
+
+
+from owlapy.class_expression import OWLObjectSomeValuesFrom, OWLThing, \
+                                     OWLObjectMaxCardinality, OWLObjectMinCardinality, OWLObjectIntersectionOf
+from owlapy.iri import IRI
+from owlapy.owl_property import OWLObjectProperty
+
+from owlapy.owl_ontology_manager import OntologyManager
+from owlapy.owl_reasoner import OntologyReasoner, FastInstanceCheckerReasoner
+from owlapy.parser import DLSyntaxParser
+from owlapy.converter import Owl2SparqlConverter
+from rdflib import Graph
+
+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
+def check_reasoner_instances_in_sparql_results(sparql_results: rdflib.query.Result,
+                                               reasoner_results: set) -> bool:
+    sparql_results_set = set()
+    for row in sparql_results:
+        individual_iri = row[rdflib.Variable('x')]
+        individual_iri_str = individual_iri.toPython()
+        if "#" in individual_iri_str:
+            sparql_results_set.add(individual_iri_str.split('#')[-1])
+        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:
+            print()
+            print(result.iri.get_short_form(), "Not found in SPARQL results set")
+            return False
+    return True
+
+
+class Test_Owl2SparqlConverter(unittest.TestCase):
+    _root_var_ = '?x'
+    maxDiff = None
+
+    def test_as_query(self):
+        prop_s = OWLObjectProperty(IRI.create("http://dl-learner.org/carcinogenesis#hasBond"))
+        ce = OWLObjectSomeValuesFrom(
+            prop_s,
+            OWLObjectIntersectionOf((
+                OWLObjectMaxCardinality(
+                    4,
+                    OWLObjectProperty(IRI.create("http://dl-learner.org/carcinogenesis#hasAtom")),
+                    OWLThing
+                ),
+                OWLObjectMinCardinality(
+                    1,
+                    OWLObjectProperty(IRI.create("http://dl-learner.org/carcinogenesis#hasAtom")),
+                    OWLThing
+                )
+            ))
+        )
+        cnv = Owl2SparqlConverter()
+        root_var = "?x"
+        query = cnv.as_query(root_var, ce, False)
+        print(query)
+        query_t = """SELECT
+ DISTINCT ?x WHERE { 
+?x <http://dl-learner.org/carcinogenesis#hasBond> ?s_1 . 
+{
+{ SELECT ?s_1 WHERE { 
+?s_1 <http://dl-learner.org/carcinogenesis#hasAtom> ?s_2 . 
+?s_2 a <http://www.w3.org/2002/07/owl#Thing> . 
+ } GROUP BY ?s_1 HAVING ( COUNT ( ?s_2 ) <= 4 ) }
+} UNION {
+?s_1 ?s_3 ?s_4 . 
+FILTER NOT EXISTS { 
+?s_1 <http://dl-learner.org/carcinogenesis#hasAtom> ?s_5 . 
+?s_5 a <http://www.w3.org/2002/07/owl#Thing> . 
+ } }
+{ SELECT ?s_1 WHERE { 
+?s_1 <http://dl-learner.org/carcinogenesis#hasAtom> ?s_6 . 
+?s_6 a <http://www.w3.org/2002/07/owl#Thing> . 
+ } GROUP BY ?s_1 HAVING ( COUNT ( ?s_6 ) >= 1 ) }
+ }"""
+#         query_t = """SELECT
+#  DISTINCT ?x WHERE {
+# ?x <http://dl-learner.org/carcinogenesis#hasBond> ?s_1 .
+# ?s_1 <http://dl-learner.org/carcinogenesis#hasAtom> ?s_2 .
+# ?s_1 <http://dl-learner.org/carcinogenesis#hasAtom> ?s_3 .
+#  }
+# GROUP BY ?x
+#  HAVING (
+# COUNT ( ?s_2 ) <= 4 && COUNT ( ?s_3 ) >= 1
+#  )"""
+        self.assertEqual(query, query_t)  # add assertion here
+
+    # def test_Single(self):
+    #     # rdf graph - using rdflib
+    #     family_rdf_graph = Graph()
+    #     family_rdf_graph.parse(location=PATH_FAMILY)
+    #     # knowledge base - using OWLReasoner
+    #     mgr = OntologyManager()
+    #     onto = mgr.load_ontology(IRI.create(PATH_FAMILY))
+    #     base_reasoner = Reasoner(onto)
+    #     family_kb_reasoner = FastInstanceCheckerReasoner(onto, base_reasoner=base_reasoner, negation_default=True)
+    #
+    #     ce_str = "Brother"
+    #     ce_parsed = DLSyntaxParser(namespace="http://www.benchmark.org/family#").parse_expression(expression_str=ce_str)
+    #     actual_query = Owl2SparqlConverter().as_query(root_variable=self._root_var_, ce=ce_parsed, count=False,
+    #                                                   values=None, named_individuals=True)
+    #     expected_query = """SELECT DISTINCT ?x
+    #                         WHERE {
+    #                             ?x <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.benchmark.org/family#Brother> .
+    #                         }"""
+    #
+    #     sparql_results_actual = family_rdf_graph.query(actual_query)
+    #     sparql_results_expected = family_rdf_graph.query(expected_query)
+    #     reasoner_results = set(family_kb_reasoner.instances(ce_parsed))
+    #
+    #     self.assertEqual(len(sparql_results_actual), len(sparql_results_expected))
+    #     self.assertEqual(len(sparql_results_actual), len(reasoner_results))
+    #     self.assertTrue(check_reasoner_instances_in_sparql_results(sparql_results_actual, reasoner_results))
+    #
+    #     ce_str = "Male"
+    #     ce_parsed = DLSyntaxParser(namespace="http://www.benchmark.org/family#").parse_expression(expression_str=ce_str)
+    #     actual_query = Owl2SparqlConverter().as_query(root_variable=self._root_var_, ce=ce_parsed, count=False,
+    #                                                   values=None, named_individuals=True)
+    #     expected_query = """SELECT DISTINCT ?x
+    #                                 WHERE {
+    #                                     ?x <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.benchmark.org/family#Male> .
+    #                                 }"""
+    #
+    #     sparql_results_actual = family_rdf_graph.query(actual_query)
+    #     sparql_results_expected = family_rdf_graph.query(expected_query)
+    #     reasoner_results = set(family_kb_reasoner.instances(ce_parsed))
+    #
+    #     self.assertEqual(len(sparql_results_actual), len(sparql_results_expected))
+    #     self.assertEqual(len(sparql_results_actual), len(reasoner_results))
+    #     self.assertTrue(check_reasoner_instances_in_sparql_results(sparql_results_actual, reasoner_results))
+    #
+    # def test_Intersection(self):
+    #     # rdf graph - using rdflib
+    #     family_rdf_graph = Graph()
+    #     family_rdf_graph.parse(location=PATH_FAMILY)
+    #     # knowledge base - using OWLReasoner
+    #     mgr = OntologyManager()
+    #     onto = mgr.load_ontology(IRI.create(PATH_FAMILY))
+    #     base_reasoner = Reasoner(onto)
+    #     family_kb_reasoner = FastInstanceCheckerReasoner(onto, base_reasoner=base_reasoner, negation_default=True)
+    #
+    #     ce_str = "Brother ⊓ Father"
+    #     ce_parsed = DLSyntaxParser(namespace="http://www.benchmark.org/family#").parse_expression(expression_str=ce_str)
+    #     actual_query = Owl2SparqlConverter().as_query(root_variable=self._root_var_, ce=ce_parsed, count=False,
+    #                                                   values=None, named_individuals=True)
+    #     expected_query = """SELECT DISTINCT ?x
+    #                         WHERE {
+    #                             ?x <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.benchmark.org/family#Brother> .
+    #                             ?x <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.benchmark.org/family#Father> .
+    #                         }"""
+    #
+    #     sparql_results_actual = family_rdf_graph.query(actual_query)
+    #     sparql_results_expected = family_rdf_graph.query(expected_query)
+    #     reasoner_results = set(family_kb_reasoner.instances(ce_parsed))
+    #
+    #     self.assertEqual(len(sparql_results_actual), len(sparql_results_expected))
+    #     self.assertEqual(len(sparql_results_actual), len(reasoner_results))
+    #     self.assertTrue(check_reasoner_instances_in_sparql_results(sparql_results_actual, reasoner_results))
+    #
+    # def test_Union(self):
+    #     # rdf graph - using rdflib
+    #     family_rdf_graph = Graph()
+    #     family_rdf_graph.parse(location=PATH_FAMILY)
+    #     # knowledge base - using OWLReasoner
+    #     mgr = OntologyManager()
+    #     onto = mgr.load_ontology(IRI.create(PATH_FAMILY))
+    #     base_reasoner = Reasoner(onto)
+    #     family_kb_reasoner = FastInstanceCheckerReasoner(onto, base_reasoner=base_reasoner, negation_default=True)
+    #
+    #     ce_str = "Sister ⊔ Mother"
+    #     ce_parsed = DLSyntaxParser(namespace="http://www.benchmark.org/family#").parse_expression(expression_str=ce_str)
+    #     actual_query = Owl2SparqlConverter().as_query(root_variable=self._root_var_, ce=ce_parsed, count=False,
+    #                                                   values=None, named_individuals=True)
+    #     expected_query = """SELECT DISTINCT ?x
+    #                         WHERE {
+    #                             { ?x <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.benchmark.org/family#Sister> . }
+    #                             UNION
+    #                             { ?x <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.benchmark.org/family#Mother> . }
+    #                         }"""
+    #
+    #     sparql_results_actual = family_rdf_graph.query(actual_query)
+    #     sparql_results_expected = family_rdf_graph.query(expected_query)
+    #     reasoner_results = set(family_kb_reasoner.instances(ce_parsed))
+    #
+    #     self.assertEqual(len(sparql_results_actual), len(sparql_results_expected))
+    #     self.assertEqual(len(sparql_results_actual), len(reasoner_results))
+    #     self.assertTrue(check_reasoner_instances_in_sparql_results(sparql_results_actual, reasoner_results))
+    #
+    # def test_Complement(self):
+    #     # rdf graph - using rdflib
+    #     family_rdf_graph = Graph()
+    #     family_rdf_graph.parse(location=PATH_FAMILY)
+    #     # knowledge base - using OWLReasoner
+    #     mgr = OntologyManager()
+    #     onto = mgr.load_ontology(IRI.create(PATH_FAMILY))
+    #     base_reasoner = Reasoner(onto)
+    #     family_kb_reasoner = FastInstanceCheckerReasoner(onto, base_reasoner=base_reasoner, negation_default=True)
+    #
+    #     ce_str = "¬Mother"
+    #     ce_parsed = DLSyntaxParser(namespace="http://www.benchmark.org/family#").parse_expression(expression_str=ce_str)
+    #     actual_query = Owl2SparqlConverter().as_query(root_variable=self._root_var_, ce=ce_parsed, count=False,
+    #                                                   values=None, named_individuals=True)
+    #     expected_query = """SELECT DISTINCT ?x
+    #                         WHERE {
+    #                                 ?x a <http://www.w3.org/2002/07/owl#NamedIndividual> .
+    #                                 ?x ?p ?o .
+    #                                 FILTER NOT EXISTS { ?x a <http://www.benchmark.org/family#Mother> . }
+    #                         }"""
+    #
+    #     sparql_results_actual = family_rdf_graph.query(actual_query)
+    #     sparql_results_expected = family_rdf_graph.query(expected_query)
+    #     reasoner_results = set(family_kb_reasoner.instances(ce_parsed))
+    #
+    #     self.assertEqual(len(sparql_results_actual), len(sparql_results_expected))
+    #     self.assertEqual(len(sparql_results_actual), len(reasoner_results))
+    #     self.assertTrue(check_reasoner_instances_in_sparql_results(sparql_results_actual, reasoner_results))
+    #
+    # def test_Exists(self):
+    #     # rdf graph - using rdflib
+    #     family_rdf_graph = Graph()
+    #     family_rdf_graph.parse(location=PATH_FAMILY)
+    #     # knowledge base - using OWLReasoner
+    #     mgr = OntologyManager()
+    #     onto = mgr.load_ontology(IRI.create(PATH_FAMILY))
+    #     base_reasoner = Reasoner(onto)
+    #     family_kb_reasoner = FastInstanceCheckerReasoner(onto, base_reasoner=base_reasoner, negation_default=True)
+    #
+    #     ce_str = "∃hasChild.Male"
+    #     ce_parsed = DLSyntaxParser(namespace="http://www.benchmark.org/family#").parse_expression(expression_str=ce_str)
+    #     actual_query = Owl2SparqlConverter().as_query(root_variable=self._root_var_, ce=ce_parsed, count=False,
+    #                                                   values=None, named_individuals=True)
+    #     expected_query = """SELECT DISTINCT ?x
+    #                         WHERE {
+    #                             ?x <http://www.benchmark.org/family#hasChild> ?s .
+    #                             ?s a <http://www.benchmark.org/family#Male> .
+    #                         }"""
+    #
+    #     sparql_results_actual = family_rdf_graph.query(actual_query)
+    #     sparql_results_expected = family_rdf_graph.query(expected_query)
+    #     reasoner_results = set(family_kb_reasoner.instances(ce_parsed))
+    #
+    #     self.assertEqual(len(sparql_results_actual), len(sparql_results_expected))
+    #     self.assertEqual(len(sparql_results_actual), len(reasoner_results))
+    #     self.assertTrue(check_reasoner_instances_in_sparql_results(sparql_results_actual, reasoner_results))
+    #
+    # def test_ForAll(self):
+    #     # rdf graph - using rdflib
+    #     family_rdf_graph = Graph()
+    #     family_rdf_graph.parse(location=PATH_FAMILY)
+    #     # knowledge base - using OWLReasoner
+    #     mgr = OntologyManager()
+    #     onto = mgr.load_ontology(IRI.create(PATH_FAMILY))
+    #     base_reasoner = Reasoner(onto)
+    #     family_kb_reasoner = FastInstanceCheckerReasoner(onto, base_reasoner=base_reasoner, negation_default=True)
+    #
+    #     ce_str = "∀hasChild.Male"
+    #     ce_parsed = DLSyntaxParser(namespace="http://www.benchmark.org/family#").parse_expression(expression_str=ce_str)
+    #     actual_query = Owl2SparqlConverter().as_query(root_variable=self._root_var_, ce=ce_parsed, count=False,
+    #                                                   values=None, named_individuals=True)
+    #     expected_query = """SELECT DISTINCT ?x
+    #                         WHERE {
+    #                             ?x a <http://www.w3.org/2002/07/owl#NamedIndividual> .
+    #                             {
+    #                                 ?x <http://www.benchmark.org/family#hasChild> ?s0 .
+    #                                 {
+    #                                     SELECT ?x (COUNT(DISTINCT ?s1) as ?c1)
+    #                                     WHERE {
+    #                                         ?x <http://www.benchmark.org/family#hasChild> ?s1 .
+    #                                         ?s1 a <http://www.benchmark.org/family#Male> .
+    #                                     }
+    #                                     GROUP BY ?x
+    #                                 }
+    #                                 {
+    #                                     SELECT ?x (COUNT(DISTINCT ?s2) as ?c2)
+    #                                     WHERE {
+    #                                         ?x <http://www.benchmark.org/family#hasChild> ?s2 .
+    #                                     }
+    #                                     GROUP BY ?x
+    #                                 }
+    #                                 FILTER (?c1 = ?c2)
+    #                             }
+    #                             UNION
+    #                             {
+    #                                 ?x ?p1 ?o1 FILTER NOT EXISTS { ?x <http://www.benchmark.org/family#hasChild> ?o2 . }
+    #                             }
+    #                         }"""
+    #
+    #     sparql_results_actual = family_rdf_graph.query(actual_query)
+    #     sparql_results_expected = family_rdf_graph.query(expected_query)
+    #     reasoner_results = set(family_kb_reasoner.instances(ce_parsed))
+    #
+    #     self.assertEqual(len(sparql_results_actual), len(sparql_results_expected))
+    #     self.assertEqual(len(sparql_results_actual), len(reasoner_results))
+    #     self.assertTrue(check_reasoner_instances_in_sparql_results(sparql_results_actual, reasoner_results))
+    #
+    # def test_ExistsForAllDeMorgan(self):
+    #     # rdf graph - using rdflib
+    #     family_rdf_graph = Graph()
+    #     family_rdf_graph.parse(location=PATH_FAMILY)
+    #     # knowledge base - using OWLReasoner
+    #     mgr = OntologyManager()
+    #     onto = mgr.load_ontology(IRI.create(PATH_FAMILY))
+    #     base_reasoner = Reasoner(onto)
+    #     family_kb_reasoner = FastInstanceCheckerReasoner(onto, base_reasoner=base_reasoner, negation_default=True)
+    #
+    #     ce_str = "∀hasChild.Male"
+    #     ce_parsed = DLSyntaxParser(namespace="http://www.benchmark.org/family#").parse_expression(expression_str=ce_str)
+    #     actual_query = Owl2SparqlConverter().as_query(root_variable=self._root_var_, ce=ce_parsed, count=False,
+    #                                                   values=None, named_individuals=True)
+    #     ce_str_neg = "¬∃hasChild.¬Male"
+    #     ce_parsed_neg = DLSyntaxParser(namespace="http://www.benchmark.org/family#").parse_expression(
+    #         expression_str=ce_str_neg)
+    #     # actual_query_neg = Owl2SparqlConverter().as_query(root_variable=self._root_var_, ce=ce_parsed_neg,
+    #     #                                                   count=False, values=None, named_individuals=True)
+    #
+    #     sparql_results = family_rdf_graph.query(actual_query)
+    #     # sparql_results_neg = family_rdf_graph.query(actual_query_neg)
+    #     reasoner_results = set(family_kb_reasoner.instances(ce_parsed))
+    #     reasoner_results_neg = set(family_kb_reasoner.instances(ce_parsed_neg))
+    #
+    #     self.assertEqual(len(sparql_results), len(reasoner_results))
+    #     self.assertEqual(len(sparql_results), len(reasoner_results_neg))
+    #
+    #     self.assertTrue(check_reasoner_instances_in_sparql_results(sparql_results, reasoner_results))
+    #     self.assertTrue(check_reasoner_instances_in_sparql_results(sparql_results, reasoner_results_neg))
+    #
+    #     # the commented out assertion fails because of a bug in rdf_lib (https://github.com/RDFLib/rdflib/issues/2484).
+    #     # in apache jena, the queries return results of the same size
+    #     # self.assertTrue(len(sparql_results_neg), len(sparql_results))
+    #
+    # def test_LengthyConcepts(self):
+    #     # rdf graph - using rdflib
+    #     family_rdf_graph = Graph()
+    #     family_rdf_graph.parse(location=PATH_FAMILY)
+    #     # knowledge base - using OWLReasoner
+    #     mgr = OntologyManager()
+    #     onto = mgr.load_ontology(IRI.create(PATH_FAMILY))
+    #     base_reasoner = Reasoner(onto)
+    #     family_kb_reasoner = FastInstanceCheckerReasoner(onto, base_reasoner=base_reasoner, negation_default=True)
+    #
+    #     concepts = [
+    #         "∀hasChild.(∃hasChild.¬Male)",
+    #         "∀hasChild.(∃hasChild.(Brother ⊔ Sister))",
+    #         "(Male ⊔ Male) ⊓ (Male ⊓ Male)",
+    #         "(Male ⊓ Male) ⊔ (Male ⊓ Male)",
+    #         "(Male ⊓ Male) ⊓ (Male ⊓ Male)",
+    #         "(Male ⊓ Male) ⊔ ((≥ 2 hasChild.(Male ⊔ Female)) ⊓ (≥ 3 hasChild.(Male ⊔ Female)))",
+    #     ]
+    #
+    #     for ce_str in concepts:
+    #         ce_parsed = DLSyntaxParser(namespace="http://www.benchmark.org/family#").parse_expression(
+    #             expression_str=ce_str)
+    #         actual_query = Owl2SparqlConverter().as_query(root_variable=self._root_var_, ce=ce_parsed, count=False,
+    #                                                       values=None, named_individuals=True)
+    #
+    #         sparql_results_actual = family_rdf_graph.query(actual_query)
+    #         reasoner_results = set(family_kb_reasoner.instances(ce_parsed))
+    #
+    #         self.assertEqual(len(sparql_results_actual), len(reasoner_results), ce_str)
+    #         self.assertTrue(check_reasoner_instances_in_sparql_results(sparql_results_actual, reasoner_results), ce_str)
+    #
+    # def test_QualifiedCardinalityRestriction(self):
+    #     # rdf graph - using rdflib
+    #     family_rdf_graph = Graph()
+    #     family_rdf_graph.parse(location=PATH_FAMILY)
+    #     # knowledge base - using OWLReasoner
+    #     mgr = OntologyManager()
+    #     onto = mgr.load_ontology(IRI.create(PATH_FAMILY))
+    #     base_reasoner = Reasoner(onto)
+    #     family_kb_reasoner = FastInstanceCheckerReasoner(onto, base_reasoner=base_reasoner, negation_default=True)
+    #
+    #     concepts = [
+    #         "≥ 2 hasChild.(Male ⊔ Female)",
+    #         "≥ 2 hasChild.(Male ⊔ Female)",
+    #         "≤ 3 hasChild.Female"
+    #     ]
+    #
+    #     for ce_str in concepts:
+    #         ce_parsed = DLSyntaxParser(namespace="http://www.benchmark.org/family#").parse_expression(
+    #             expression_str=ce_str)
+    #         actual_query = Owl2SparqlConverter().as_query(root_variable=self._root_var_, ce=ce_parsed, count=False,
+    #                                                       values=None, named_individuals=True)
+    #
+    #         sparql_results_actual = family_rdf_graph.query(actual_query)
+    #         reasoner_results = set(family_kb_reasoner.instances(ce_parsed))
+    #
+    #         self.assertEqual(len(sparql_results_actual), len(reasoner_results), ce_str)
+    #         self.assertTrue(check_reasoner_instances_in_sparql_results(sparql_results_actual, reasoner_results), ce_str)
+    #
+    #     # need to further investigate the case for 0
+    #     # ce_str = "≥ 0 hasChild.Male"
+    #     # ce_parsed = DLSyntaxParser(namespace="http://www.benchmark.org/family#").parse_expression(expression_str=
+    #     #                                                                                           ce_str)
+    #     # actual_query = Owl2SparqlConverter().as_query(root_variable=self._root_var_, ce=ce_parsed, count=False,
+    #     #                                               values=None, named_individuals=True)
+    #     #
+    #     # sparql_results_actual = family_rdf_graph.query(actual_query)
+    #     # reasoner_results = set(family_kb_reasoner.instances(ce_parsed))
+    #     #
+    #     # self.assertEqual(len(sparql_results_actual), len(reasoner_results))
+    #     # self.assertTrue(check_reasoner_instances_in_sparql_results(sparql_results_actual, reasoner_results))
+
+
+if __name__ == '__main__':
+    unittest.main()