Search code examples
prologspanning-treeprolog-cut

Cut at the end of a Prolog statement


I have encountered this cut which should return true if there exists an edge A-B or B-A for some node B of graph Graph.

node(A,Graph) :- adjacent(A,_,Graph),!.

The problem is I do not understand why removing this cut would have any effects on the returned solutions.

As I understand it the only use for a cut at the end of a Prolog statement is when there is another statement with the same name [another node(...)] which we don't want to be called if the first one succeedes. An example would be a function that takes X and Y and returns the larger one as the third parameter.

max1(X, Y, X) :- X > Y, !.
max1(_X, Y, Y).

However, there is no other statement called node(...) so I can't see how the cut could affect the solutions.

This is my code. It should find a spanning tree. It is explained in detail here. The compiler is SWI-Prolog 7.6.4 on linux.

:- op(900, fy, not).

stree1( Graph, Tree)  :-
    subset( Graph, Tree),  tree( Tree),  covers( Tree, Graph).

tree( Tree)  :-
    connected( Tree),  not hasacycle( Tree).

connected( Graph)  :-
    not ( node( A, Graph), node( B, Graph), not path( A, B, Graph, _) ).

hasacycle( Graph)  :-
     adjacent( Node1, Node2, Graph),
     path( Node1, Node2, Graph, [Node1, X, Y | _] ). 

covers( Tree, Graph)  :-
    not ( node( Node, Graph), not node( Node, Tree) ).

subset( [], [] ). 
subset( [X | Set], Subset)  :-  subset( Set, Subset).
subset( [X | Set], [X | Subset])  :-  subset( Set, Subset).

adjacent( Node1, Node2, Graph)  :-
      member( Node1-Node2, Graph)
      ;
      member( Node2-Node1, Graph).

node( Node, Graph)  :-  adjacent( Node, _, Graph). 

path( A, Z, Graph, Path)  :-
      path1( A, [Z], Graph, Path).

path1( A, [A | Path1], _, [A | Path1] ).

path1( A, [Y | Path1], Graph, Path)  :-
      adjacent( X, Y, Graph),
      not member( X, Path1), 
      path1( A, [X, Y | Path1], Graph, Path).

Solutions returned without cut (correct)

?- stree1([a-b, b-c, b-d, c-d], Tree).
Tree = [a-b, b-d, c-d] ;
Tree = [a-b, b-c, c-d] ;
Tree = [a-b, b-c, b-d] ;
false.

Solutions returned with cut (incorrect)

?- stree1([a-b, b-c, b-d, c-d], Tree).
Tree = [a-b] ;
Tree = [a-b, c-d] ;
Tree = [a-b, b-d] ;
Tree = [a-b, b-d, c-d] ;
Tree = [a-b, b-c] ;
Tree = [a-b, b-c, c-d] ;
Tree = [a-b, b-c, b-d] ;
false.

Solution

  • "As I understand it the only use for a cut at the end of a Prolog statement is when there is another statement with the same name [another node(...)] which we don't want to be called if the first one succeedes."

    Well, this is not true for your condition since you are using member/2 predicate which will work with backtracking. In that situation, using cuts will prune the solution tree that Prolog uses in backtracking and can cause a change in the results you get. To explain with an example see the slightly modified code of your original post here:

    node( Node, Graph , Target)  :-  adjacent( Node, Target, Graph),!.
    
    adjacent( Node1, Node2, Graph)  :-
      member( Node1-Node2, Graph)
      ;
      member( Node2-Node1, Graph).
    

    When you run this query in the console, you will see the output :

    ?- node(l,[l-x,l-y],Target).
    Target = x
    

    Why? Because in the beginning, there are two leaves of your search tree. Either the pair (l-x) or (l-y) satisfies the condition in adjacent/3. Then, according to the depth-first search, the pair (l-x) gets selected, and since you have a ! statement in your code, the rest of the search tree is now pruned. Hence, you get the result as Target = x

    However, if you remove the ! statement from the code, what you will see is:

    ?- node(l,[l-x,l-y],Target).
    Target = x ;
    Target = y ;
    false.
    

    Here, you see that both leaves of the search tree are executed sequentially, according to the depth-first order, and you see two results. This is what causes you to see different results when the ! is existent or not.