w3c / N3

W3C's Notation 3 (N3) Community Group
48 stars 18 forks source link

Custom built-in for negation-as-failure #18

Open william-vw opened 5 years ago

william-vw commented 5 years ago

Realizing negation-as-failure is currently realized by chaining two or three built-ins, i.e., log:semantics (log:conclusions) log:notIncludes. See here for an example. This seems quite cumbersome for representing a single concept.

If we introduce a custom built-in (such as notIn) we only require a single built-in to represent NAF, which could be a good selling point for N3. Implementation-wise it could be simply macro-expanded to the aforementioned chain of built-ins (where backward-chaining systems could optimize its implementation). On the other hand, this requires going beyond what N3 currently provides, which could lead to a slippery slope of new built-ins that bog down the standardization process.

A similar issue occurs with representing a "scoped" universal quantification as exemplified here. This can also be represented using a combination of log:semantics (log:conclusions) log:notIncludes, as shown here. Nevertheless it seems trickier to define a custom built-in for this as compared to the above case.

Note that Eye also has custom built-ins (including findall, fail) that can be used to represent NAF (see examples directory).

Thoughts?

william-vw commented 5 years ago

To elaborate on the second "type" of use-case for scoped negation as failure:

This involves identifying cases where all resources in an identified set have to adhere to a given condition. For instance, when needing to check whether all subtasks of a composite task have a completed status. In an open-world context this would not be possible, since it requires assuming that there's no other graph that defines another subtask (that, in this case, is not completed).

One can represent this using scoped negation as failure (see here): using log:notIncludes, one can rule out the existence of a counter-example (i.e., a non-completed subtask) and solve the problem.

However this requires an N3 developer to themselves redefine the problem from (scoped) universal quantification to scoped negation as failure, which I do not think is trivial.

Hence an option here could be to introduce a new builtin to represent "scoped" universal quantification. This would however not be a simple "macro expansion" as with the SNAF builtin (i.e., expanding the built-in to a simple chain of existing builtins), since there's no current way to express negation in N3 (?) For instance, inventing a new built-in (@forAll <varlist> @in (<doc>|<formula>)):

@forAll :t. { 
  :t a :CompositeTask.
  @forAll :st @in <subtasks.n3> {
    { :t :subTask :st. } log:implies { :st a :Completed. }.
   }.
} log:implies {
  :t a :AllTasksCompleted.
}.

Would have to be converted to:

@forAll :t. {
  :t a :CompositeTask. 
  <subtasks.n3> log:semantics ?sem.
  ?sem log:notIncludes { :t :subTask _:st. _:st a :NotCompleted. } 
}  log:implies { 
    :t a :AllTasksCompleted.
}.

I.e., the negation of Completed would need to be known as well. (Not saying that this is an insurmountable problem, but still.)

doerthe commented 5 years ago

Ah, I finally get your idea and think that is related to the eye predicate e:findall.

I am a little bit surprised that you propose some formulas with nested quantifiers here since I normally have to think a lot about such constructs (you can now picture me with a piece of paper in front of the computer trying to translate this formula to some normal form :) ).

I see many problems with the construct you propose, but I start with the biggest one: in my opinion, the scope of your @in is not porperly defined.

Maybe I am mistaken, but I understand your @in construct as "every resource mentioned in the document subtasks.n3". If that is the case, you did not solve your open-world-problem here:

Imagine that your file subtasks.n3 does contain the random triple :william :likes :chocolate. In that case, you cannot be sure whether :william is also a subtask of your composite task. The fact that this is not mentioned anywhere does not mean that he isn't (open world).

Actually, you do not even need your random triple, what if subtasks.n3 contains

:task1 :subtask :task2.

How do you know that :task1 and subtask are not themselves subtasks again?

So, what you would need it some notation that you can or cannot find a certain triple in a concrete document. but: then we are back at log:semantics.

doerthe commented 5 years ago

OK, I further thought about it. Instead of complaining (which i will do later), I ask a question since I think that I misunderstood your example:

Could it be that your built-in actually has three values instead of two?

@forAll <varlist> @in (<doc>|<formula>) <FORMULA>

where this last field "formula" needs o be a cited formula which needs to be verifiable by only using <doc>? Than we already get closer, but then I dislike that we have to verify an implication.

william-vw commented 5 years ago

Yes, the builtin was supposed to be used as defined (i.e., the @ in belonging to @ forAll) but your explanation helps to clarify its usage. Apologies for any confusion (I first meant to call it @ forAllIn but I thought that was a bit ugly .. Lol)

Not sure what you mean by the following:

I dislike that we have to verify an implication.

It seems to me that the only difference here is that a scope is introduced.

Note that the current goal involves basing the semantics of this builtin on SNAF (it could of course be implemented in any way, such as by Eye using e:findall behind the scenes)

doerthe commented 5 years ago

My main question was not about the relation between the @in and the @forAll but about the relation of these two keys to the formula after: Do you want that the formula after can be directly be verified by searching the content of whatever comes after @in?

If so, we come to the part you didn't understand:

Not sure what you mean by the following:

I dislike that we have to verify an implication.

The formula in your example was

  @forAll :st @in <subtasks.n3> {
    { :t :subTask :st. } log:implies { :st a :Completed. }.
   }.

So we need an extra reasoning step which tests for all :t :subTask :st. whether we also find :st a :Completed., in other words: we need to verify the implication and that is something we normally do not do. So it makes the reasoning more complex. Of course we can translate just as you said. But you could also put more complex constructs in there.

I know that this is highly subjective, but I still don't understand how this rather complex construct should be easier to understand than a formula without nested quantification and nested rules.

Would it help if we would try to define another predicate instead of using keywords?

Or, let's meet in the middle: what do you dislike about e:findall?

william-vw commented 5 years ago

So we need an extra reasoning step which tests for all :t :subTask :st. whether we also find :st a :Completed., in other words: we need to verify the implication and that is something we normally do not do.

Still the only difference here seems that the implication is verified within a given scope (i.e., document). It's true that the translation to SNAF could be imperiled when putting more complex constructs in there, but note that its syntax could be restricted as well.

In general, the goal here is to represent the aforementioned problem in a more user-friendly way - if you think that the example representation doesn't meet that goal, then feel free to suggest another one :-)

Or, let's meet in the middle: what do you dislike about e:findall?

Are you implying here to add e:findall to the N3 spec? (no problem if you are, just for clarification)

In that case, my interest lies in keeping any new builtin as close as possible to the already available builtins. In my opinion, the usage of e:findall, with its scope, select and where clauses, is quite dissimilar from other N3 builtins. Also, it's solution for this particular problem (as you described elsewhere) is a bit unintuitive as well. Nothing personally against e:findall at all, but if we would e.g., opt for a notIn-style builtin then it seems like a scoped universal quantifier should have a similar syntax.

josd commented 5 years ago

Fair enough and I did an attempt to implement your proposed built-ins. This is done in https://github.com/josd/eye/tree/master/reasoning/n3c The built-ins are actually not inside the reasoner, but written as N3 components i.e. rules using <= in N3 https://github.com/josd/eye/blob/master/reasoning/n3c/components.n3

PREFIX n3: <https://www.w3.org/ns/n3#>
PREFIX log: <http://www.w3.org/2000/10/swap/log#>
PREFIX e: <http://eulersharp.sourceforge.net/2003/03swap/log-rules#>
PREFIX : <http://example.org/test#>

{?BGP n3:notIn ?DOC} <= {
    ?DOC log:semantics ?F.
    ?F log:notIncludes ?BGP.
}.

{(?BGP1 ?BGP2) n3:allIn ?DOC} <= {
    ?DOC log:semantics ?F.
    ("forall" {?F log:includes ?BGP1} {?F log:includes ?BGP2}) e:derive true.
}.

The built-ins are using the proposed n3: namespace.

For the query https://github.com/josd/eye/blob/master/reasoning/n3c/allIn_query.n3

PREFIX n3: <https://www.w3.org/ns/n3#>
PREFIX : <http://example.org/test#>

{
    ?T a :CompositeTask.
    ({?T :subTask ?ST} {?ST a :Completed}) n3:allIn <subtasks.n3>.
}
    =>
{
    ?T a :AllTasksCompleted.
}.

the answer https://github.com/josd/eye/blob/master/reasoning/n3c/allIn_answer.n3 is :task1 a :AllTasksCompleted.

william-vw commented 5 years ago

Thanks very much for this @josd ! Yes, to me a (for)AllIn-style builtin seems very much suited towards resolving these kinds of problems (just a slight variation of your proposal):

{n3:forAllIn (?BGP1 ?BGP2) ?DOC} <= {
    ?DOC log:semantics ?F.
    ("forall" {?F log:includes ?BGP1} {?F log:includes ?BGP2}) e:derive true.
}.

So we can write something like

n3:forAllIn ({?T :subTask ?ST} {?ST a :Completed}) <subtasks.n3>.

(possibly one could include a ?VAR at the start of the list as well, i.e., ?ST in this example)

josd commented 5 years ago

n3:forAllIn is indeed more appealing than n3:allIn and I updated https://github.com/josd/eye/tree/master/reasoning/n3c accordingly. Thanks @william-vw

-- https://josd.github.io/ http://josd.github.io/

On Sun, Jul 14, 2019 at 5:06 PM William Van Woensel < notifications@github.com> wrote:

Thanks very much for this @josd https://github.com/josd ! Yes, to me a (for)AllIn-style builtin seems very much suited towards resolving these kinds of problems (just a slight variation of your proposal):

{n3:forAllIn (?BGP1 ?BGP2) ?DOC} <= { ?DOC log:semantics ?F. ("forall" {?F log:includes ?BGP1} {?F log:includes ?BGP2}) e:derive true. }.

So we can write something like

n3:forAllIn ({?T :subTask ?ST} {?ST a :Completed}) .

(possibly one could include a ?VAR at the start of the list as well, i.e., ?ST in this example)

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/w3c/N3/issues/18?email_source=notifications&email_token=AABPHJGGPLLQK6L3OQXEI53P7M6HXA5CNFSM4H5IOUF2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODZ4HHBQ#issuecomment-511210374, or mute the thread https://github.com/notifications/unsubscribe-auth/AABPHJFFXSMA5EP23Y3N263P7M6HXANCNFSM4H5IOUFQ .

josd commented 5 years ago

Another alternative that is currently implemented in EYE is using false <= formula. for negation-as-failure. There is an example at https://github.com/josd/eye/blob/master/reasoning/4color/4color_rules.n3

josd commented 5 years ago

This is not scoped hence EYE is back at using ?SCOPE e:fail {formula}.

josd commented 3 years ago

Yet another alternative is to use the core built-in log:notIncludes with a subject like described at https://github.com/josd/eye/blob/master/log-rules.n3#L61 For a concrete example see https://github.com/josd/eye/tree/master/reasoning/snaf

william-vw commented 3 years ago

@josd it's really neat that a concrete list parameter can also be passed as scope to log:notIncludes: http://ppr.cs.dal.ca:3002/n3/editor/s/x1orcudC !

Figuring out this scope parameter for myself has been on my TODO list for some time .. What does the recursion number mean (sorry for my ignorance here) Also, would it be possible / desirable to have scope refer to the deductive closure of the current document (in fact, my implementation currently supports the latter, for better or worse ..)

josd commented 3 years ago

@william-vw sorry for the confusion, but an example like http://ppr.cs.dal.ca:3002/n3/editor/s/zfpCXb8t will not work as you expect :-( The reason is that in our definition of e:Scope at https://github.com/josd/eye/blob/master/log-rules.n3#L65

e:Scope a rdfs:Class;
    rdfs:comment "class of (context recursion) pairs where a context is a list of consulted URI's and a recursion is the deductive closure recursion number";
    rdfs:subClassOf e:Tuple;
    e:tupleTypes (e:Context e:Recursion).

context really means a list of consulted URI's that is consulted as command line arguments when the reasoning process starts. So if you give an explicit scope it must match with that list of consulted URI's, otherwise the built-in fails. The scope also refers to the deductive closure of all those consulted URI's, even more, because the built-ins can give rise to an extension of the deductive closure we have to number the deductive closures which is done with the recursion number.

william-vw commented 3 years ago

@josd

context really means a list of consulted URI's that is consulted as command line arguments when the reasoning process starts.

Really the way I use the scope argument for "universal" builtins is to refer to the contents (deductive closure, really) of the current document. This is a toy example (but at least it's self-contained):

(:r1 :r2 :r3 :r4) a :ResourceList .

:r1 :isConstant false .
:r2 :isConstant true .
:r3 :isConstant false .
:r4 :isConstant true.

{   ?l a :ResourceList .
    (?m { ?l list:member ?m . ?m :isConstant false } ?sl) log:collectAllIn _:x .

} => { :test1 :has ?sl } .

I believe in one or two cases I use a similar setup for log:notIncludes (i.e., whether the current deductive closure includes or not some given formula).

I'm wondering what triggered the need for this type of setup (i.e., with the cmd line providing arguments) - and whether you see a possibility here to extend this builtin to accept a concrete list of URIs to be consulted (i.e., within the N3 code).

Just asking since most other builtins accept concrete arguments directly in the N3 code - other reasoners may not be command-line based (although their API, if any, could be extended with a similar purpose, of course).

(Currently the jen3 reasoner simply assumes a blank node, which is assumed to refer to the current document..)

Just thinking out loud: for consistency, maybe the scope argument could be defined to accept any N3 graph - it could then be chained with log:conclusion or log:semantics depending on whether deductive closures are needed. (One could even simply refer to an embedded quoted graph inside the document.) A special value (e.g., a blank node, or empty q-name) could then refer to the deductive closure of the current document.

So if you give an explicit scope it must match with that list of consulted URI's, otherwise the built-in fails.

But in this example I believe that the rule succeeds (?)

The scope also refers to the deductive closure of all those consulted URI's, even more, because the built-ins can give rise to an extension of the deductive closure we have to number the deductive closures which is done with the recursion number.

Is this also dictated by the cmd line arguments (i.e., whether the deductive closure is calculated), or does it occur by default?

josd commented 3 years ago

But in this example I believe that the rule succeeds (?)

Well, as far as I can see it here, the conclusion :Alice :likes :Somebody. does not follow.

The scope also refers to the deductive closure of all those consulted URI's, even more, because the built-ins can give rise to an extension of the deductive closure we have to number the deductive closures which is done with the recursion number.

Is this also dictated by the cmd line arguments (i.e., whether the deductive closure is calculated), or does it occur by default?

In EYE eveything that is taken from the web is dictated by the command line arguments and the only exception is log:semantics