Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Load and insert class and property assertions from a YAML file #17

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions common/assertions/test_apartment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class_assertions:
Male: [Alex, Sushant]
Female: [Argentina]
Room: [RopodLab, atWorkLab]
Chair: [AlexChair, ArgentinaChair, SushantChair]
HighTable: [RopodWorkDesk, atWorkDesk]
property_assertions:
defaultLocation:
AlexChair: RopodLab
ArgentinaChair: RopodLab
SushantChair: RopodLab
RopodWorkDesk: RopodLab
atWorkDesk: atWorkLab
locatedAt:
AlexChair: RopodLab
ArgentinaChair: RopodLab
SushantChair: atWorkLab
RopodWorkDesk: RopodLab
atWorkDesk: atWorkLab
nextTo:
AlexChair: RopodWorkDesk
ArgentinaChair: RopodWorkDesk
SushantChair: atWorkDesk
133 changes: 133 additions & 0 deletions common/mas_knowledge_utils/abox_yaml_loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import yaml
import argparse
import os
from ontology_query_interface import OntologyQueryInterface

class ABoxYAMLLoader:
'''Updates an ontology with class and property assertions
that are specified in a yaml file.

Constructor arguments:
@param ontology_file -- full URL of an ontology file (of the form file://<absolute-path>)
@param ontology_class_prefix -- class prefix of the items in the given ontology
@param assertions_file -- absolute-path to the yaml file containing assertions

@author Sushant Chavan
@contact [email protected]

'''
def __init__(self, ontology_file, ontology_class_prefix, assertions_file):
self.ontology_if = OntologyQueryInterface(ontology_file=ontology_file,
class_prefix=ontology_class_prefix)
self.class_assertions = None
self.property_assertions = None

self.__load_assertions(assertions_file)

def update_ontology(self, export_path):
'''Inserts the class and property assertions and updates/exports the ontology.

Keyword arguments:
export_path -- string representing the filepath (of the form file://<absolute-path>)
if the ontology has to be exported to a new file.
If it is 'None', the loaded ontology will be updated with the assertions

'''
self.process_class_assertions()
self.process_property_assertions()
self.export_ontology(export_path)

def process_class_assertions(self):
'''Inserts the class assertions into the loaded ontology.

'''
if self.class_assertions is None:
return

for class_name in self.class_assertions.keys():
instance_names = self.class_assertions[class_name]
for instance_name in instance_names:
self.ontology_if.insert_class_assertion(class_name, instance_name)
#print("Inserting class assertion: {0}, {1}".format(class_name, instance_name))

def process_property_assertions(self):
'''Inserts the property assertions into the loaded ontology.

'''
if self.property_assertions is None:
return

for property_name in self.property_assertions.keys():
for subj, obj in self.property_assertions[property_name].items():
self.ontology_if.insert_property_assertion(property_name, (subj, obj))
#print("Inserting property assertion: {0}, {1}, {2}".format(subj, property_name, obj))

def export_ontology(self, export_path):
'''Updates/exports the ontology.

Keyword arguments:
export_path -- string representing the filepath (of the form file://<absolute-path>)
if the ontology has to be exported to a new file.
If it is 'None', the loaded ontology will be updated with the assertions

'''
if export_path is None:
# Overwrite the existing ontology file with the updated ontology
print("No export filepath specified. Updating the loaded ontology.")
self.ontology_if.update()
else:
# Save the updated ontology at the given file location
print("Exporting the ontology to", export_path)
self.ontology_if.export(export_path)

def __load_assertions(self, assertions_file):
'''Loads the class and property assertions as two separate
dictionaries from the yaml file

Keyword arguments:
assertions_file -- absolute-path to the yaml file containing assertions

'''
assertions = None
with open(assertions_file, 'r') as f:
assertions = yaml.load(f, Loader=yaml.FullLoader)

if assertions is None:
raise Exception("Could not load the assertion file!")
else:
self.class_assertions = assertions['class_assertions']
self.property_assertions = assertions['property_assertions']

if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('asserts', type=str,
help="Name of the yaml file containing the assertions \
(eg.: apartment_asserts)")
parser.add_argument('-o', '--ontology', type=str, action='store',
help="Filename of the ontology (default: apartment_go_2019)",
default='apartment_go_2019')
parser.add_argument('-c', '--class-prefix', type=str, action='store',
help="class prefix (or namespace) of the items in the \
given ontology (default: apartment)",
default='apartment')
parser.add_argument('-e', '--export-file', type=str, action='store',
help="Filename for exporting the updated ontology \
(eg.: apartment_go_2019_updated). If not \
specified, the loaded ontology will be updated.",
default=None)

args = parser.parse_args()

script_dir = os.path.abspath(os.path.dirname(__file__))
ontology_dir = os.path.join(os.path.dirname(script_dir), "ontology")
assertions_dir = os.path.join(os.path.dirname(script_dir), "assertions")

ontology_file_path = "file://" + os.path.join(ontology_dir, args.ontology + ".owl")
assertions_file_path = os.path.join(assertions_dir, args.asserts + ".yaml")

export_file_path = None
if args.export_file is not None:
export_file_path = "file://" + os.path.join(ontology_dir, args.export_file + ".owl")

loader = ABoxYAMLLoader(ontology_file_path, args.class_prefix, assertions_file_path)
loader.update_ontology(export_file_path)
134 changes: 134 additions & 0 deletions common/mas_knowledge_utils/ontology_query_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ def __init__(self, ontology_file, class_prefix):
self.knowledge_graph.load(ontology_file)
self.class_prefix = class_prefix
self.ontology_url = ontology_file[0:ontology_file.rfind('/')]
self.ontology_file = ontology_file
self.__instance_names = None

def get_classes(self):
'''Returns a list with the names of all classes in the ontology.
Expand All @@ -43,6 +45,19 @@ def get_object_properties(self):
if triple[1] == URIRefConstants.RDF_TYPE and \
triple[2] == URIRefConstants.OWL_OBJECT_PROPERTY]

def get_instances(self):
''' Returns a list of all the instances defined in the ontology

'''
if self.__instance_names is not None:
return self.__instance_names

self.__instance_names = []
class_list = self.get_classes()
for c in class_list:
self.__instance_names.extend(self.get_instances_of(c))
return list(set(self.__instance_names))

def is_instance_of(self, obj_name, class_name):
'''Checks whether 'obj_name' is an instance of 'class_name'.

Expand Down Expand Up @@ -189,6 +204,125 @@ def get_property_domain_range(self, prop):
break
return (prop_domain, prop_range)

def is_class(self, class_name):
'''Checks whether 'class_name' is defined as a class in the ontology.

Keyword arguments:
class_name -- string representing the name of the class

'''
return class_name in self.get_classes()

def is_instance(self, instance_name):
'''Checks whether 'instance_name' is defined as a class instance in the ontology.

Keyword arguments:
instance_name -- string representing the name of the class instance

'''
return instance_name in self.get_instances()

def is_property(self, property_name):
'''Checks whether 'property_name' is defined as a property in the ontology.

Keyword arguments:
property_name -- string representing the name of the property

'''
return property_name in self.get_object_properties()

def insert_class_assertion(self, class_name, instance_name):
''' Adds a new instance of a class to the ontology

Keyword arguments:
class_name -- string representing the name of the class
instance_name -- string representing the name of the instance

'''
if self.is_class(class_name):
ns = rdflib.Namespace("http://{0}#".format(self.class_prefix))
self.knowledge_graph.add((rdflib.URIRef(self.__get_entity_url(instance_name)),
rdflib.RDF.type,
rdflib.URIRef(ns[class_name])))
# Reset instance names list to ensure that the newly added
# instance is included in the next query to the instance_list
self.__instance_names = None
else:
raise ValueError("The \"{0}\" class does not exist in the ontology!".format(class_name))

def insert_property_assertion(self, property_name, instance):
''' Adds a new predicate between a subject and an object to the ontology

Keyword arguments:
property_name -- string representing the name of the predicate
instance -- tuple(string, string) representing the subject and the object respectively

'''
if not self.is_instance(instance[0]):
raise ValueError("The subject \"{0}\" does not exist in the ontology as an instance of a class!".format(instance[0]))
elif not self.is_instance(instance[1]):
raise ValueError("The object \"{0}\" does not exist in the ontology as an instance of a class!".format(instance[1]))
elif not self.is_property(property_name):
raise ValueError("The property \"{0}\" is not defined in the ontology!".format(property_name))
else:
ns = rdflib.Namespace("http://{0}#".format(self.class_prefix))
self.knowledge_graph.add((rdflib.URIRef(self.__get_entity_url(instance[0])),
rdflib.URIRef(ns[property_name]),
rdflib.URIRef(self.__get_entity_url(instance[1]))))

def remove_class_assertion(self, class_name, instance_name):
''' Removes an existing instance of a class from the ontology

Keyword arguments:
class_name -- string representing the name of the class
instance_name -- string representing the name of the instance

'''
if not self.is_class(class_name):
raise ValueError("The \"{0}\" class does not exist in the ontology!".format(class_name))
elif not self.is_instance(instance_name):
raise ValueError("The \"{0}\" instance does not exist in the ontology!".format(instance_name))
else:
ns = rdflib.Namespace("http://{0}#".format(self.class_prefix))
self.knowledge_graph.remove((rdflib.URIRef(self.__get_entity_url(instance_name)),
rdflib.RDF.type,
rdflib.URIRef(ns[class_name])))

def remove_property_assertion(self, property_name, instance):
''' Removes an existing predicate between a subject and an object from the ontology

Keyword arguments:
property_name -- string representing the name of the predicate
instance -- tuple(string, string) representing the subject and the object respectively

'''
if not self.is_instance(instance[0]):
raise ValueError("The subject \"{0}\" does not exist in the ontology as an instance of a class!".format(instance[0]))
elif not self.is_instance(instance[1]):
raise ValueError("The object \"{0}\" does not exist in the ontology as an instance of a class!".format(instance[1]))
elif not self.is_property(property_name):
raise ValueError("The property \"{0}\" is not defined in the ontology!".format(property_name))
else:
ns = rdflib.Namespace("http://{0}#".format(self.class_prefix))
self.knowledge_graph.remove((rdflib.URIRef(self.__get_entity_url(instance[0])),
rdflib.URIRef(ns[property_name]),
rdflib.URIRef(self.__get_entity_url(instance[1]))))

def export(self, ontology_file, format='xml'):
'''Exports the ontology as an xml document.

Keyword arguments:
@param ontology_file -- full URL of the ontology file (of the form file://<absolute-path>)

'''
self.knowledge_graph.serialize(ontology_file, format=format)

def update(self, format='xml'):
'''Overwrites the loaded ontology file with the current version of the ontology.

'''
self.export(self.ontology_file, format=format)

def __format_class_name(self, class_name):
'''Returns a string of the format "self.class_prefix:class_name".

Expand Down
8 changes: 2 additions & 6 deletions common/ontology/apartment_go_2019.owl
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,6 @@
<rdfs:subClassOf rdf:resource="apartment:Room"/>
</owl:Class>

<owl:Class rdf:about="apartment:Furniture">
<rdfs:subClassOf rdf:resource="apartment:Object"/>
</owl:Class>

<!-- Furniture Subclass -->
<owl:Class rdf:about="apartment:Bed">
<rdfs:subClassOf rdf:resource="apartment:Furniture"/>
Expand Down Expand Up @@ -160,7 +156,7 @@
<rdfs:subClassOf rdf:resource="apartment:Furniture"/>
</owl:Class>

<owl:Class rdf:about="apartment:Cupboard">
<owl:Class rdf:about="apartment:CupBoard">
<rdfs:subClassOf rdf:resource="apartment:Furniture"/>
</owl:Class>

Expand Down Expand Up @@ -188,7 +184,7 @@
<rdfs:subClassOf rdf:resource="apartment:Furniture"/>
</owl:Class>

<owl:Class rdf:about="apartment:Coathanger">
<owl:Class rdf:about="apartment:CoatHanger">
<rdfs:subClassOf rdf:resource="apartment:Furniture"/>
</owl:Class>

Expand Down