Re: SHACL Shape definition for including and excluding parent types

Thank you for your considered ideas, Irene.  Unfortunately, while I've
asked for an enhancement for SHACL AF, in the meantime I must rely on SHACL
alone.  Also, the system is used by downstream systems and others.
Therefore changing the class structure is not an option (that's a
real-world constraint).

I'll be more careful with my terminology. Thanks for the explanation.

-- Scott

On Wed, Sep 15, 2021 at 12:56 PM Irene Polikoff <irene@topquadrant.com>
wrote:

> Hi Scott,
>
> If I understand this correctly (and I am not sure I do), there are the
> following options:
>
> 1. Use sh:or to say that either values of ex:parent come from members of
> below classes and the additional constraints apply OR values of ex:parent
> do not come from members of below classes.
>
> To make the expressions easier to understand and more maintainable, you
> may consider creating a parent class ex:Cls and make ex:Cls1, ex:Cls2 and ex:Cls3
> its subclasses. Then, you would simply add or remove subclasses if your
> model evolves.
>
> You could also create two untargeted node shapes - just to make it simpler
> to understand e.g.,
>
>  my_sh:ClassShape
>       a sh:NodeShape ;
>       sh:targetClass ex:Cls1 ; .
>       sh:or ( ex:ParentsCls
>
>       ex:ParentsNotCls) ;
>
>
> ex:ParentsCls and ex:ParentsNotCls would be the two untargeted NodeShapes
>
> 2. Use SPARQL-based constraint components option to create a new
> constraint component that would encapsulate your conditions and take as
> parameters ex:Cls1, ex:Cls2 and ex:Cls3.
>
> 3. Use SPARQL-based targets to target only some members of ex:Cls1 - those
> with only certain values in the ex:parent property
>
> This option is available only in the Advanced SHACL Features specification.
>
> As an aside, I wanted to comment on the terminology used because using the
> right terminology is helpful in articulating requirements and solutions:
>
> 1. Everything in SHACL is either a target, shape or constraint
>
> 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.
>
>
> Not quite. The fact that a value of ex:property is a member of a certain
> class is a constraint
>
> 2.
>
> 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 is not a shape. It is data.
>
> I understand that you mean “this data is of valid shape” or "this data
> would conform to the SHACL shapes I am needing to create”. However, in
> SHACL, when you say “the following is a shape”, you are expected to be
> referring to either a SHACL Node Shape or a SHACL Property Shape.
>
> The same applies to the word “target”. Data does not target anything. A
> target can only be defined in a shape. A shape targets some data.
>
>
> Hope this helps,
>
> Irene
>
> On Sep 14, 2021, at 11:44 PM, Scott Henninger <scotthenninger@gmail.com>
> wrote:
>
> 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 21:34:24 UTC