1

I do not find a Prolog cut in Dijsktras "if fi", since he says "otherwise an arbitrary guarded list with a true guard will be selected for execution.". So his construct, does not choose the first match, as a Prolog cut would do:

if Cond1 -> Action11, .., Action1n1
[] Cond2 -> Action21, .., Action2n2
...
[] Condm -> Actionm1, .., Actionmn2
if

Is there maybe a Prolog cut in the "do od" construct, which loops as long as at least one condition of a guarded list is true? Or maybe some other approach to realize it in Prolog, I assume a loop can be translated to a recursive call. So how would we do this sorting of q1,q2,q3,q4 in Prolog:

do q1 > q2 -> q1,q2 := q2,q1
[] q2 > q3 -> q2,q3 := q3,q2
[] q3 > q4 -> q3,q4 := q4,q3
od

How many non-deterministic execution paths aka Prolog solutions will the Prolog program have for input 7,11,5,3 that all provide the same answer?

  • 1
    "an *arbitrary* guarded list with a `true` guard will be *selected* for execution" and all the others dropped, i.e. the chosen one committed to, I always thought. if I had to implement it I'd spawn all the branches's guards, check on them at some timeout intervals, and as soon as one or more are successful I'd pick one of them randomly, kill all the rest, and proceeded with the winner. i.e. committing to it. that is my understanding anyway. (or maybe the timeout should be random as well?) – Will Ness Feb 11 '21 at 20:45
  • This seems like a "probabilistic" execution model, which is not what standard Prolog provides, even with delays (the delayed predicates start to execute as soon as their "freeze" conditions are satisfied). I seem to recall that Parlog or maybe Concurrent Prolog could compute guards in parallel - but the overhead of starting and coordinating even low overhead threads made this impractical. – Peter Ludemann Feb 11 '21 at 21:44

3 Answers3

0

If we assume no guard calculation can diverge, I think the following expresses the same computation as the sort example from the question:

dgsort([A,B,C,D],R):-
  ( A > B -> X1 = [1]    ; X1 = [] ),
  ( B > C -> X2 = [2|X1] ; X2 = X1 ),
  ( C > D -> X3 = [3|X2] ; X3 = X2 ),
  (   X3 = [] -> R = [A,B,C,D]
  ;   random_member( X, X3),
      (  X =:= 1 -> dgsort([B,A,C,D],R)
      ;  X =:= 2 -> dgsort([A,C,B,D],R)
      ;  X =:= 3 -> dgsort([A,B,D,C],R) )).

comments: "an arbitrary guarded list with a true guard will be selected for execution" and all the others dropped, i.e. the chosen one is committed to, I always thought. Thus if I had to implement it I'd spawn all the branches's guards, check on them at some timeout intervals, and as soon as one or more are successful I'd pick one of them randomly, kill all the rest, and proceeded with the winner. i.e. committing to it. that is my understanding anyway. (or maybe the timeout should be random as well?)

I also thought the do-od construct chose the true guard by the same mechanism. i.e. "at least one" and we don't care which. If we assume all the guards calculations terminate, we can just execute all of them, pick the winner randomly, then proceed with its actions. Which is what the code above is doing.


To find out the number of execution paths we need to change random_member to just member, run the predicate through findall and measure the length of the resulting list:

dgsortn([A,B,C,D],R):-
  ( A > B -> X1 = [1]    ; X1 = [] ),
  ( B > C -> X2 = [2|X1] ; X2 = X1 ),
  ( C > D -> X3 = [3|X2] ; X3 = X2 ),
  (   X3 = [] -> R = [A,B,C,D]
  ;   member( X, X3),
      (  X =:= 1 -> dgsortn([B,A,C,D],R)
      ;  X =:= 2 -> dgsortn([A,C,B,D],R)
      ;  X =:= 3 -> dgsortn([A,B,D,C],R) )).

Running it succeeds 5 times:

9 ?- dgsortn( [7,11,5,3], R).
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11].

We could also add a counter to see the number of steps taken to produce each solution.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • ( X =:= 1 -> sort([B,A,C,D],R) ?? Why not dgsort? An how many different execution paths are there for the input [7,11,5,3]. –  Feb 11 '21 at 21:19
  • I think pick_randomly would be random_member/2 in SWI-Prolog, which is deterministic. And if random_member/2 is replaced by non-deterministic member/2, you can count all possible execution paths? –  Feb 11 '21 at 21:25
  • yes, that's in the last edit. thanks for the random_member reference, will edit it in too. – Will Ness Feb 11 '21 at 21:30
  • Interesting, when using member/2 no last choice point! Not like in my solutions. The list building does a kind of choice point look-ahead. –  Feb 11 '21 at 21:33
  • I just did the simplest thing possible. :) – Will Ness Feb 11 '21 at 21:34
0

I directly takled the counting problem and came up with this solution. It has the defect that the negation as failure will re-evaluate the conditions.

sortloop([Q1,Q2,Q3,Q4], R) :- Q1 > Q2, sortloop([Q2,Q1,Q3,Q4], R).
sortloop([Q1,Q2,Q3,Q4], R) :- Q2 > Q3, sortloop([Q1,Q3,Q2,Q4], R).
sortloop([Q1,Q2,Q3,Q4], R) :- Q3 > Q4, sortloop([Q1,Q2,Q4,Q3], R).
sortloop([Q1,Q2,Q3,Q4], [Q1,Q2,Q3,Q4]) :- \+ Q1 > Q2, \+ Q2 > Q3, \+ Q3 > Q4.

But it shows there are 5 execution paths:

?- sortloop([7,11,5,3], R).
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11] ;
false.

But can we improve that the conditions are only evaluated once? What came to my mind was the soft cut (*->)/2 since the action lists do not abort:

sortloop2([Q1,Q2,Q3,Q4], R) :-
  ((Q1 > Q2, sortloop2([Q2,Q1,Q3,Q4], R);
    Q2 > Q3, sortloop2([Q1,Q3,Q2,Q4], R);
    Q3 > Q4, sortloop2([Q1,Q2,Q4,Q3], R)) *-> true; R=[Q1,Q2,Q3,Q4]).

The soft cut solution gives the same result:

?- sortloop2([7,11,5,3], R).
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11] ;
R = [3, 5, 7, 11] ;
false.

But none of the solutions uses a Prolog cut that would replace the arrow between condition and action list in the Dijkstra guards. Situation is a little bit more complicated.

Edit 12.02.2021:
In case somebody is currious how the 5 paths look like as a DAG:

enter image description here

0

I think you can do something like this:

do([Q1,Q2,Q3,Q4], [swap(Q1,Q2)|P], S) :-
    Q1 > Q2,
    do([Q2,Q1,Q3,Q4], P, S).

do([Q1,Q2,Q3,Q4], [swap(Q2,Q3)|P], S) :-
    Q2 > Q3,
    do([Q1,Q3,Q2,Q4], P, S).

do([Q1,Q2,Q3,Q4], [swap(Q3,Q4)|P], S) :-
    Q3 > Q4,
    do([Q1,Q2,Q4,Q3], P, S).

do([Q1,Q2,Q3,Q4], [], [Q1,Q2,Q3,Q4]) :- % termination state
    Q1 =< Q2,
    Q2 =< Q3,
    Q3 =< Q4.

Execution:

?- do([7,11,5,3],P,S).
P = [swap(11, 5), swap(7, 5), swap(11, 3), swap(7, 3), swap(5, 3)],
S = [3, 5, 7, 11] ;
P = [swap(11, 5), swap(11, 3), swap(7, 5), swap(7, 3), swap(5, 3)],
S = [3, 5, 7, 11] ;
P = [swap(11, 5), swap(11, 3), swap(5, 3), swap(7, 3), swap(7, 5)],
S = [3, 5, 7, 11] ;
P = [swap(5, 3), swap(11, 3), swap(7, 3), swap(11, 5), swap(7, 5)],
S = [3, 5, 7, 11] ;
P = [swap(5, 3), swap(11, 3), swap(11, 5), swap(7, 3), swap(7, 5)],
S = [3, 5, 7, 11] ;
false.

slago
  • 5,025
  • 2
  • 10
  • 23