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)].
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
.
Some problems in your code are:
=..
), 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
satisfy(Base, Condition) :- member(Condition, Base), !.
, as this prevents predicate member/2
backtracking to find alternative answers (e.g., frog(fritz)
and frog(frotz)
).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)
.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).