Search code examples
prologfailure-slice

Prolog infinite loop issue


I have the below functions. When I call it with final_filter([(2, 2)], R), it prints a lot of "2 2" pairs. When I comment get_all_sums(S, _) it works fine, however if I test separately get_all_sums(4, R). I works also fine, what could be the problem?

get_all_sums_R(NR, IT, []):- IT > NR - 2.
get_all_sums_R(NR, IT, R):-
    A is IT,
    B is NR-IT,
    A >= B,
    NEXT_IT is IT + 1,
    get_all_sums_R(NR, NEXT_IT, R_NEXT),
    append([(A, B)], R_NEXT, R).
get_all_sums_R(NR, IT, R):-
    A is IT,
    B is NR-IT,
    A < B,
    NEXT_IT is IT + 1,
    get_all_sums_R(NR, NEXT_IT, R).

get_all_sums(NR, R):-get_all_sums_R(NR, 2, R).


get_all_divisors(X, IT, []):-IT > X/2.
get_all_divisors(X, IT, R):-
    IT =< X/2,
    TMP_RES is mod(X, IT),
    TMP_RES =:= 0,
    NEXT_IT is IT + 1,
    get_all_divisors(X, NEXT_IT, R_NEXT),
    append([IT], R_NEXT, R).
get_all_divisors(X, IT, R):-
    IT =< X/2,
    NEXT_IT is IT + 1,
    get_all_divisors(X, NEXT_IT, R).

get_all_products_R([], _, []).
get_all_products_R([H|L], NR, R):-
    A is H,
    B is NR/H,
    A >= B,
    get_all_products_R(L, NR, NEXT_R),
    append([(A, B)], NEXT_R, R).
get_all_products_R([H|L], NR, R):-
    A is H,
    B is NR/H,
    A < B,
    get_all_products_R(L, NR, R).

get_all_products(NR, R):-
    get_all_divisors(NR, 2, R_LEFT),
    get_all_products_R(R_LEFT, NR, R).

single_element([_]).

final_filter([(A, B)|_], _):-
    write(A),
    P is A*B,
    S is A+B,
    get_all_products(P, PRODUCTS),
    get_all_sums(S, _),
    write(A),write(' '), write(B), write('\n'),
    not(single_element(PRODUCTS)).

Solution

  • The central misunderstanding here is what termination means in Prolog. That is, universal termination. The query of interest is the following:

    ?- get_all_sums(4,R).
       R = [(2,2)]█
    

    Prolog's top-level shell offers you first a single answer. But you get more of them by entering ; or SPACE. So Prolog was not finished when showing you the first answer/solution. In fact:

    ?- get_all_sums(4,R).
       R = [(2,2)]
    ;  R = [(2,2),(3,1)]
    ;  R = [(2,2),(3,1),(4,0)]
    ;  R = [(2,2),(3,1),(4,0),(5,-1)]
    ;  R = [(2,2),(3,1),(4,0),(5,-1),(6,-2)]
    ;  R = [(2,2),(3,1),(4,0),(5,-1),(6,-2),(7,-3)]
    ;  R = [(2,2),(3,1),(4,0),(5,-1),(6,-2),(7,-3),(8,-4)]
    ;  R = [(2,2),(3,1),(4,0),(5,-1),(6,-2),(7,-3),(8,-4),(9,-5)]
    ; ... .
    

    Do you really want to enumerate all negative sums? Looks infinite to me!

    But let's concentrate on the non-termination property alone...

    Maybe the system will stop after 8 solutions? Is there a better way to be sure? Simply "turn off" the display of solutions as follows:

    ?- get_all_sums(4,R), false.
    

    Now we are no longer irritated by solutions/answers, and we can concentrate on the termination property alone. I will further add such goals false and more into your program, to localize the actual reason for non-termination. The resulting program is called a failure-slice. And no matter how I add false goals, it always holds that: If the failure-slice does not terminate, then the original program does not terminate either. After a bit of trying, I get:

    get_all_sums_R(NR, IT, []):- false, IT > NR - 2.
    get_all_sums_R(NR, IT, R):- NR = 4,
        A is IT,
        B is NR-IT,
        A >= B,
        NEXT_IT is IT + 1,
        get_all_sums_R(NR, NEXT_IT, R_NEXT), false,
        append([(A, B)], R_NEXT, R).
    get_all_sums_R(NR, IT, R):- false,
        A is IT,
        B is NR-IT,
        A < B,
        NEXT_IT is IT + 1,
        get_all_sums_R(NR, NEXT_IT, R).
    
    get_all_sums(NR, R):-get_all_sums_R(NR, 2, R), false.
    
    ?- get_all_sums(4, R), false.
    

    So this little remaining part is responsible for non-termination. In order to fix this, you will have to add some goals here. To be sure: NR = 4, A increases by 1, B decreases by one. And as long as A >= B, this loop will continue.

    You might be tempted to add somewhere a cut, or a once/1. But this will lead you only from one bug to the other. Better address the problem above.