From 022927109a47fdf24bfc45e20fbdbc000217d986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Lamercerie?= <aurelien.lamercerie@tetras-libre.fr> Date: Tue, 8 Aug 2023 14:19:59 +0200 Subject: [PATCH] Update module Ontology to extract all elements --- .gitignore | 4 + .../__pycache__/metrics.cpython-311.pyc | Bin 1704 -> 2460 bytes .../__pycache__/ontology.cpython-311.pyc | Bin 1930 -> 8889 bytes ontoScorer/__pycache__/scorer.cpython-311.pyc | Bin 1726 -> 1772 bytes ontoScorer/metrics.py | 9 +- ontoScorer/ontology.py | 143 ++++++++++++++++-- ontoScorer/scorer.py | 2 +- tests/context.py | 10 ++ tests/test_data/ontology_a.ttl | 104 +++++++++++++ tests/test_data/ontology_b.ttl | 109 +++++++++++++ tests/test_ontology.py | 86 ++++++++++- 11 files changed, 446 insertions(+), 21 deletions(-) create mode 100644 tests/context.py create mode 100644 tests/test_data/ontology_a.ttl create mode 100644 tests/test_data/ontology_b.ttl diff --git a/.gitignore b/.gitignore index b0a5122..ad06501 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ +*.pyc +__pycache__ +*.todo + env/* diff --git a/ontoScorer/__pycache__/metrics.cpython-311.pyc b/ontoScorer/__pycache__/metrics.cpython-311.pyc index b3d7b0f5b4273d827168b62f4a05c4bc64426e93..261155446af384b3fa8a8d952545d43dda6c6dec 100644 GIT binary patch delta 1178 zcmZ3%J4cvrIWI340|Ns?r{SfPEi4oHB-|Vr7#OBAq%fo~<}l<kMlt3xMKR?vM=>*k z#F%nea#^ET85x)u+!<1sTNqMUmN79ftY!k40m4yiDXhT^nrssj40tpdZ*lnNmE`B- zr&n&AAj-(dH+dPOsX8kI0|PSy1H<QOj0_CzZ0+pR8A=#IdLcN4v4x?AaRKAxkBlNt z%NQ6KRzt)Y7#Lg_Q2EOk85mZ>wRW+i7{k=UP{RZ<#t2m#0b|%Y*%vT@Y?tU{L$#rn zxr058F@<@u9Fw>-TN+~uOAAL0GrDoLtdpylLYPYUC)Y5^3vj?(pTZE#pvmetc_XtJ zzZp1;(-~?QV#R71YbL*E6i0L8M5Z3`V1|_pD;aOG<R#{&u4KB!q-Su8IXS1e7!=qF z3PpUAU74-b6c`v78W=7pL_p96<^{sbB^F66lv*MM5xJlcQ6w;V9kV1hTMbb|24ZWG zFarZakqC$oV_;x_+XoVmoUF{E&n5+8f(*5t%+Jgt2-cv<TqF(>mSA9Dn5@Ap!^Q{V zfz)d;N=&X{He>{8uoIF634?5aYnc3xSsqDsy(CBl$X`VQ3=9mKoVS>ZQ%i1fr>B<0 zC+8#<7pE5AVlB<f%+D(VC8Hux`YmDs>EQ+upyXA=1JcM*#LK|Ia7(BtH7&I$H7_|e zJ|CJIZwaNR=A{-TmZYX2#JCf4azGa7f^A@{j4vrFO})id8DCJ8no^_$GFu)L9H1Cg z00ReyBEHFcSrn5%-YLGyz`*e1M+3uM8F^^Pn|oe254~g_deJ=mih1}2g@}s^5myu< zF3Ln+k%_#(AxXT-2b15h)G`W8&SKS8<_Eb%8$^Jt1&2`)IIKYqD*_R`lXtKxt8lSu ze_+5QAR0i*{507o|7TTjz9kF_m*C|5qSPWi5EGPPi`YOmfRuq_7orPf%`Xm{-29Z% voK(9aLk0#0Q1&a%n(V=*>3M@e;{q!Bz#_n?{ec0K_y`jF0wU04!S(<EV*m?Z delta 607 zcmbOuyn>f+IWI340|Ns?*u^s`*O({rNm!XMFfdGKNMT4}%wfo7jAG1Xiek!Tj$&qH zU}A7*NMUMWNMT;a#K5qc38W8%qgYZ{f*CYfC*~MzTp`NH$UAu-qp1iB0|Nsy0|Ue7 zR}2gc?TqbA(-}G^Phb%@>10~Q$iT3g5u_D_YneM3(->12TR3W%T^N=zFfgnJ34?Jh zO9xXLV+zyce@x<x%#$6M<+<V3mav2PlTDe8*;5#T88n&wCJV5-X{}_?WWL3uXK;%- zIj8uRU{PvXYEf!la%y~XPGWI!YH=~hQ3?tTMZA;QS*$1TVAW;hpUlf@D_kVRz`#%> z!oa|w$y_7~Vv9{SW0Pm&1u-QiN3&_Ufkh=je14EJ_FJr_d71fnMIa-KI6>kdUl)Nw zw1^eN<pwEZE8+pM#X*EL0|Ue4-)yqH#cT`=3<?Pe3JMBEyps*t75PBc7dJ96F#Pz@ zz;I*o7WP_3{>erh+RA(&jS3(F<hUY`)kR=Ov4g}w1kdCK4rLW~R_zZAm;^)vNSU7| z>*V7c3YtYA4%nv<Ignw$IBatBQ%ZAE?TS<x7#KjYQyei_i&NA50}C&s_6G(`;v-1x L3y46I1)B;0r`>e} diff --git a/ontoScorer/__pycache__/ontology.cpython-311.pyc b/ontoScorer/__pycache__/ontology.cpython-311.pyc index aafe00e11d0aad998e8c002a193da51608e8d62d..b71e1d0f3cad1e8ed6574796cb86610805e54287 100644 GIT binary patch literal 8889 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 delta 1222 zcmdn#+QnbLoR^o2fq{YH+=VkKRV)k)k3k$5W`Qz3+b}RNOlL@8NMX!j$YqRT%w>vV zVg&J-a+q^jqF5Lim>Ap{QkYv9QdpKTF)*xVg6d?5VohNUX3%7N2@=p`y2a{Vlvt2) zi`hTiN0adu=VVq!c}BI#t=#G?AXSqeaL;CC2xeHxIC&wD1p6)4bdc)F`*<XzZm|@n z=A_-?&Ce^z&&f}(j4wzm$tVWdGkG7Qznq{yR9r7UJ~J<~BtD*xfq|jel!1Yvf#Jqv zDPAL{&kU3Oc=H+kCV%HuaN=iRU?>9l{1#(+F*5@LgMxxWkswHrn}LC$N(^pXPJUua zJj_yIkeO;=Gw)80<kOsNDkm`c0tYMOgUJ`96j@RjYndj0lv1u|TE@u0u$mF%XApK_ zh;@o#W?-mgu4SoZtzk-GuHt22sAZ~QEnx%6LvR)>xEZoR-eM?HF9Ep)BFRw0jA~{K z69YpnTP=GH;{qfzAd2fjR1G_-21bS+=NiUf22B>fB9Qr-%zl1;FF|h9WWU8+oLW-E z!@$6Bi@l^Mvmht6xCrE$TddAGiN(c5tRNZY#FP|Gjv{c#fdcmyA4F+MaeQ)qX<o@K z#^PIy1-BUUZz(ax-{MLJ>5R`y%uOxkfP}9C2tWdvce1^ZWW59^IEx^`DI_{kb&6^S z`wdB{4!#@W(o?Fgi)&sI*Sw%*b5Y#(in#3u1{O{YFwyDW;n-0)LGcEU)QrT7Jn|iW zH~0mnI#2QJs_Uq`tDw0;aUstVo{78@7$;cW5D=fvJ&AjU*F^#KD+1~l1k``r;1}!Q zD-vd4n7mO&+)ECev}+i$Kw$>PHH-*$8dESs5hnu!gC=7U$j_QgVE^CZ%P+}DErO>D zNIHy{*HBghc}WQDr5pUhJvB4puE-i+;5WX&VGOd8!B3NYvOkMfJt$_2ctPn+oq>U2 zB?DLz5;p80Q4j%=E)D>j)xZFOADEa~wLUPw2`*+<K@b;8aB;ATe_((UB8;pi9~dx+ zB4JPpDFWHyr^$AUttcfeCo`!Clno%!E&vhH1IJ!LVsa`t1%NdaX@i^x$_QZB{^GF7 z%}*)KNwq7|V_;waC8y$K1_p)?%#4hTA4C`!<t{MDVj|BQ3~Cop(FYbWMw1T=n8Zhr M*cT9iCJPQT0MtYoBLDyZ diff --git a/ontoScorer/__pycache__/scorer.cpython-311.pyc b/ontoScorer/__pycache__/scorer.cpython-311.pyc index 15863905c230a177ffba8eb4a80f491d1fd94ecb..7696fe257f510bead13598ef2ee71e027b9b4612 100644 GIT binary patch delta 162 zcmdnT`-YcyIWI340|NuYC&NoA>Kl2dF>$dlFfcGPFfe?cF?lVM?_>=Y9;OAXlMgZp zt1;9tWP#LzaScNj6PUrkz_5&ofnha_H(8#!n$c<UY-VL8o*tg-9CDX9<YtsykvH4H qa>c^`B1ga#j(`g=wD~&oB}PV<$*Wiu_yQR}Ffg&g$jMh(UjhKS>L{!L delta 132 zcmaFEyN{Q5IWI340|NuY^NVLv_%`xRW19Gyld){_S|(prCWab@EEbR?2xqZ^ND!`J z$YKW3Alx_Ek-3`Da`IkgWl6aXmL8rf9C8a-t|*vY;4p`y%}gwp7#STWUu9L`w)nun M#0nuMGqb$}03Y2TO#lD@ diff --git a/ontoScorer/metrics.py b/ontoScorer/metrics.py index e905353..73fd413 100644 --- a/ontoScorer/metrics.py +++ b/ontoScorer/metrics.py @@ -8,6 +8,7 @@ #============================================================================== from sklearn.metrics import precision_score, recall_score, f1_score +from ontoScorer.ontology import Ontology class Metrics: def __init__(self): @@ -15,12 +16,14 @@ class Metrics: self.recall = 0 self.f1 = 0 - def calculate(self, reference_classes, generated_classes): + def calculate(self, reference_ontology, generated_ontology): + reference_classes = set([cls.name() for cls in reference_ontology.get_classes()]) + generated_classes = set([cls.name() for cls in generated_ontology.get_classes()]) + all_classes = reference_classes.union(generated_classes) y_true = [1 if cls in reference_classes else 0 for cls in all_classes] y_pred = [1 if cls in generated_classes else 0 for cls in all_classes] self.precision = precision_score(y_true, y_pred) self.recall = recall_score(y_true, y_pred) - self.f1 = f1_score(y_true, y_pred) - + self.f1 = f1_score(y_true, y_pred) \ No newline at end of file diff --git a/ontoScorer/ontology.py b/ontoScorer/ontology.py index f9403cb..90d4329 100644 --- a/ontoScorer/ontology.py +++ b/ontoScorer/ontology.py @@ -2,34 +2,149 @@ # -*-coding:Utf-8 -* #============================================================================== -# ontoScorer: [brief description of the module] +# Ontology Analyzer #------------------------------------------------------------------------------ -# Detailed module description, if needed +# Extracts various elements (Classes, Object Properties, Data Properties, +# Individuals, Annotations) from RDF/OWL ontologies. Enables foundational +# comparisons between different ontologies. #============================================================================== -from rdflib import Graph, OWL +from rdflib import Graph, RDF, RDFS, OWL +from rdflib import URIRef, BNode from rdflib.namespace import split_uri + +#============================================================================== +# Classes: Element, NamedElement and BlankElement +#============================================================================== + +class Element: + def __init__(self, reference, graph): + self.reference = reference + self.graph = graph + + def properties(self): + """Retrieve properties associated with the element.""" + return list(self.graph.predicate_objects(subject=self.reference)) + + def name(self): + raise NotImplementedError("The method name() must be implemented by subclasses.") + +class NamedElement(Element): + def __init__(self, uri, graph): + super().__init__(uri, graph) + self.uri = uri + + def __str__(self): + return str(self.uri) + + def name(self): + _, element_name = split_uri(self.uri) + return element_name + +class BlankElement(Element): + def __init__(self, bnode, graph): + super().__init__(bnode, graph) + self.id = str(bnode) + + def __str__(self): + return f"BNode: {self.id}" + + def name(self): + return self.id + + + +#============================================================================== +# Class: Ontology +#============================================================================== + class Ontology: + + #-------------------------------------------------------------------------- + # Constructor(s) + #-------------------------------------------------------------------------- + def __init__(self, ontology_path): + """Initialize the Ontology object.""" self.path = ontology_path self.graph = self.load_ontology(ontology_path) self.classes = self.get_classes() + + #-------------------------------------------------------------------------- + # Base Method(s) + #-------------------------------------------------------------------------- + def load_ontology(self, path): + """Load the ontology from the given path into an RDF graph.""" g = Graph() g.parse(path, format="ttl") return g + def _get_elements_of_type(self, rdf_type): + """Extract all elements of a specific RDF type from the ontology.""" + elements = [] + for s, _, o in self.graph.triples((None, RDF.type, rdf_type)): + if isinstance(s, BNode): + elements.append(BlankElement(s, self.graph)) + elif isinstance(s, URIRef): + elements.append(NamedElement(s, self.graph)) + return elements + + + #-------------------------------------------------------------------------- + # Extracting Method(s) + #-------------------------------------------------------------------------- + def get_classes(self): - classes = set() - triplets_count = 0 - for s, p, o in self.graph.triples((None, None, None)): - triplets_count += 1 - if o == OWL.Class: - _, class_name = split_uri(s) - classes.add(class_name) - return classes - - def compare_to(self, other_ontology): - return self.classes, other_ontology.classes \ No newline at end of file + """Extract all classes from the ontology.""" + return self._get_elements_of_type(OWL.Class) + + def get_object_properties(self): + """Extract all object properties from the ontology.""" + return self._get_elements_of_type(OWL.ObjectProperty) + + def get_data_properties(self): + """Extract all data properties from the ontology.""" + return self._get_elements_of_type(OWL.DatatypeProperty) + + def get_restrictions(self): + """Extract all restrictons from the ontology.""" + return self._get_elements_of_type(OWL.Restriction) + + def get_individuals(self) -> list: + """Extract all individuals from the ontology.""" + all_types = set(self.graph.subjects(RDF.type)) + non_individuals = {element.reference for element in + self.get_classes() + + self.get_object_properties() + + self.get_data_properties()} + + individuals = all_types - non_individuals + + return [NamedElement(i, self.graph) if isinstance(i, URIRef) else BlankElement(i, self.graph) + for i in individuals] + + def get_annotations(self): + """Extract all annotation comments from the ontology.""" + annotations = set() + for _, _, o in self.graph.triples((None, RDFS.label, None)): + annotations.add(str(o)) + return annotations + + + #-------------------------------------------------------------------------- + # Comparison Method(s) + #-------------------------------------------------------------------------- + + def compare_to(self, other_ontology) -> tuple: + """Compare classes of the current ontology with another.""" + self_classes = {c.name() for c in self.classes} + other_classes = {c.name() for c in other_ontology.classes} + + # Pour des comparaisons plus avancées, ajustez ce code + unique_to_self = self_classes - other_classes + unique_to_other = other_classes - self_classes + + return unique_to_self, unique_to_other diff --git a/ontoScorer/scorer.py b/ontoScorer/scorer.py index 2ed8085..763d930 100644 --- a/ontoScorer/scorer.py +++ b/ontoScorer/scorer.py @@ -19,7 +19,7 @@ class OntoScorer: def compare(self): self.comparison_result = self.reference_ontology.compare_to(self.generated_ontology) - self.metrics.calculate(*self.comparison_result) + self.metrics.calculate(self.reference_ontology, self.generated_ontology) def generate_report(self): report = Report(self.reference_ontology, diff --git a/tests/context.py b/tests/context.py new file mode 100644 index 0000000..c611d6e --- /dev/null +++ b/tests/context.py @@ -0,0 +1,10 @@ +import os +import sys + +CURRENT_DIRPATH = os.path.dirname(os.path.abspath(__file__)) +LIB_PATH = os.path.dirname(f'{CURRENT_DIRPATH}/../..') +print(f'Test Context: {LIB_PATH}') +sys.path.insert(0, os.path.abspath(LIB_PATH)) + +import ontoScorer + diff --git a/tests/test_data/ontology_a.ttl b/tests/test_data/ontology_a.ttl new file mode 100644 index 0000000..2b5ee47 --- /dev/null +++ b/tests/test_data/ontology_a.ttl @@ -0,0 +1,104 @@ +@prefix ns1: <https://tenet.tetras-libre.fr/base-ontology#> . +@prefix owl: <http://www.w3.org/2002/07/owl#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . + +<https://tenet.tetras-libre.fr/extract-result#SolarSystem> a owl:Individual, + <https://tenet.tetras-libre.fr/extract-result#system>, + <https://tenet.tetras-libre.fr/extract-result#system-hasPart-object>, + <https://tenet.tetras-libre.fr/extract-result#system-hasPart-sun> ; + rdfs:label "SolarSystem" ; + ns1:fromStructure "unknown" . + +<https://tenet.tetras-libre.fr/extract-result#direct> a owl:ObjectProperty ; + rdfs:label "direct" ; + rdfs:subPropertyOf ns1:Out_ObjectProperty ; + ns1:fromStructure "unknown" . + +<https://tenet.tetras-libre.fr/extract-result#gravitation-bind-system> a owl:Class ; + rdfs:subClassOf [ a owl:Restriction ; + owl:onProperty <https://tenet.tetras-libre.fr/extract-result#bind> ; + owl:someValuesFrom <https://tenet.tetras-libre.fr/extract-result#system> ], + <https://tenet.tetras-libre.fr/extract-result#gravitation> ; + ns1:fromStructure "unknown" . + +<https://tenet.tetras-libre.fr/extract-result#hasManner> a owl:ObjectProperty ; + rdfs:label "hasManner" ; + rdfs:subPropertyOf ns1:Out_ObjectProperty ; + ns1:fromStructure "unknown" . + +<https://tenet.tetras-libre.fr/extract-result#not-direct> a owl:ObjectProperty ; + rdfs:subPropertyOf ns1:Out_ObjectProperty ; + ns1:fromStructure "unknown" . + +<https://tenet.tetras-libre.fr/extract-result#object-orbit-hasManner-direct-sun> a owl:Class ; + rdfs:subClassOf [ a owl:Restriction ; + owl:onProperty <https://tenet.tetras-libre.fr/extract-result#orbit-hasManner-direct> ; + owl:someValuesFrom <https://tenet.tetras-libre.fr/extract-result#sun> ], + <https://tenet.tetras-libre.fr/extract-result#object> ; + ns1:fromStructure "unknown" . + +<https://tenet.tetras-libre.fr/extract-result#object-orbit-hasManner-not-direct-sun> a owl:Class ; + rdfs:subClassOf [ a owl:Restriction ; + owl:onProperty <https://tenet.tetras-libre.fr/extract-result#orbit-hasManner-not-direct> ; + owl:someValuesFrom <https://tenet.tetras-libre.fr/extract-result#sun> ], + <https://tenet.tetras-libre.fr/extract-result#object> ; + ns1:fromStructure "unknown" . + +<https://tenet.tetras-libre.fr/extract-result#bind> a owl:ObjectProperty ; + rdfs:label "bind" ; + rdfs:subPropertyOf ns1:Out_ObjectProperty ; + ns1:fromStructure "unknown" . + +<https://tenet.tetras-libre.fr/extract-result#gravitation> a owl:Class ; + rdfs:label "gravitation" ; + rdfs:subClassOf ns1:Entity ; + ns1:fromStructure "unknown" . + +<https://tenet.tetras-libre.fr/extract-result#orbit-hasManner-direct> a owl:ObjectProperty ; + rdfs:subPropertyOf <https://tenet.tetras-libre.fr/extract-result#orbit> ; + ns1:fromStructure "unknown" . + +<https://tenet.tetras-libre.fr/extract-result#orbit-hasManner-not-direct> a owl:ObjectProperty ; + rdfs:subPropertyOf <https://tenet.tetras-libre.fr/extract-result#orbit> ; + ns1:fromStructure "unknown" . + +<https://tenet.tetras-libre.fr/extract-result#system-hasPart-object> a owl:Class ; + rdfs:subClassOf [ a owl:Restriction ; + owl:onProperty <https://tenet.tetras-libre.fr/extract-result#hasPart> ; + owl:someValuesFrom <https://tenet.tetras-libre.fr/extract-result#object> ], + <https://tenet.tetras-libre.fr/extract-result#system> ; + ns1:fromStructure "unknown" . + +<https://tenet.tetras-libre.fr/extract-result#system-hasPart-sun> a owl:Class ; + rdfs:subClassOf [ a owl:Restriction ; + owl:onProperty <https://tenet.tetras-libre.fr/extract-result#hasPart> ; + owl:someValuesFrom <https://tenet.tetras-libre.fr/extract-result#sun> ], + <https://tenet.tetras-libre.fr/extract-result#system> ; + ns1:fromStructure "unknown" . + +<https://tenet.tetras-libre.fr/extract-result#hasPart> a owl:ObjectProperty ; + rdfs:label "hasPart" ; + rdfs:subPropertyOf ns1:Out_ObjectProperty ; + ns1:fromStructure "unknown" . + +<https://tenet.tetras-libre.fr/extract-result#orbit> a owl:ObjectProperty ; + rdfs:label "orbit" ; + rdfs:subPropertyOf ns1:Out_ObjectProperty ; + ns1:fromStructure "unknown" . + +<https://tenet.tetras-libre.fr/extract-result#object> a owl:Class ; + rdfs:label "object" ; + rdfs:subClassOf ns1:Entity ; + ns1:fromStructure "unknown" . + +<https://tenet.tetras-libre.fr/extract-result#sun> a owl:Class ; + rdfs:label "sun" ; + rdfs:subClassOf ns1:Entity ; + ns1:fromStructure "unknown" . + +<https://tenet.tetras-libre.fr/extract-result#system> a owl:Class ; + rdfs:label "system" ; + rdfs:subClassOf ns1:Entity, + ns1:Undetermined_Thing ; + ns1:fromStructure "unknown" . + diff --git a/tests/test_data/ontology_b.ttl b/tests/test_data/ontology_b.ttl new file mode 100644 index 0000000..2b97132 --- /dev/null +++ b/tests/test_data/ontology_b.ttl @@ -0,0 +1,109 @@ +@prefix base: <https://reference.tetras-libre.fr/base-ontology#> . +@prefix owl: <http://www.w3.org/2002/07/owl#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix result: <https://reference.tetras-libre.fr/expected-result#> . + +result:SolarSystem a owl:Individual, + result:system, + result:system-isBindBy-gravitation, + result:system-hasPart-object-orbit-sun, + result:system-hasPart-sun ; + rdfs:label "SolarSystem" . + +result:direct a owl:ObjectProperty ; + rdfs:label "direct" ; + rdfs:subPropertyOf base:Out_ObjectProperty . + +result:gravitation-bind-system a owl:Class ; + rdfs:subClassOf [ a owl:Restriction ; + owl:onProperty result:bind ; + owl:someValuesFrom result:system ], + result:gravitation . + +result:hasManner a owl:ObjectProperty ; + rdfs:label "hasManner" ; + rdfs:subPropertyOf base:Out_ObjectProperty . + +result:not-direct a owl:ObjectProperty ; + rdfs:subPropertyOf base:Out_ObjectProperty . + +result:object-orbit-sun a owl:Class ; + rdfs:subClassOf [ a owl:Restriction ; + owl:onProperty result:orbit ; + owl:someValuesFrom result:sun ], + result:object . + +result:object-orbit-hasManner-direct-sun a owl:Class ; + rdfs:subClassOf [ a owl:Restriction ; + owl:onProperty result:orbit-hasManner-direct ; + owl:someValuesFrom result:sun ], + result:object-orbit-sun . + +result:object-orbit-hasManner-not-direct-sun a owl:Class ; + rdfs:subClassOf [ a owl:Restriction ; + owl:onProperty result:orbit-hasManner-not-direct ; + owl:someValuesFrom result:sun ], + result:object-orbit-sun . + +result:gravitation a owl:Class ; + rdfs:label "gravitation" ; + rdfs:subClassOf base:Entity . + +result:system-isBindBy-gravitation a owl:Class ; + rdfs:subClassOf [ a owl:Restriction ; + owl:onProperty result:isBindBy ; + owl:someValuesFrom result:gravitation ], + result:system . + +result:system-hasPart-object a owl:Class ; + rdfs:subClassOf [ a owl:Restriction ; + owl:onProperty result:hasPart ; + owl:someValuesFrom result:object ], + result:system . + +result:system-hasPart-object-orbit-sun a owl:Class ; + rdfs:subClassOf [ a owl:Restriction ; + owl:onProperty result:hasPart ; + owl:someValuesFrom result:object-orbit-sun ], + result:system-hasPart-object . + +result:system-hasPart-sun a owl:Class ; + rdfs:subClassOf [ a owl:Restriction ; + owl:onProperty result:hasPart ; + owl:someValuesFrom result:sun ], + result:system . + +result:bind a owl:ObjectProperty ; + rdfs:label "bind" ; + rdfs:subPropertyOf base:Out_ObjectProperty . + +result:isBindBy owl:inverseOf result:bind ; + rdfs:label "isBindBy" ; + rdfs:subPropertyOf base:Out_ObjectProperty . + +result:orbit a owl:ObjectProperty ; + rdfs:label "orbit" ; + rdfs:subPropertyOf base:Out_ObjectProperty . + +result:orbit-hasManner-direct a owl:ObjectProperty ; + rdfs:subPropertyOf result:orbit . + +result:orbit-hasManner-not-direct a owl:ObjectProperty ; + rdfs:subPropertyOf result:orbit . + +result:hasPart a owl:ObjectProperty ; + rdfs:label "hasPart" ; + rdfs:subPropertyOf base:Out_ObjectProperty . + +result:object a owl:Class ; + rdfs:label "object" ; + rdfs:subClassOf base:Entity . + +result:system a owl:Class ; + rdfs:label "system" ; + rdfs:subClassOf base:Entity. + +result:sun a owl:Class ; + rdfs:label "sun" ; + rdfs:subClassOf base:Entity . + diff --git a/tests/test_ontology.py b/tests/test_ontology.py index 4415d78..8da3b2e 100644 --- a/tests/test_ontology.py +++ b/tests/test_ontology.py @@ -2,9 +2,89 @@ # -*-coding:Utf-8 -* #============================================================================== -# ontoScorer: [brief description of the module] +# test_ontology: Ontology Testing Module #------------------------------------------------------------------------------ -# Detailed module description, if needed +# Contains tests for verifying functionality of the Ontology class. #============================================================================== -# TODO +import unittest +import os +from context import ontoScorer +from ontoScorer.ontology import Ontology +from rdflib import URIRef + +class TestOntology(unittest.TestCase): + + def setUp(self): + # Test ontology paths + DATA_FOLDER_PATH = f'{os.path.dirname(os.path.abspath(__file__))}/test_data' + self.ontology1_path = f"{DATA_FOLDER_PATH}/ontology_a.ttl" + self.ontology2_path = f"{DATA_FOLDER_PATH}/ontology_b.ttl" + self.onto1 = Ontology(self.ontology1_path) + self.onto2 = Ontology(self.ontology2_path) + + def test_load_ontology(self): + self.assertIsNotNone(self.onto1.graph) + self.assertIsNotNone(self.onto2.graph) + + def test_get_classes(self): + classes1_names = {c.name() for c in self.onto1.get_classes()} + classes2_names = {c.name() for c in self.onto2.get_classes()} + + self.assertIn("gravitation-bind-system", classes1_names) + self.assertIn("gravitation-bind-system", classes1_names) + self.assertIn("object-orbit-hasManner-direct-sun", classes1_names) + self.assertIn("object-orbit-hasManner-not-direct-sun", classes1_names) + self.assertIn("gravitation", classes1_names) + self.assertIn("system-hasPart-object", classes1_names) + self.assertIn("system-hasPart-sun", classes1_names) + self.assertIn("object", classes1_names) + self.assertIn("sun", classes1_names) + self.assertIn("system", classes1_names) + self.assertNotIn("system-isBindBy-gravitation", classes1_names) + self.assertIn("system-isBindBy-gravitation", classes2_names) + + def test_get_object_properties(self): + object_properties_names = {op.name() for op in self.onto1.get_object_properties()} + + self.assertIn("direct", object_properties_names) + self.assertIn("hasManner", object_properties_names) + self.assertIn("not-direct", object_properties_names) + self.assertIn("bind", object_properties_names) + self.assertIn("orbit-hasManner-direct", object_properties_names) + self.assertIn("orbit-hasManner-not-direct", object_properties_names) + self.assertIn("hasPart", object_properties_names) + self.assertIn("orbit", object_properties_names) + + def test_get_data_properties(self): + data_properties_names = {dp.name() for dp in self.onto1.get_data_properties()} + + self.assertEqual(len(data_properties_names), 0) + + def test_get_restrictions(self): + restrictions = self.onto1.get_restrictions() + expected_restrictions_count = 5 + self.assertEqual(len(restrictions), expected_restrictions_count) + + def test_get_individuals(self): + individuals_names = {ind.name() for ind in self.onto1.get_individuals()} + + self.assertIn("SolarSystem", individuals_names) + self.assertNotIn("gravitation", individuals_names) + + def test_get_annotations(self): + annotations = self.onto1.get_annotations() + self.assertIn("SolarSystem", annotations) + self.assertIn("direct", annotations) + self.assertIn("gravitation", annotations) + self.assertIn("orbit", annotations) + self.assertIn("object", annotations) + self.assertIn("sun", annotations) + self.assertIn("system", annotations) + + def test_compare_to(self): + comparison_result = self.onto1.compare_to(self.onto2) + self.assertIsInstance(comparison_result, tuple) + +if __name__ == "__main__": + unittest.main() -- GitLab