--- jupytext: formats: md:myst text_representation: extension: .md format_name: myst format_version: 0.13 jupytext_version: 1.16.4 kernelspec: display_name: Python 3 (ipykernel) language: python name: python3 --- # RDFS range bunny carrot ```{code-cell} ipython3 import rdflib import owlrl ``` ```{code-cell} ipython3 ttl = """ @prefix : . @prefix rdf: . @prefix rdfs: . @prefix 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? ```{code-cell} ipython3 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 * NEW 2025-04-23: Tempted to ask ChatGPT for an explanation of model 1? see {doc}`rdfs-range-bunny-carrot-model1-chat` **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**: * IF `aaa rdfs:range xxx` AND `yyy aaa zzz` * THEN `zzz rdf:type xxx` . We add a respective rdfs:range entailment rule: ```{code-cell} ipython3 ttl += """ :eat rdfs:range :Carrot . """ ``` ### Graphs g1a and g1b +++ We initialize two graphs with the same ttl code. ```{code-cell} ipython3 g1a = rdflib.Graph().parse(data=ttl) g1b = rdflib.Graph().parse(data=ttl) print(f"#triples: {len(g1a)=}, {len(g1b)=}") ``` 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. ```{code-cell} ipython3 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`: ```{code-cell} ipython3 interesting = ["Carrot", "Bunny", "eat"] print(focus(interesting, g1a.serialize())) ``` We now apply RDFS inferencing to `g1b`. ```{code-cell} ipython3 owlrl.DeductiveClosure(owlrl.OWLRL_Semantics, axiomatic_triples = False).expand(g1b) print(f"#triples: {len(g1a)=}, {len(g1b)=}") ``` A lot of new triples are added -- and also something interesting happened! ```{code-cell} ipython3 # this would give a lot of lines # print(g1b.serialize()) ``` These are the interesting parts of `g1b`: ```{code-cell} ipython3 print(focus(interesting, g1b.serialize())) ``` 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. ```{code-cell} ipython3 ttl2 = ttl + """ :eat rdfs:range :Lettuce . """ ``` ```{code-cell} ipython3 g2a = rdflib.Graph().parse(data=ttl2) g2b = rdflib.Graph().parse(data=ttl2) print(f"#triples: {len(g2a)=}, {len(g2b)=}") ``` These are the interesting parts of `g2a`: ```{code-cell} ipython3 interesting = ["eat"] print(focus(interesting, g2a.serialize())) ``` ```{code-cell} ipython3 owlrl.DeductiveClosure(owlrl.OWLRL_Semantics, axiomatic_triples = False).expand(g2b) print(f"#triples: {len(g2a)=}, {len(g2b)=}") ``` And these are the interesting parts of `g2b`: ```{code-cell} ipython3 print(focus(interesting, g2b.serialize())) ``` 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 * NEW 2025-04-23: Tempted to ask ChatGPT for an explanation of model 2? see {doc}`rdfs-range-bunny-carrot-model2-chat` **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. ```{code-cell} ipython3 ttl3 = ttl + """ :lovs rdfs:range :Carrot_Lettuce_undecided . :Carrot_Lettuce_undecided owl:unionOf ( :Carrot :Lettuce ) . :Snowball :lovs :treat_456 . """ ``` ### Graph g3 ```{code-cell} ipython3 g3a = rdflib.Graph().parse(data=ttl3) g3b = rdflib.Graph().parse(data=ttl3) print(f"#triples: {len(g3a)=}, {len(g3b)=}") ``` ```{code-cell} ipython3 interesting = ["treat_456", "lovs", "Carrot", "Lettuce"] print(focus(interesting, g3a.serialize())) ``` ```{code-cell} ipython3 owlrl.DeductiveClosure(owlrl.OWLRL_Semantics, axiomatic_triples = False).expand(g3b) print(f"#triples: {len(g3a)=}, {len(g3b)=}") ``` 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` ```{code-cell} ipython3 print(focus(interesting, g3b.serialize())) ``` 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`. ```{code-cell} ipython3 ```