Skip to content

Commit b14e21a

Browse files
Chipe1norvig
authored andcommitted
Implemented fol_fc_ask() (aimacode#535)
1 parent 4c873c8 commit b14e21a

File tree

3 files changed

+94
-45
lines changed

3 files changed

+94
-45
lines changed

aima-data

logic.py

Lines changed: 72 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,16 @@ def prop_symbols(x):
224224
return list(set(symbol for arg in x.args for symbol in prop_symbols(arg)))
225225

226226

227+
def constant_symbols(x):
228+
"""Return a list of all constant symbols in x."""
229+
if not isinstance(x, Expr):
230+
return []
231+
elif is_prop_symbol(x.op) and not x.args:
232+
return [x]
233+
else:
234+
return list({symbol for arg in x.args for symbol in constant_symbols(arg)})
235+
236+
227237
def tt_true(s):
228238
"""Is a propositional sentence a tautology?
229239
>>> tt_true('P | ~P')
@@ -845,26 +855,6 @@ def subst(s, x):
845855
return Expr(x.op, *[subst(s, arg) for arg in x.args])
846856

847857

848-
def fol_fc_ask(KB, alpha):
849-
"""A simple forward-chaining algorithm. [Figure 9.3]"""
850-
new = []
851-
while new is not None:
852-
for rule in KB.clauses:
853-
p, q = parse_definite_clause(standardize_variables(rule))
854-
for p_ in KB.clauses:
855-
if p != p_:
856-
for theta in KB.clauses:
857-
if subst(theta, p) == subst(theta, p_):
858-
q_ = subst(theta, q)
859-
if not unify(q_, KB.sentence in KB) or not unify(q_, new):
860-
new.append(q_)
861-
phi = unify(q_, alpha)
862-
if phi is not None:
863-
return phi
864-
KB.tell(new)
865-
return None
866-
867-
868858
def standardize_variables(sentence, dic=None):
869859
"""Replace all the variables in sentence with new variables."""
870860
if dic is None:
@@ -921,31 +911,42 @@ def fetch_rules_for_goal(self, goal):
921911
return self.clauses
922912

923913

924-
test_kb = FolKB(
925-
map(expr, ['Farmer(Mac)',
926-
'Rabbit(Pete)',
927-
'Mother(MrsMac, Mac)',
928-
'Mother(MrsRabbit, Pete)',
929-
'(Rabbit(r) & Farmer(f)) ==> Hates(f, r)',
930-
'(Mother(m, c)) ==> Loves(m, c)',
931-
'(Mother(m, r) & Rabbit(r)) ==> Rabbit(m)',
932-
'(Farmer(f)) ==> Human(f)',
933-
# Note that this order of conjuncts
934-
# would result in infinite recursion:
935-
# '(Human(h) & Mother(m, h)) ==> Human(m)'
936-
'(Mother(m, h) & Human(h)) ==> Human(m)'
937-
]))
914+
def fol_fc_ask(KB, alpha):
915+
"""A simple forward-chaining algorithm. [Figure 9.3]"""
916+
# TODO: Improve efficiency
917+
def enum_subst(KB):
918+
kb_vars = list({v for clause in KB.clauses for v in variables(clause)})
919+
kb_consts = list({c for clause in KB.clauses for c in constant_symbols(clause)})
920+
for assignment_list in itertools.product(kb_consts, repeat=len(kb_vars)):
921+
theta = {x: y for x, y in zip(kb_vars, assignment_list)}
922+
yield theta
923+
924+
# check if we can answer without new inferences
925+
for q in KB.clauses:
926+
phi = unify(q, alpha, {})
927+
if phi is not None:
928+
yield phi
938929

939-
crime_kb = FolKB(
940-
map(expr, ['(American(x) & Weapon(y) & Sells(x, y, z) & Hostile(z)) ==> Criminal(x)',
941-
'Owns(Nono, M1)',
942-
'Missile(M1)',
943-
'(Missile(x) & Owns(Nono, x)) ==> Sells(West, x, Nono)',
944-
'Missile(x) ==> Weapon(x)',
945-
'Enemy(x, America) ==> Hostile(x)',
946-
'American(West)',
947-
'Enemy(Nono, America)'
948-
]))
930+
while True:
931+
new = []
932+
for rule in KB.clauses:
933+
p, q = parse_definite_clause(rule)
934+
for theta in enum_subst(KB):
935+
if any([set(subst(theta, p)) == set(subst(theta, p_))
936+
for p_ in itertools.combinations(KB.clauses, len(p))]):
937+
q_ = subst(theta, q)
938+
if all([unify(x, q_, {}) is None for x in KB.clauses + new]):
939+
print('Added', q_)
940+
new.append(q_)
941+
phi = unify(q_, alpha, {})
942+
if phi is not None:
943+
print(q_, alpha)
944+
yield phi
945+
if not new:
946+
break
947+
for clause in new:
948+
KB.tell(clause)
949+
return None
949950

950951

951952
def fol_bc_ask(KB, query):
@@ -972,6 +973,33 @@ def fol_bc_and(KB, goals, theta):
972973
for theta2 in fol_bc_and(KB, rest, theta1):
973974
yield theta2
974975

976+
977+
test_kb = FolKB(
978+
map(expr, ['Farmer(Mac)',
979+
'Rabbit(Pete)',
980+
'Mother(MrsMac, Mac)',
981+
'Mother(MrsRabbit, Pete)',
982+
'(Rabbit(r) & Farmer(f)) ==> Hates(f, r)',
983+
'(Mother(m, c)) ==> Loves(m, c)',
984+
'(Mother(m, r) & Rabbit(r)) ==> Rabbit(m)',
985+
'(Farmer(f)) ==> Human(f)',
986+
# Note that this order of conjuncts
987+
# would result in infinite recursion:
988+
# '(Human(h) & Mother(m, h)) ==> Human(m)'
989+
'(Mother(m, h) & Human(h)) ==> Human(m)'
990+
]))
991+
992+
crime_kb = FolKB(
993+
map(expr, ['(American(x) & Weapon(y) & Sells(x, y, z) & Hostile(z)) ==> Criminal(x)',
994+
'Owns(Nono, M1)',
995+
'Missile(M1)',
996+
'(Missile(x) & Owns(Nono, x)) ==> Sells(West, x, Nono)',
997+
'Missile(x) ==> Weapon(x)',
998+
'Enemy(x, America) ==> Hostile(x)',
999+
'American(West)',
1000+
'Enemy(Nono, America)'
1001+
]))
1002+
9751003
# ______________________________________________________________________________
9761004

9771005
# Example application (not in the book).

tests/test_logic.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,11 @@ def test_prop_symbols():
190190
assert set(prop_symbols(expr('(x & B(z)) ==> Farmer(y) | A'))) == {A, expr('Farmer(y)'), expr('B(z)')}
191191

192192

193+
def test_constant_symbols():
194+
assert set(constant_symbols(expr('x & y & z | A'))) == {A}
195+
assert set(constant_symbols(expr('(x & B(z)) & Father(John) ==> Farmer(y) | A'))) == {A, expr('John')}
196+
197+
193198
def test_eliminate_implications():
194199
assert repr(eliminate_implications('A ==> (~B <== C)')) == '((~B | ~C) | ~A)'
195200
assert repr(eliminate_implications(A ^ B)) == '((A & ~B) | (~A & B))'
@@ -258,6 +263,22 @@ def test_ask(query, kb=None):
258263
assert repr(test_ask('Criminal(x)', crime_kb)) == '[{x: West}]'
259264

260265

266+
def test_fol_fc_ask():
267+
def test_ask(query, kb=None):
268+
q = expr(query)
269+
test_variables = variables(q)
270+
answers = fol_fc_ask(kb or test_kb, q)
271+
print(answers)
272+
return sorted(
273+
[dict((x, v) for x, v in list(a.items()) if x in test_variables)
274+
for a in answers], key=repr)
275+
## Take too long to run
276+
#assert repr(test_ask('Farmer(x)')) == '[{x: Mac}]'
277+
#assert repr(test_ask('Human(x)')) == '[{x: Mac}, {x: MrsMac}]'
278+
#assert repr(test_ask('Rabbit(x)')) == '[{x: MrsRabbit}, {x: Pete}]'
279+
#assert repr(test_ask('Criminal(x)', crime_kb)) == '[{x: West}]'
280+
281+
261282
def test_d():
262283
assert d(x * x - x, x) == 2 * x - 1
263284

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy