Re: SHACL Shape definition for including and excluding parent types

Thanks for the detailed analysis, Ashley.  It gave me some insights.
However, I'm guilty of not expressing the problem correctly.  The main
requirements are that 1) the target is ex:Cls1 and 2) the ex:parent is 3)
one of n classes, which I have arbitrarily named ex:P_Cls{1, 2, 3}.   But
these are intended not as constraints but as a prerequisite.  I.e. if the
three are met, then there is a set of constraints that can be evaluated -
such as ex:Cls1 must have one ex:name property. Otherwise the constraints
are ignored.

So let me start over with some examples.

Case 1.  Adding the constraint that the target class must have an ex:name,
the following is valid for both the class and property constraints.
   ex:Child1
      a ex:Cls1 ;
      ex:parent ex:Parent1 ;
      ex:name "John" .
   ex:Parent1
      a ex:P_cls1 .

Case 2.  This is invalid because the ex:name is missing.
   ex:Child1
      a ex:Cls1 ;
      ex:parent ex:Parent1 .
   ex:Parent1
      a ex:P_cls2 .

Case 3.  This is valid because the parent is not in ex:P_Cls{1, 2, 3} and
therefore no shapes are applied.
   ex:Child1
      a ex:Cls1 ;
      ex:parent ex:Parent1 .
   ex:Parent1
      a ex:P_cls_x .

That to me is the challenging part.  I need to express that the three main
requirements are present before the other constraints can be validated.
I.e. the three main requirements (above 1), 2), 3) ) are prerequisites for
applying other shapes.

Here's an attempt that does some of this (and taking your advice not to
overthink).  The only problem is that I do not want a violation on the
class defs.
   my_sh:ClassShape
      a sh:NodeShape ;
      sh:targetClass ex:Cls1 ; .
      sh:xone ( [sh:path ex:parent ; sh:class ex:ex:P_cls1 ]
                [sh:path ex:parent ; sh:class ex:ex:P_cls2 ]
                [sh:path ex:parent ; sh:class ex:ex:P_cls3 ]
      ) ;
      sh:message "Should not be a violation" ;
      sh:property my_sh:MustHaveAName .

  my_sh:MustHaveAName
      a sh:PropertyShape ;
      sh:path ex:name ;
      sh:minCount 1 ;
      sh:maxCount 1 ;
      sh:message "Instance of targeted class must have one and only one
ex:name" .

What I would want this to expressis that only Case 2 should cause a
violation.

Sorry for bungling the initial question (and thanks again for a thorough
response).  I'm not entirely sure whether or how SHACL could satisfy this
kind of "prerequisite" constraint.  The above fails, as it will throw a
violation if the parent is not one of ex:P_cls{1, 2, 3}.  If anyone has
ideas on how to avoid this, I'm all ears.

Let me know if anything isn't clear or is incorrect (thanks for catching my
sh:not inside of sh:or mistake, BTW).

Thanks again for taking a look and helping out.
-- Scott

On Tue, Sep 14, 2021 at 6:44 PM Sommer, Ashley (L&W, Dutton Park) <
Ashley.Sommer@csiro.au> wrote:

> Hi Scott,
>
> I think you're over thinking this. From what I can tell, you have a simple
> requirement.
>
> Firstly, an obvious error: You have your sh:not constraint in your list of
> sh:or options.
> So you're effectively saying "class is P_cls1 OR P_cls2 OR P_cls3 OR NOT
> ex:P_clsInvalid_a" which as you can see is not logically consistent,
> because it would pass for *any*​ parent class that is
> NOT ex:P_clsInvalid_a.
> You instead need an AND in there, like "(class is P_cls1 OR P_cls2
> OR P_cls3) AND (NOT ex:P_clsInvalid_a)" like this:
> sh:and (
>   [
>     sh:or (
>       [ sh:class  ex:P_cls1 ]
>       [ sh:class  ex:P_cls2 ]
>       [ sh:class  ex:P_cls3 ]
>     )
>   ]
>   [ sh:not [ sh:class ex:P_clsInvalid_a ] ]
> )
>
> Secondly, to match one and only one of those in the OR list, use
> exclusive-or logic (sh:xone in SHACL
> <https://www.w3.org/TR/shacl/#XoneConstraintComponent>). Like this:
> sh:xone (
>   [ sh:class  ex:P_cls1 ]
>   [ sh:class  ex:P_cls2 ]
>   [ sh:class  ex:P_cls3 ]
> ) ;
>
> Next, if you want to maintain a list of invalid options, you can use sh:in
> <https://www.w3.org/TR/shacl/#InConstraintComponent> wrapped in a sh:not,
> to make a "not in" constraint. Like this:
> sh:not [ sh:in ( ex:P_clsInvalid_a ex:P_clsInvalid_b ) ] ;
>
> But when you break it down that far, why do you need the NOT? Wouldn't
> just "(class is P_cls1 XOR P_cls2 XOR P_cls3)" do the trick? Anything
> with a parent not in that list will be invalid, why does there need to be
> invalid classes specified, unless those invalid class are subclasses of
> P_cls1, P_cls2, P_cls3 ?
> Or are there other unspecified parent classes which are not in the list of
> three possible required, but also not invalid? If that is the case, your
> requirements need to be restated like:
>
>    - path ex:parent will have only one value
>    - the value for ex:parent can have one or more types (classes)
>    - one of those types needs to be {P_cls1 OR P_cls2 OR P_cls3}
>    - maximum of one of those types can be {P_cls1, P_cls2, P_cls3}
>    - parent's types cannot be one of {ex:P_clsInvalid_a,
>    ex:P_clsInvalid_b}
>
> You can use qualifiedvalueshape to ensure that at least one of the parents
> match the OR constraint, not need to use sh:xone in this case.
> Note, to emulate sh:class semantics on value-taking shapes like
> qualifiedValueShape and sh:in, I am using the same transitive SHACL
> Properly Path as demonstrated in the sh:class docs.
> <https://www.w3.org/TR/shacl/#ClassConstraintComponent>
>
> So, if I were writing a shape to match the requirements above, it would
> look like this:
>
> my_sh:ParentTypeRestriction
>   a sh:NodeShape ;
>   sh:targetClass ex:Cls1 ;
>   sh:property [
>     sh:path ex:parent ;
>     sh:minCount 1 ;
>     sh:maxCount 1 ;
>     sh:property [
>       sh:path ( rdf:type [ sh:zeroOrMorePath rdfs:subClassOf ] ) ;
>       sh:minCount 1 ;
>       sh:qualifiedMinCount 1 ;
>       sh:qualifiedMaxCount 1 ;
>       sh:qualifiedValueShape [
>         sh:or (
>           [ sh:hasValue ex:P_cls1 ]
>           [ sh:hasValue ex:P_cls2 ]
>           [ sh:hasValue ex:P_cls3 ]
>         )
>       ] ;
>       sh:not [ sh:in ( ex:P_clsInvalid_a ex:P_clsInvalid_b ) ] ;
>     ] ;
>     sh:message "parent type for ex:Cls1 is not in x:P_cls{1, 2, 3}" ;
>   ] ;
> .
> (Tested in PySHACL, using the valid and invalid examples you gave in your
> correspondence).
>
> This may be overkill for your requirements, and I may have misinterpreted
> your use-case. But it should at least give you something to work with.
> It could be taken even further by doing regex on type names to match
> P_cls{1,2,3}, if there is a known pattern they match, but that might be
> going too far.
>
> - Ashley
>
> ------------------------------
> *From:* Scott Henninger <scotthenninger@gmail.com>
> *Sent:* Tuesday, 14 September 2021 3:24 PM
> *To:* public-shacl@w3.org <public-shacl@w3.org>
> *Subject:* SHACL Shape definition for including and excluding parent types
>
> I have a couple of scenarios that involve restrictions on classes.  Most
> of them I can get working, but a couple I'm finding a bit difficult to
> figure out.  To make it simple, I'll start with the parent type restriction
> I'm trying to express.
>
> The following would be a valid shape as it targets ex:Cls1, specifies a
> parent with ex:parent, and the parent has one of the types ex:P_cls{1, 2,
> 3}.  (In all of these cases the targetClass is ex:Cls1).
>    ex:Child1
>       a ex:Cls1 ;
>       ex:parent ex:Parent1 ;
> .
>    ex:Parent1
>       a ex:Pcls1 ;
> .
>
> This would be an invalid shape because ex:Pcls{1,2,3} types must be
> defined for the parent:
>    ex:Child1
>       a ex:Cls1 ;
>       ex:parent ex:Parent1 ;
> .
>    ex:Parent1
>       a ex:P_clsInvalid_a ;
> .
>
> This one is valid because it includes one of ex:Pcls{1,2,3}:
>    ex:Child1
>       a ex:Cls1 ;
>       ex:parent ex:Parent1 ;
>    .
>    ex:Parent1
>       a ex:P_cls2, ex:P_clsInvalid_a ;
>    .
>
> The fourth is invalid because only one of ex:Pcls{1, 2, 3} is allowed:
>    ex:Child1
>       a ex:Cls1 ;
>       ex:parent ex:Parent1 ;
> .
>    ex:Parent1
>       a ex:P_cls1, ex:P_cls2;
> .
>
> If I could live with a list of disallowed types (for the sake of
> maintenance I'd rather say allow only ex:P_cls{1, 2, 3}) then the following
> seems to work:
>    my_sh:ParentTypeRestriction
>          a sh:NodeShape ;
>         sh:targetClass ex:Cls1 ;
>         sh:property[
>          s h:path ex:parent ;
>          sh:or (
>             [ sh:class  ex:P_cls1 ]
>             [ sh:class  ex:P_cls2 ]
>             [ sh:class  ex:P_cls3 ]
>             [ sh:not [ sh:class ex:P_clsInvalid_a ] ]
>         ) ;
>      sh:message "parent type for ex:Cls1 is not in x:P_cls{1, 2, 3}" ;
> ] ;
> .
>
> ..where I include an exhaustive list for sh:not.  As stated before, this
> isn't the best when the model changes and I need to find all the places
> where it needs to be excluded.
>
> So any ideas on how to 1) improve this shape, or 2) future-proof it for
> future additions to the rsf:type list?
>
> Thanks a bunch for taking a look.
> -- Scott
>
> Scott Henninger
> scotthenninger@gmail.com
>

Received on Wednesday, 15 September 2021 03:45:26 UTC