from utils4e import *
x = expr('A | B')
x
(A | B)
type(x)
utils4e.Expr
x.op
'|'
x.args
(A, B)
w = expr('A <== B')
y = expr('A ==> B')
z = expr('A <=> B')
w, y, z
((A <== B), (A ==> B), (A <=> B))
x1 = expr('(A | B) ==> ~(C & D)')
x1
((A | B) ==> ~(C & D))
Expr is a recursive data structure.
x1.op
'==>'
x1.args
((A | B), ~(C & D))
type(x1.args[0])
utils4e.Expr
x1.args[0].op
'|'
expr can also be used for arithmetic expressions¶
[expr('a + b ** c / d'), expr('x - sin(y) * e**x')]
[(a + ((b ** c) / d)), (x - (sin(y) * (e ** x)))]
This is useful if you are implementing computer algebra systems that perform symbolic mathematics.
PropKB and logic.py¶
We are going to focus on expr objects that represent propositional
from logic import *
We will create an instance of knowledge base for propositional logic.
kb = PropKB()
We add the above propositions to the knowledge base, as clauses.
kb.tell(x)
kb.tell(y)
kb.tell(z)
kb.tell(w)
kb.tell(x1)
kb.clauses
[x, y, z, (A | ~B), (~A | ~C | ~D), (~B | ~C | ~D)]
Note that the various ==>, <==, and <=> expressions were converted to disjunctive forms, using the or operator: |. That is to make it easier to perform inference.
Rules of inference: Modus Ponens¶
ikb = PropKB()
ikb.tell('A ==> B')
ikb.tell('A')
ikb.clauses
[(B | ~A), A]
ikb.ask(expr('B'))
{}
kb.tell() adds a clause to the KB. kb.ask() queries the KB to see if the given clause can be deduced. If the deduction is valid, ask() returns the binding list required to make it true. An empty dictionary, {}, indicates (somewhat obscurely) that no substitution is required. We can avoid this possible confusion by using ask_if_true()
ikb.ask_if_true(expr('B'))
True
ikb.ask(expr('C'))
False
ikb.ask_if_true(expr('C'))
False
ikb.clauses
[(B | ~A), A]
Note that even though ikb deduced that 'B' is valid, it did not add it to the knowledge base.
ikb.tell('B ==> C')
ikb.clauses
[(B | ~A), A, (C | ~B)]
ikb.ask(expr('C'))
{}
ikb.ask_if_true(expr('C'))
True
And elimination¶
Fixed errors in expr: changed ^ to &
ikb.tell(expr('D & E'))
ikb.clauses
[(B | ~A), A, (C | ~B), D, E]
ikb.ask(expr('E'))
{}
It looks like and elimination works now. Let's try and introduction. We first retract the D & E clause.
ikb.retract(expr('D & E'))
ikb.clauses
[(B | ~A), A, (C | ~B)]
ikb.tell(expr('C'))
ikb.tell(expr('D'))
ikb.clauses
[(B | ~A), A, (C | ~B), C, D]
ikb.ask(expr('C & D'))
{}
Good Now we try or introduction.
ikb.ask_if_true(expr('C | B'))
True
ikb.ask(expr('D | E'))
{}
Eureka! That works! Now we try doublenegation elimination.
ikb.clauses
[(B | ~A), A, (C | ~B), C, D]
ikb.tell(expr('~~E'))
ikb.clauses
[(B | ~A), A, (C | ~B), C, D, E]
It looks like that happened automagically!
Unit Resolution¶
ikb.clauses
[(B | ~A), A, (C | ~B), C, D, E]
ikb.tell(expr('F | G'))
ikb.clauses
[(B | ~A), A, (C | ~B), C, D, E, (F | G)]
ikb.tell(expr('~G'))
ikb.ask(expr('F'))
{}
Great! It works!
Resolution¶
ikb.clauses
[(B | ~A), A, (C | ~B), C, D, E, (F | G), ~G]
ikb.tell(expr('H | I'))
ikb.tell(expr('~I | J'))
ikb.clauses
[(B | ~A), A, (C | ~B), C, D, E, (F | G), ~G, (H | I), (~I | J)]
ikb.ask(expr('H | J'))
{}
Success! This is how resolution works!
Implication is transitive¶
ikb.tell('K ==> L')
ikb.tell('L ==> M')
ikb.clauses
[(B | ~A), A, (C | ~B), C, D, E, (F | G), ~G, (H | I), (~I | J), (L | ~K), (M | ~L)]
ikb.ask(expr('K ==> M'))
{}
This works!
from logic import *
## must start with letter
is_symbol('R2D2')
True
is_symbol('123go')
False
is_symbol('x25')
True
## must be lower case
is_var_symbol('R2D2')
False
is_var_symbol('x25')
True
## must be upper case
is_prop_symbol('R2D2')
True
is_prop_symbol('x25')
False
variables(expr('F(x, x) & G(x, y) & H(y, z) & R(A, z, 2)'))
{x, y, z}
is_definite_clause(expr('Farmer(Mac)'))
True
parse_definite_clause(expr('Farmer(Mac)'))
([], Farmer(Mac))
tt_entails(expr('P & Q'), expr('Q'))
True
tt_entails(expr('P & Q'), expr('R'))
False
## calls tt_check_all
tt_entails(expr('P | Q'), expr('Q'))
False
prop_symbols(expr('Farmer(Mac)'))
{Farmer(Mac)}
prop_symbols(expr('P | Q'))
{P, Q}
prop_symbols(expr('x | y'))
set()
constant_symbols(expr('A | B'))
{A, B}
constant_symbols(expr('x & y'))
set()
constant_symbols(expr('Farmer(Mac)'))
{Mac}
## check for tautology
tt_true('P | ~P')
True
# is propositional logic statement in given model (environment)
pl_true(P, {})
pl_true(P, {}) is None
True
pl_true(P, {P: True})
True
pl_true(P, {P: False})
False
from notebook4e import psource
## check out definition of pl_true
psource(pl_true)
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.
>>> pl_true(P, {}) is None
True
"""
if exp in (True, False):
return exp
op, args = exp.op, exp.args
if is_prop_symbol(op):
return model.get(exp)
elif op == '~':
p = pl_true(args[0], model)
if p is None:
return None
else:
return not p
elif op == '|':
result = False
for arg in args:
p = pl_true(arg, model)
if p is True:
return True
if p is None:
result = None
return result
elif op == '&':
result = True
for arg in args:
p = pl_true(arg, model)
if p is False:
return False
if p is 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 is None:
return None
qt = pl_true(q, model)
if qt is None:
return None
if op == '<=>':
return pt == qt
elif op == '^': # xor or 'not equivalent'
return pt != qt
else:
raise ValueError("illegal operator in logic expression" + str(exp))
to_cnf()¶
Convert an expression to conjunctive normal form (CNF)
to_cnf(expr('A ==> B'))
(B | ~A)
to_cnf(expr('A <== B'))
(A | ~B)
to_cnf(expr('A <=> B'))
((A | ~B) & (B | ~A))
to_cnf(expr('A ^ B'))
((~A | A) & (B | A) & (~A | ~B) & (B | ~B))
## called by to_cnf()
eliminate_implications(expr('A ==> B'))
(B | ~A)
## called by to_cnf()
move_not_inwards(~(A | B))
(~A & ~B)
## called by to_cnf()
distribute_and_over_or((A & B) | C)
((A | C) & (B | C))
join or flatten with given op
associate('&', [(A&B),(B|C),(B&C)])
(A & B & (B | C) & B & C)
associate('|', [A|(B|(C|(A&B)))])
(A | B | C | (A & B))
Given an associative op, return a flattened list result
dissociate('&', [A & B])
[A, B]
Return a list of the conjuncts in the sentence s.
conjuncts(A & B)
[A, B]
conjuncts(A | B)
[(A | B)]
Return a list of the disjuncts in the sentence
disjuncts(A | B)
[A, B]
disjuncts(A & B)
[(A & B)]
Propositional-logic resolution: say if alpha follows from KB.
horn_clauses_KB.clauses
[(P ==> Q), ((L & M) ==> P), ((B & L) ==> M), ((A & P) ==> L), ((A & B) ==> L), A, B]
pl_resolution(horn_clauses_KB, A)
True
class PropDefiniteKB(PropKB):¶
## Use forward chaining to see if a PropDefiniteKB entails symbol q.
pl_fc_entails(horn_clauses_KB, expr('Q'))
True
DPLL-Satisfiable¶
dpll_satisfiable(A |'<=>'| B)
{B: True, A: True}
Walk-SAT [Figure 7.18]¶
WalkSAT([A & ~A], 0.5, 100)
WalkSAT([A & ~A], 0.5, 100) is None
True
WalkSAT([A | ~A], 0.5, 100)
{A: True}
unify(x, 3, {})
{x: 3}
unify(x, y, {})
{x: y}
unify(expr('x & y'), z, {})
{z: (x & y)}
unify(z, expr('x & y'), {})
{z: (x & y)}
FolKB(KB) first-order logic knowledge base¶
kb0 = FolKB([expr('Farmer(Mac)'), expr('Rabbit(Pete)'),
expr('(Rabbit(r) & Farmer(f)) ==> Hates(f, r)')])
kb0.tell(expr('Rabbit(Flopsie)'))
kb0.clauses
[Farmer(Mac), Rabbit(Pete), ((Rabbit(r) & Farmer(f)) ==> Hates(f, r)), Rabbit(Flopsie)]
kb0.retract(expr('Rabbit(Pete)'))
kb0.clauses
[Farmer(Mac), ((Rabbit(r) & Farmer(f)) ==> Hates(f, r)), Rabbit(Flopsie)]
kb0.ask(expr('Hates(Mac, x)'))[x]
Flopsie
kb0.ask(expr('Wife(Pete, x)'))
False
## differentiate
diff(x * x, x)
((x * 1) + (x * 1))
## simplify
simp(diff(x * x, x))
(2 * x)
## differentiate and then simplify
d(x * x, x)
(2 * x)
d(x * x - x, x)
((2 * x) - 1)
d(x * x * x - x, x)
(((x ** 2) + (x * (2 * x))) - 1)
simp(d(x * x * x - x, x))
(((x ** 2) + (x * (2 * x))) - 1)
should be 3 * x * x - 1