5

I have the following query that I run recursively in Python using ontospy:

SELECT ?c WHERE {
    ?c rdfs:subClassOf ?restriction .
    ?restriction owl:onProperty :has_part ; owl:someValuesFrom ?p .
    VALUES ?p { <some_uri> }
}

Basically, I take the values returned from that and re-run the query to follow a hierarchy of "has part some" relationships within an ontology. I am hoping to avoid making multiple SPARQL queries by adding the recursion to the query itself. I know how to do this for a single triple using rdfs:subClassOf* but cannot figure out the syntax for combining the two triples:

?c rdfs:subClassOf ?restriction .
?restriction owl:onProperty :has_part ; owl:someValuesFrom ?p .

Is this possible?

Fiver
  • 9,909
  • 9
  • 43
  • 63
  • 2
    Obviously, it is possible to write something like `(rdfs:subClassOf/owl:someValuesFrom)*`, however, in general, it is impossible in this way to specify that restrictions are on the `:has_part` property. See also [this question](https://stackoverflow.com/questions/38641984/sparql-applying-limiting-criteria-to-predicates) and linked ones, and also https://wiki.blazegraph.com/wiki/index.php/PropertyPaths. – Stanislav Kralin Mar 15 '18 at 20:19
  • @StanislavKralin I see, thanks for the info. To quote The Dude, "That's a bummer, man!" – Fiver Mar 15 '18 at 21:02
  • Please add the "python" tag if relevant. – Stanislav Kralin Mar 19 '18 at 12:05
  • Yep, was just thinking the same. Thanks for the answer, I'll look it over today. Really appreciate the help! – Fiver Mar 19 '18 at 12:07

1 Answers1

4

I can't give a formal proof, but this looks impossible. This is not what property paths were designed for, and why some extensions exist (1, 2).

Under certain commitments (e.g. with tree-like structures), it is possible to figure out something using FILTER NOT EXISTS, however, this is not a general solution.

The idea is to do this in two queries. In essence, this is SELECT over CONSTRUCT. By the way, such SPARQL extension has already been proposed.


Let's use on which Ontospy is based, because

Ontospy does not offer any ontology-editing features, nor it can be used to interrogate a triplestore.

Input (ontology.ttl)

@prefix : <http://www.example.org/ontology#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@base <http://www.example.org/ontology> .

<http://www.example.org/ontology> rdf:type owl:Ontology .

:hasPart rdf:type owl:ObjectProperty .

:Country rdf:type owl:Class ;
         rdfs:subClassOf [ rdf:type owl:Restriction ;
                           owl:onProperty :hasPart ;
                           owl:someValuesFrom :State
                         ] .

:State rdf:type owl:Class ;
       rdfs:subClassOf [ rdf:type owl:Restriction ;
                         owl:onProperty :hasPart ;
                         owl:someValuesFrom :City
                       ] .

:City rdf:type owl:Class .

Python code

import rdflib

g = rdflib.Graph()
g.parse("ontology.ttl", format="n3")

qres = g.update(
    """PREFIX : <http://www.example.org/ontology#> 
       INSERT { ?c :hasSome ?p } 
       WHERE  { ?c rdfs:subClassOf [ owl:onProperty :hasPart ; 
                                     owl:someValuesFrom ?p ] }""")

qres = g.query(
    """PREFIX : <http://www.example.org/ontology#> 
       SELECT ?a ?b WHERE {?a :hasSome+ ?b }""")

for row in qres:
    print("%s :hasSome+ %s" % row)

qres = g.update(
    """PREFIX : <http://www.example.org/ontology#> 
       DELETE { ?s :hasSome ?o } WHERE { ?s :hasSome ?o }""")

Output

:Country :hasSome+ :State
:State   :hasSome+ :City
:Country :hasSome+ :City

If you don't want to modify initial RDFLib graph, just create another one:

import rdflib

g1 = rdflib.Graph()
g1.parse("ontology.ttl", format="n3")

qres = g1.query(
    """PREFIX : <http://www.example.org/ontology#> 
       CONSTRUCT {?c :hasSome ?p } WHERE {
           ?c rdfs:subClassOf [ owl:onProperty :hasPart ;
                                owl:someValuesFrom ?p  ] }""")

g2 = rdflib.Graph();
for triple in qres: # quite a few triples
    g2.add(triple)

qres = g2.query(
    """PREFIX : <http://www.example.org/ontology#> 
       SELECT ?a ?b WHERE { ?a :hasSome+ ?b }""")

for row in qres:
    print("%s :hasSome+ %s" % row)

Probably you can use transitiveClosure() or transitive_objects() instead of the second query in both cases.

Stanislav Kralin
  • 11,070
  • 4
  • 35
  • 58
  • Thanks Stanislav, if no one else posts a better answer I'll award the bounty to this answer before it expires. – Fiver Mar 22 '18 at 14:16
  • @Fiver, as you wish! Perhaps @AndyS[eaborn] can give a formal proof, or there is something like such proof in articles of Claudio Gutierrez. BTW, it seems that there are some problems in SPARQL formal semantics (see e.g. section 2 in [this paper](https://arxiv.org/pdf/1606.01441.pdf)). Perhaps you could also ask you question at [public-sparql-dev](https://lists.w3.org/Archives/Public/public-sparql-dev/) – Stanislav Kralin Mar 23 '18 at 08:48