From 8686589c16cf616cae3b2afa57f84fd12b4a692e Mon Sep 17 00:00:00 2001 From: Chipe1 Date: Tue, 6 Jun 2017 16:32:57 +0530 Subject: [PATCH] Implemented fol_fc_ask() --- aima-data | 2 +- logic.py | 116 +++++++++++++++++++++++++++----------------- tests/test_logic.py | 21 ++++++++ 3 files changed, 94 insertions(+), 45 deletions(-) diff --git a/aima-data b/aima-data index a21fc108f..6ce56c0b6 160000 --- a/aima-data +++ b/aima-data @@ -1 +1 @@ -Subproject commit a21fc108f52ad551344e947b0eb97df82f8d2b2b +Subproject commit 6ce56c0b67206bae91b04fb20f0d8d70c9a86b6a diff --git a/logic.py b/logic.py index e3d326e68..525b65642 100644 --- a/logic.py +++ b/logic.py @@ -224,6 +224,16 @@ def prop_symbols(x): return list(set(symbol for arg in x.args for symbol in prop_symbols(arg))) +def constant_symbols(x): + """Return a list of all constant symbols in x.""" + if not isinstance(x, Expr): + return [] + elif is_prop_symbol(x.op) and not x.args: + return [x] + else: + return list({symbol for arg in x.args for symbol in constant_symbols(arg)}) + + def tt_true(s): """Is a propositional sentence a tautology? >>> tt_true('P | ~P') @@ -845,26 +855,6 @@ def subst(s, x): return Expr(x.op, *[subst(s, arg) for arg in x.args]) -def fol_fc_ask(KB, alpha): - """A simple forward-chaining algorithm. [Figure 9.3]""" - new = [] - while new is not None: - for rule in KB.clauses: - p, q = parse_definite_clause(standardize_variables(rule)) - for p_ in KB.clauses: - if p != p_: - for theta in KB.clauses: - if subst(theta, p) == subst(theta, p_): - q_ = subst(theta, q) - if not unify(q_, KB.sentence in KB) or not unify(q_, new): - new.append(q_) - phi = unify(q_, alpha) - if phi is not None: - return phi - KB.tell(new) - return None - - def standardize_variables(sentence, dic=None): """Replace all the variables in sentence with new variables.""" if dic is None: @@ -921,31 +911,42 @@ def fetch_rules_for_goal(self, goal): return self.clauses -test_kb = FolKB( - map(expr, ['Farmer(Mac)', - 'Rabbit(Pete)', - 'Mother(MrsMac, Mac)', - 'Mother(MrsRabbit, Pete)', - '(Rabbit(r) & Farmer(f)) ==> Hates(f, r)', - '(Mother(m, c)) ==> Loves(m, c)', - '(Mother(m, r) & Rabbit(r)) ==> Rabbit(m)', - '(Farmer(f)) ==> Human(f)', - # Note that this order of conjuncts - # would result in infinite recursion: - # '(Human(h) & Mother(m, h)) ==> Human(m)' - '(Mother(m, h) & Human(h)) ==> Human(m)' - ])) +def fol_fc_ask(KB, alpha): + """A simple forward-chaining algorithm. [Figure 9.3]""" + # TODO: Improve efficiency + def enum_subst(KB): + kb_vars = list({v for clause in KB.clauses for v in variables(clause)}) + kb_consts = list({c for clause in KB.clauses for c in constant_symbols(clause)}) + for assignment_list in itertools.product(kb_consts, repeat=len(kb_vars)): + theta = {x: y for x, y in zip(kb_vars, assignment_list)} + yield theta + + # check if we can answer without new inferences + for q in KB.clauses: + phi = unify(q, alpha, {}) + if phi is not None: + yield phi -crime_kb = FolKB( - map(expr, ['(American(x) & Weapon(y) & Sells(x, y, z) & Hostile(z)) ==> Criminal(x)', - 'Owns(Nono, M1)', - 'Missile(M1)', - '(Missile(x) & Owns(Nono, x)) ==> Sells(West, x, Nono)', - 'Missile(x) ==> Weapon(x)', - 'Enemy(x, America) ==> Hostile(x)', - 'American(West)', - 'Enemy(Nono, America)' - ])) + while True: + new = [] + for rule in KB.clauses: + p, q = parse_definite_clause(rule) + for theta in enum_subst(KB): + if any([set(subst(theta, p)) == set(subst(theta, p_)) + for p_ in itertools.combinations(KB.clauses, len(p))]): + q_ = subst(theta, q) + if all([unify(x, q_, {}) is None for x in KB.clauses + new]): + print('Added', q_) + new.append(q_) + phi = unify(q_, alpha, {}) + if phi is not None: + print(q_, alpha) + yield phi + if not new: + break + for clause in new: + KB.tell(clause) + return None def fol_bc_ask(KB, query): @@ -972,6 +973,33 @@ def fol_bc_and(KB, goals, theta): for theta2 in fol_bc_and(KB, rest, theta1): yield theta2 + +test_kb = FolKB( + map(expr, ['Farmer(Mac)', + 'Rabbit(Pete)', + 'Mother(MrsMac, Mac)', + 'Mother(MrsRabbit, Pete)', + '(Rabbit(r) & Farmer(f)) ==> Hates(f, r)', + '(Mother(m, c)) ==> Loves(m, c)', + '(Mother(m, r) & Rabbit(r)) ==> Rabbit(m)', + '(Farmer(f)) ==> Human(f)', + # Note that this order of conjuncts + # would result in infinite recursion: + # '(Human(h) & Mother(m, h)) ==> Human(m)' + '(Mother(m, h) & Human(h)) ==> Human(m)' + ])) + +crime_kb = FolKB( + map(expr, ['(American(x) & Weapon(y) & Sells(x, y, z) & Hostile(z)) ==> Criminal(x)', + 'Owns(Nono, M1)', + 'Missile(M1)', + '(Missile(x) & Owns(Nono, x)) ==> Sells(West, x, Nono)', + 'Missile(x) ==> Weapon(x)', + 'Enemy(x, America) ==> Hostile(x)', + 'American(West)', + 'Enemy(Nono, America)' + ])) + # ______________________________________________________________________________ # Example application (not in the book). diff --git a/tests/test_logic.py b/tests/test_logic.py index be172e664..ba128883e 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -190,6 +190,11 @@ def test_prop_symbols(): assert set(prop_symbols(expr('(x & B(z)) ==> Farmer(y) | A'))) == {A, expr('Farmer(y)'), expr('B(z)')} +def test_constant_symbols(): + assert set(constant_symbols(expr('x & y & z | A'))) == {A} + assert set(constant_symbols(expr('(x & B(z)) & Father(John) ==> Farmer(y) | A'))) == {A, expr('John')} + + def test_eliminate_implications(): assert repr(eliminate_implications('A ==> (~B <== C)')) == '((~B | ~C) | ~A)' assert repr(eliminate_implications(A ^ B)) == '((A & ~B) | (~A & B))' @@ -258,6 +263,22 @@ def test_ask(query, kb=None): assert repr(test_ask('Criminal(x)', crime_kb)) == '[{x: West}]' +def test_fol_fc_ask(): + def test_ask(query, kb=None): + q = expr(query) + test_variables = variables(q) + answers = fol_fc_ask(kb or test_kb, q) + print(answers) + return sorted( + [dict((x, v) for x, v in list(a.items()) if x in test_variables) + for a in answers], key=repr) + ## Take too long to run + #assert repr(test_ask('Farmer(x)')) == '[{x: Mac}]' + #assert repr(test_ask('Human(x)')) == '[{x: Mac}, {x: MrsMac}]' + #assert repr(test_ask('Rabbit(x)')) == '[{x: MrsRabbit}, {x: Pete}]' + #assert repr(test_ask('Criminal(x)', crime_kb)) == '[{x: West}]' + + def test_d(): assert d(x * x - x, x) == 2 * x - 1