% file: rdr_impl.pl % author: Douglas M. Auclair % created: September 20, 2008 % synopsis: This file covers creating and running RDR (ripple-down rules). % % For creating RDR, we start with init_rule_env/1 and build the % knowledge base with add_rule/4. An example of building from an % initialized rule_env/2 is the pred test_rule_env/1 % % For running an RDR system, we have run_rule/2 after having % initialized our environement from init_rule_env/1, and then adding % the situational awareness with update_env/[3,4]. After which we % derive our answer with run_concl/2. An example of running the rules % to get to our conclusion are the preds test_run_rule/1 and % test_run_concl/1. % Takes a rule-tree and environment of attributed values and runs the system % to find the best-fitting conclusion. run_rule(rule_env(Tree, Env0), RuleEnv) :- rule_at(Tree, Rule), !, test_rule(Rule, Dir, Env0, Env1), advance(Dir, Tree, Tree1), run_rule(rule_env(Tree1, Env1), RuleEnv) ; withdraw(Tree, Bough), RuleEnv = rule_env(Bough, Env0). % Gives us an initially empty rule environment (the tree and an empty % set of attributed values) to build our knowledge base. init_rule_env(rule_env(zip([], branch(rule(cond(just_true), True), leaf, leaf)), env([], True))) :- True = concl(k(none)). % Adds a rule at the preset node in the rule tree. add_rule(Dir, Rule, zip(Hist, Tree), zip(Hist, Root)) :- NewBranch = branch(Rule, leaf, leaf), mutate(Dir, Tree, NewBranch, Root), true. % settle(NewTree, Root). % The following preds manipulate and move (forward or backward) through % the rule tree: rule_at/2, advance/3, go/3, withdraw/2, mutate/3 rule_at(zip(_, branch(Rule, _, _)), Rule). advance(Dir, zip(Hist, Branch), zip([Dir - Branch|Hist], NewBranch)) :- go(Dir, Branch, NewBranch). go(left, branch(_, L, _), L). go(right, branch(_, _, R), R). settle(zip([], Tree), zip([], Tree)). settle(zip([H|T], Tree), Zip) :- withdraw(zip([H|T], Tree), NewZip), settle(NewZip, Zip). withdraw(zip([Dir - Tree|Hist], Branch), zip(Hist, NewTree)) :- mutate(Dir, Tree, Branch, NewTree). mutate(left, branch(Rule, _, R), L, branch(Rule, L, R)). mutate(right, branch(Rule, L, _), R, branch(Rule, L, R)). % Once we are at a branch, we need to query that branch against the environment % to see in which direction we should continue. test_rule(rule(cond(Pred), Essayed), Direction, env(KeyVals, Assumed), Env) :- build_pred(Pred, KeyVals, Test), (call(Test), !, Direction = left, Env = env(KeyVals, Essayed) ; Direction = right, Env = env(KeyVals, Assumed)). % little helper pred to test_rule/4 build_pred(Pred, Arg, Test) :- Pred =.. PredList, snoc(PredList, Arg, TestList), Test =.. TestList. snoc(List, Elt, NewList) :- append(List, [Elt], NewList). % runs the conclusion predicate, deriving the answer from it and the % key-values in the environment run_concl(env(KeyVals, concl(AnsPred)), Ans) :- AnsPred =.. AnsList, snoc(AnsList, KeyVals, AL0), snoc(AL0, Ans, PredList), Pred =.. PredList, call(Pred). % The following pred help in building a rule/2 by helping to build its % constituent parts, the cond/1 and concl/1: make_equiv_cond/2, present/2, % k/3, and assume/2 % for env/2, we assume KeyVal is an assoc_list make_equiv_cond(Key - Value, cond(member(Key - Value))). % present just checks for the existence of a key, regardless of value. present(Key, Cond) :- make_equiv_cond(Key - _, Cond). % A little combinator logic to brighten my day: k(A, _, A). % and here's the magical true combinator for the top level rule. just_true(_). % assume creates a constifying conclusion (it ignores any input) assume(Val, concl(k(Val))). % Given that we're making a rule that only tests for an attribute % using present/2 and gives a straightforward conclusion with assume/2, % we have this pred to create a simple rule composing those to results. make_simple_rule(Key, Ans, rule(Cond, Concl)) :- present(Key, Cond), assume(Ans, Concl). % Here we add some new information to our environment, resetting the % conclusion, if necessary, so we have update_env/[3,4]. update_env(KeyVal, Concl, env(Dict, _), env([KeyVal|Dict], Concl)). update_env(KeyVal, env(Dict, Concl), env([KeyVal|Dict], Concl)). % n.b.: KeyVal is usually of the form (Key - Val) for an assoc_list % knowledge store, but I make it one parameter to give you enough % flexible rope to hang yourself in whichever manner you so choose. % If we're just working with attributes, here's the mapping pred that % extracts them from the key-val list: attribs(env([Key - _|KeyVals], _), [Key|Rest]) :- attribs(env(KeyVals, _), Rest). attribs(env([], _), []).