From 04f4d9ec3ca1cbe0caf30b36045ecd0eab0512c0 Mon Sep 17 00:00:00 2001 From: Alex Nelson Date: Tue, 18 Jan 2022 17:35:20 -0500 Subject: [PATCH 1/2] Add test to demonstrate owl:Axiom interpretations in inferencing This patch adds one test to give the RDFLib OWL-RL project's expected behavior. From the OWL 2 Mapping to RDF document (cited in test), it appears that Table 7, row 7, implies a class declaration axiom should be induced by a populated `owl:Axiom` ("populated" meaning annotated{Source,Property, Target} are all given), whether or not the referenced axiom is directly asserted (written as a triple). The left column pattern contrasts with Table 17, row 1, which specifies the triple must already be asserted (to receive further annotations). This patch runs up against an undefined behavior in the OWL-RDF mapping: What should be deduced from an IRI that is an `owl:Axiom`? A subtle detail throughout the document is `owl:Axiom` only appears in references to blank nodes (`_:x`, rather than `*:x`). A follow-on patch may be necessary to clarify decisions (and assertions) around what may or may not be undefined behavior. Signed-off-by: Alex Nelson --- test/test_owl_axiom.py | 65 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 test/test_owl_axiom.py diff --git a/test/test_owl_axiom.py b/test/test_owl_axiom.py new file mode 100644 index 0000000..f9c0693 --- /dev/null +++ b/test/test_owl_axiom.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 + +# This software was developed at the National Institute of Standards +# and Technology by employees of the Federal Government in the course +# of their official duties. Pursuant to title 17 Section 105 of the +# United States Code this software is not subject to copyright +# protection and is in the public domain. NIST assumes no +# responsibility whatsoever for its use by other parties, and makes +# no guarantees, expressed or implied, about its quality, +# reliability, or any other characteristic. +# +# We would appreciate acknowledgement if the software is used. + +from pathlib import Path + +import owlrl +from rdflib import BNode, Graph, Namespace +from rdflib.namespace import OWL, RDF + +EX = Namespace("http://example.org/") +RELS = Namespace("http://example.org/relatives#") + +def test_owl_axiom() -> None: + """ + Test whether an axiom written only as an owl:Axiom is an asserted axiom. + + https://www.w3.org/TR/2012/REC-owl2-mapping-to-rdf-20121211/ + Table: 7, row 7 + """ + g = Graph() + relatives_ttl_path = Path(__file__).parent / "relatives.ttl" + g.parse(str(relatives_ttl_path), format="turtle") + + # Define three individuals - "Max", but in three ways. + + # First, Max, directly asserted. + g.add((EX.MaxByAssertion, RDF.type, RELS.Person)) + + # Second, Max, not directly asserted, but would be asserted if this blank-node axiom were not reified. + # NOTE - The OWL-RDF mapping, Table 7 row 7, matches this pattern with a Class Declaration. + ax = BNode() + g.add((ax, RDF.type, OWL.Axiom)) + g.add((ax, OWL.annotatedSource, EX.MaxByBlankAxiom)) + g.add((ax, OWL.annotatedProperty, RDF.type)) + g.add((ax, OWL.annotatedTarget, RELS.Person)) + + # Third, Max, not directly asserted, but would be asserted if this named axiom were not reified. + # NOTE - The OWL-RDF mapping, Table 7 row 7, matches this pattern with a Class Declaration, EXCEPT the node is an IRI instead of a blank node. This may be undefined behavior. + g.add((EX.NamedAxiom, RDF.type, OWL.Axiom)) + g.add((EX.NamedAxiom, OWL.annotatedSource, EX.MaxByNamedAxiom)) + g.add((EX.NamedAxiom, OWL.annotatedProperty, RDF.type)) + g.add((EX.NamedAxiom, OWL.annotatedTarget, RELS.Person)) + + # Before defining and expanding closure, confirm the direct and both reified Maxes are, are not, and are not in the graph, respectively. + assert (EX.MaxByAssertion, RDF.type, RELS.Person) in g + assert not (EX.MaxByBlankAxiom, RDF.type, RELS.Person) in g + assert not (EX.MaxByNamedAxiom, RDF.type, RELS.Person) in g + + owlrl.DeductiveClosure(owlrl.OWLRL_Extension).expand(g) + + # After defining and expanding closure, confirm the direct and reified Maxes are in the graph according to OWL-RDF mapping specification. + assert (EX.MaxByAssertion, RDF.type, RELS.Person) in g + assert (EX.MaxByBlankAxiom, RDF.type, RELS.Person) in g + # TODO - This last behavior might be undefined. + assert not (EX.MaxByNamedAxiom, RDF.type, RELS.Person) in g From 6e2266348650413bd444e6a9c28c077c78bc038f Mon Sep 17 00:00:00 2001 From: Alex Nelson Date: Fri, 11 Feb 2022 17:19:22 -0500 Subject: [PATCH 2/2] Cite expected behaviors of owl:Axioms and blank nodes A follow-on patch may be necessary to clarify closure behaviors. Signed-off-by: Alex Nelson --- test/test_owl_axiom.py | 66 ++++++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/test/test_owl_axiom.py b/test/test_owl_axiom.py index f9c0693..078a2c6 100644 --- a/test/test_owl_axiom.py +++ b/test/test_owl_axiom.py @@ -11,21 +11,41 @@ # # We would appreciate acknowledgement if the software is used. +""" +This file tests behaviors of owl:Axioms. + +The OWL-RDF mapping [2], Table 7 row 7, would match the introduction +pattern pertaining to EX.NamedAxiom with a Class Declaration, EXCEPT the +axiom's identifier is an IRI instead of a blank node. The document only +defines behaviors for `owl:Axiom`s identifying as blank nodes. Given +this pattern non-match, the RDF graph will contain these triples after +the mapping process of [2] Section 3 should have emptied the RDF graph. + +The requirement at the end of [2] Section 3, "At the end of this +process, the graph G MUST be empty." fails, meaning [1] Section 2.1's +requirement "conformant OWL 2 tools that take ontology documents as +input(s) MUST accept ontology documents using the RDF/XML serialization" +fails, placing the graph into OWL 2 FULL and providing no behaviors for +or against constructing a class declaration. + +[1] https://www.w3.org/TR/2012/REC-owl2-conformance-20121211/ +[2] https://www.w3.org/TR/2012/REC-owl2-mapping-to-rdf-20121211/ +""" + +import logging from pathlib import Path import owlrl +import pytest from rdflib import BNode, Graph, Namespace from rdflib.namespace import OWL, RDF EX = Namespace("http://example.org/") RELS = Namespace("http://example.org/relatives#") -def test_owl_axiom() -> None: +def _test_owl_axiom(include_named_axiom: bool) -> None: """ Test whether an axiom written only as an owl:Axiom is an asserted axiom. - - https://www.w3.org/TR/2012/REC-owl2-mapping-to-rdf-20121211/ - Table: 7, row 7 """ g = Graph() relatives_ttl_path = Path(__file__).parent / "relatives.ttl" @@ -37,29 +57,45 @@ def test_owl_axiom() -> None: g.add((EX.MaxByAssertion, RDF.type, RELS.Person)) # Second, Max, not directly asserted, but would be asserted if this blank-node axiom were not reified. - # NOTE - The OWL-RDF mapping, Table 7 row 7, matches this pattern with a Class Declaration. + # NOTE - [2] Table 7 row 7 matches this pattern with a Class Declaration. ax = BNode() g.add((ax, RDF.type, OWL.Axiom)) g.add((ax, OWL.annotatedSource, EX.MaxByBlankAxiom)) g.add((ax, OWL.annotatedProperty, RDF.type)) g.add((ax, OWL.annotatedTarget, RELS.Person)) - # Third, Max, not directly asserted, but would be asserted if this named axiom were not reified. - # NOTE - The OWL-RDF mapping, Table 7 row 7, matches this pattern with a Class Declaration, EXCEPT the node is an IRI instead of a blank node. This may be undefined behavior. - g.add((EX.NamedAxiom, RDF.type, OWL.Axiom)) - g.add((EX.NamedAxiom, OWL.annotatedSource, EX.MaxByNamedAxiom)) - g.add((EX.NamedAxiom, OWL.annotatedProperty, RDF.type)) - g.add((EX.NamedAxiom, OWL.annotatedTarget, RELS.Person)) + if include_named_axiom: + # Third, Max, not directly asserted, but would be asserted if this named axiom were not reified. + # NOTE - [2] Table 7 row 7 does not match this pattern with a Class Declaration. + g.add((EX.NamedAxiom, RDF.type, OWL.Axiom)) + g.add((EX.NamedAxiom, OWL.annotatedSource, EX.MaxByNamedAxiom)) + g.add((EX.NamedAxiom, OWL.annotatedProperty, RDF.type)) + g.add((EX.NamedAxiom, OWL.annotatedTarget, RELS.Person)) # Before defining and expanding closure, confirm the direct and both reified Maxes are, are not, and are not in the graph, respectively. assert (EX.MaxByAssertion, RDF.type, RELS.Person) in g assert not (EX.MaxByBlankAxiom, RDF.type, RELS.Person) in g assert not (EX.MaxByNamedAxiom, RDF.type, RELS.Person) in g - owlrl.DeductiveClosure(owlrl.OWLRL_Extension).expand(g) + try: + owlrl.DeductiveClosure(owlrl.OWLRL_Extension).expand(g) + except: + if include_named_axiom: + pytest.xfail("The OWL 2 RL closure correctly rejected an IRI-identified owl:Axiom.") + else: + raise + + if include_named_axiom: + assert False, "The OWL 2 RL closure should have rejected an IRI-identified owl:Axiom." - # After defining and expanding closure, confirm the direct and reified Maxes are in the graph according to OWL-RDF mapping specification. + # After defining and expanding closure, confirm the direct and reified Maxes are in the graph according to [2]. assert (EX.MaxByAssertion, RDF.type, RELS.Person) in g assert (EX.MaxByBlankAxiom, RDF.type, RELS.Person) in g - # TODO - This last behavior might be undefined. - assert not (EX.MaxByNamedAxiom, RDF.type, RELS.Person) in g + + +def test_owl_axiom_blank() -> None: + _test_owl_axiom(False) + + +def test_owl_axiom_iri() -> None: + _test_owl_axiom(True)