I am trying to implement a dcg that takes a set of strings of the form {a,b,c,d}*.The problem i have is if I have a query of the form s([a,c,b],[]),It returns true which is the right answer but when i have a query of the form s([a,c,f],[]),It does not return an answer and it runs out of local stack.
s --> [].
s --> s,num.
num --> [a].
num--> [b].
num--> [c].
num--> [d].
Use phrase/2
Let's try phrase(s,[a,b,c]) in place of s([a,b,c],[]). The reason is very simple: In this manner we are making clear that we are using a DCG (dcg) and not an ordinary predicate. phrase/2 is the "official" interface to grammars.
So your first question is why does phrase(s,[a,c,f]) not terminate while phrase(s,[a,b,c]) "gives the right answer" — as you say. Now, that is quick to answer: both do not terminate! But phrase(s,[a,b,c]) finds a solution/answer.
Universal termination
These are two things to distinguish: If you enter a query and you get an answer like true or X = a; you might be interested to get more. Usually you do this by entering SPACE or ;ENTER at the toplevel. A query thus might start looping only after the first or several answers are found. This gets pretty confusing over time: Should you always remember that this predicate might produce an answer ; another predicate produces two and only later will loop?
The easiest way out is to establish the notion of universal termination which is the most robust notion here. A Goal terminates iff Goal, false terminates. This false goal corresponds to hitting SPACE indefinitely ; up to the moment when the entire query fails.
So now try:
?- phrase(s,[a,c,f]), false.
loops.
But also:
?- phrase(s,[a,b,c]), false.
loops.
From the viewpoint of universal termination both queries do not terminate. In the most frequent usage of the words, termination is tantamount to universal termination. And finding an answer or a solution is just that, but no kind of termination. So there are queries which look harmless as long as you are happy with an answer but which essentially do not terminate. But be happy that you found out about this so quickly: It would be much worse if you found this out only in a running application.
Identify the reason
As a next step let's identify the reason for non-termination. You might try a debugger or a tracer but most probably it will not give you a good explanation at all. But there is an easier way out: use a failure-slice. Simply add non-terminals {false} into your grammar ; and goals false into predicates. We can exploit here a very beautiful property:
If the failure-slice does not terminate then the original program does not terminate.
So, if we are lucky and we find such a slice, then we know for sure that termination will only happen if the remaining visible part is changed somehow. The slice which is most helpful is:
?- phrase(s,[a,b,c]), false
s --> [], {false}.
s --> s, {false}, num.
There is not much left of your program! Gone is num//0! Nobody cares about num//0. That means: num//0 could describe anything, no matter what — the program would still loop.
To fix the problem we have to change something in the visible part. There is not much left! As you have already observed, we have here a left recursion. The classical way to fix it is:
Reformulate the grammar
You can easily reformulate your grammar into right recursion:
s --> [].
s --> num, s.
Now both queries terminate. This is the classical way also known in compiler construction.
But there are situations where a reformulation of the grammar is not appropriate. This simple example is not of this kind, but it frequently happens in grammars with some intended ambiguity. In that case you still can:
Add termination inducing arguments
?- Xs = [a,b,c], phrase(s(Xs,[]), Xs).
s(Xs,Xs) --> [].
s([_|Xs0],Xs) --> s(Xs0,Xs1), num, {Xs1=Xs}.
Inherently non-terminating queries
Whatever you do, keep in mind that not every query can terminate. If you ask: »Tell me all the natural numbers that exist – really all of them, one by one!« Then the only way to answer this is by starting with, say, 0 and count them up. So there are queries, where there is an infinity of answers/solutions and we cannot blame poor Prolog to attempt to fulfill our wish. However, what we most like in such a situation is to enumerate all solutions in a fair manner. We can do this best with a grammar with good termination properties; that is, a grammar that terminates for a list of fixed length. Like so:
?- length(Xs, M), phrase(s, Xs).
For about other examples how to apply failure-slices, see tag failure-slice.
I don't know if this is any help, because the prolog I'm using seems to have a very different syntax, but I just wrote the following program to try and match yours and it works ok.
Program
s([]).
s([X|Xs]) :- num(X), s(Xs).
num(a).
num(b).
num(c).
num(d).
Output
?- [prologdcg].
% prologdcg compiled 0.00 sec, 2,480 bytes
true.
?- s([a,c,b]).
true.
?- s([a,c,f]).
false.
Run using SWI-prolog.
Related
In many Prolog guides the following code is used to illustrate "negation by failure" in Prolog.
not(Goal) :- call(Goal), !, fail.
not(Goal).
However, those same tutorials and texts warn that this is not "logical negation".
Question: What is the difference?
I have tried to read those texts further, but they don't elaborate on the difference.
I like #TesselatingHeckler's answer because it puts the finger on the heart of the matter. You might still be wondering, what that means for Prolog in more concrete terms. Consider a simple predicate definition:
p(something).
On ground terms, we get the expected answers to our queries:
?- p(something).
true.
?- \+ p(something).
false.
?- p(nothing).
false.
?- \+ p(nothing).
true.
The problems start, when variables and substitution come into play:
?- \+ p(X).
false.
p(X) is not always false because p(something) is true. So far so good. Let's use equality to express substitution and check if we can derive \+ p(nothing) that way:
?- X = nothing, \+ p(X).
X = nothing.
In logic, the order of goals does not matter. But when we want to derive a reordered version, it fails:
?- \+ p(X), X = nothing.
false.
The difference to X = nothing, \+ p(X) is that when we reach the negation there, we have already unified X such that Prolog tries to derive \+p(nothing) which we know is true. But in the other order the first goal is the more general \+ p(X) which we saw was false, letting the whole query fail.
This should certainly not happen - in the worst case we would expect non-termination but never failure instead of success.
As a consequence, we cannot rely on our logical interpretation of a clause anymore but have to take Prolog's execution strategy into account as soon as negation is involved.
Logical claim: "There is a black swan".
Prolog claim: "I found a black swan".
That's a strong claim.
Logical negation: "There isn't a black swan".
Prolog negation: "I haven't found a black swan".
Not such a strong a claim; the logical version has no room for black swans, the Prolog version does have room: bugs in the code, poor quality code not searching everywhere, finite resource limits to searching the entire universe down to swan size areas.
The logical negation doesn't need anyone to look anywhere, the claim stands alone separate from any proof or disproof. The Prolog logic is tangled up in what Prolog can and cannot prove using the code you write.
There are a couple of reasons why,
Insufficient instantiation
A goal not(Goal_0) will fail, iff Goal0 succeeds at the point in time when this not/1 is executed. Thus, its meaning depends on the very instantiations that happen to be present when this goal is executed. Changing the order of goals may thus change the outcome of not/1. So conjunction is not commutative.
Sometimes this problem can be solved by reformulating the actual query.
Another way to prevent incorrect answers is to check if the goal is sufficiently instantiated, by checking that say ground(Goal_0) is true producing an instantiation error otherwise. The downside of this approach is that quite too often instantiation errors are produced and people do not like them.
And even another way is to delay the execution of Goal_0 appropriately. The techniques to improve the granularity of this approach are called constructive negation. You find quite some publications about it but they have not found their way into general Prolog libraries. One reason is that such programs are particularly hard to debug when many delayed goals are present.
Things get even worse when combining Prolog's negation with constraints. Think of X#>Y,Y#>X which does not have a solution but not/1 just sees its success (even if that success is conditional).
Semantic ambiguity
With general negation, Prolog's view that there exists exactly one minimal model no longer holds. This is not a problem as long as only stratified programs are considered. But there are many programs that are not stratified yet still correct, like a meta-interpreter that implements negation. In the general case there are several minimal models. Solving this goes far beyond Prolog.
When learning Prolog stick to the pure, monotonic part first. This part is much richer than many expect. And you need to master that part in any case.
Looking at the code below:
multiple(X,0).
multiple(X,Y) :- lt(0,X), lt(0,Y), diff(Y,X,D), multiple(X,D).
There happens to be something wrong. For your reference:
lt/2 is whether the first argument is less than the second.
diff/3 is whether the third argument is equal to the first argument minus the second.
lt/2 and diff/3 are defined correctly.
Is there a logical mistake in the definition? Is assuming that 0 is the multiple of every number problematic or is the logical mistake somewhere else? I get correct answers but the query goes to infinite loop I think.
EDIT:
here are the other definitions.
natNum(0).
natNum(s(X)) :- natNum(X).
lt(0,s(X)) :- natNum(X).
lt(s(X),s(Y)) :- lt(X,Y).
sum(0,X,X).
sum(s(X),Y,s(Z)) :- sum(X,Y,Z).
diff(X,Y,Z) :- sum(Z,Y,X).
?- multiple(X, s(s(s(s(s(s(0))))))).
where s(0) is 1, s(s(0)) is 2 etc. It gives all the desired answers for X but after the last answer, it gets stuck. I assume in an infinite recursive loop?
What is happening in your program? Does it loop forever, or does it only take some time since you haven't updated your hardware in recent decades? We cannot tell. (Actually, we could tell by looking at your program, but that is much too complex for the moment).
What we can do with ease is narrow down the source of this costly effort. And this, without a deep understanding of your program. Let's start with the query:
?- multiple(X, s(s(s(s(s(s(0))))))).
X = s(0)
; X = s(s(0))
; X = s(s(s(0)))
; X = s(s(s(s(s(s(0))))))
; loops. % or takes too long
Isn't there an easier way to do this? All this semicolon typing. Instead, simply add false to your query. In this manner the solutions found are no longer shown and we can concentrate on this annoying looping. And, if we're at it, you can also add false goals into your program! By such goals the number of inferences might be reduced (or stays the same). And if the resulting fragment (called a failure-slice) is looping, then this is a reason why your original program loops:
multiple(_X,0) :- false.
multiple(X,Y) :- lt(0,X), false, lt(0,Y), diff(Y,X,D), multiple(X,D).
natNum(0) :- false.
natNum(s(X)) :- natNum(X), false.
lt(0,s(X)) :- natNum(X), false.
lt(s(X),s(Y)) :- false, lt(X,Y).
?- multiple(X, s(s(s(s(s(s(0))))))), false.
loops.
Do your recognize your program? Only those parts remained that are needed for a loop. And, actually in this case, we have an infinite loop.
To fix this, we need to modify something in the remaining, visible part. I'd go for lt/2 whose first clause can be generalized to lt(0, s(_)).
But wait! Why is it OK to generalize away the requirement that we have a natural number? Look at the fact multiple(X,0). which you have written. You have not demanded that X is a natural number either. This kind of over-generalizations often appears in Prolog programs. They improve termination properties at a relatively low price: Sometimes they are too general but all terms that additionally fit into the generalization are not natural numbers. They are terms like any or [a,b,c], so if they appear somewhere you know that they do not belong to the solutions.
So the idea was to put false goals into your program such that the resulting program (failure-slice) still loops. In the worst case you put false at a wrong place and the program terminates. By trial-and-error you get a minimal failure-slice. All those things that are now stroked through are irrelevant! In particular diff/3. So no need to understand it (for the moment). It suffices to look at the remaining program.
I am currently trying to understand the basics of prolog.
I have a knowledge base like this:
p(a).
p(X) :- p(X).
If I enter the query p(b), the unification with the fact fails and the rule p(X) :- p(X) is used which leads the unification with the fact to fail again. Why is the rule applied over and over again after that? Couldn't prolog return false at this point?
After a certain time I get the message "Time limit exceeded".
I'm not quite sure why prolog uses the rule over and over again, but since it is, I don't understand why I get a different error message as in the following case.
To be clear, I do understand that "p(X) if p(X)" is an unreasonable rule, but I would like to understand what exactly happens there.
If I have a knowledge base like this:
p(X) :- p(X).
p(a).
There is no chance to come to a result even with p(a) because the fact is below the rule and the rule is called over and over again. For this variant I receive a different error message almost instantly "ERROR: Out of local stack" which is comprehensible.
Now my question - what is the difference between those cases?
Why am I receiving different error messages and why is prolog not returning false after the first application of the rule in the above case? My idea would be that in the above case the procedure is kind of restarted each time the rule gets called and in the below case the same procedure calls the rule over and over again. I would be grateful if somebody could elaborate this.
Update: If I query p(a). to the 2nd KB as said I receive "Out of local stack", but if I query p(b). to the same KB I get "Time limit exceeded". This is even more confusing to me, shouldn't the constant be irrelevant for the infinite loop?
Let us first consider the following program fragment that both examples have in common:
p(X) :- p(X).
As you correctly point out, it is obvious that no particular solutions are described by this fragment in isolation. Declaratively, we can read it as: "p(X) holds if p(X) holds". OK, so we cannot deduce any concrete solution from only this clause.
This explains why p(b) cannot hold if only this fragment is considered. Additionally, p(a) does not imply p(b) either, so no matter where you put the fact p(a), you will never derive p(b) from these two clauses.
Procedurally, Prolog still attempts to find cases where p(X) holds. So, if you post ?- p(X). as a query, Prolog will try to find a resolution refutation, disregarding what it has "already tried". For this reason, it will try to prove p(X) over and over. Prolog's default resolution strategy, SLDNF resolution, keeps no memory of which branches have already been tried, and also for this reason can be implemented very efficiently, with little overhead compared to other programming languages.
The difference between an infinite deduction attempt and an out of local stack error error can only be understood procedurally, by taking into account how Prolog executes these fragments.
Prolog systems typically apply an optimization that is called tail call optimization. This is applicable if no more choice-points remain, and means that it can discard (or reuse) existing stack frames.
The key difference between your two examples is obviously where you add the fact: Either before or after the recursive clause.
In your case, if the recursive clause comes last, then no more choice-points remain at the time the goal p(X) is invoked. For this reason, an existing stack frame can be reused or discarded.
On the other hand, if you write the recursive clause first, and then query ?- q(X). (or ?- q(a).), then both clauses are applicable, and Prolog remembers this by creating a choice-point. When the recursive goal is invoked, the choice-point still exists, and therefore the stack frames pile up until they exceed the available limits.
If you query ?- p(b)., then argument indexing detects that p(a) is not applicable, and again only the recursive clause applies, independent of whether you write it before or after the fact. This explains the difference between querying p(X) (or p(a)) and p(b) (or other queries). Note that Prolog implementations differ regarding the strength of their indexing mechanisms. In any case, you should expect your Prolog system to index at least on the outermost functor and arity of the first argument. If necessary, more complex indexing schemes can be constructed manually on top of this mechanism. Modern Prolog systems provide JIT indexing, deep indexing and other mechanisms, and so they often automatically detect the exact subset of clauses that are applicable.
Note that there is a special form of resolution called SLG resolution, which you can use to improve termination properties of your programs in such cases. For example, in SWI-Prolog, you can enable SLG resolution by adding the following directives before your program:
:- use_module(library(tabling)).
:- table p/1.
With these directives, we obtain:
?- p(X).
X = a.
?- p(b).
false.
This coincides with the declarative semantics you expect from your definitions. Several other Prolog systems provide similar facilities.
It should be easy to grasp the concept of infinite loop by studying how standard repeat/0 is implemented:
repeat.
repeat :- repeat.
This creates an infinite number of choice points. First clause, repeat., simply allows for a one time execution. The second clause, repeat :- repeat. makes it infinitely deep recursion.
Adding any number of parameters:
repeat(_, _, ..., _).
repeat(Param1, Param2, ..., ParamN) :- repeat(Param1, Param2, ..., ParamN).
You may have bodies added to these clauses and have parameters of the first class having meaningful names depending on what you are trying to archive. If bodies won't contain cuts, direct or inherited from predicates used, this will be an infinite loop too just as repeat/0.
My code takes an expression like or(lit(true),lit(X)),X) and outputs it as a list of lists.
tocnf(Tree, Expr) :-
trans(Tree ,Expr, []).
trans(lit(X)) -->bbool(X).
trans(or(lit(X1),lit(X2))) --> bconj(X1), bdisj(X2).
trans(and(lit(X1),lit(X2))) --> bbool(X1), bconj(X2).
bdisj(Conj) --> bconj(Conj).
bconj(Bool) --> bbool(Bool).
bbool(X) --> [[X]].
this code should take something like
tocnf(lit(X),X)
output it as
[[X]]
or
tocnf(or(lit(true),lit(X)),X)
and output it as
[[true],[X]].
Question is why when I do
tocnf(or(lit(true), and(lit(X),lit(true))),X)
it outputs
false.
Preliminaries
First, a note on style: You should always use the phrase/2 interface to access DCGs, so write tocnf/2 as:
tocnf(Tree, Expr) :-
phrase(trans(Tree), Expr).
Further, tocnf/2 is a rather imperative name, since it implies a direction of use ("to" CNF). However, the relation also makes sense in other directions, for example to generate answers. Therefore, try to find a better name, that does justice to this general nature of Prolog. I leave this as an exercise.
Declarative debugging
Now, on to your actual question. Apply declarative debugging to find the reason for the failure.
We start with the query you posted:
?- tocnf(or(lit(true), and(lit(X),lit(true))), X).
false.
This means that the program is unexpectedly too specific: It fails in a case we expect to succeed.
Now, we generalize the query, to find simpler cases that still fail. This is completely admissible because your program is written using the monotonic subset of Prolog, as is highly recommended to make declarative debugging applicable.
To generalize the query, I use variables instead of some subterms. For example:
?- tocnf(or(lit(_), and(lit(X),lit(true))), X).
false.
Aha! This still fails, and therefore every more specific query will also fail.
So, we proceed like this, using variables instead of some subterms:
?- tocnf(or(lit(_), and(lit(X),lit(_))), X).
false.
?- tocnf(or(_, and(lit(X),lit(_))), X).
false.
?- tocnf(or(_, and(_,lit(_))), X).
false.
?- tocnf(or(_, and(_,_)), X).
false.
All of these queries also fail.
Now, we take it just one step further:
?- tocnf(or(_, _), X).
X = [[_G793], [_G795]].
Aha! So we have found a case that succeeds, and one slightly more specific though still very simple case that fails:
?- tocnf(or(_, and(_,_)), X).
false.
This is the case I would start with: Think about why your relation does not work for terms of the form or(_, and(_,_)).
Automated solution
A major attraction of pure monotonic Prolog is that the reasoning above can be automated:
The machine should find the reason for the failure, so that we can focus on more important tasks.
One way to do this was generously made available by Ulrich Neumerkel.
To try it out, you need to install:
library(diadem) and
library(lambda).
Now, to recapitulate: We have found a query that unexpectedly fails. It was:
?- tocnf(or(lit(true), and(lit(X),lit(true))), X).
false.
To find a reason for this, we first load library(diadem):
?- use_module(library(diadem)).
true.
Then, we repost the query with a slight twist:
?- tocnf(or(lit(true), and(lit(X),lit(true))), X).?Generalization.
That is, I have simply appended ?Generalization. to the previous query.
In response, we get:
Generalization = tocnf(or(_, and(_, _)), _) .
Thus, Generalization is a more general goal that still fails. Since the Prolog program we are considering is completely pure and monotonic, we know that every more specific query will also fail. Therefore, I suggest you focus on this simpler and more general case, which was found automatically in this case, and is the same goal we also found manually after several steps.
Unexpected failure is a common issue when learning Prolog, and automated declarative debugging lets you quickly find the reasons.
The matter of deterministic success of some Prolog goal has turned up time and again in—at least—the following questions:
Reification of term equality/inequality
Intersection and union of 2 lists
Remove duplicates in list (Prolog)
Prolog: How can I implement the sum of squares of two largest numbers out of three?
Ordering lists with constraint logic programming)
Different methods were used (e.g., provoking certain resource errors, or looking closely at the exact answers given by the Prolog toplevel), but they all appear somewhat ad-hack to me.
I'm looking for a generic, portable, and ISO-conformant way to find out if the execution of some Prolog goal (which succeeded) left some choice-point(s) behind. Some meta predicate, maybe?
Could you please hint me in the right direction? Thank you in advance!
Good news everyone: setup_call_cleanup/3 (currently a draft proposal for ISO) lets you do that in a quite portable and beautiful way.
See the example:
setup_call_cleanup(true, (X=1;X=2), Det=yes)
succeeds with Det == yes when there are no more choice points left.
EDIT: Let me illustrate the awesomeness of this construct, or rather of the very closely related predicate call_cleanup/2, with a simple example:
In the excellent CLP(B) documentation of SICStus Prolog, we find in the description of labeling/1 a very strong guarantee:
Enumerates all solutions by backtracking, but creates choicepoints only if necessary.
This is really a strong guarantee, and at first it may be hard to believe that it always holds. Luckily for us, it is extremely easy to formulate and generate systematic test cases in Prolog to verify such properties, in essence using the Prolog system to test itself.
We start with systematically describing what a Boolean expression looks like in CLP(B):
:- use_module(library(clpb)).
:- use_module(library(lists)).
sat(_) --> [].
sat(a) --> [].
sat(~_) --> [].
sat(X+Y) --> [_], sat(X), sat(Y).
sat(X#Y) --> [_], sat(X), sat(Y).
There are in fact many more cases, but let us restrict ourselves to the above subset of CLP(B) expressions for now.
Why am I using a DCG for this? Because it lets me conveniently describe (a subset of) all Boolean expressions of specific depth, and thus fairly enumerate them all. For example:
?- length(Ls, _), phrase(sat(Sat), Ls).
Ls = [] ;
Ls = [],
Sat = a ;
Ls = [],
Sat = ~_G475 ;
Ls = [_G475],
Sat = _G478+_G479 .
Thus, I am using the DCG only to denote how many available "tokens" have already been consumed when generating expressions, limiting the total depth of the resulting expressions.
Next, we need a small auxiliary predicate labeling_nondet/1, which acts exactly as labeling/1, but is only true if a choice-point still remains. This is where call_cleanup/2 comes in:
labeling_nondet(Vs) :-
dif(Det, true),
call_cleanup(labeling(Vs), Det=true).
Our test case (and by this, we actually mean an infinite sequence of small test cases, which we can very conveniently describe with Prolog) now aims to verify the above property, i.e.:
If there is a choice-point, then there is a further solution.
In other words:
The set of solutions of labeling_nondet/1 is a proper subset of that of labeling/1.
Let us thus describe what a counterexample of the above property looks like:
counterexample(Sat) :-
length(Ls, _),
phrase(sat(Sat), Ls),
term_variables(Sat, Vs),
sat(Sat),
setof(Vs, labeling_nondet(Vs), Sols),
setof(Vs, labeling(Vs), Sols).
And now we use this executable specification in order to find such a counterexample. If the solver works as documented, then we will never find a counterexample. But in this case, we immediately get:
| ?- counterexample(Sat).
Sat = a+ ~_A,
sat(_A=:=_B*a) ? ;
So in fact the property does not hold. Broken down to the essence, although no more solutions remain in the following query, Det is not unified with true:
| ?- sat(a + ~X), call_cleanup(labeling([X]), Det=true).
X = 0 ? ;
no
In SWI-Prolog, the superfluous choice-point is obvious:
?- sat(a + ~X), labeling([X]).
X = 0 ;
false.
I am not giving this example to criticize the behaviour of either SICStus Prolog or SWI: Nobody really cares whether or not a superfluous choice-point is left in labeling/1, least of all in an artificial example that involves universally quantified variables (which is atypical for tasks in which one uses labeling/1).
I am giving this example to show how nicely and conveniently guarantees that are documented and intended can be tested with such powerful inspection predicates...
... assuming that implementors are interested to standardize their efforts, so that these predicates actually work the same way across different implementations! The attentive reader will have noticed that the search for counterexamples produces quite different results when used in SWI-Prolog.
In an unexpected turn of events, the above test case has found a discrepancy in the call_cleanup/2 implementations of SWI-Prolog and SICStus. In SWI-Prolog (7.3.11):
?- dif(Det, true), call_cleanup(true, Det=true).
dif(Det, true).
?- call_cleanup(true, Det=true), dif(Det, true).
false.
whereas both queries fail in SICStus Prolog (4.3.2).
This is the quite typical case: Once you are interested in testing a specific property, you find many obstacles that are in the way of testing the actual property.
In the ISO draft proposal, we see:
Failure of [the cleanup goal] is ignored.
In the SICStus documentation of call_cleanup/2, we see:
Cleanup succeeds determinately after performing some side-effect; otherwise, unexpected behavior may result.
And in the SWI variant, we see:
Success or failure of Cleanup is ignored
Thus, for portability, we should actually write labeling_nondet/1 as:
labeling_nondet(Vs) :-
call_cleanup(labeling(Vs), Det=true),
dif(Det, true).
There is no guarantee in setup_call_cleanup/3 that it detects determinism, i.e. missing choice points in the success of a goal. The 7.8.11.1 Description draft proposal only says:
c) The cleanup handler is called exactly once; no later than
upon failure of G. Earlier moments are:
If G is true or false, C is called at an implementation
dependent moment after the last solution and after the last
observable effect of G.
So there is currently no requirement that:
setup_call_cleanup(true, true, Det=true)
Returns Det=true in the first place. This is also reflected in the test cases 7.8.11.4 Examples that the draf proposal gives, we find one test case which says:
setup_call_cleanup(true, true, X = 2).
Either: Succeeds, unifying X = 2.
Or: Succeeds.
So its both a valid implementation, to detect determinism and not to detect determinism.