Search code examples
declarativedataloganswer-set-programming

DLV Rule is not safe


I am starting to work with DLV (Disjunctive Datalog) and I have a rule that is reporting a "Rule is not safe" error, when running the code. The rule is the following:

foo(R, 1) :- not foo(R, _)

I have read the manual and seen that "cyclic dependencies are disallowed". I guess this is why I am being reported the error, but I am not sure how this statement is so problematic to DLV. The final goal is to have some kind of initialization in case that the predicate has not been defined.

More precisely, if there is no occurrence of 'foo' with the parameter R (and anything else), then define it with parameters R and 1. Once it is defined, the rule shouldn't be triggered again. So, this is not a real recursion in my opinion.

Any comments on how to solve this issue are welcomed!


I have realised that I probably need another predicate to match the parameter R in the body of the rule. Something like this:

foo(R, 1) :- not foo(R, _), bar(R)

Since, otherwise there would be no way to know whether there are no occurrences of foo(R, _). I don't know whether I made myself clear.

Anyway, this doesn't work either :(


Solution

  • To the particular "Rule is not safe" error: First of all this has nothing to do with cyclic or acyclic dependencies. The same error message shows up for the non-cyclic program:

    foo2(R, 1) :- not foo(R,_), bar(R).
    

    The problem is that the program is actually not safe (http://www.dlvsystem.com/html/DLV_User_Manual.html#SAFETY). As mentioned in the section on negative rules (anchor #AEN375, I am only allowed to use 2 links in my answer):

    Variables, which occur in a negated literal, must also occur in a positive literal in the body.

    Observe that the _ is an anonymous variable. I.e., the program

    foo(R,1) :- not foo(R,_), bar(R).
    

    can be equivalently written as (and is equivalent to)

    foo(R,1) :- not foo(R,X), bar(R).
    

    Anonymous variables (DLV manual, anchor #AEN264 - at the end of the section) just allow us to avoid inventing names for variables that will only occur once within the rule (i.e. for variables that only express "there is some value, I absolutely do not care about it), but they are variables nevertheless. And since negation with not is "negation" and not "true negation" (or "strong negation" as it is also often called), none of the three safety conditions is satisfied by the rule.

    A very rough and high-level intuition for safety is that it guarantees that every variable in the program can be assigned to some finite domain - as it is now the case with R by adding bar(R). However, the same also must be the case for the anonymous variable _ .

    To the actual problem of defining default values: As pointed out by lambda.xy.x, the problem here is the Answer Set (or stable model) semantics of DLV: Trying to do it in one rule does not give any solution: In order to get a safe program, we could replace the above problems e.g. by

    foo(1,2). bar(1). bar(2).
    tmp(R) :- foo(R,_).
    foo(R,1) :- not tmp(R), bar(R).
    

    This has no stable model: Assume the answer is, as intended, {foo(1,2), bar(1), bar(2), foo(2,1)} However, this is not a valid model, since tmp(R) :- foo(R,_) would require it to contain tmp(2). But then, "not tmp(2)" is no longer true, and therefore having foo(2,1) in the model violates the required minimality of the model. (This is not exactly what is going on, more a rough intuition. More technical details could be found in any article on answer set programming, a quick Google search gave me this paper as one of the first results: http://www.kr.tuwien.ac.at/staff/tkren/pub/2009/rw2009-asp.pdf)

    In order to solve the problem, it is therefore somehow necessary to "break the cycle". One possibility would be:

    foo(1,2). bar(1). bar(2). bar(3). 
    tmp(R) :- foo(R,X), X!=1.
    foo(R,1) :- bar(R), not tmp(R).
    

    I.e., by explicitly stating that we want to add R into the intermediate atom only if the value is different from 1, having foo(2,1) in the model does not contradict tmp(2) not being part of the model as well. Of course, this no longer allows to distinguish whether foo(R,1) is there as default value or by input, but if this is not required ...

    Another possibility would be to not use foo for the computation, but some foo1 instead. I.e. having

    foo1(R,X) :- foo(R,X).
    tmp(R) :- foo(R,_).
    foo1(R,1) :- bar(R), not tmp(R).
    

    and then just use foo1 instead of foo.