I encountered a difficulty: I created an object property in owlready2 (for example: has_parent) and defined it as inverse of another one (for example: has_child). When I relate an individual (“John” for example) to the other (“Kevin” for example) by the first property to create “John has_parent Kevin” as a triple, it is expectable for the inverse triple “Kevin has_child John” to be created after the reasoning process. But it isn’t so. And when I Query “to which individual does relate Kevin by the has_child property” before the reasoning process, it answers: John! But this is not the problem itself. The problem arises when I perform the reasoning process and then save both reasoning results and the main ontology itself: the triple “Kevin has_child John” doesn’t exist in the ontology, nor does in the reasoning results!
Another problem is that when I define a domain and a range for an object property in owlready2, and define another property to be the inverse of the first one, it is expectable for the domain and the range of the inverse property to be set as the range and the domain of the first one respectively after the reasoning. But it isn’t so. And no domain and range is set for the inverse property after reasoning.
I tried to define the inverse properties in another way but it wasn't successful.
heres the code:
from owlready2 import *
onto = get_ontology("http://test.org/Family#")
with onto:
'''defining base classes'''
class Human(Thing): pass
class Man(Human): pass
class Woman(Human): pass
AllDisjoint([Man, Woman])
'''defining properties'''
class has_couple(Human>>Human): pass
class has_wife(has_couple, Man>>Woman): pass
class has_husbend(has_couple):
inverse=has_wife
class has_child (Human>>Human): pass
class has_parent (ObjectProperty):
inverse=has_child
'''defining individuals'''
John = Man("John")
Kevin = Man("Kevin")
'''defining relations between individuals'''
John = Man("John", has_parent=[Kevin])
print(Kevin.has_child)
'''reasoning'''
with onto:
sync_reasoner_pellet(infer_property_values=True, infer_data_property_values = True)
print(has_husbend.domain)
'''saving'''
onto.save("test.owl", format = "rdfxml")
This answer is only about the inverse property, not about the domain and range.
I could not verify it in the documentation but I assume it is a design decision of
owlready2to implement the semantics ofowl:inverseOf, and therefore return additional triples in a query, but to exclude these additional triples from thesave()function.The reason for this is the general principle that you should not add (redundant) triples to a knowledge base that were not present in the original ontology. Adding such triples automatically could cause a large and unwanted increase in file size and memory requirements, for example if there is a definition for an inverse property that applies to a property that each individual has.
Furthermore it is possible that the automatic application of the inverse semantics leads to incorrect facts for certain more complex relations, and you want a human-in-the-loop or some explainability algorithm to check if the results are correct. However, if you just enter the derived facts in the knowledge base then it is no longer possible to distinguish original facts from new facts, so in that case checking results (or even knowing that new facts have been added) is no longer possible.
In summary: this is expected behavior from
owlready2. If you want to use the derived facts like Kevin has_child John then you should useowlready2itself or another engine that implementsowl:inverseOf.However, if you do want all triples in xml, then you can output them yourself using
rdflibby looping over all instances, and for each instance looping over all properties.Code:
This prints the following:
Some notes:
has_husband=[Kevin]to test the inverse has_husband/has_wife as wellowlready2itself. The only difference that I could not modify is thatowlready2uses a plain relation<has_child>whereasrdflibwith this code produces<fam:has_child>with the aliasfam:for the namespace.