RDFS range bunny carrot#
import rdflib
import owlrl
ttl = """
@prefix : <http://example.org/ns#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
"""
Example:
Rabbits eat carrots and lettuce.
carrots and lettuce are subclasses of rabbit food.
The rabbit Bunny eats a :treat_123 (DE: “Leckerli”).
We want do infer the type of :treat_123: a carrot? a lettuce?
ttl += """
# subclass relationship
:Carrot rdfs:subClassOf :rabbit_food .
:Lettuce rdfs:subClassOf :rabbit_food .
# assertion on instance level
:Bunny a :Rabbit ;
:eat :treat_123 .
"""
Model 1#
two single, unrelated rdfs3 statements; relation: :eat
We want to entail the rdf:type
of :treat_123
. This can be done with RDFS entailment rule rdfs3.
https://www.w3.org/TR/rdf11-mt/#rdfs-entailment, rule rdfs3:
IF
aaa rdfs:range xxx
ANDyyy aaa zzz
THEN
zzz rdf:type xxx
.
We add a respective rdfs:range entailment rule:
ttl += """
:eat rdfs:range :Carrot .
"""
Graphs g1a and g1b#
We initialize two graphs with the same ttl code.
g1a = rdflib.Graph().parse(data=ttl)
g1b = rdflib.Graph().parse(data=ttl)
print(f"#triples: {len(g1a)=}, {len(g1b)=}")
#triples: len(g1a)=5, len(g1b)=5
Let’s define a utility function focus()
. It shows only the paragraphs in a serialized ttl file which contains at least one of a list of interesting strings.
def focus(focus_curie_list, ttl):
return "\n\n".join( [ paragraph for paragraph in ttl.split("\n\n") \
if any( [ focus_curie in paragraph for focus_curie in focus_curie_list ] ) ] )
These are the interesting parts of g1a
:
interesting = ["Carrot", "Bunny", "eat"]
print(focus(interesting, g1a.serialize()))
:Bunny a :Rabbit ;
:eat :treat_123 .
:eat rdfs:range :Carrot .
:Carrot rdfs:subClassOf :rabbit_food .
We now apply RDFS inferencing to g1b
.
owlrl.DeductiveClosure(owlrl.OWLRL_Semantics,
axiomatic_triples = False).expand(g1b)
print(f"#triples: {len(g1a)=}, {len(g1b)=}")
#triples: len(g1a)=5, len(g1b)=122
A lot of new triples are added – and also something interesting happened!
# this would give a lot of lines
# print(g1b.serialize())
These are the interesting parts of g1b
:
print(focus(interesting, g1b.serialize()))
:Bunny a :Rabbit ;
:eat :treat_123 ;
owl:sameAs :Bunny .
:eat rdfs:range :Carrot,
:rabbit_food ;
owl:sameAs :eat .
:treat_123 a :Carrot,
:rabbit_food ;
owl:sameAs :treat_123 .
:Carrot rdfs:subClassOf :rabbit_food ;
owl:sameAs :Carrot .
As we can see the inferencing system derived the fact :treat_123 a :Carrot
and :treat_123 a :rabbit_food
.
Graphs g2a and g2b#
What if we additionally want to say that rabbits eat lettuce? Let’s add a second rdfs:range rule.
ttl2 = ttl + """
:eat rdfs:range :Lettuce .
"""
g2a = rdflib.Graph().parse(data=ttl2)
g2b = rdflib.Graph().parse(data=ttl2)
print(f"#triples: {len(g2a)=}, {len(g2b)=}")
#triples: len(g2a)=6, len(g2b)=6
These are the interesting parts of g2a
:
interesting = ["eat"]
print(focus(interesting, g2a.serialize()))
:Bunny a :Rabbit ;
:eat :treat_123 .
:eat rdfs:range :Carrot,
:Lettuce .
owlrl.DeductiveClosure(owlrl.OWLRL_Semantics,
axiomatic_triples = False).expand(g2b)
print(f"#triples: {len(g2a)=}, {len(g2b)=}")
#triples: len(g2a)=6, len(g2b)=124
And these are the interesting parts of g2b
:
print(focus(interesting, g2b.serialize()))
:Bunny a :Rabbit ;
:eat :treat_123 ;
owl:sameAs :Bunny .
:eat rdfs:range :Carrot,
:Lettuce,
:rabbit_food ;
owl:sameAs :eat .
:treat_123 a :Carrot,
:Lettuce,
:rabbit_food ;
owl:sameAs :treat_123 .
As we can see, we now have :treat_123 a :Carrot, :Lettuce
: Our treat is classified being a :Carrot
and being a :Lettuce
.
This probably is not what we wanted really to express.
Model 2#
one single rdfs3, combined with a owl:unionOf; new relation: :lovs
Idea: We only want to use one single rdfs3 rule. Thus we definde a class :Carrot_Lettuce_undecided
which is the union of :Carrot
and :Lettuce
.
An instance of :Carrot_Lettuce_undecided
is either a :Carrot
or a :Lettuce
(or both, if they are not disjoint) – but we do not know which one is infact true.
ttl3 = ttl + """
:lovs rdfs:range :Carrot_Lettuce_undecided .
:Carrot_Lettuce_undecided
owl:unionOf ( :Carrot :Lettuce ) .
:Snowball :lovs :treat_456 .
"""
Graph g3#
g3a = rdflib.Graph().parse(data=ttl3)
g3b = rdflib.Graph().parse(data=ttl3)
print(f"#triples: {len(g3a)=}, {len(g3b)=}")
#triples: len(g3a)=12, len(g3b)=12
interesting = ["treat_456", "lovs", "Carrot", "Lettuce"]
print(focus(interesting, g3a.serialize()))
:Snowball :lovs :treat_456 .
:eat rdfs:range :Carrot .
:lovs rdfs:range :Carrot_Lettuce_undecided .
:Carrot_Lettuce_undecided owl:unionOf ( :Carrot :Lettuce ) .
:Lettuce rdfs:subClassOf :rabbit_food .
:Carrot rdfs:subClassOf :rabbit_food .
owlrl.DeductiveClosure(owlrl.OWLRL_Semantics,
axiomatic_triples = False).expand(g3b)
print(f"#triples: {len(g3a)=}, {len(g3b)=}")
#triples: len(g3a)=12, len(g3b)=144
In the ttl-code below we can see:
:Carrot
and:Lettuce
are subclasses of:Carrot_Lettuce_undecided
:treat_456
is an instance of:Carrot_Lettuce_undecided
, but not an instance of:Carrot
or:Lettuce
print(focus(interesting, g3b.serialize()))
:Snowball :lovs :treat_456 ;
owl:sameAs :Snowball .
:eat rdfs:range :Carrot,
:Carrot_Lettuce_undecided,
:rabbit_food ;
owl:sameAs :eat .
:lovs rdfs:range :Carrot_Lettuce_undecided ;
owl:sameAs :lovs .
:Lettuce rdfs:subClassOf :Carrot_Lettuce_undecided,
:rabbit_food ;
owl:sameAs :Lettuce .
:treat_123 a :Carrot,
:Carrot_Lettuce_undecided,
:rabbit_food ;
owl:sameAs :treat_123 .
:treat_456 a :Carrot_Lettuce_undecided ;
owl:sameAs :treat_456 .
:Carrot rdfs:subClassOf :Carrot_Lettuce_undecided,
:rabbit_food ;
owl:sameAs :Carrot .
:Carrot_Lettuce_undecided owl:sameAs :Carrot_Lettuce_undecided ;
owl:unionOf _:n671c0c466d164064ac36face96fbb9d6b1 .
_:n671c0c466d164064ac36face96fbb9d6b1 rdf:first :Carrot ;
rdf:rest _:n671c0c466d164064ac36face96fbb9d6b2 ;
owl:sameAs _:n671c0c466d164064ac36face96fbb9d6b1 .
_:n671c0c466d164064ac36face96fbb9d6b2 rdf:first :Lettuce ;
rdf:rest () ;
owl:sameAs _:n671c0c466d164064ac36face96fbb9d6b2 .
Explanation:
:Carrot_Lettuce_undecided owl:unionOf ( :Carrot :Lettuce ) .
This code defines a new class :Carrot_Lettuce_undecided
, which is the superclass of both :Carrot
and :Lettuce
.
In effect :Carrot_Lettuce_undecided
is sort of a sister-class of :rabbit-food
.