From 61cd27378a2d36724edb5b6601f1eaee541a2523 Mon Sep 17 00:00:00 2001 From: Sushant Chavan Date: Thu, 12 Mar 2020 18:35:42 +0100 Subject: [PATCH 1/6] Remove duplicated Furniture class declaration --- common/ontology/apartment_go_2019.owl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/common/ontology/apartment_go_2019.owl b/common/ontology/apartment_go_2019.owl index 8368a1e..be6d54f 100644 --- a/common/ontology/apartment_go_2019.owl +++ b/common/ontology/apartment_go_2019.owl @@ -99,10 +99,6 @@ - - - - From 171cf741a32b9428aa212a276d83b65f893bbe17 Mon Sep 17 00:00:00 2001 From: Sushant Chavan Date: Thu, 12 Mar 2020 22:12:24 +0100 Subject: [PATCH 2/6] Fix class name typos in the ontology --- common/ontology/apartment_go_2019.owl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/ontology/apartment_go_2019.owl b/common/ontology/apartment_go_2019.owl index be6d54f..aa0a51b 100644 --- a/common/ontology/apartment_go_2019.owl +++ b/common/ontology/apartment_go_2019.owl @@ -156,7 +156,7 @@ - + @@ -184,7 +184,7 @@ - + From 79f1ebe259bdbc8790fefea1a605d62de0f56376 Mon Sep 17 00:00:00 2001 From: Sushant Chavan Date: Thu, 12 Mar 2020 22:52:09 +0100 Subject: [PATCH 3/6] Add methods to insert and remove class and property assertions --- .../ontology_query_interface.py | 178 ++++++++++++++++++ 1 file changed, 178 insertions(+) diff --git a/common/mas_knowledge_utils/ontology_query_interface.py b/common/mas_knowledge_utils/ontology_query_interface.py index 9987ea6..4a2f82b 100644 --- a/common/mas_knowledge_utils/ontology_query_interface.py +++ b/common/mas_knowledge_utils/ontology_query_interface.py @@ -27,8 +27,13 @@ def __init__(self, ontology_file, class_prefix): self.knowledge_graph = rdflib.Graph() self.knowledge_graph.load(ontology_file) self.class_prefix = class_prefix + self.ontology_file = ontology_file self.ontology_url = ontology_file[0:ontology_file.rfind('/')] + self.__class_names = None + self.__instance_names = None + self.__property_names = None + def get_classes(self): '''Returns a list with the names of all classes in the ontology. ''' @@ -189,6 +194,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_class_names() + + 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_instance_names() + + 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_property_names() + + 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://) + + ''' + 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". @@ -226,3 +350,57 @@ def __extract_obj_name(self, obj_url): ''' return obj_url[obj_url.rfind('/')+1:] + + def __get_class_names(self): + ''' Returns a list of all the classes defined in the ontology + + ''' + if self.__class_names is not None: + return self.__class_names + + subclasses = self.knowledge_graph.query('SELECT ?s ?o WHERE { ?s \ + rdfs:subClassOf ?o } \ + ORDER BY ?s') + instance_types = self.knowledge_graph.query('SELECT DISTINCT ?type \ + WHERE { ?s a ?type. \ + FILTER( STRSTARTS(STR(?type), \ + str(' + self.class_prefix + \ + ':)) ) }') + class_names = set() + for res in subclasses: + subj = res[0][res[0].find(':')+1:] + obj = res[1][res[1].find(':')+1:] + class_names.update((subj, obj)) + + for res in instance_types: + type_name = res[0][res[0].find('#')+1:] + class_names.add(type_name) + + self.__class_names = sorted(list(class_names)) + return self.__class_names + + def __get_property_names(self): + ''' Returns a list of all the properties defined in the ontology + + ''' + if self.__property_names is not None: + return self.__property_names + + query_result = self.knowledge_graph.query('SELECT DISTINCT ?p \ + WHERE { ?s ?p ?o } \ + ORDER BY ?p') + self.__property_names = [x[0][x[0].find('#')+1:] for x in query_result] + return self.__property_names + + def __get_instance_names(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_class_names() + for c in class_list: + self.__instance_names.extend(self.get_instances_of(c)) + return self.__instance_names From 4511fd759504367fb75f44799aec0ee741d40136 Mon Sep 17 00:00:00 2001 From: Sushant Chavan Date: Fri, 13 Mar 2020 10:28:34 +0100 Subject: [PATCH 4/6] Add python script to load and insert assertions specified in a yaml file --- common/assertions/test_apartment.yaml | 23 +++ common/mas_knowledge_utils/ABox.py | 134 ++++++++++++++++++ .../ontology_query_interface.py | 1 + 3 files changed, 158 insertions(+) create mode 100644 common/assertions/test_apartment.yaml create mode 100644 common/mas_knowledge_utils/ABox.py diff --git a/common/assertions/test_apartment.yaml b/common/assertions/test_apartment.yaml new file mode 100644 index 0000000..003d586 --- /dev/null +++ b/common/assertions/test_apartment.yaml @@ -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 diff --git a/common/mas_knowledge_utils/ABox.py b/common/mas_knowledge_utils/ABox.py new file mode 100644 index 0000000..48cb587 --- /dev/null +++ b/common/mas_knowledge_utils/ABox.py @@ -0,0 +1,134 @@ +import yaml +import argparse +import os +from ontology_query_interface import OntologyQueryInterface + +# TODO: name of this class? +class PopulateABox: + '''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://) + @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 sushant.chavan@smail.inf.h-brs.de + + ''' + 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://) + 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://) + 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") + + aBox = PopulateABox(ontology_file_path, args.class_prefix, assertions_file_path) + aBox.update_ontology(export_file_path) diff --git a/common/mas_knowledge_utils/ontology_query_interface.py b/common/mas_knowledge_utils/ontology_query_interface.py index 4a2f82b..1a4fa05 100644 --- a/common/mas_knowledge_utils/ontology_query_interface.py +++ b/common/mas_knowledge_utils/ontology_query_interface.py @@ -298,6 +298,7 @@ def remove_property_assertion(self, property_name, instance): rdflib.URIRef(ns[property_name]), rdflib.URIRef(self.__get_entity_url(instance[1])))) + # TODO: default export file format? def export(self, ontology_file, format='xml'): '''Exports the ontology as an xml document. From 3028ad8ed5938bf795a12bdb26fbf7525b271024 Mon Sep 17 00:00:00 2001 From: Sushant Chavan Date: Fri, 13 Mar 2020 18:05:54 +0100 Subject: [PATCH 5/6] Rename aBoxYamlLoader class --- .../mas_knowledge_utils/{ABox.py => abox_yaml_loader.py} | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) rename common/mas_knowledge_utils/{ABox.py => abox_yaml_loader.py} (96%) diff --git a/common/mas_knowledge_utils/ABox.py b/common/mas_knowledge_utils/abox_yaml_loader.py similarity index 96% rename from common/mas_knowledge_utils/ABox.py rename to common/mas_knowledge_utils/abox_yaml_loader.py index 48cb587..931a5bd 100644 --- a/common/mas_knowledge_utils/ABox.py +++ b/common/mas_knowledge_utils/abox_yaml_loader.py @@ -3,8 +3,7 @@ import os from ontology_query_interface import OntologyQueryInterface -# TODO: name of this class? -class PopulateABox: +class ABoxYAMLLoader: '''Updates an ontology with class and property assertions that are specified in a yaml file. @@ -130,5 +129,5 @@ def __load_assertions(self, assertions_file): if args.export_file is not None: export_file_path = "file://" + os.path.join(ontology_dir, args.export_file + ".owl") - aBox = PopulateABox(ontology_file_path, args.class_prefix, assertions_file_path) - aBox.update_ontology(export_file_path) + loader = ABoxYAMLLoader(ontology_file_path, args.class_prefix, assertions_file_path) + loader.update_ontology(export_file_path) From 49a3cb36213a00ec23fc8e0ec2fcb390f4041364 Mon Sep 17 00:00:00 2001 From: Sushant Chavan Date: Fri, 13 Mar 2020 18:32:22 +0100 Subject: [PATCH 6/6] Rebase Fixes --- .../ontology_query_interface.py | 79 ++++--------------- 1 file changed, 17 insertions(+), 62 deletions(-) diff --git a/common/mas_knowledge_utils/ontology_query_interface.py b/common/mas_knowledge_utils/ontology_query_interface.py index 1a4fa05..eb9862e 100644 --- a/common/mas_knowledge_utils/ontology_query_interface.py +++ b/common/mas_knowledge_utils/ontology_query_interface.py @@ -27,12 +27,9 @@ def __init__(self, ontology_file, class_prefix): self.knowledge_graph = rdflib.Graph() self.knowledge_graph.load(ontology_file) self.class_prefix = class_prefix - self.ontology_file = ontology_file self.ontology_url = ontology_file[0:ontology_file.rfind('/')] - - self.__class_names = None + self.ontology_file = ontology_file self.__instance_names = None - self.__property_names = None def get_classes(self): '''Returns a list with the names of all classes in the ontology. @@ -48,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'. @@ -201,7 +211,7 @@ def is_class(self, class_name): class_name -- string representing the name of the class ''' - return class_name in self.__get_class_names() + 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. @@ -210,7 +220,7 @@ def is_instance(self, instance_name): instance_name -- string representing the name of the class instance ''' - return instance_name in self.__get_instance_names() + 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. @@ -219,7 +229,7 @@ def is_property(self, property_name): property_name -- string representing the name of the property ''' - return property_name in self.__get_property_names() + 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 @@ -298,7 +308,6 @@ def remove_property_assertion(self, property_name, instance): rdflib.URIRef(ns[property_name]), rdflib.URIRef(self.__get_entity_url(instance[1])))) - # TODO: default export file format? def export(self, ontology_file, format='xml'): '''Exports the ontology as an xml document. @@ -351,57 +360,3 @@ def __extract_obj_name(self, obj_url): ''' return obj_url[obj_url.rfind('/')+1:] - - def __get_class_names(self): - ''' Returns a list of all the classes defined in the ontology - - ''' - if self.__class_names is not None: - return self.__class_names - - subclasses = self.knowledge_graph.query('SELECT ?s ?o WHERE { ?s \ - rdfs:subClassOf ?o } \ - ORDER BY ?s') - instance_types = self.knowledge_graph.query('SELECT DISTINCT ?type \ - WHERE { ?s a ?type. \ - FILTER( STRSTARTS(STR(?type), \ - str(' + self.class_prefix + \ - ':)) ) }') - class_names = set() - for res in subclasses: - subj = res[0][res[0].find(':')+1:] - obj = res[1][res[1].find(':')+1:] - class_names.update((subj, obj)) - - for res in instance_types: - type_name = res[0][res[0].find('#')+1:] - class_names.add(type_name) - - self.__class_names = sorted(list(class_names)) - return self.__class_names - - def __get_property_names(self): - ''' Returns a list of all the properties defined in the ontology - - ''' - if self.__property_names is not None: - return self.__property_names - - query_result = self.knowledge_graph.query('SELECT DISTINCT ?p \ - WHERE { ?s ?p ?o } \ - ORDER BY ?p') - self.__property_names = [x[0][x[0].find('#')+1:] for x in query_result] - return self.__property_names - - def __get_instance_names(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_class_names() - for c in class_list: - self.__instance_names.extend(self.get_instances_of(c)) - return self.__instance_names