Forward chaining in Prolog, problem with multiple rules

Viewed 93

in my previous question's best answer, I found a good example of forward chaining in Prolog. I have modified it a bit, but there is a problem with the last rule I tried to define (path). It doesn't work. With the current facts, I should be able to derive the path([a,b,c,d,e]), but it doesn't work.

Forward code:

:- op(1100, xfx, if).
:- op(1000, xfy, and).
:- op(900, xfy, or).
:- dynamic rule/1.                                                       

forward(Facts) :-
    fixed_point(nil, [true], Facts).                % Start with the base with only true

fixed_point(Base, Base, Base) :- !.                 % Reached a fixpoint
fixed_point(_, Base, Facts) :-                      % Base =/= Facts => not a fixpoint
    setof(Fact, derived(Fact, Base), NewFacts),     % Derive new facts
    ord_union(NewFacts, Base, NewBase),             % Add new facts to base
    fixed_point(Base, NewBase, Facts).              % Repeat with new base

derived(Fact, Base) :-                              
    rule(Fact if Condition),                        % Match a rule
    satisfy(Base, Condition).                       % Verify if condition is satisfied given base

satisfy(Base, Condition) :-
    Condition =.. [and, Elem|Bag],!,
    member(Elem, Base),                                     
    (   Bag = []
    ->  true
    ;   NewAtom =.. [and|Bag],       
        satisfy(Base, NewAtom)).
satisfy(Base, Condition) :-
    Condition =.. [or, Elem|Bag],!,
    (   member(Elem, Base)
    ->  true
    ;   (   Bag = []
        ->  false
        ;   NewAtom =.. [or|Bag],       
            satisfy(Base, NewAtom))).
satisfy(Base, Condition) :-
    member(Condition, Base),!.
satisfy(_, true):-!.
satisfy(_, false):-!,fail.

Here are rules and facts:

rule(eats_flies(fritz) if true).
rule(croaks(fritz)     if true).
rule(eats_flies(frotz) if true). % ADDED IN EDIT
rule(croaks(frotz)     if true). % ADDED IN EDIT
rule(sings(tweety)     if true).
rule(chips(tweety)     if true).
rule(has_wings(tweety) if true).
rule(croaks(krogr)    if true).
rule(chips(kroger)     if true).
rule(canary(X)         if and(sings(X),chips(X),has_wings(X))).
rule(frog(X)           if and(croaks(X),eats_flies(X))).
rule(green(X)          if frog(X)).
rule(yellow(X)         if canary(X)).
rule(node(a) if true).
rule(node(b) if true).
rule(node(c) if true).
rule(node(d) if true).
rule(node(e) if true).
rule(connected(a,b) if true).
rule(connected(b,c) if true).
rule(connected(c,d) if true).
rule(connected(d,e) if true).
rule(funny(c,d) if true).
rule(wonderful(X,Y) if or(nice(X,Y),funny(X,Y))).
rule(connected(X,Z) if and(connected(X,Y),connected(Y,Z))).
rule(path([Node|[]]) if node(Node)).
rule(path([Node, NextNode|Nodes]) if and(connected(Node, NextNode),path([NextNode|Nodes]))).

Critical rules:

rule(path([Node|[]]) if node(Node)).
rule(path([Node, NextNode|Nodes]) if and(connected(Node, NextNode),path([NextNode|Nodes]))).

What I get as output of the forward:

?- forward(A).
A = [true,canary(tweety),chips(kroger),chips(tweety),croaks(fritz),croaks(krogr),eats_flies(fritz),frog(fritz),green(fritz),has_wings(tweety),node(a),node(b),node(c),node(d),node(e),path([a]),sings(tweety),yellow(tweety),connected(a,b),connected(a,c),connected(b,c),connected(c,d),connected(d,e),funny(c,d),wonderful(c,d)].

EDIT:

Testing the code I found out that another problem is that if I add similar rules, such as:

rule(eats_flies(fritz) if true).
rule(croaks(fritz)     if true).
rule(eats_flies(frotz) if true).
rule(croaks(frotz)     if true).

The forward will only find that fritz is a frog, but not frotz.

1 Answers

Some problems in your code are:

  • Inappropriate use of operator univ (=..), just use unification (=). See why:
    ?- (p and q and r) =.. [and,G|Gs], N =.. [and|Gs].
    G = p,                        % <== this is the first goal of the condition
    Gs = [(q and r)],
    N = and((q and r)).           % <== this IS NOT a valid conjunction of the remaining goals
    
    ?- (p and q and r) = (G and Gs).
    G = p,                        % <== this is the first goal of the condition
    Gs =  (q and r).              % <== this IS a valid conjunction of the remaining goals 
    
  • Do not use cut in satisfy(Base, Condition) :- member(Condition, Base), !., as this prevents predicate member/2 backtracking to find alternative answers (e.g., frog(fritz) and frog(frotz)).
  • As initially Base is the set [true], there is no need for the clause satisfy(_, true) :- !.. Note that true is already naturally satisfied by clause satisfy(Base, Condition) :- member(Condition, Base).
  • In Prolog, you only need to declare what is true, so there is no need for the clause satisfy(_, false):- !, fail..

Taking into account all these observations, a correct code is as follows:

:- op(1100, xfx, if).
:- op(1000, xfy, and).
:- op(900, xfy, or).
:- dynamic rule/1.

forward(Facts) :-
    fixed_point(nil, [true], Facts).                % Start with the base with only true

fixed_point(Base, Base, Base) :- !.                 % Reached a fixpoint
fixed_point(_, Base, Facts) :-                      % Base =/= Facts => not a fixpoint
    setof(Fact, derived(Fact, Base), NewFacts),     % Derive new facts
    ord_union(NewFacts, Base, NewBase),             % Add new facts to base
    fixed_point(Base, NewBase, Facts).              % Repeat with new base

derived(Fact, Base) :-
    rule(Fact if Condition),                        % Match a rule
    satisfy(Base, Condition).                       % Verify if condition is satisfied given base

satisfy(Base, G and Gs) :-    % Condition unifies with (G and Gs)
    !,
    member(G, Base),
    satisfy(Base, Gs).

satisfy(Base, G or Gs) :-     % Condition unifies with (G or Gs)
    !,
    (   member(G, Base)
    ;   satisfy(Base, Gs) ).

satisfy(Base, Condition) :-   % Condition is an atomic proposition
    member(Condition, Base).

% Knowledge base

rule(eats_flies(fritz) if true).
rule(croaks(fritz)     if true).
rule(eats_flies(frotz) if true). 
rule(croaks(frotz)     if true). 
rule(sings(tweety)     if true).
rule(chips(tweety)     if true).
rule(has_wings(tweety) if true).
rule(croaks(krogr)    if true).
rule(chips(kroger)     if true).
rule(canary(X)         if and(sings(X),chips(X),has_wings(X))).
rule(frog(X)           if and(croaks(X),eats_flies(X))).
rule(green(X)          if frog(X)).
rule(yellow(X)         if canary(X)).
rule(node(a) if true).
rule(node(b) if true).
rule(node(c) if true).
rule(node(d) if true).
rule(node(e) if true).
rule(connected(a,b) if true).
rule(connected(b,c) if true).
rule(connected(c,d) if true).
rule(connected(d,e) if true).
rule(funny(c,d) if true).
rule(wonderful(X,Y) if or(nice(X,Y),funny(X,Y))).
rule(connected(X,Z) if and(connected(X,Y),connected(Y,Z))).
rule(path([Node|[]]) if node(Node)).
rule(path([Node, NextNode|Nodes]) if and(connected(Node, NextNode),path([NextNode|Nodes]))).

Examples:

?- forward(Base), member(path(P), Base), length(P, 5).
Base = [true, chips(kroger), chips(tweety), croaks(fritz), croaks(frotz), croaks(krogr), eats_flies(fritz), eats_flies(frotz), frog(...)|...],
P = [a, b, c, d, e] .

?- forward(Base), member(frog(X), Base).
Base = [true, chips(kroger), chips(tweety), croaks(fritz), croaks(frotz), croaks(krogr), eats_flies(fritz), eats_flies(frotz), frog(...)|...],
X = fritz ;
Base = [true, chips(kroger), chips(tweety), croaks(fritz), croaks(frotz), croaks(krogr), eats_flies(fritz), eats_flies(frotz), frog(...)|...],
X = frotz ;
false.

REMARK I didn't check the correctness of all your code, just the most relevant parts to get the answers you wanted. But I saw that the precedence you assigned to the operator or is not adequate, as the logical connective or has lower priority than the logical connective and (note that, in predicate op/3, smaller numbers indicate higher priorities).

Related