-
Notifications
You must be signed in to change notification settings - Fork 28
failure to infer domain across multiple subproperty levels when destination is a named graph #76
Description
SKOS has: skos:broader -> skos:broaderTransitive -> skos:semanticRelation
where skos:semanticRelation has domain and range skos:Concept.
An OWL-RL reasoner should infer the domain/range for both skos:broader and skos:broaderTransitive to be skos:Concept.
owlrl correctly infers both of these when operating on the default graph of a Dataset, but it fails to infer to the 2nd level of the property hierarchy (skos:broader) these when the destination of the inferencing is a named graph.
NB: from the below it can be seen that there is a large difference in inferred triples between the two destinations, so this is likely a bigger issue than just domain (or range) propagation across subproperties.
The following tests owlrl with a SKOS-like multi-level subproperty hierarchy in which the domain (or range, I imagine) is specified for the superproperty. This is the output, and the script to produce it is below:
=== Test 1: Default Graph ===
Parse into default: 11 triples in total:
{ '<urn:x-rdflib:default>': 11}
selected domain inferences: 0
Inference into <urn:inferences1>: 139 triples in total:
{ '<urn:inferences1>': 128,
'<urn:x-rdflib:default>': 11}
selected domain inferences: 2
0: [<urn:inferences1> ] ex:broader -rdfs:domain-> ex:Concept
1: [<urn:inferences1> ] ex:broaderTransitive -rdfs:domain-> ex:Concept
=== Test 2: Named Graph ===
Parse into named graph <urn:ontology>: 11 triples in total:
{ '<urn:ontology>': 11,
'<urn:x-rdflib:default>': 0}
selected domain inferences: 0
Inference into <urn:inferences2>: 78 triples in total:
{ '<urn:inferences2>': 67,
'<urn:ontology>': 11,
'<urn:x-rdflib:default>': 0}
selected domain inferences: 1
0: [<urn:inferences2> ] ex:broaderTransitive -rdfs:domain-> ex:Concept
=== Summary ===
Default graph infers broaderTransitive domain: ✅ True
Default graph infers broader domain: ✅ True
Named graph infers broaderTransitive domain: ✅ True
Named graph infers broader domain: ❌ False
Script to produce the above:
from pprint import pprint
from owlrl import DeductiveClosure
from owlrl.OWLRL import OWLRL_Semantics
from rdflib import OWL, RDF, RDFS, Dataset, Graph, Namespace, URIRef
EX = Namespace("http://example.org/")
def create_skos_like_ontology():
"""Create SKOS-like multi-level property hierarchy."""
g = Graph()
g.bind("ex", EX)
# Define the top-level property with domain and range
g.add((EX.semanticRelation, RDF.type, RDF.Property))
g.add((EX.semanticRelation, RDF.type, OWL.ObjectProperty))
g.add((EX.semanticRelation, RDFS.domain, EX.Concept))
g.add((EX.semanticRelation, RDFS.range, EX.Concept))
# Define middle-level transitive property
g.add((EX.broaderTransitive, RDF.type, RDF.Property))
g.add((EX.broaderTransitive, RDF.type, OWL.ObjectProperty))
g.add((EX.broaderTransitive, RDF.type, OWL.TransitiveProperty))
g.add((EX.broaderTransitive, RDFS.subPropertyOf, EX.semanticRelation))
# Define leaf property (like skos:broader)
g.add((EX.broader, RDF.type, RDF.Property))
g.add((EX.broader, RDF.type, OWL.ObjectProperty))
g.add((EX.broader, RDFS.subPropertyOf, EX.broaderTransitive))
return g
def print_ds(title: str, ds: Dataset) -> None:
"""Print dataset summary and selected inferences."""
print(f"\n{title}: {len(ds)} triples in total:")
pprint({g.n3(): ln for g, ln in graph_lengths(ds).items()}, indent=4, width=20)
domain_assertions = list(ds.quads((EX.broader, RDFS.domain, None))) + list(
ds.quads((EX.broaderTransitive, RDFS.domain, None))
)
print(f"selected domain inferences: {len(domain_assertions)}")
nm = ds.namespace_manager # For pretty printing
for ii, (s, p, o, g) in enumerate(domain_assertions):
_g = g or URIRef("NONE")
print(f" {ii:d}: [{_g.n3(nm):<20}] {s.n3(nm):>20} -{p.n3(nm)}-> {o.n3(nm)} ")
print("=== Test 1: Default Graph ===")
ds1 = Dataset()
ds1.bind("ex", EX)
ds1.parse(data=create_skos_like_ontology().serialize(format="turtle"), format="turtle")
print_ds("Parse into default", ds1)
inferences1 = ds1.graph(URIRef("urn:inferences1"))
DeductiveClosure(OWLRL_Semantics).expand(ds1, inferences1)
print_ds(f"Inference into {inferences1.identifier.n3()}", ds1)
print("\n=== Test 2: Named Graph ===")
ds2 = Dataset()
named_graph = ds2.graph(URIRef("urn:ontology"))
named_graph.parse(
data=create_skos_like_ontology().serialize(format="turtle"), format="turtle"
)
print_ds(f"Parse into named graph {named_graph.identifier.n3()}", ds2)
inferences2 = ds2.graph(URIRef("urn:inferences2"))
DeductiveClosure(OWLRL_Semantics).expand(named_graph, inferences2)
print_ds(f"Inference into {inferences2.identifier.n3()}", ds2)
print("\n=== Summary ===")
print(
"Default graph infers broaderTransitive domain: "
+ (
"✅ True"
if any(ds1.quads((EX.broaderTransitive, RDFS.domain, None)))
else "❌ False"
)
)
print(
"Default graph infers broader domain: "
+ ("✅ True" if any(ds1.quads((EX.broader, RDFS.domain, None))) else "❌ False")
)
print(
"Named graph infers broaderTransitive domain: "
+ (
"✅ True"
if any(ds2.quads((EX.broaderTransitive, RDFS.domain, None)))
else "❌ False"
)
)
print(
"Named graph infers broader domain: "
+ ("✅ True" if any(ds2.quads((EX.broader, RDFS.domain, None))) else "❌ False")
)