Search code examples
rdfinferenceturtle-rdfshacl

Implementing SHACL rule inference through expression in sh:object


Currently I am trying to infer a new property maps:mapstoclass based on the triples below. The idea is that I can use the inferred outcome (together with an rdf file containing alignment of data:classes) to determine the similarity between data0:object100 and its overlapping objects from data1:, specified in maps:hasOverlap.

maps:relation_obj1  a     maps:OverlapRelations ;
        maps:hasOverlap   [ a                      data1:classA ;
                            maps:mainRelativeArea  "80.0"^^xsd:float ;
                            maps:secRelativeArea   "100.0"^^xsd:float ;
                            maps:secfeature        data1:object1 ;
                          ] ;
        maps:hasOverlap   [ a                      data1:classX ;
                            maps:mainRelativeArea  "40.0"^^xsd:float ;
                            maps:secRelativeArea   "100.0"^^xsd:float ;
                            maps:secfeature        data1:object2 ;
                          ] ;
        maps:mainfeature  data0:object100 ;
        maps:mainclass     data0:classB .

Firstly, I've looked at whether the object properties of maps:hasOverlap satisfy my qualifiedValueShape(the shacl shapes/rule are given at the end). In this case only the 'hasOverlap' object with maps:secfeature data1:object1 satisfies the condition. Thus the object of 'maps:mapsto' should be data1:object1. The result I expect is:

maps:relation_obj1 maps:mapstoclass data1:object1. 

However, I currently get:

maps:relation_obj1 maps:mapstoclass data1:object1, data1:object2.

What am I doing wrong? Does the sh:condition of the rule need to be applied explicitly within sh:object? I've looked at node expressions, but did not succeed in using it - and I could not find applicable examples in the documentation.

The shapes used:

ex:mainAreaShape
    rdf:type sh:NodeShape;
    sh:property [
        sh:path maps:mainRelativeArea ;
        sh:minInclusive 80 ;
    ].

ex:secAreaShape
    rdf:type sh:NodeShape;
    sh:property [
        sh:path maps:secRelativeArea ;
        sh:minInclusive 80 ;
    ].


ex:OverlapRelations
    rdf:type rdfs:Class, sh:NodeShape ;
    sh:targetClass maps:OverlapRelations;
    rdfs:label "whether overlap between features is enough to generate relation" ;
    sh:rule [
        rdf:type sh:TripleRule ;
        sh:subject sh:this ;
        sh:predicate maps:mapstoclass;
        sh:object [sh:path (maps:hasOverlap
                    rdf:type) ;
                    ];  
        sh:condition ex:OverlapRelations;
        sh:condition [
            sh:property [
            sh:path maps:hasOverlap ;
            sh:nodeKind sh:BlankNode ;
            sh:minCount 1;
            sh:qualifiedValueShape [
                    sh:and (ex:mainAreaShape ex:secAreaShape);  
                                    ];
                    sh:qualifiedMinCount 1;
                    sh:qualifiedMaxCount 1;
                        ];
                    ];
            ].

Solution

  • The sh:condition only filters out the focus nodes to which the rule applies, but then has no impact on the evaluation of sh:object. In your case, without verifying, I assume that your (single) focus node maps:relation_obj1 does fulfill the condition because one of its values conforms to the QVS. However, the sh:object expression is still being evaluated for all values of the path maps:hasOverlap/rdf:type, which then delivers the types of both.

    One option would be to express what you need in SPARQL and use a SPARQL-based rule.

    Another option would be to move the logic currently in sh:condition into the sh:object node expression. I believe sh:filterShape

    https://w3c.github.io/shacl/shacl-af/#node-expressions-filter-shape

    could be used here. Keep in mind that node expressions are basically pipelines, where nodes go in on one side and other nodes go out on the other side. In your case, I think you want to

    1. start with all values of maps:hasOverlap
    2. then filter only those where your value shape applies, i.e. filterShape sh:and (ex:mainAreaShape ex:secAreaShape)
    3. of those return the rdf:type

    Sorry I cannot spend more time on this particular example, but maybe it's something like

    sh:object [  # 3.
        sh:path rdf:type ;
        sh:nodes [ # 2.
            sh:filterShape [ 
                sh:nodeKind sh:BlankNode ;
                sh:node ex:mainAreaShape, ex:secAreaShape ;  # same as your sh:not
            ] ;
            sh:nodes [ # 1.
                sh:path maps:hasOverlap ;
            ]
        ]
    ]
    

    You need to "read" node expressions from the inside out, so the innermost hasOverlap path is evaluated first, then the results of that are run through the filter. If there are no resulting nodes, or those don't have an rdf:type then no sh:object is found and thus no triple inferred.

    BTW the sh:targetClass is not needed and I think the whole thing could also be expressed using the (newer) sh:values keyword as

    ex:OverlapRelations
        a rdfs:Class, sh:NodeShape ;
        rdfs:label "whether overlap between features is enough to generate relation" ;
        sh:property [
            sh:path maps:mapstoclass ;
            sh:values [ # 1.
                sh:path rdf:type ;
                sh:nodes [ # 2.
                    sh:filterShape [ 
                        sh:nodeKind sh:BlankNode ;
                        sh:node ex:mainAreaShape, ex:secAreaShape ;
                    ] ;
                    sh:nodes [ # 1.
                        sh:path maps:hasOverlap ;
                    ]
                ]
            ]
        ] .
    

    Again, untested so please apologize any glitches :)