% Welcome to rules-swi.pl (~cs9414/public_html/notes/kr/rules/rules-swi.pl).
% To run the system, save a copy in your home directory, then (on a host that
% runs SWI Prolog), do:
% % prolog -s rules.pl
% ?- wm(X). % should reply that the only working memory facts are a, b, c.
% ?- trace. % optional - this makes Prolog show the goals it's executing
% ?- run. % runs rule-based system. See note below.
% ?- notrace. % turn off tracing
% ?- wm(X). % this time it should say that a, b, c, d, & e are all facts.
% ?- ^D
% Note: as there there are two rules (rule1 and rule2), and they are both
% eligible to fire given that a, b, and c are all true, both rules should
% fire, and the conclusion d of rule1 and the conclusion e of rule2
% should both be facts when execution finishes.
%
% Original code by Claude Sammut, date lost in the mists of time.
% Modified for iProlog by Bill Wilson, October 2001.
% Modified for SWI Prolog by Bill Wilson, April 2006.
:- dynamic wm/1. % this allows inferred wm-facts to be asserted.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% To express rules in a natural way, we define some operators
% First argument to op is the precedence
% Second argument is the associativity (left, right, nonassoc)
% Third argument is operator name (or list of names)
% This is basically a cosmetic trick to avoid writing the rules as
% if(rule1, then(and(a, and(b, c)), d).
% if(rule2, then(and(a, b), e).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
:- op(900, xfx ,if).
:- op(800, xfx, then).
:- op(700, xfy, and).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Example Rules
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
rule1
if a and b and c then d.
rule2
if a and b then e.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Initial Working memory
% We use "wm" to distinguish working memory elements from
% other entries in Prolog's data base
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
wm(a).
wm(b).
wm(c).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Reset
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 1. Assert a dummy already_fired(_,_) fact so retract will not fail if there
% are no real such facts to retract.
% 2. Next retract all already_fired(_,_) assertions, in case we are rerunning
% the system and there are some old assertions lying around from last time.
% 3. Now re-assert the dummy already_fired(_,_) fact so that "can_fire" will
% be confused by the fact that there is no such predicate as already_fired
% the first time around the match-resolve-act cycle.
% 4. Finally, the cut prevents this predicate endlessly cycling, asserting and
% retracting.
init :-
assert(already_fired(null, false)),
retract(already_fired(_, _)),
assert(already_fired(null, false)), !.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Start execution
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
run :-
init,
exec.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Select a rule, fire it and repeat until no rules are satisfied
% f "fire" succeeds, cut will prevent backtracking
% If "fire" fails, the cycle will repeat
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
exec :-
repeat, % Always succeeds on backtracking
select_rule(R), % Select a single rule for firing
fire(R), !.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% "findall" collects all solutions to "can_fire"
% resolve selects one of those rules
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
select_rule(SelectedRule) :-
findall(Rule, can_fire(Rule), Candidates),
resolve(Candidates, SelectedRule).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Find a rule that hasn't fired before and has its conditions satisfied
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
can_fire(RuleName if Condition then Conclusion) :-
RuleName if Condition then Conclusion, % Look up rule in data base
not(already_fired(RuleName, Condition)), % Has it already fired?
satisfied(Condition). % Are all conditions satisfied?
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% If pattern is "A and ..." then look for A in working memory
% then check rest recursively.
%
% (A and B) = (x and y and z)
% A = x
% B = y and Z
%
% If pattern is a single predicate. Look it up.
% Note that "!" prevents a conjunction reaching the second clause
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
satisfied(A and B) :- !,
wm(A),
satisfied(B).
satisfied(A) :-
wm(A).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Very simple conflict resolution strategy: pick the first rule
% Also check in case no rules were found
%
% Exercise: rewrite this to choose the rule which has the most conditions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
resolve([], []).
resolve([X|_], X).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Add "already_fired" clause to data base so that this instance of the rule
% is never fired again.
% Add all terms in conclusion to database, if not already there
% Fail to force backtracking so that a new execution cycle begins
%
% If there is no rule to fire, succeed. This terminates execution cycle
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
fire(RuleName if Condition then Conclusion) :- !,
assert(already_fired(RuleName, Condition)),
add_to_wm(Conclusion),
fail.
fire(_).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% For each term in condition
% add it to workking memory if it is not already there.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
add_to_wm(A and B) :- !,
assert_if_not_present(A),
add_to_wm(B).
add_to_wm(A) :-
assert_if_not_present(A).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% If term is in working memory, don't do anything
% Otherwise, add new term to working memory.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
assert_if_not_present(A) :-
wm(A), !.
assert_if_not_present(A) :-
assert(wm(A)).