TL;DR: This question is about one particular aspect of evaluating candidate failure-slices.
The following code of ancestor_of/2
expresses the transitive-closure of child_of/2
:
ancestor_of(X, Y) :- child_of(Y, X). ancestor_of(X, Z) :- child_of(Z, Y), ancestor_of(X, Y).
If we define child_of/2
and include several cycles ...
child_of(xx, xy). child_of(xy, xx). child_of(x, x).
... ancestor_of(X, Y)
does not terminate universally:
?- ancestor_of(X, Y), false. **LOOPS**
With SWI-Prolog, we can limit the amount of work invested in the execution of the query:
?- time(call_with_inference_limit((ancestor_of(_,_),false), 10000, R)). % 10,008 inferences, 0.002 CPU in 0.002 seconds (100% CPU, 4579243 Lips) R = inference_limit_exceeded ; % 5 inferences, 0.000 CPU in 0.000 seconds (85% CPU, 235172 Lips) false.
Alright, but it could be better!
- First, we might be able to prove definitely that the goal loops.
- Second, we might be able to do so with less effort.
Let's add an additional argument to ancestor_of/2
for transferring call stack information!
ancestor_of_open(X, Y, Open) :- G = ancestor_of(X,Y), ( member(G, Open) -> throw(loop(G,Open)) ; ancestor_of_aux(X, Y, [G|Open]) ). ancestor_of_aux(X, Y, _Open) :- child_of(Y, X). ancestor_of_aux(X, Z, Open) :- child_of(Z, Y), ancestor_of_open(X, Y, Open).
Sample query:
?- time(ancestor_of_open(X,Y,[])). % 4 inferences, 0.000 CPU in 0.000 seconds (92% CPU, 219696 Lips) X = xy, Y = xx ; % 1 inferences, 0.000 CPU in 0.000 seconds (79% CPU, 61839 Lips) X = xx, Y = xy ; % 1 inferences, 0.000 CPU in 0.000 seconds (79% CPU, 61084 Lips) X = Y, Y = x ; % 8 inferences, 0.000 CPU in 0.000 seconds (87% CPU, 317473 Lips) X = Y, Y = xx ; % 11 inferences, 0.000 CPU in 0.000 seconds (90% CPU, 262530 Lips) ERROR: Unhandled exception: loop(ancestor_of(_G282,xx),[ancestor_of(_G282,xy),ancestor_of(_G282,xx)])
Is this it?! That was too easy. Does this cover the general case?
I feel like I'm missing something important, but I can't quite point my finger at it. Please help!