Skip to content

Commit

Permalink
Implemented fol_fc_ask() (#535)
Browse files Browse the repository at this point in the history
  • Loading branch information
Chipe1 authored and norvig committed Jun 7, 2017
1 parent 4c873c8 commit b14e21a
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 45 deletions.
2 changes: 1 addition & 1 deletion aima-data
Submodule aima-data updated 2 files
+0 −21 LICENSE
+2 −0 LICENSE.md
116 changes: 72 additions & 44 deletions logic.py
Expand Up @@ -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')
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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):
Expand All @@ -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).
Expand Down
21 changes: 21 additions & 0 deletions tests/test_logic.py
Expand Up @@ -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))'
Expand Down Expand Up @@ -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

Expand Down

0 comments on commit b14e21a

Please sign in to comment.