1
2
3
4
5
6
7
8
9 from nltk.sem.logic import *
10 from nltk.internals import Counter
11 from nltk.inference.api import Prover, ProverCommand
12 from nltk import defaultdict
13
14 """
15 Module for a resolution-based First Order theorem prover.
16 """
17
18 _skolem_function_counter = Counter()
19
21
23 ANSWER_KEY = 'ANSWER'
24 _assume_false=True
25
26 - def prove(self, goal=None, assumptions=[], verbose=False):
27 """
28 @param goal: Input expression to prove
29 @type goal: L{logic.Expression}
30 @param assumptions: Input expressions to use as assumptions in the proof
31 @type assumptions: L{list} of logic.Expression objects
32 """
33 result = None
34 try:
35 clauses = []
36 if goal:
37 clauses.extend(clausify(-goal))
38 for a in assumptions:
39 clauses.extend(clausify(a))
40 result, proof = self._attempt_proof(clauses)
41 except RuntimeError, e:
42 if self._assume_false and str(e).startswith('maximum recursion depth exceeded'):
43 result = False
44 proof = ''
45 else:
46 if verbose:
47 print e
48 else:
49 raise e
50 return (result, proof)
51
53
54 tried = defaultdict(list)
55
56 i = 0
57 while i < len(clauses):
58 if not clauses[i].is_tautology():
59
60
61 if tried[i]:
62 j = tried[i][-1] + 1
63 else:
64 j = i + 1
65
66 while j < len(clauses):
67
68
69 if i != j and j and not clauses[j].is_tautology():
70 tried[i].append(j)
71 newclauses = clauses[i].unify(clauses[j])
72 if newclauses:
73 for newclause in newclauses:
74 newclause._parents = (i+1, j+1)
75 clauses.append(newclause)
76 if not len(newclause):
77 return (True, clauses)
78 i=-1
79 break
80 j += 1
81 i += 1
82 return (False, clauses)
83
85 - def __init__(self, goal=None, assumptions=[]):
86 """
87 @param goal: Input expression to prove
88 @type goal: L{logic.Expression}
89 @param assumptions: Input expressions to use as assumptions in
90 the proof.
91 @type assumptions: C{list} of L{logic.Expression}
92 """
93 ProverCommand.__init__(self, Resolution(), goal, assumptions)
94 self._clauses = None
95
96 - def prove(self, verbose=False):
97 """
98 Perform the actual proof. Store the result to prevent unnecessary
99 re-proving.
100 """
101 if self._result is None:
102 self._result, clauses = self._prover.prove(self.goal(),
103 self.assumptions(),
104 verbose)
105 self._clauses = clauses
106 self._proof = self.decorate_proof(clauses)
107 return self._result
108
121
123 """
124 Decorate the proof output.
125 """
126 out = ''
127 max_clause_len = max([len(str(clause)) for clause in clauses])
128 max_seq_len = len(str(len(clauses)))
129 for i in range(len(clauses)):
130 parents = 'A'
131 taut = ''
132 if clauses[i].is_tautology():
133 taut = 'Tautology'
134 if clauses[i]._parents:
135 parents = str(clauses[i]._parents)
136 parents = ' '*(max_clause_len-len(str(clauses[i]))+1) + parents
137 seq = ' '*(max_seq_len-len(str(i+1))) + str(i+1)
138 out += '[%s] %s %s %s\n' % (seq, clauses[i], parents, taut)
139 return out
140
143 list.__init__(self, data)
144 self._is_tautology = None
145 self._parents = None
146
147 - def unify(self, other, bindings=None, used=None, skipped=None, debug=False):
148 """
149 Attempt to unify this Clause with the other, returning a list of
150 resulting, unified, Clauses.
151
152 @param other: C{Clause} with which to unify
153 @param bindings: C{BindingDict} containing bindings that should be used
154 during the unification
155 @param used: C{tuple} of two C{list}s of atoms. The first lists the
156 atoms from 'self' that were successfully unified with atoms from
157 'other'. The second lists the atoms from 'other' that were successfully
158 unified with atoms from 'self'.
159 @param skipped: C{tuple} of two C{Clause}s. The first is a list of all
160 the atoms from the 'self' Clause that have not been unified with
161 anything on the path. The second is same thing for the 'other' Clause.
162 @param debug: C{bool} indicating whether debug statements should print
163 @return: C{list} containing all the resulting C{Clause}s that could be
164 obtained by unification
165 """
166 if bindings is None: bindings = BindingDict()
167 if used is None: used = ([],[])
168 if skipped is None: skipped = ([],[])
169 if isinstance(debug, bool): debug = DebugObject(debug)
170
171 newclauses = _iterate_first(self, other, bindings, used, skipped, _complete_unify_path, debug)
172
173
174
175 subsumed = []
176 for i, c1 in enumerate(newclauses):
177 if i not in subsumed:
178 for j, c2 in enumerate(newclauses):
179 if i!=j and j not in subsumed and c1.subsumes(c2):
180 subsumed.append(j)
181 result = []
182 for i in range(len(newclauses)):
183 if i not in subsumed:
184 result.append(newclauses[i])
185
186 return result
187
189 """
190 Return True iff every term in 'self' is a term in 'other'.
191
192 @param other: C{Clause}
193 @return: C{bool}
194 """
195 for a in self:
196 if a not in other:
197 return False
198 return True
199
201 """
202 Return True iff 'self' subsumes 'other', this is, if there is a
203 substitution such that every term in 'self' can be unified with a term
204 in 'other'.
205
206 @param other: C{Clause}
207 @return: C{bool}
208 """
209 negatedother = []
210 for atom in other:
211 if isinstance(atom, NegatedExpression):
212 negatedother.append(atom.term)
213 else:
214 negatedother.append(-atom)
215
216 negatedotherClause = Clause(negatedother)
217
218 bindings = BindingDict()
219 used = ([],[])
220 skipped = ([],[])
221 debug = DebugObject(False)
222
223 return len(_iterate_first(self, negatedotherClause, bindings, used,
224 skipped, _subsumes_finalize,
225 debug)) > 0
226
229
231 return Clause([a for a in self if a not in other])
232
235
237 """
238 Self is a tautology if it contains ground terms P and -P. The ground
239 term, P, must be an exact match, ie, not using unification.
240 """
241 if self._is_tautology is not None:
242 return self._is_tautology
243 for i,a in enumerate(self):
244 if not isinstance(a, EqualityExpression):
245 j = len(self)-1
246 while j > i:
247 b = self[j]
248 if isinstance(a, NegatedExpression):
249 if a.term == b:
250 self._is_tautology = True
251 return True
252 elif isinstance(b, NegatedExpression):
253 if a == b.term:
254 self._is_tautology = True
255 return True
256 j -= 1
257 self._is_tautology = False
258 return False
259
261 s = set()
262 for atom in self:
263 s |= atom.free(False)
264 return s
265
266 - def replace(self, variable, expression):
267 """
268 Replace every instance of variable with expression across every atom
269 in the clause
270
271 @param variable: C{Variable}
272 @param expression: C{Expression}
273 """
274 return Clause([atom.replace(variable, expression) for atom in self])
275
277 """
278 Replace every binding
279
280 @param bindings: A C{list} of tuples mapping VariableExpressions to the
281 Expressions to which they are bound
282 @return: C{Clause}
283 """
284 return Clause([atom.substitute_bindings(bindings) for atom in self])
285
287 return '{' + ', '.join([str(item) for item in self]) + '}'
288
291
292 -def _iterate_first(first, second, bindings, used, skipped, finalize_method, debug):
293 """
294 This method facilitates movement through the terms of 'self'
295 """
296 debug.line('unify(%s,%s) %s'%(first, second, bindings))
297
298 if not len(first) or not len(second):
299 return finalize_method(first, second, bindings, used, skipped, debug)
300 else:
301
302 result = _iterate_second(first, second, bindings, used, skipped, finalize_method, debug+1)
303
304
305 newskipped = (skipped[0]+[first[0]], skipped[1])
306 result += _iterate_first(first[1:], second, bindings, used, newskipped, finalize_method, debug+1)
307
308 try:
309 newbindings, newused, unused = _unify_terms(first[0], second[0], bindings, used)
310
311
312 newfirst = first[1:] + skipped[0] + unused[0]
313 newsecond = second[1:] + skipped[1] + unused[1]
314 result += _iterate_first(newfirst, newsecond, newbindings, newused, ([],[]), finalize_method, debug+1)
315 except BindingException:
316
317 pass
318
319 return result
320
321 -def _iterate_second(first, second, bindings, used, skipped, finalize_method, debug):
322 """
323 This method facilitates movement through the terms of 'other'
324 """
325 debug.line('unify(%s,%s) %s'%(first, second, bindings))
326
327 if not len(first) or not len(second):
328 return finalize_method(first, second, bindings, used, skipped, debug)
329 else:
330
331 newskipped = (skipped[0], skipped[1]+[second[0]])
332 result = _iterate_second(first, second[1:], bindings, used, newskipped, finalize_method, debug+1)
333
334 try:
335 newbindings, newused, unused = _unify_terms(first[0], second[0], bindings, used)
336
337
338 newfirst = first[1:] + skipped[0] + unused[0]
339 newsecond = second[1:] + skipped[1] + unused[1]
340 result += _iterate_second(newfirst, newsecond, newbindings, newused, ([],[]), finalize_method, debug+1)
341 except BindingException:
342
343 pass
344
345 return result
346
348 """
349 This method attempts to unify two terms. Two expressions are unifiable
350 if there exists a substitution function S such that S(a) == S(-b).
351
352 @param a: C{Expression}
353 @param b: C{Expression}
354 @param bindings: C{BindingDict} a starting set of bindings with which
355 the unification must be consistent
356 @return: C{BindingDict} A dictionary of the bindings required to unify
357 @raise C{BindingException}: If the terms cannot be unified
358 """
359 assert isinstance(a, Expression)
360 assert isinstance(b, Expression)
361
362 if bindings is None: bindings = BindingDict()
363 if used is None: used = ([],[])
364
365
366 if isinstance(a, NegatedExpression) and isinstance(b, ApplicationExpression):
367 newbindings = most_general_unification(a.term, b, bindings)
368 newused = (used[0]+[a], used[1]+[b])
369 unused = ([],[])
370 elif isinstance(a, ApplicationExpression) and isinstance(b, NegatedExpression):
371 newbindings = most_general_unification(a, b.term, bindings)
372 newused = (used[0]+[a], used[1]+[b])
373 unused = ([],[])
374
375
376 elif isinstance(a, EqualityExpression):
377 newbindings = BindingDict([(a.first.variable, a.second)])
378 newused = (used[0]+[a], used[1])
379 unused = ([],[b])
380 elif isinstance(b, EqualityExpression):
381 newbindings = BindingDict([(b.first.variable, b.second)])
382 newused = (used[0], used[1]+[b])
383 unused = ([a],[])
384
385 else:
386 raise BindingException((a, b))
387
388 return newbindings, newused, unused
389
398
400 if not len(skipped[0]) and not len(first):
401
402
403
404
405
406 return [True]
407 else:
408 return []
409
422
445
446
448 """
449 Skolemize the expression and convert to conjunctive normal form (CNF)
450 """
451 if univ_scope is None:
452 univ_scope = set()
453
454 if isinstance(expression, AllExpression):
455 term = skolemize(expression.term, univ_scope|set([expression.variable]))
456 return term.replace(expression.variable, IndividualVariableExpression(unique_variable()))
457 elif isinstance(expression, AndExpression):
458 return skolemize(expression.first, univ_scope) &\
459 skolemize(expression.second, univ_scope)
460 elif isinstance(expression, OrExpression):
461 return to_cnf(skolemize(expression.first, univ_scope),
462 skolemize(expression.second, univ_scope))
463 elif isinstance(expression, ImpExpression):
464 return to_cnf(skolemize(-expression.first, univ_scope),
465 skolemize(expression.second, univ_scope))
466 elif isinstance(expression, IffExpression):
467 return to_cnf(skolemize(-expression.first, univ_scope),
468 skolemize(expression.second, univ_scope)) &\
469 to_cnf(skolemize(expression.first, univ_scope),
470 skolemize(-expression.second, univ_scope))
471 elif isinstance(expression, EqualityExpression):
472 return expression
473 elif isinstance(expression, NegatedExpression):
474 negated = expression.term
475 if isinstance(negated, AllExpression):
476 term = skolemize(-negated.term, univ_scope)
477 if univ_scope:
478 return term.replace(negated.variable, _get_skolem_function(univ_scope))
479 else:
480 skolem_constant = IndividualVariableExpression(unique_variable())
481 return term.replace(negated.variable, skolem_constant)
482 elif isinstance(negated, AndExpression):
483 return to_cnf(skolemize(-negated.first, univ_scope),
484 skolemize(-negated.second, univ_scope))
485 elif isinstance(negated, OrExpression):
486 return skolemize(-negated.first, univ_scope) &\
487 skolemize(-negated.second, univ_scope)
488 elif isinstance(negated, ImpExpression):
489 return skolemize(negated.first, univ_scope) &\
490 skolemize(-negated.second, univ_scope)
491 elif isinstance(negated, IffExpression):
492 return to_cnf(skolemize(-negated.first, univ_scope),
493 skolemize(-negated.second, univ_scope)) &\
494 to_cnf(skolemize(negated.first, univ_scope),
495 skolemize(negated.second, univ_scope))
496 elif isinstance(negated, EqualityExpression):
497 return expression
498 elif isinstance(negated, NegatedExpression):
499 return skolemize(negated.term, univ_scope)
500 elif isinstance(negated, ExistsExpression):
501 term = skolemize(-negated.term, univ_scope|set([negated.variable]))
502 return term.replace(negated.variable, IndividualVariableExpression(unique_variable()))
503 elif isinstance(negated, ApplicationExpression):
504 return expression
505 else:
506 raise ProverParseError()
507 elif isinstance(expression, ExistsExpression):
508 term = skolemize(expression.term, univ_scope)
509 if univ_scope:
510 return term.replace(expression.variable, _get_skolem_function(univ_scope))
511 else:
512 skolem_constant = IndividualVariableExpression(unique_variable())
513 return term.replace(expression.variable, skolem_constant)
514 elif isinstance(expression, ApplicationExpression):
515 return expression
516 else:
517 raise ProverParseError()
518
520 """
521 Convert this split disjunction to conjunctive normal form (CNF)
522 """
523 if isinstance(first, AndExpression):
524 r_first = to_cnf(first.first, second)
525 r_second = to_cnf(first.second, second)
526 return r_first & r_second
527 elif isinstance(second, AndExpression):
528 r_first = to_cnf(first, second.first)
529 r_second = to_cnf(first, second.second)
530 return r_first & r_second
531 else:
532 return first | second
533
542
543
546 """
547 @param binding_list: C{list} of (C{VariableExpression}, C{AtomicExpression}) to initialize the dictionary
548 """
549 self.d = {}
550
551 if binding_list:
552 for (v, b) in binding_list:
553 self[v] = b
554
556 """
557 A binding is consistent with the dict if its variable is not already bound, OR if its
558 variable is already bound to its argument.
559
560 @param variable: C{Variable} The variable to bind
561 @param binding: C{Expression} The atomic to which 'variable' should be bound
562 @raise BindingException: If the variable cannot be bound in this dictionary
563 """
564 assert isinstance(variable, Variable)
565 assert isinstance(binding, Expression)
566
567 try:
568 existing = self[variable]
569 except KeyError:
570 existing = None
571
572 if not existing or binding == existing:
573 self.d[variable] = binding
574 elif isinstance(binding, IndividualVariableExpression):
575
576 try:
577 existing = self[binding.variable]
578 except KeyError:
579 existing = None
580
581 if is_indvar(variable.name):
582 binding2 = IndividualVariableExpression(variable)
583 else:
584 binding2 = VariableExpression(variable)
585
586 if not existing or binding2 == existing:
587 self.d[binding.variable] = binding2
588 else:
589 raise BindingException('Variable %s already bound to another '
590 'value' % (variable))
591 else:
592 raise BindingException('Variable %s already bound to another '
593 'value' % (variable))
594
596 """
597 Return the expression to which 'variable' is bound
598 """
599 assert isinstance(variable, Variable)
600
601 intermediate = self.d[variable]
602 while intermediate:
603 try:
604 intermediate = self.d[intermediate]
605 except KeyError:
606 return intermediate
607
609 return item in self.d
610
612 """
613 @param other: C{BindingDict} The dict with which to combine self
614 @return: C{BindingDict} A new dict containing all the elements of both parameters
615 @raise BindingException: If the parameter dictionaries are not consistent with each other
616 """
617 try:
618 combined = BindingDict()
619 for v in self.d:
620 combined[v] = self.d[v]
621 for v in other.d:
622 combined[v] = other.d[v]
623 return combined
624 except BindingException:
625 raise BindingException("Attempting to add two contradicting "
626 "BindingDicts: '%s' and '%s'"
627 % (self, other))
628
631
633 return '{' + ', '.join(['%s: %s' % (v, self.d[v]) for v in self.d]) + '}'
634
637
638
640 """
641 Find the most general unification of the two given expressions
642
643 @param a: C{Expression}
644 @param b: C{Expression}
645 @param bindings: C{BindingDict} a starting set of bindings with which the
646 unification must be consistent
647 @return: a list of bindings
648 @raise BindingException: if the Expressions cannot be unified
649 """
650 if bindings is None:
651 bindings = BindingDict()
652
653 if a == b:
654 return bindings
655 elif isinstance(a, VariableExpression) and is_indvar(a.variable.name):
656 return _mgu_var(a, b, bindings)
657 elif isinstance(b, VariableExpression) and is_indvar(b.variable.name):
658 return _mgu_var(b, a, bindings)
659 elif isinstance(a, ApplicationExpression) and\
660 isinstance(b, ApplicationExpression):
661 return most_general_unification(a.function, b.function, bindings) +\
662 most_general_unification(a.argument, b.argument, bindings)
663 raise BindingException((a, b))
664
665 -def _mgu_var(var, expression, bindings):
670
671
674 if isinstance(arg, tuple):
675 Exception.__init__(self, "'%s' cannot be bound to '%s'" % arg)
676 else:
677 Exception.__init__(self, arg)
678
681 Exception.__init__(self, "'%s' cannot unify with '%s'" % (a,b))
682
683
685 - def __init__(self, enabled=True, indent=0):
686 self.enabled = enabled
687 self.indent = indent
688
691
692 - def line(self, line):
693 if self.enabled:
694 print ' '*self.indent + line
695
696
698 resolution_test(r'man(x)')
699 resolution_test(r'(man(x) -> man(x))')
700 resolution_test(r'(man(x) -> --man(x))')
701 resolution_test(r'-(man(x) and -man(x))')
702 resolution_test(r'(man(x) or -man(x))')
703 resolution_test(r'(man(x) -> man(x))')
704 resolution_test(r'-(man(x) and -man(x))')
705 resolution_test(r'(man(x) or -man(x))')
706 resolution_test(r'(man(x) -> man(x))')
707 resolution_test(r'(man(x) iff man(x))')
708 resolution_test(r'-(man(x) iff -man(x))')
709 resolution_test('all x.man(x)')
710 resolution_test('-all x.some y.F(x,y) & some x.all y.(-F(x,y))')
711 resolution_test('some x.all y.sees(x,y)')
712
713 p1 = LogicParser().parse(r'all x.(man(x) -> mortal(x))')
714 p2 = LogicParser().parse(r'man(Socrates)')
715 c = LogicParser().parse(r'mortal(Socrates)')
716 print '%s, %s |- %s: %s' % (p1, p2, c, ProverCommand(Resolution(), c, [p1,p2]).prove())
717
718 p1 = LogicParser().parse(r'all x.(man(x) -> walks(x))')
719 p2 = LogicParser().parse(r'man(John)')
720 c = LogicParser().parse(r'some y.walks(y)')
721 print '%s, %s |- %s: %s' % (p1, p2, c, ProverCommand(Resolution(), c, [p1,p2]).prove())
722
723 p = LogicParser().parse(r'some e1.some e2.(believe(e1,john,e2) & walk(e2,mary))')
724 c = LogicParser().parse(r'some e0.walk(e0,mary)')
725 print '%s |- %s: %s' % (p, c, ProverCommand(Resolution(), c, [p]).prove())
726
731
756
757 if __name__ == '__main__':
758 test_clausify()
759 print
760 testResolution()
761