1 """Representations and Inference for Logic (Chapters 7-10)
2
3 Covers both Propositional and First-Order Logic. First we have four
4 important data types:
5
6 KB Abstract class holds a knowledge base of logical expressions
7 KB_Agent Abstract class subclasses agents.Agent
8 Expr A logical expression
9 substitution Implemented as a dictionary of var:value pairs, {x:1, y:x}
10
11 Be careful: some functions take an Expr as argument, and some take a KB.
12 Then we implement various functions for doing logical inference:
13
14 pl_true Evaluate a propositional logical sentence in a model
15 tt_entails Say if a statement is entailed by a KB
16 pl_resolution Do resolution on propositional sentences
17 dpll_satisfiable See if a propositional sentence is satisfiable
18 WalkSAT (not yet implemented)
19
20 And a few other functions:
21
22 to_cnf Convert to conjunctive normal form
23 unify Do unification of two FOL sentences
24 diff, simp Symbolic differentiation and simplification
25 """
26
27 from __future__ import generators
28 import re
29
30 -def every(predicate, seq):
31 """True if every element of seq satisfies predicate.
32 >>> every(callable, [min, max])
33 1
34 >>> every(callable, [min, 3])
35 0
36 """
37 for x in seq:
38 if not predicate(x): return False
39 return True
40
42 "Is x a number? We say it is if it has a __int__ method."
43 return hasattr(x, '__int__')
44
46 "Is x a sequence? We say it is if it has a __getitem__ method."
47 return hasattr(x, '__getitem__')
48
50 """The argument is a string; convert to a number if possible, or strip it.
51 >>> num_or_str('42')
52 42
53 >>> num_or_str(' 42x ')
54 '42x'
55 """
56 if isnumber(x): return x
57 try:
58 return int(x)
59 except ValueError:
60 try:
61 return float(x)
62 except ValueError:
63 return str(x).strip()
64
65
66
68 """A Knowledge base to which you can tell and ask sentences.
69 To create a KB, first subclass this class and implement
70 tell, ask_generator, and retract. Why ask_generator instead of ask?
71 The book is a bit vague on what ask means --
72 For a Propositional Logic KB, ask(P & Q) returns True or False, but for an
73 FOL KB, something like ask(Brother(x, y)) might return many substitutions
74 such as {x: Cain, y: Abel}, {x: Abel, y: Cain}, {x: George, y: Jeb}, etc.
75 So ask_generator generates these one at a time, and ask either returns the
76 first one or returns False."""
77
80
81 - def tell(self, sentence):
82 "Add the sentence to the KB"
83 abstract
84
85 - def ask(self, query):
86 """Ask returns a substitution that makes the query true, or
87 it returns False. It is implemented in terms of ask_generator."""
88 if issubclass(query.__class__,str):
89 query = expr(query)
90 try:
91 return self.ask_generator(query).next()
92 except StopIteration:
93 return False
94
96 "Yield all the substitutions that make query true."
97 abstract
98
100 "Remove the sentence from the KB"
101 abstract
102
103
105 "A KB for Propositional Logic. Inefficient, with no indexing."
106
108 self.clauses = []
109 if sentence:
110 self.tell(sentence)
111
112 - def tell(self, sentence):
113 "Add the sentence's clauses to the KB"
114 self.clauses.extend(conjuncts(to_cnf(sentence)))
115
117 "Yield the empty substitution if KB implies query; else False"
118 if not tt_entails(Expr('&', *self.clauses), query):
119 return
120 yield {}
121
123 "Remove the sentence's clauses from the KB"
124 for c in conjuncts(to_cnf(sentence)):
125 if c in self.clauses:
126 self.clauses.remove(c)
127
128
129
130
131 """A generic logical knowledge-based agent. [Fig. 7.1]"""
132 """def __init__(self, KB):
133 t = 0
134 def program(percept):
135 KB.tell(self.make_percept_sentence(percept, t))
136 action = KB.ask(self.make_action_query(t))
137 KB.tell(self.make_action_sentence(action, t))
138 t = t + 1
139 return action
140 self.program = program
141
142 def make_percept_sentence(self, percept, t):
143 return(Expr("Percept")(percept, t))
144
145 def make_action_query(self, t):
146 return(expr("ShouldDo(action, %d)" % t))
147
148 def make_action_sentence(self, action, t):
149 return(Expr("Did")(action, t))"""
150
151
152
154 """A symbolic mathematical expression. We use this class for logical
155 expressions, and for terms within logical expressions. In general, an
156 Expr has an op (operator) and a list of args. The op can be:
157 Null-ary (no args) op:
158 A number, representing the number itself. (e.g. Expr(42) => 42)
159 A symbol, representing a variable or constant (e.g. Expr('F') => F)
160 Unary (1 arg) op:
161 '~', '-', representing NOT, negation (e.g. Expr('~', Expr('P')) => ~P)
162 Binary (2 arg) op:
163 '>>', '<<', representing forward and backward implication
164 '+', '-', '*', '/', '**', representing arithmetic operators
165 '<', '>', '>=', '<=', representing comparison operators
166 '<=>', '^', representing logical equality and XOR
167 N-ary (0 or more args) op:
168 '&', '|', representing conjunction and disjunction
169 A symbol, representing a function term or FOL proposition
170
171 Exprs can be constructed with operator overloading: if x and y are Exprs,
172 then so are x + y and x & y, etc. Also, if F and x are Exprs, then so is
173 F(x); it works by overloading the __call__ method of the Expr F. Note
174 that in the Expr that is created by F(x), the op is the str 'F', not the
175 Expr F. See http://www.python.org/doc/current/ref/specialnames.html
176 to learn more about operator overloading in Python.
177
178 WARNING: x == y and x != y are NOT Exprs. The reason is that we want
179 to write code that tests 'if x == y:' and if x == y were the same
180 as Expr('==', x, y), then the result would always be true; not what a
181 programmer would expect. But we still need to form Exprs representing
182 equalities and disequalities. We concentrate on logical equality (or
183 equivalence) and logical disequality (or XOR). You have 3 choices:
184 (1) Expr('<=>', x, y) and Expr('^', x, y)
185 Note that ^ is bitwose XOR in Python (and Java and C++)
186 (2) expr('x <=> y') and expr('x =/= y').
187 See the doc string for the function expr.
188 (3) (x % y) and (x ^ y).
189 It is very ugly to have (x % y) mean (x <=> y), but we need
190 SOME operator to make (2) work, and this seems the best choice.
191
192 WARNING: if x is an Expr, then so is x + 1, because the int 1 gets
193 coerced to an Expr by the constructor. But 1 + x is an error, because
194 1 doesn't know how to add an Expr. (Adding an __radd__ method to Expr
195 wouldn't help, because int.__add__ is still called first.) Therefore,
196 you should use Expr(1) + x instead, or ONE + x, or expr('1 + x').
197 """
198
200 "Op is a string or number; args are Exprs (or are coerced to Exprs)."
201 assert isinstance(op, str) or (isnumber(op) and not args)
202 self.op = num_or_str(op)
203 self.args = map(expr, args)
204
206 """Self must be a symbol with no args, such as Expr('F'). Create a new
207 Expr with 'F' as op and the args as arguments."""
208 assert is_symbol(self.op) and not self.args
209 return Expr(self.op, *args)
210
212 "Show something like 'P' or 'P(x, y)', or '~P' or '(P | Q | R)'"
213 if len(self.args) == 0:
214 return str(self.op)
215 elif is_symbol(self.op):
216 return '%s(%s)' % (self.op, ', '.join(map(repr, self.args)))
217 elif len(self.args) == 1:
218 return self.op + repr(self.args[0])
219 else:
220 return '(%s)' % (' '+self.op+' ').join(map(repr, self.args))
221
223 """x and y are equal iff their ops and args are equal."""
224 return (other is self) or (isinstance(other, Expr)
225 and self.op == other.op and self.args == other.args)
226
228 "Need a hash method so Exprs can live in dicts."
229 return hash(self.op) ^ hash(tuple(self.args))
230
231
232
233 - def __lt__(self, other): return Expr('<', self, other)
234 - def __le__(self, other): return Expr('<=', self, other)
235 - def __ge__(self, other): return Expr('>=', self, other)
236 - def __gt__(self, other): return Expr('>', self, other)
237 - def __add__(self, other): return Expr('+', self, other)
238 - def __sub__(self, other): return Expr('-', self, other)
239 - def __and__(self, other): return Expr('&', self, other)
240 - def __div__(self, other): return Expr('/', self, other)
245 - def __mul__(self, other): return Expr('*', self, other)
247 - def __or__(self, other): return Expr('|', self, other)
248 - def __pow__(self, other): return Expr('**', self, other)
249 - def __xor__(self, other): return Expr('^', self, other)
250 - def __mod__(self, other): return Expr('<=>', self, other)
251
252
253
255 """Create an Expr representing a logic expression by parsing the input
256 string. Symbols and numbers are automatically converted to Exprs.
257 In addition you can use alternative spellings of these operators:
258 'x ==> y' parses as (x >> y) # Implication
259 'x <== y' parses as (x << y) # Reverse implication
260 'x <=> y' parses as (x % y) # Logical equivalence
261 'x =/= y' parses as (x ^ y) # Logical disequality (xor)
262 But BE CAREFUL; precedence of implication is wrong. expr('P & Q ==> R & S')
263 is ((P & (Q >> R)) & S); so you must use expr('(P & Q) ==> (R & S)').
264 >>> expr('P <=> Q(1)')
265 (P <=> Q(1))
266 >>> expr('P & Q | ~R(x, F(x))')
267 ((P & Q) | ~R(x, F(x)))
268 """
269 if isinstance(s, Expr): return s
270 if isnumber(s): return Expr(s)
271
272 s = s.replace('==>', '>>').replace('<==', '<<')
273 s = s.replace('<=>', '%').replace('=/=', '^')
274
275 s = re.sub(r'([a-zA-Z0-9_.]+)', r'Expr("\1")', s)
276
277 return eval(s, {'Expr':Expr})
278
280 "A string s is a symbol if it starts with an alphabetic char."
281 return isinstance(s, str) and s[0].isalpha()
282
284 "A logic variable symbol is an initial-lowercase string."
285 return is_symbol(s) and s[0].islower()
286
288 """A proposition logic symbol is an initial-uppercase string other than
289 TRUE or FALSE."""
290 return is_symbol(s) and s[0].isupper() and s != 'TRUE' and s != 'FALSE'
291
293 """s is an unnegated logical expression
294 >>> is_positive(expr('F(A, B)'))
295 True
296 >>> is_positive(expr('~F(A, B)'))
297 False
298 """
299 return s.op != '~'
300
302 """s is a negated logical expression
303 >>> is_negative(expr('F(A, B)'))
304 False
305 >>> is_negative(expr('~F(A, B)'))
306 True
307 """
308 return s.op == '~'
309
311 """s is a FOL literal
312 >>> is_literal(expr('~F(A, B)'))
313 True
314 >>> is_literal(expr('F(A, B)'))
315 True
316 >>> is_literal(expr('F(A, B) & G(B, C)'))
317 False
318 """
319 return is_symbol(s.op) or (s.op == '~' and is_literal(s.args[0]))
320
322 """returns the list of literals of logical expression s.
323 >>> literals(expr('F(A, B)'))
324 [F(A, B)]
325 >>> literals(expr('~F(A, B)'))
326 [~F(A, B)]
327 >>> literals(expr('(F(A, B) & G(B, C)) ==> R(A, C)'))
328 [F(A, B), G(B, C), R(A, C)]
329 """
330 op = s.op
331 if op in set(['&', '|', '<<', '>>', '%', '^']):
332 result = []
333 for arg in s.args:
334 result.extend(literals(arg))
335 return result
336 elif is_literal(s):
337 return [s]
338 else:
339 return []
340
342 """returns the set of variables in logical expression s.
343 >>> ppset(variables(F(x, A, y)))
344 set([x, y])
345 >>> ppset(variables(expr('F(x, x) & G(x, y) & H(y, z) & R(A, z, z)')))
346 set([x, y, z])
347 """
348 if is_literal(s):
349 return set([v for v in s.args if is_variable(v)])
350 else:
351 vars = set([])
352 for lit in literals(s):
353 vars = vars.union(variables(lit))
354 return vars
355
357 """returns True for exprs s of the form A & B & ... & C ==> D,
358 where all literals are positive. In clause form, this is
359 ~A | ~B | ... | ~C | D, where exactly one clause is positive.
360 >>> is_definite_clause(expr('Farmer(Mac)'))
361 True
362 >>> is_definite_clause(expr('~Farmer(Mac)'))
363 False
364 >>> is_definite_clause(expr('(Farmer(f) & Rabbit(r)) ==> Hates(f, r)'))
365 True
366 >>> is_definite_clause(expr('(Farmer(f) & ~Rabbit(r)) ==> Hates(f, r)'))
367 False
368 """
369 op = s.op
370 return (is_symbol(op) or
371 (op == '>>' and every(is_positive, literals(s))))
372
373
374 TRUE, FALSE, ZERO, ONE, TWO = map(Expr, ['TRUE', 'FALSE', 0, 1, 2])
375 A, B, C, F, G, P, Q, x, y, z = map(Expr, 'ABCFGPQxyz')
376
377
378
380 """Use truth tables to determine if KB entails sentence alpha. [Fig. 7.10]
381 >>> tt_entails(expr('P & Q'), expr('Q'))
382 True
383 """
384 return tt_check_all(kb, alpha, prop_symbols(kb & alpha), {})
385
387 "Auxiliary routine to implement tt_entails."
388 if not symbols:
389 if pl_true(kb, model): return pl_true(alpha, model)
390 else: return True
391 assert result != None
392 else:
393 P, rest = symbols[0], symbols[1:]
394 return (tt_check_all(kb, alpha, rest, extend(model, P, True)) and
395 tt_check_all(kb, alpha, rest, extend(model, P, False)))
396
398 "Return a list of all propositional symbols in x."
399 if not isinstance(x, Expr):
400 return []
401 elif is_prop_symbol(x.op):
402 return [x]
403 else:
404 s = set(())
405 for arg in x.args:
406 for symbol in prop_symbols(arg):
407 s.add(symbol)
408 return list(s)
409
411 """Is the sentence alpha a tautology? (alpha will be coerced to an expr.)
412 >>> tt_true(expr("(P >> Q) <=> (~P | Q)"))
413 True
414 """
415 return tt_entails(TRUE, expr(alpha))
416
418 """Return True if the propositional logic expression is true in the model,
419 and False if it is false. If the model does not specify the value for
420 every proposition, this may return None to indicate 'not obvious';
421 this may happen even when the expression is tautological."""
422 op, args = exp.op, exp.args
423 if exp == TRUE:
424 return True
425 elif exp == FALSE:
426 return False
427 elif is_prop_symbol(op):
428 return model.get(exp)
429 elif op == '~':
430 p = pl_true(args[0], model)
431 if p == None: return None
432 else: return not p
433 elif op == '|':
434 result = False
435 for arg in args:
436 p = pl_true(arg, model)
437 if p == True: return True
438 if p == None: result = None
439 return result
440 elif op == '&':
441 result = True
442 for arg in args:
443 p = pl_true(arg, model)
444 if p == False: return False
445 if p == None: result = None
446 return result
447 p, q = args
448 if op == '>>':
449 return pl_true(~p | q, model)
450 elif op == '<<':
451 return pl_true(p | ~q, model)
452 pt = pl_true(p, model)
453 if pt == None: return None
454 qt = pl_true(q, model)
455 if qt == None: return None
456 if op == '<=>':
457 return pt == qt
458 elif op == '^':
459 return pt != qt
460 else:
461 raise ValueError, "illegal operator in logic expression" + str(exp)
462
463
464
465
466
468 """Convert a propositional logical sentence s to conjunctive normal form.
469 That is, of the form ((A | ~B | ...) & (B | C | ...) & ...) [p. 215]
470 >>> to_cnf("~(B|C)")
471 (~B & ~C)
472 >>> to_cnf("B <=> (P1|P2)")
473 ((~P1 | B) & (~P2 | B) & (P1 | P2 | ~B))
474 >>> to_cnf("a | (b & c) | d")
475 ((b | a | d) & (c | a | d))
476 >>> to_cnf("A & (B | (D & E))")
477 (A & (D | B) & (E | B))
478 """
479 if isinstance(s, str): s = expr(s)
480 s = eliminate_implications(s)
481 s = move_not_inwards(s)
482 return distribute_and_over_or(s)
483
485 """Change >>, <<, and <=> into &, |, and ~. That is, return an Expr
486 that is equivalent to s, but has only &, |, and ~ as logical operators.
487 >>> eliminate_implications(A >> (~B << C))
488 ((~B | ~C) | ~A)
489 """
490 if not s.args or is_symbol(s.op): return s
491 args = map(eliminate_implications, s.args)
492 a, b = args[0], args[-1]
493 if s.op == '>>':
494 return (b | ~a)
495 elif s.op == '<<':
496 return (a | ~b)
497 elif s.op == '<=>':
498 return (a | ~b) & (b | ~a)
499 else:
500 return Expr(s.op, *args)
501
503 """Rewrite sentence s by moving negation sign inward.
504 >>> move_not_inwards(~(A | B))
505 (~A & ~B)
506 >>> move_not_inwards(~(A & B))
507 (~A | ~B)
508 >>> move_not_inwards(~(~(A | ~B) | ~~C))
509 ((A | ~B) & ~C)
510 """
511 if s.op == '~':
512 NOT = lambda b: move_not_inwards(~b)
513 a = s.args[0]
514 if a.op == '~': return move_not_inwards(a.args[0])
515 if a.op =='&': return NaryExpr('|', *map(NOT, a.args))
516 if a.op =='|': return NaryExpr('&', *map(NOT, a.args))
517 return s
518 elif is_symbol(s.op) or not s.args:
519 return s
520 else:
521 return Expr(s.op, *map(move_not_inwards, s.args))
522
524 """Given a sentence s consisting of conjunctions and disjunctions
525 of literals, return an equivalent sentence in CNF.
526 >>> distribute_and_over_or((A & B) | C)
527 ((A | C) & (B | C))
528 """
529 if s.op == '|':
530 s = NaryExpr('|', *s.args)
531 if len(s.args) == 0:
532 return FALSE
533 if len(s.args) == 1:
534 return distribute_and_over_or(s.args[0])
535 conj = find_if((lambda d: d.op == '&'), s.args)
536 if not conj:
537 return NaryExpr(s.op, *s.args)
538 others = [a for a in s.args if a is not conj]
539 if len(others) == 1:
540 rest = others[0]
541 else:
542 rest = NaryExpr('|', *others)
543 return NaryExpr('&', *map(distribute_and_over_or,
544 [(c|rest) for c in conj.args]))
545 elif s.op == '&':
546 return NaryExpr('&', *map(distribute_and_over_or, s.args))
547 else:
548 return s
549
550 _NaryExprTable = {'&':TRUE, '|':FALSE, '+':ZERO, '*':ONE}
551
553 """Create an Expr, but with an nary, associative op, so we can promote
554 nested instances of the same op up to the top level.
555 >>> NaryExpr('&', (A&B),(B|C),(B&C))
556 (A & B & (B | C) & B & C)
557 """
558 arglist = []
559 for arg in args:
560 if arg.op == op: arglist.extend(arg.args)
561 else: arglist.append(arg)
562 if len(args) == 1:
563 return args[0]
564 elif len(args) == 0:
565 return _NaryExprTable[op]
566 else:
567 return Expr(op, *arglist)
568
570 """Return a list of the conjuncts in the sentence s.
571 >>> conjuncts(A & B)
572 [A, B]
573 >>> conjuncts(A | B)
574 [(A | B)]
575 """
576 if isinstance(s, Expr) and s.op == '&':
577 return s.args
578 else:
579 return [s]
580
582 """Return a list of the disjuncts in the sentence s.
583 >>> disjuncts(A | B)
584 [A, B]
585 >>> disjuncts(A & B)
586 [(A & B)]
587 """
588 if isinstance(s, Expr) and s.op == '|':
589 return s.args
590 else:
591 return [s]
592
593
594
596 "Propositional Logic Resolution: say if alpha follows from KB. [Fig. 7.12]"
597 clauses = KB.clauses + conjuncts(to_cnf(~alpha))
598 new = set()
599 while True:
600 n = len(clauses)
601 pairs = [(clauses[i], clauses[j])
602 for i in range(n) for j in range(i+1, n)]
603 for (ci, cj) in pairs:
604 resolvents = pl_resolve(ci, cj)
605 if FALSE in resolvents: return True
606 new = new.union(set(resolvents))
607 if new.issubset(set(clauses)): return False
608 for c in new:
609 if c not in clauses: clauses.append(c)
610
612 """Return all clauses that can be obtained by resolving clauses ci and cj.
613 >>> for res in pl_resolve(to_cnf(A|B|C), to_cnf(~B|~C|F)):
614 ... ppset(disjuncts(res))
615 set([A, C, F, ~C])
616 set([A, B, F, ~B])
617 """
618 clauses = []
619 for di in disjuncts(ci):
620 for dj in disjuncts(cj):
621 if di == ~dj or ~di == dj:
622 dnew = unique(removeall(di, disjuncts(ci)) +
623 removeall(dj, disjuncts(cj)))
624 clauses.append(NaryExpr('|', *dnew))
625 return clauses
626
627
628
630 "A KB of Propositional Horn clauses."
631
632 - def tell(self, sentence):
633 "Add a Horn Clauses to this KB."
634 op = sentence.op
635 assert op == '>>' or is_prop_symbol(op), "Must be Horn Clause"
636 self.clauses.append(sentence)
637
639 "Yield the empty substitution if KB implies query; else False"
640 if not pl_fc_entails(self.clauses, query):
641 return
642 yield {}
643
645 "Remove the sentence's clauses from the KB"
646 for c in conjuncts(to_cnf(sentence)):
647 if c in self.clauses:
648 self.clauses.remove(c)
649
651 """The list of clauses in KB that have p in the premise.
652 This could be cached away for O(1) speed, but we'll recompute it."""
653 return [c for c in self.clauses
654 if c.op == '>>' and p in conjuncts(c.args[0])]
655
657 """Use forward chaining to see if a HornKB entails symbol q. [Fig. 7.14]
658 >>> pl_fc_entails(Fig[7,15], expr('Q'))
659 True
660 """
661 count = dict([(c, len(conjuncts(c.args[0]))) for c in KB.clauses
662 if c.op == '>>'])
663 inferred = DefaultDict(False)
664 agenda = [s for s in KB.clauses if is_prop_symbol(s.op)]
665 if q in agenda: return True
666 while agenda:
667 p = agenda.pop()
668 if not inferred[p]:
669 inferred[p] = True
670 for c in KB.clauses_with_premise(p):
671 count[c] -= 1
672 if count[c] == 0:
673 if c.args[1] == q: return True
674 agenda.append(c.args[1])
675 return False
676
677
678
679
680
681
682
683
684
685
686
687
688
690 """Check satisfiability of a propositional sentence.
691 This differs from the book code in two ways: (1) it returns a model
692 rather than True when it succeeds; this is more useful. (2) The
693 function find_pure_symbol is passed a list of unknown clauses, rather
694 than a list of all clauses and the model; this is more efficient.
695 >>> ppsubst(dpll_satisfiable(A&~B))
696 {A: True, B: False}
697 >>> dpll_satisfiable(P&~P)
698 False
699 """
700 clauses = conjuncts(to_cnf(s))
701 symbols = prop_symbols(s)
702 return dpll(clauses, symbols, {})
703
704 -def dpll(clauses, symbols, model):
705 "See if the clauses are true in a partial model."
706 unknown_clauses = []
707 for c in clauses:
708 val = pl_true(c, model)
709 if val == False:
710 return False
711 if val != True:
712 unknown_clauses.append(c)
713 if not unknown_clauses:
714 return model
715 P, value = find_pure_symbol(symbols, unknown_clauses)
716 if P:
717 return dpll(clauses, removeall(P, symbols), extend(model, P, value))
718 P, value = find_unit_clause(clauses, model)
719 if P:
720 return dpll(clauses, removeall(P, symbols), extend(model, P, value))
721 P = symbols.pop()
722 return (dpll(clauses, symbols, extend(model, P, True)) or
723 dpll(clauses, symbols, extend(model, P, False)))
724
726 """Find a symbol and its value if it appears only as a positive literal
727 (or only as a negative) in clauses.
728 >>> find_pure_symbol([A, B, C], [A|~B,~B|~C,C|A])
729 (A, True)
730 """
731 for s in symbols:
732 found_pos, found_neg = False, False
733 for c in unknown_clauses:
734 if not found_pos and s in disjuncts(c): found_pos = True
735 if not found_neg and ~s in disjuncts(c): found_neg = True
736 if found_pos != found_neg: return s, found_pos
737 return None, None
738
740 """A unit clause has only 1 variable that is not bound in the model.
741 >>> find_unit_clause([A|B|C, B|~C, A|~B], {A:True})
742 (B, False)
743 """
744 for clause in clauses:
745 num_not_in_model = 0
746 for literal in disjuncts(clause):
747 sym = literal_symbol(literal)
748 if sym not in model:
749 num_not_in_model += 1
750 P, value = sym, (literal.op != '~')
751 if num_not_in_model == 1:
752 return P, value
753 return None, None
754
755
757 """The symbol in this literal (without the negation).
758 >>> literal_symbol(P)
759 P
760 >>> literal_symbol(~P)
761 P
762 """
763 if literal.op == '~':
764 return literal.args[0]
765 else:
766 return literal
767
768
769
770
771
772 -def WalkSAT(clauses, p=0.5, max_flips=10000):
773
774
775 model = dict([(s, random.choice([True, False]))
776 for s in prop_symbols(clauses)])
777 for i in range(max_flips):
778 satisfied, unsatisfied = [], []
779 for clause in clauses:
780 if_(pl_true(clause, model), satisfied, unsatisfied).append(clause)
781 if not unsatisfied:
782 return model
783 clause = random.choice(unsatisfied)
784 if probability(p):
785 sym = random.choice(prop_symbols(clause))
786 else:
787
788 raise NotImplementedError
789 model[sym] = not model[sym]
790
791
792
793
794 """An agent for the wumpus world that does logical inference. [Fig. 7.19]"""
795 """def __init__(self):
796 KB = FOLKB() ## shouldn't this be a propositional KB? ***
797 x, y, orientation = 1, 1, (1, 0)
798 visited = set() ## squares already visited
799 action = None
800 plan = []
801
802 def program(percept):
803 stench, breeze, glitter = percept
804 x, y, orientation = update_position(x, y, orientation, action)
805 KB.tell('%sS_%d,%d' % (if_(stench, '', '~'), x, y))
806 KB.tell('%sB_%d,%d' % (if_(breeze, '', '~'), x, y))
807 if glitter: action = 'Grab'
808 elif plan: action = plan.pop()
809 else:
810 for [i, j] in fringe(visited):
811 if KB.ask('~P_%d,%d & ~W_%d,%d' % (i, j, i, j)) != False:
812 raise NotImplementedError
813 KB.ask('~P_%d,%d | ~W_%d,%d' % (i, j, i, j)) != False
814 if action == None:
815 action = random.choice(['Forward', 'Right', 'Left'])
816 return action
817
818 self.program = program"""
819
821 if action == 'TurnRight':
822 orientation = turn_right(orientation)
823 elif action == 'TurnLeft':
824 orientation = turn_left(orientation)
825 elif action == 'Forward':
826 x, y = x + vector_add((x, y), orientation)
827 return x, y, orientation
828
829
830
832 """Unify expressions x,y with substitution s; return a substitution that
833 would make x,y equal, or None if x,y can not unify. x and y can be
834 variables (e.g. Expr('x')), constants, lists, or Exprs. [Fig. 9.1]
835 >>> ppsubst(unify(x + y, y + C, {}))
836 {x: y, y: C}
837 """
838 if s == None:
839 return None
840 elif x == y:
841 return s
842 elif is_variable(x):
843 return unify_var(x, y, s)
844 elif is_variable(y):
845 return unify_var(y, x, s)
846 elif isinstance(x, Expr) and isinstance(y, Expr):
847 return unify(x.args, y.args, unify(x.op, y.op, s))
848 elif isinstance(x, str) or isinstance(y, str) or not x or not y:
849
850 return None
851 elif issequence(x) and issequence(y) and len(x) == len(y):
852
853 return unify(x[1:], y[1:], unify(x[0], y[0], s))
854 else:
855 return None
856
858 "A variable is an Expr with no args and a lowercase symbol as the op."
859 return isinstance(x, Expr) and not x.args and is_var_symbol(x.op)
860
862 if var in s:
863 return unify(s[var], x, s)
864 elif occur_check(var, x, s):
865 return None
866 else:
867 return extend(s, var, x)
868
870 """Return true if variable var occurs anywhere in x
871 (or in subst(s, x), if s has a binding for x)."""
872
873 if var == x:
874 return True
875 elif is_variable(x) and s.has_key(x):
876 return occur_check(var, s[x], s)
877
878 elif isinstance(x, Expr):
879
880 return (occur_check(var, x.op, s) or
881 occur_check(var, x.args, s))
882 elif isinstance(x, list) and x != []:
883
884 return (occur_check(var, x[0], s) or
885 occur_check(var, x[1:], s))
886 else:
887
888 return False
889
890
891
892
893
894
895
896
898 """Copy the substitution s and extend it by setting var to val;
899 return copy.
900
901 >>> ppsubst(extend({x: 1}, y, 2))
902 {x: 1, y: 2}
903 """
904 s2 = s.copy()
905 s2[var] = val
906 return s2
907
909 """Substitute the substitution s into the expression x.
910 >>> subst({x: 42, y:0}, F(x) + y)
911 (F(42) + 0)
912 """
913 if isinstance(x, list):
914 return [subst(s, xi) for xi in x]
915 elif isinstance(x, tuple):
916 return tuple([subst(s, xi) for xi in x])
917 elif not isinstance(x, Expr):
918 return x
919 elif is_var_symbol(x.op):
920 return s.get(x, x)
921 else:
922 return Expr(x.op, *[subst(s, arg) for arg in x.args])
923
925 """Inefficient forward chaining for first-order logic. [Fig. 9.3]
926 KB is an FOLHornKB and alpha must be an atomic sentence."""
927 while True:
928 new = {}
929 for r in KB.clauses:
930 r1 = standardize_apart(r)
931 ps, q = conjuncts(r.args[0]), r.args[1]
932 raise NotImplementedError
933
935 """Replace all the variables in sentence with new variables.
936 >>> e = expr('F(a, b, c) & G(c, A, 23)')
937 >>> len(variables(standardize_apart(e)))
938 3
939 >>> variables(e).intersection(variables(standardize_apart(e)))
940 set([])
941 >>> is_variable(standardize_apart(expr('x')))
942 True
943 """
944 if not isinstance(sentence, Expr):
945 return sentence
946 elif is_var_symbol(sentence.op):
947 if sentence in dic:
948 return dic[sentence]
949 else:
950 standardize_apart.counter += 1
951 v = Expr('v_%d' % standardize_apart.counter)
952 dic[sentence] = v
953 return v
954 else:
955 return Expr(sentence.op,
956 *[standardize_apart(a, dic) for a in sentence.args])
957
958 standardize_apart.counter = 0
959
960
961
962
964 """A knowledge base consisting of first-order definite clauses
965 >>> kb0 = FolKB([expr('Farmer(Mac)'), expr('Rabbit(Pete)'),
966 ... expr('(Rabbit(r) & Farmer(f)) ==> Hates(f, r)')])
967 >>> kb0.tell(expr('Rabbit(Flopsie)'))
968 >>> kb0.retract(expr('Rabbit(Pete)'))
969 >>> kb0.ask(expr('Hates(Mac, x)'))[x]
970 Flopsie
971 >>> kb0.ask(expr('Wife(Pete, x)'))
972 False
973 """
974
975 - def __init__ (self, initial_clauses=[]):
976 self.clauses = []
977 for clause in initial_clauses:
978 self.tell(clause)
979
980 - def tell(self, sentence):
981 if is_definite_clause(sentence):
982 self.clauses.append(sentence)
983 else:
984 raise Exception("Not a definite clause: %s" % sentence)
985
988
990 self.clauses.remove(sentence)
991
1001
1002 test_kb = FolKB(
1003 map(expr, ['Farmer(Mac)',
1004 'Rabbit(Pete)',
1005 'Mother(MrsMac, Mac)',
1006 'Mother(MrsRabbit, Pete)',
1007 '(Rabbit(r) & Farmer(f)) ==> Hates(f, r)',
1008 '(Mother(m, c)) ==> Loves(m, c)',
1009 '(Mother(m, r) & Rabbit(r)) ==> Rabbit(m)',
1010 '(Farmer(f)) ==> Human(f)',
1011
1012
1013
1014 '(Mother(m, h) & Human(h)) ==> Human(m)'
1015 ])
1016 )
1017
1018
1020 """A simple backward-chaining algorithm for first-order logic. [Fig. 9.6]
1021 KB should be an instance of FolKB, and goals a list of literals.
1022
1023 >>> test_ask('Farmer(x)')
1024 ['{x: Mac}']
1025 >>> test_ask('Human(x)')
1026 ['{x: Mac}', '{x: MrsMac}']
1027 >>> test_ask('Hates(x, y)')
1028 ['{x: Mac, y: Pete}']
1029 >>> test_ask('Loves(x, y)')
1030 ['{x: MrsMac, y: Mac}', '{x: MrsRabbit, y: Pete}']
1031 >>> test_ask('Rabbit(x)')
1032 ['{x: MrsRabbit}', '{x: Pete}']
1033 """
1034
1035 if goals == []:
1036 yield theta
1037 raise StopIteration()
1038
1039 q1 = subst(theta, goals[0])
1040
1041 for r in KB.clauses:
1042 sar = standardize_apart(r)
1043
1044
1045 if is_symbol(sar.op):
1046 head = sar
1047 body = []
1048 elif sar.op == '>>':
1049 head = sar.args[1]
1050 body = sar.args[0]
1051 else:
1052 raise Exception("Invalid clause in FolKB: %s" % r)
1053
1054 theta1 = unify(head, q1, {})
1055
1056 if theta1 is not None:
1057 if body == []:
1058 new_goals = goals[1:]
1059 else:
1060 new_goals = conjuncts(body) + goals[1:]
1061
1062 for ans in fol_bc_ask(KB, new_goals, subst_compose(theta1, theta)):
1063 yield ans
1064
1065 raise StopIteration()
1066
1068 """Return the substitution which is equivalent to applying s2 to
1069 the result of applying s1 to an expression.
1070
1071 >>> s1 = {x: A, y: B}
1072 >>> s2 = {z: x, x: C}
1073 >>> p = F(x) & G(y) & expr('H(z)')
1074 >>> subst(s1, p)
1075 ((F(A) & G(B)) & H(z))
1076 >>> subst(s2, p)
1077 ((F(C) & G(y)) & H(x))
1078
1079 >>> subst(s2, subst(s1, p))
1080 ((F(A) & G(B)) & H(x))
1081 >>> subst(subst_compose(s1, s2), p)
1082 ((F(A) & G(B)) & H(x))
1083
1084 >>> subst(s1, subst(s2, p))
1085 ((F(C) & G(B)) & H(A))
1086 >>> subst(subst_compose(s2, s1), p)
1087 ((F(C) & G(B)) & H(A))
1088 >>> ppsubst(subst_compose(s1, s2))
1089 {x: A, y: B, z: x}
1090 >>> ppsubst(subst_compose(s2, s1))
1091 {x: C, y: B, z: A}
1092 >>> subst(subst_compose(s1, s2), p) == subst(s2, subst(s1, p))
1093 True
1094 >>> subst(subst_compose(s2, s1), p) == subst(s1, subst(s2, p))
1095 True
1096 """
1097 sc = {}
1098 for x, v in s1.items():
1099 if s2.has_key(v):
1100 w = s2[v]
1101 sc[x] = w
1102 else:
1103 sc[x] = v
1104 for x, v in s2.items():
1105 if not (s1.has_key(x)):
1106 sc[x] = v
1107
1108 return sc
1109
1110
1111
1112
1113
1114
1115
1117 """Return the symbolic derivative, dy/dx, as an Expr.
1118 However, you probably want to simplify the results with simp.
1119 >>> diff(x * x, x)
1120 ((x * 1) + (x * 1))
1121 >>> simp(diff(x * x, x))
1122 (2 * x)
1123 """
1124 if y == x: return ONE
1125 elif not y.args: return ZERO
1126 else:
1127 u, op, v = y.args[0], y.op, y.args[-1]
1128 if op == '+': return diff(u, x) + diff(v, x)
1129 elif op == '-' and len(args) == 1: return -diff(u, x)
1130 elif op == '-': return diff(u, x) - diff(v, x)
1131 elif op == '*': return u * diff(v, x) + v * diff(u, x)
1132 elif op == '/': return (v*diff(u, x) - u*diff(v, x)) / (v * v)
1133 elif op == '**' and isnumber(x.op):
1134 return (v * u ** (v - 1) * diff(u, x))
1135 elif op == '**': return (v * u ** (v - 1) * diff(u, x)
1136 + u ** v * Expr('log')(u) * diff(v, x))
1137 elif op == 'log': return diff(u, x) / u
1138 else: raise ValueError("Unknown op: %s in diff(%s, %s)" % (op, y, x))
1139
1141 if not x.args: return x
1142 args = map(simp, x.args)
1143 u, op, v = args[0], x.op, args[-1]
1144 if op == '+':
1145 if v == ZERO: return u
1146 if u == ZERO: return v
1147 if u == v: return TWO * u
1148 if u == -v or v == -u: return ZERO
1149 elif op == '-' and len(args) == 1:
1150 if u.op == '-' and len(u.args) == 1: return u.args[0]
1151 elif op == '-':
1152 if v == ZERO: return u
1153 if u == ZERO: return -v
1154 if u == v: return ZERO
1155 if u == -v or v == -u: return ZERO
1156 elif op == '*':
1157 if u == ZERO or v == ZERO: return ZERO
1158 if u == ONE: return v
1159 if v == ONE: return u
1160 if u == v: return u ** 2
1161 elif op == '/':
1162 if u == ZERO: return ZERO
1163 if v == ZERO: return Expr('Undefined')
1164 if u == v: return ONE
1165 if u == -v or v == -u: return ZERO
1166 elif op == '**':
1167 if u == ZERO: return ZERO
1168 if v == ZERO: return ONE
1169 if u == ONE: return ONE
1170 if v == ONE: return u
1171 elif op == 'log':
1172 if u == ONE: return ZERO
1173 else: raise ValueError("Unknown op: " + op)
1174
1175 return Expr(op, *args)
1176
1178 "Differentiate and then simplify."
1179 return simp(diff(y, x))
1180
1181
1182
1183
1184
1185
1186
1193
1195 """Print the dictionary d.
1196
1197 Prints a string representation of the dictionary
1198 with keys in sorted order according to their string
1199 representation: {a: A, d: D, ...}.
1200 >>> pretty_dict({'m': 'M', 'a': 'A', 'r': 'R', 'k': 'K'})
1201 "{'a': 'A', 'k': 'K', 'm': 'M', 'r': 'R'}"
1202 >>> pretty_dict({z: C, y: B, x: A})
1203 '{x: A, y: B, z: C}'
1204 """
1205
1206 def format(k, v):
1207 return "%s: %s" % (repr(k), repr(v))
1208
1209 ditems = d.items()
1210 ditems.sort(key=str)
1211 k, v = ditems[0]
1212 dpairs = format(k, v)
1213 for (k, v) in ditems[1:]:
1214 dpairs += (', ' + format(k, v))
1215 return '{%s}' % dpairs
1216
1218 """Print the set s.
1219
1220 >>> pretty_set(set(['A', 'Q', 'F', 'K', 'Y', 'B']))
1221 "set(['A', 'B', 'F', 'K', 'Q', 'Y'])"
1222 >>> pretty_set(set([z, y, x]))
1223 'set([x, y, z])'
1224 """
1225
1226 slist = list(s)
1227 slist.sort(key=str)
1228 return 'set(%s)' % slist
1229
1232
1234 """Pretty-print substitution s"""
1235 ppdict(s)
1236
1239
1242
1243
1244
1245 -class logicTest: """
1246 ### PropKB
1247 >>> kb = PropKB()
1248 >>> kb.tell(A & B)
1249 >>> kb.tell(B >> C)
1250 >>> kb.ask(C) ## The result {} means true, with no substitutions
1251 {}
1252 >>> kb.ask(P)
1253 False
1254 >>> kb.retract(B)
1255 >>> kb.ask(C)
1256 False
1257
1258 >>> pl_true(P, {})
1259 >>> pl_true(P | Q, {P: True})
1260 True
1261
1262 # Notice that the function pl_true cannot reason by cases:
1263 >>> pl_true(P | ~P)
1264
1265 # However, tt_true can:
1266 >>> tt_true(P | ~P)
1267 True
1268
1269 # The following are tautologies from [Fig. 7.11]:
1270 >>> tt_true("(A & B) <=> (B & A)")
1271 True
1272 >>> tt_true("(A | B) <=> (B | A)")
1273 True
1274 >>> tt_true("((A & B) & C) <=> (A & (B & C))")
1275 True
1276 >>> tt_true("((A | B) | C) <=> (A | (B | C))")
1277 True
1278 >>> tt_true("~~A <=> A")
1279 True
1280 >>> tt_true("(A >> B) <=> (~B >> ~A)")
1281 True
1282 >>> tt_true("(A >> B) <=> (~A | B)")
1283 True
1284 >>> tt_true("(A <=> B) <=> ((A >> B) & (B >> A))")
1285 True
1286 >>> tt_true("~(A & B) <=> (~A | ~B)")
1287 True
1288 >>> tt_true("~(A | B) <=> (~A & ~B)")
1289 True
1290 >>> tt_true("(A & (B | C)) <=> ((A & B) | (A & C))")
1291 True
1292 >>> tt_true("(A | (B & C)) <=> ((A | B) & (A | C))")
1293 True
1294
1295 # The following are not tautologies:
1296 >>> tt_true(A & ~A)
1297 False
1298 >>> tt_true(A & B)
1299 False
1300
1301 ### [Fig. 7.13]
1302 >>> alpha = expr("~P12")
1303 >>> to_cnf(Fig[7,13] & ~alpha)
1304 ((~P12 | B11) & (~P21 | B11) & (P12 | P21 | ~B11) & ~B11 & P12)
1305 >>> tt_entails(Fig[7,13], alpha)
1306 True
1307 >>> pl_resolution(PropKB(Fig[7,13]), alpha)
1308 True
1309
1310 ### [Fig. 7.15]
1311 >>> pl_fc_entails(Fig[7,15], expr('SomethingSilly'))
1312 False
1313
1314 ### Unification:
1315 >>> unify(x, x, {})
1316 {}
1317 >>> unify(x, 3, {})
1318 {x: 3}
1319
1320
1321 >>> to_cnf((P&Q) | (~P & ~Q))
1322 ((~P | P) & (~Q | P) & (~P | Q) & (~Q | Q))
1323 """
1324