RDFS range bunny carrot

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 AND yyy 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.