logic Module (logic.py)


"""Representations and Inference for Logic (Chapters 7,9)

Covers both Propositional and First-Order Logic. First we provide the
class Expr, which represents a logical expression. We use operator
overloading so that we don't have to write a parser for expressions.
The function truth evaluates an expression in a model, and tt_entails,
and other functions do inference."""

import re, string
from utils import *


class KB: """A Knowledge base to which you can tell and ask sentences. This is the abstract interface which must be subclassed.""" def tell(self, sentence): abstract() def ask(self, sentence): abstract()
class Expr: """This class represents a mathematical expression. We use it for logical expressions, and for terms within logical expressions. In general, an Expr has an op (a string, like '~', '+' or 'F') and a list of args. Unary operators have 1 arg, constants have 0 args. Exprs are designed to be constructed with operator overloading, so that if x and y are Exprs, then so are x + y and x | y, etc. BUT BE CAREFUL: x == y is an Expr too, so you must use x is y or x.equals(y) to do comparison. Note this means that Exprs cannot be the keys of dicts. If x, y are Exprs, then so are: x & y, x | y, ~x # Logical AND, OR, NOT x >> y, x << y # Logical implication x == y, x <> y # Equivalence, disequivalence x + y, x / y, x ^ y, x - y , x * y # Arithmetic expressions -x, x ** y, x % y # More arithmetic x < y, x > y, x >= y, x <= y, # Comparisons You can also use the function expr, which parses strings to Exprs. All the above work, and in addition you get three alternative spellings: x ==> y means x >> y # Implication x <== y means x << y # Reverse implication x <=> y means x == y # Equivalence """ def __init__(self, op, *args): self.op = op; self.args = map(K, args) def __call__(self, *args): assert len(self.args) == 0 return Expr(self.op, *args) def __repr__(self): "Show something like 'P' or 'P(x, y)', or '~P' or '(P | Q | R)'" if len(self.args) == 0: # Constant or proposition with arity 0 return str(self.op) elif is_prop_symbol(self.op[0]): # Functional or Propositional operator return '%s(%s)' % (self.op, ', '.join(map(repr, self.args))) elif len(self.args) == 1: # Prefix operator return self.op + repr(self.args[0]) else: # Infix operator return '(%s)' % (' '+self.op+' ').join(map(repr, self.args)) def equals(self, other): """x.equals(y) iff their op and args are equal. NOTE: if x,y are Exprs, (x == y) is an Expr; not a boolean.""" return (isinstance(other, Expr) and self.op == other.op and self.args == other.args) # See http://www.python.org/doc/current/lib/module-operator.html # Not implemented: not, abs, pos, concat, contains, *item, *slice def __lt__(self, other): return Expr('<', self, other) def __le__(self, other): return Expr('<=', self, other) def __eq__(self, other): return Expr('==', self, other) def __ne__(self, other): return Expr('<>', self, other) def __ge__(self, other): return Expr('>=', self, other) def __gt__(self, other): return Expr('>', self, other) def __add__(self, other): return Expr('+', self, other) def __and__(self, other): return Expr('&', self, other) def __div__(self, other): return Expr('/', self, other) def __invert__(self): return Expr('~', self) def __lshift__(self, other): return Expr('<<', self, other) def __mod__(self, other): return Expr('%', self, other) def __mul__(self, other): return Expr('*', self, other) def __neg__(self, other): return Expr('-', self) def __or__(self, other): return Expr('|', self, other) def __pow__(self, other): return Expr('**', self, other) def __rshift__(self, other): return Expr('>>', self, other) def __sub__(self, other): return Expr('-', self, other) def __xor__(self, other): return Expr('^', self, other) def K(x): """Return a constant Expr. K(1) returns the constant Expr for 1. The difference is that 1 + 1 is the int 2, while K(1) + K(1) is an Expr.""" if isinstance(x, Expr): return x else: return Expr(x) TRUE, FALSE = K('TRUE'), K('FALSE') #Do not confuse these with True, False def is_var_symbol(x): "A logic variable symbol is an Expr with no args and a lowercase name." return isinstance(x, Expr) and len(x.args) == 0 and x.op[0].islower() def is_prop_symbol(x): """A proposition logic symbol is an Expr with no args and an uppercase name that is not TRUE or FALSE.""" return (isinstance(x, Expr) and len(x.args) == 0 and x.op[0].isupper() and x is not TRUE and x is not FALSE) def expr(s): """Create an Expr representing a logic expression by parsing the input string. TRUE, FALSE are constants. You may use ==>, <==, <=> instead of >>, <<, <>, but be careful; precedence is still wrong. Ex: expr('P & Q | ~R(x, F(x))')""" localvars, constants = {}, {'TRUE': TRUE, 'FALSE': FALSE, 'K': Expr} for name in re.findall('([a-zA-Z][a-zA-Z0-9_]*)', s): localvars[name] = constants.get(name.upper(), Expr(name)) s = s.replace('==>', '>>').replace('<==', '<<').replace('<=>', '==') return eval(s, globals(), localvars)
def tt_entails(kb, alpha): """Use truth tables to determine if KB entails sentence alpha. [Fig. 7.10] Ex: tt_entails(expr('P & Q'), expr('Q')) ==> True""" def prop_symbols(x): if is_prop_symbol(x): return [x.op] elif isinstance(x, Expr): s = set(()) for arg in x.args: s.union_update(prop_symbols(arg)) return list(s) else: return [] return tt_check_all(kb, alpha, prop_symbols(kb & alpha), {}) def tt_check_all(kb, alpha, symbols, model): "Auxiliary routine to implement tt_entails." if not symbols: if pl_true(kb, model): return pl_true(alpha, model) else: return True assert result != None else: P, rest = symbols[0], symbols[1:] return (tt_check_all(kb, alpha, rest, extend(model, P, True)) and tt_check_all(kb, alpha, rest, extend(model, P, False))) def tt_true(alpha): "Is the sentence alpha a tautology?" return tt_entails(TRUE, alpha) def pl_true(exp, model={}): """Return True if the propositional logic expression is true in the model, and False if it is false. If the model does not specify the value for every proposition, this may return None to indicate 'not obvious'; this may happen even when the expression is tautological.""" op, args = exp.op, exp.args if exp is TRUE: return True elif exp is FALSE: return False elif is_prop_symbol(exp): return model.get(op) elif op == '~': p = pl_true(args[0], model) if p == None: return None else: return not p elif op == '|': result = False for arg in args: p = pl_true(arg, model) if p == True: return True if p == None: result = None return result elif op == '&': result = True for arg in args: p = pl_true(arg, model) if p == False: return False if p == None: result = None return result p, q = args if op == '>>': return pl_true(~p | q, model) elif op == '<<': return pl_true(p | ~q, model) pt = pl_true(p, model) if pt == None: return None qt = pl_true(q, model) if qt == None: return None if op == '==': return pt == qt elif op == '<>': return pt != qt else: raise ValueError, "illegal operator in logic expression" + str(exp)
# PL-Resolution [Fig. 7.2] # PL-FC-Entails [Fig. 7.14]
# DPLL-Satisfiable [Fig. 7.16] def dpll_satisfiable(s): clauses = to_cnf(s).args symbols = s.symbols() return dpll(clauses, symbols, {}) def dpll(clauses, symbols, model): all_true = True for c in clauses: val = pl_true(c, model) if val == False: return False if val != True: all_true = False if all_true: return True P, value = find_pure_symbol(symbols, clauses, model) if P: return dpll(clauses, removeall(P, symbols), extend(model, P, value)) P, value = find_unit_clause(clauses, model) if P: return dpll(clauses, removeall(P, symbols), extend(model, P, value)) P = symbols.pop() return (dpll(clauses, symbols, extend(model, P, True)) or dpll(clauses, symbols, extend(model, P, False))) def find_pure_symbol(symbols, clauses, model): for c in clauses: if False: #??? if c.op == '~': return c.arg[0], False else: return c, True return None, None def to_cnf(p, vars=()): p = eliminate_implications(p) if p.op == '~': p2 = move_not_inwards(p.arg[0]) if is_literal(p2): return p2 else: return to_cnf(p2, vars) elif p.op == '&': pass # Walk-SAT [Fig. 7.17] # PL-Wumpus-Agent [Fig. 7.19] def unify(x, y, s): """[Fig. 9.1]""" print 'unify', x, y, s, type(x), type(y) raw_input('ok?') if s == None: return None elif is_var_symbol(x): return unify_var(x, y, s) elif is_var_symbol(y): return unify_var(y, x, s) elif isinstance(x, Expr) and isinstance(y, Expr): return unify(x.args, y.args, unify(x.op, y.op, s)) elif issequence(x) and issequence(y) and len(x) == len(y): if len(x) == 0 or isinstance(x, str): return if_(x == y, s, None) else: return unify(x[1:], y[1:], unify(x[0], y[0], s)) elif not isinstance(x, Expr) and x == y: return s else: return None def unify_var(var, x, s): if var.op in s: return unify(s[var.op], x, s) elif occur_check(var, x): return None else: return extend(s, var, x) def occur_check(var, x): "Return true if var occurs anywhere in x." if is_var_symbol(x): return var.op == x.op elif isinstance(var, Expr): return occur_check(var, x.args) elif issequence(x): return some(lambda xi: occur_check(var, xi), x) else: return False def extend(dict, var, val): "Copy dict and then extend it by setting var to val; return the copy." dict2 = dict.copy() dict2[var] = val return dict2 # FOL-FC-Ask [Fig. 9.3] # FOL-BC-Ask [Fig. 9.6] # Otter (??) [Fig. 9.14] _docex = """# More tests for Logic. A, B, C, P, Q = map(Expr, 'ABCPQ') pl_true(P, {}) ==> None pl_true(P | Q, {'P': True}) ==> True # Notice that the function pl_true cannot reason by cases: pl_true(P | ~P) ==> None # However, tt_entails (or equivalently, tt_true) can: tt_entails(TRUE, P | ~P) ==> True tt_true(P | ~P) ==> True # The following are tautologies from [Fig. 7.11]: tt_true(A & B == B & A) ==> True tt_true(A | B == B | A) ==> True tt_true(((A & B) & C) == (A & (B & C))) ==> True tt_true(((A | B) | C) == (A | (B | C))) ==> True tt_true(~~A == A) ==> True tt_true((A >> B) == (~B >> ~A)) ==> True tt_true((A >> B) == (~A | B)) ==> True tt_true((A == B) == ((A >> B) & (B >> A))) ==> True tt_true(~(A & B) == (~A | ~B)) ==> True tt_true(~(A | B) == (~A & ~B)) ==> True tt_true((A & (B | C)) == ((A & B) | (A & C))) ==> True tt_true((A | (B & C)) == ((A | B) & (A | C))) ==> True """



# Copyright: Peter Norvig, 2002.
# AIMA: Python Code, Example Output.
# Python.org: Tutorial, Language Ref, Libraries.