|
|
|
# Natural Language Toolkit: First-Order Tableau Theorem Prover
|
|
|
|
#
|
|
|
|
# Copyright (C) 2001-2020 NLTK Project
|
|
|
|
# Author: Dan Garrette <dhgarrette@gmail.com>
|
|
|
|
#
|
|
|
|
# URL: <http://nltk.org/>
|
|
|
|
# For license information, see LICENSE.TXT
|
|
|
|
|
|
|
|
"""
|
|
|
|
Module for a tableau-based First Order theorem prover.
|
|
|
|
"""
|
|
|
|
|
|
|
|
from nltk.internals import Counter
|
|
|
|
|
|
|
|
from nltk.sem.logic import (
|
|
|
|
VariableExpression,
|
|
|
|
EqualityExpression,
|
|
|
|
ApplicationExpression,
|
|
|
|
Expression,
|
|
|
|
AbstractVariableExpression,
|
|
|
|
AllExpression,
|
|
|
|
NegatedExpression,
|
|
|
|
ExistsExpression,
|
|
|
|
Variable,
|
|
|
|
ImpExpression,
|
|
|
|
AndExpression,
|
|
|
|
unique_variable,
|
|
|
|
LambdaExpression,
|
|
|
|
IffExpression,
|
|
|
|
OrExpression,
|
|
|
|
FunctionVariableExpression,
|
|
|
|
)
|
|
|
|
|
|
|
|
from nltk.inference.api import Prover, BaseProverCommand
|
|
|
|
|
|
|
|
_counter = Counter()
|
|
|
|
|
|
|
|
|
|
|
|
class ProverParseError(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class TableauProver(Prover):
|
|
|
|
_assume_false = False
|
|
|
|
|
|
|
|
def _prove(self, goal=None, assumptions=None, verbose=False):
|
|
|
|
if not assumptions:
|
|
|
|
assumptions = []
|
|
|
|
|
|
|
|
result = None
|
|
|
|
try:
|
|
|
|
agenda = Agenda()
|
|
|
|
if goal:
|
|
|
|
agenda.put(-goal)
|
|
|
|
agenda.put_all(assumptions)
|
|
|
|
debugger = Debug(verbose)
|
|
|
|
result = self._attempt_proof(agenda, set(), set(), debugger)
|
|
|
|
except RuntimeError as e:
|
|
|
|
if self._assume_false and str(e).startswith(
|
|
|
|
"maximum recursion depth exceeded"
|
|
|
|
):
|
|
|
|
result = False
|
|
|
|
else:
|
|
|
|
if verbose:
|
|
|
|
print(e)
|
|
|
|
else:
|
|
|
|
raise e
|
|
|
|
return (result, "\n".join(debugger.lines))
|
|
|
|
|
|
|
|
def _attempt_proof(self, agenda, accessible_vars, atoms, debug):
|
|
|
|
(current, context), category = agenda.pop_first()
|
|
|
|
|
|
|
|
# if there's nothing left in the agenda, and we haven't closed the path
|
|
|
|
if not current:
|
|
|
|
debug.line("AGENDA EMPTY")
|
|
|
|
return False
|
|
|
|
|
|
|
|
proof_method = {
|
|
|
|
Categories.ATOM: self._attempt_proof_atom,
|
|
|
|
Categories.PROP: self._attempt_proof_prop,
|
|
|
|
Categories.N_ATOM: self._attempt_proof_n_atom,
|
|
|
|
Categories.N_PROP: self._attempt_proof_n_prop,
|
|
|
|
Categories.APP: self._attempt_proof_app,
|
|
|
|
Categories.N_APP: self._attempt_proof_n_app,
|
|
|
|
Categories.N_EQ: self._attempt_proof_n_eq,
|
|
|
|
Categories.D_NEG: self._attempt_proof_d_neg,
|
|
|
|
Categories.N_ALL: self._attempt_proof_n_all,
|
|
|
|
Categories.N_EXISTS: self._attempt_proof_n_some,
|
|
|
|
Categories.AND: self._attempt_proof_and,
|
|
|
|
Categories.N_OR: self._attempt_proof_n_or,
|
|
|
|
Categories.N_IMP: self._attempt_proof_n_imp,
|
|
|
|
Categories.OR: self._attempt_proof_or,
|
|
|
|
Categories.IMP: self._attempt_proof_imp,
|
|
|
|
Categories.N_AND: self._attempt_proof_n_and,
|
|
|
|
Categories.IFF: self._attempt_proof_iff,
|
|
|
|
Categories.N_IFF: self._attempt_proof_n_iff,
|
|
|
|
Categories.EQ: self._attempt_proof_eq,
|
|
|
|
Categories.EXISTS: self._attempt_proof_some,
|
|
|
|
Categories.ALL: self._attempt_proof_all,
|
|
|
|
}[category]
|
|
|
|
|
|
|
|
debug.line((current, context))
|
|
|
|
return proof_method(current, context, agenda, accessible_vars, atoms, debug)
|
|
|
|
|
|
|
|
def _attempt_proof_atom(
|
|
|
|
self, current, context, agenda, accessible_vars, atoms, debug
|
|
|
|
):
|
|
|
|
# Check if the branch is closed. Return 'True' if it is
|
|
|
|
if (current, True) in atoms:
|
|
|
|
debug.line("CLOSED", 1)
|
|
|
|
return True
|
|
|
|
|
|
|
|
if context:
|
|
|
|
if isinstance(context.term, NegatedExpression):
|
|
|
|
current = current.negate()
|
|
|
|
agenda.put(context(current).simplify())
|
|
|
|
return self._attempt_proof(agenda, accessible_vars, atoms, debug + 1)
|
|
|
|
else:
|
|
|
|
# mark all AllExpressions as 'not exhausted' into the agenda since we are (potentially) adding new accessible vars
|
|
|
|
agenda.mark_alls_fresh()
|
|
|
|
return self._attempt_proof(
|
|
|
|
agenda,
|
|
|
|
accessible_vars | set(current.args),
|
|
|
|
atoms | set([(current, False)]),
|
|
|
|
debug + 1,
|
|
|
|
)
|
|
|
|
|
|
|
|
def _attempt_proof_n_atom(
|
|
|
|
self, current, context, agenda, accessible_vars, atoms, debug
|
|
|
|
):
|
|
|
|
# Check if the branch is closed. Return 'True' if it is
|
|
|
|
if (current.term, False) in atoms:
|
|
|
|
debug.line("CLOSED", 1)
|
|
|
|
return True
|
|
|
|
|
|
|
|
if context:
|
|
|
|
if isinstance(context.term, NegatedExpression):
|
|
|
|
current = current.negate()
|
|
|
|
agenda.put(context(current).simplify())
|
|
|
|
return self._attempt_proof(agenda, accessible_vars, atoms, debug + 1)
|
|
|
|
else:
|
|
|
|
# mark all AllExpressions as 'not exhausted' into the agenda since we are (potentially) adding new accessible vars
|
|
|
|
agenda.mark_alls_fresh()
|
|
|
|
return self._attempt_proof(
|
|
|
|
agenda,
|
|
|
|
accessible_vars | set(current.term.args),
|
|
|
|
atoms | set([(current.term, True)]),
|
|
|
|
debug + 1,
|
|
|
|
)
|
|
|
|
|
|
|
|
def _attempt_proof_prop(
|
|
|
|
self, current, context, agenda, accessible_vars, atoms, debug
|
|
|
|
):
|
|
|
|
# Check if the branch is closed. Return 'True' if it is
|
|
|
|
if (current, True) in atoms:
|
|
|
|
debug.line("CLOSED", 1)
|
|
|
|
return True
|
|
|
|
|
|
|
|
# mark all AllExpressions as 'not exhausted' into the agenda since we are (potentially) adding new accessible vars
|
|
|
|
agenda.mark_alls_fresh()
|
|
|
|
return self._attempt_proof(
|
|
|
|
agenda, accessible_vars, atoms | set([(current, False)]), debug + 1
|
|
|
|
)
|
|
|
|
|
|
|
|
def _attempt_proof_n_prop(
|
|
|
|
self, current, context, agenda, accessible_vars, atoms, debug
|
|
|
|
):
|
|
|
|
# Check if the branch is closed. Return 'True' if it is
|
|
|
|
if (current.term, False) in atoms:
|
|
|
|
debug.line("CLOSED", 1)
|
|
|
|
return True
|
|
|
|
|
|
|
|
# mark all AllExpressions as 'not exhausted' into the agenda since we are (potentially) adding new accessible vars
|
|
|
|
agenda.mark_alls_fresh()
|
|
|
|
return self._attempt_proof(
|
|
|
|
agenda, accessible_vars, atoms | set([(current.term, True)]), debug + 1
|
|
|
|
)
|
|
|
|
|
|
|
|
def _attempt_proof_app(
|
|
|
|
self, current, context, agenda, accessible_vars, atoms, debug
|
|
|
|
):
|
|
|
|
f, args = current.uncurry()
|
|
|
|
for i, arg in enumerate(args):
|
|
|
|
if not TableauProver.is_atom(arg):
|
|
|
|
ctx = f
|
|
|
|
nv = Variable("X%s" % _counter.get())
|
|
|
|
for j, a in enumerate(args):
|
|
|
|
ctx = ctx(VariableExpression(nv)) if i == j else ctx(a)
|
|
|
|
if context:
|
|
|
|
ctx = context(ctx).simplify()
|
|
|
|
ctx = LambdaExpression(nv, ctx)
|
|
|
|
agenda.put(arg, ctx)
|
|
|
|
return self._attempt_proof(agenda, accessible_vars, atoms, debug + 1)
|
|
|
|
raise Exception("If this method is called, there must be a non-atomic argument")
|
|
|
|
|
|
|
|
def _attempt_proof_n_app(
|
|
|
|
self, current, context, agenda, accessible_vars, atoms, debug
|
|
|
|
):
|
|
|
|
f, args = current.term.uncurry()
|
|
|
|
for i, arg in enumerate(args):
|
|
|
|
if not TableauProver.is_atom(arg):
|
|
|
|
ctx = f
|
|
|
|
nv = Variable("X%s" % _counter.get())
|
|
|
|
for j, a in enumerate(args):
|
|
|
|
ctx = ctx(VariableExpression(nv)) if i == j else ctx(a)
|
|
|
|
if context:
|
|
|
|
# combine new context with existing
|
|
|
|
ctx = context(ctx).simplify()
|
|
|
|
ctx = LambdaExpression(nv, -ctx)
|
|
|
|
agenda.put(-arg, ctx)
|
|
|
|
return self._attempt_proof(agenda, accessible_vars, atoms, debug + 1)
|
|
|
|
raise Exception("If this method is called, there must be a non-atomic argument")
|
|
|
|
|
|
|
|
def _attempt_proof_n_eq(
|
|
|
|
self, current, context, agenda, accessible_vars, atoms, debug
|
|
|
|
):
|
|
|
|
###########################################################################
|
|
|
|
# Since 'current' is of type '~(a=b)', the path is closed if 'a' == 'b'
|
|
|
|
###########################################################################
|
|
|
|
if current.term.first == current.term.second:
|
|
|
|
debug.line("CLOSED", 1)
|
|
|
|
return True
|
|
|
|
|
|
|
|
agenda[Categories.N_EQ].add((current, context))
|
|
|
|
current._exhausted = True
|
|
|
|
return self._attempt_proof(
|
|
|
|
agenda,
|
|
|
|
accessible_vars | set([current.term.first, current.term.second]),
|
|
|
|
atoms,
|
|
|
|
debug + 1,
|
|
|
|
)
|
|
|
|
|
|
|
|
def _attempt_proof_d_neg(
|
|
|
|
self, current, context, agenda, accessible_vars, atoms, debug
|
|
|
|
):
|
|
|
|
agenda.put(current.term.term, context)
|
|
|
|
return self._attempt_proof(agenda, accessible_vars, atoms, debug + 1)
|
|
|
|
|
|
|
|
def _attempt_proof_n_all(
|
|
|
|
self, current, context, agenda, accessible_vars, atoms, debug
|
|
|
|
):
|
|
|
|
agenda[Categories.EXISTS].add(
|
|
|
|
(ExistsExpression(current.term.variable, -current.term.term), context)
|
|
|
|
)
|
|
|
|
return self._attempt_proof(agenda, accessible_vars, atoms, debug + 1)
|
|
|
|
|
|
|
|
def _attempt_proof_n_some(
|
|
|
|
self, current, context, agenda, accessible_vars, atoms, debug
|
|
|
|
):
|
|
|
|
agenda[Categories.ALL].add(
|
|
|
|
(AllExpression(current.term.variable, -current.term.term), context)
|
|
|
|
)
|
|
|
|
return self._attempt_proof(agenda, accessible_vars, atoms, debug + 1)
|
|
|
|
|
|
|
|
def _attempt_proof_and(
|
|
|
|
self, current, context, agenda, accessible_vars, atoms, debug
|
|
|
|
):
|
|
|
|
agenda.put(current.first, context)
|
|
|
|
agenda.put(current.second, context)
|
|
|
|
return self._attempt_proof(agenda, accessible_vars, atoms, debug + 1)
|
|
|
|
|
|
|
|
def _attempt_proof_n_or(
|
|
|
|
self, current, context, agenda, accessible_vars, atoms, debug
|
|
|
|
):
|
|
|
|
agenda.put(-current.term.first, context)
|
|
|
|
agenda.put(-current.term.second, context)
|
|
|
|
return self._attempt_proof(agenda, accessible_vars, atoms, debug + 1)
|
|
|
|
|
|
|
|
def _attempt_proof_n_imp(
|
|
|
|
self, current, context, agenda, accessible_vars, atoms, debug
|
|
|
|
):
|
|
|
|
agenda.put(current.term.first, context)
|
|
|
|
agenda.put(-current.term.second, context)
|
|
|
|
return self._attempt_proof(agenda, accessible_vars, atoms, debug + 1)
|
|
|
|
|
|
|
|
def _attempt_proof_or(
|
|
|
|
self, current, context, agenda, accessible_vars, atoms, debug
|
|
|
|
):
|
|
|
|
new_agenda = agenda.clone()
|
|
|
|
agenda.put(current.first, context)
|
|
|
|
new_agenda.put(current.second, context)
|
|
|
|
return self._attempt_proof(
|
|
|
|
agenda, accessible_vars, atoms, debug + 1
|
|
|
|
) and self._attempt_proof(new_agenda, accessible_vars, atoms, debug + 1)
|
|
|
|
|
|
|
|
def _attempt_proof_imp(
|
|
|
|
self, current, context, agenda, accessible_vars, atoms, debug
|
|
|
|
):
|
|
|
|
new_agenda = agenda.clone()
|
|
|
|
agenda.put(-current.first, context)
|
|
|
|
new_agenda.put(current.second, context)
|
|
|
|
return self._attempt_proof(
|
|
|
|
agenda, accessible_vars, atoms, debug + 1
|
|
|
|
) and self._attempt_proof(new_agenda, accessible_vars, atoms, debug + 1)
|
|
|
|
|
|
|
|
def _attempt_proof_n_and(
|
|
|
|
self, current, context, agenda, accessible_vars, atoms, debug
|
|
|
|
):
|
|
|
|
new_agenda = agenda.clone()
|
|
|
|
agenda.put(-current.term.first, context)
|
|
|
|
new_agenda.put(-current.term.second, context)
|
|
|
|
return self._attempt_proof(
|
|
|
|
agenda, accessible_vars, atoms, debug + 1
|
|
|
|
) and self._attempt_proof(new_agenda, accessible_vars, atoms, debug + 1)
|
|
|
|
|
|
|
|
def _attempt_proof_iff(
|
|
|
|
self, current, context, agenda, accessible_vars, atoms, debug
|
|
|
|
):
|
|
|
|
new_agenda = agenda.clone()
|
|
|
|
agenda.put(current.first, context)
|
|
|
|
agenda.put(current.second, context)
|
|
|
|
new_agenda.put(-current.first, context)
|
|
|
|
new_agenda.put(-current.second, context)
|
|
|
|
return self._attempt_proof(
|
|
|
|
agenda, accessible_vars, atoms, debug + 1
|
|
|
|
) and self._attempt_proof(new_agenda, accessible_vars, atoms, debug + 1)
|
|
|
|
|
|
|
|
def _attempt_proof_n_iff(
|
|
|
|
self, current, context, agenda, accessible_vars, atoms, debug
|
|
|
|
):
|
|
|
|
new_agenda = agenda.clone()
|
|
|
|
agenda.put(current.term.first, context)
|
|
|
|
agenda.put(-current.term.second, context)
|
|
|
|
new_agenda.put(-current.term.first, context)
|
|
|
|
new_agenda.put(current.term.second, context)
|
|
|
|
return self._attempt_proof(
|
|
|
|
agenda, accessible_vars, atoms, debug + 1
|
|
|
|
) and self._attempt_proof(new_agenda, accessible_vars, atoms, debug + 1)
|
|
|
|
|
|
|
|
def _attempt_proof_eq(
|
|
|
|
self, current, context, agenda, accessible_vars, atoms, debug
|
|
|
|
):
|
|
|
|
#########################################################################
|
|
|
|
# Since 'current' is of the form '(a = b)', replace ALL free instances
|
|
|
|
# of 'a' with 'b'
|
|
|
|
#########################################################################
|
|
|
|
agenda.put_atoms(atoms)
|
|
|
|
agenda.replace_all(current.first, current.second)
|
|
|
|
accessible_vars.discard(current.first)
|
|
|
|
agenda.mark_neqs_fresh()
|
|
|
|
return self._attempt_proof(agenda, accessible_vars, set(), debug + 1)
|
|
|
|
|
|
|
|
def _attempt_proof_some(
|
|
|
|
self, current, context, agenda, accessible_vars, atoms, debug
|
|
|
|
):
|
|
|
|
new_unique_variable = VariableExpression(unique_variable())
|
|
|
|
agenda.put(current.term.replace(current.variable, new_unique_variable), context)
|
|
|
|
agenda.mark_alls_fresh()
|
|
|
|
return self._attempt_proof(
|
|
|
|
agenda, accessible_vars | set([new_unique_variable]), atoms, debug + 1
|
|
|
|
)
|
|
|
|
|
|
|
|
def _attempt_proof_all(
|
|
|
|
self, current, context, agenda, accessible_vars, atoms, debug
|
|
|
|
):
|
|
|
|
try:
|
|
|
|
current._used_vars
|
|
|
|
except AttributeError:
|
|
|
|
current._used_vars = set()
|
|
|
|
|
|
|
|
# if there are accessible_vars on the path
|
|
|
|
if accessible_vars:
|
|
|
|
# get the set of bound variables that have not be used by this AllExpression
|
|
|
|
bv_available = accessible_vars - current._used_vars
|
|
|
|
|
|
|
|
if bv_available:
|
|
|
|
variable_to_use = list(bv_available)[0]
|
|
|
|
debug.line("--> Using '%s'" % variable_to_use, 2)
|
|
|
|
current._used_vars |= set([variable_to_use])
|
|
|
|
agenda.put(
|
|
|
|
current.term.replace(current.variable, variable_to_use), context
|
|
|
|
)
|
|
|
|
agenda[Categories.ALL].add((current, context))
|
|
|
|
return self._attempt_proof(agenda, accessible_vars, atoms, debug + 1)
|
|
|
|
|
|
|
|
else:
|
|
|
|
# no more available variables to substitute
|
|
|
|
debug.line("--> Variables Exhausted", 2)
|
|
|
|
current._exhausted = True
|
|
|
|
agenda[Categories.ALL].add((current, context))
|
|
|
|
return self._attempt_proof(agenda, accessible_vars, atoms, debug + 1)
|
|
|
|
|
|
|
|
else:
|
|
|
|
new_unique_variable = VariableExpression(unique_variable())
|
|
|
|
debug.line("--> Using '%s'" % new_unique_variable, 2)
|
|
|
|
current._used_vars |= set([new_unique_variable])
|
|
|
|
agenda.put(
|
|
|
|
current.term.replace(current.variable, new_unique_variable), context
|
|
|
|
)
|
|
|
|
agenda[Categories.ALL].add((current, context))
|
|
|
|
agenda.mark_alls_fresh()
|
|
|
|
return self._attempt_proof(
|
|
|
|
agenda, accessible_vars | set([new_unique_variable]), atoms, debug + 1
|
|
|
|
)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def is_atom(e):
|
|
|
|
if isinstance(e, NegatedExpression):
|
|
|
|
e = e.term
|
|
|
|
|
|
|
|
if isinstance(e, ApplicationExpression):
|
|
|
|
for arg in e.args:
|
|
|
|
if not TableauProver.is_atom(arg):
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
elif isinstance(e, AbstractVariableExpression) or isinstance(
|
|
|
|
e, LambdaExpression
|
|
|
|
):
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
class TableauProverCommand(BaseProverCommand):
|
|
|
|
def __init__(self, goal=None, assumptions=None, prover=None):
|
|
|
|
"""
|
|
|
|
:param goal: Input expression to prove
|
|
|
|
:type goal: sem.Expression
|
|
|
|
:param assumptions: Input expressions to use as assumptions in
|
|
|
|
the proof.
|
|
|
|
:type assumptions: list(sem.Expression)
|
|
|
|
"""
|
|
|
|
if prover is not None:
|
|
|
|
assert isinstance(prover, TableauProver)
|
|
|
|
else:
|
|
|
|
prover = TableauProver()
|
|
|
|
|
|
|
|
BaseProverCommand.__init__(self, prover, goal, assumptions)
|
|
|
|
|
|
|
|
|
|
|
|
class Agenda(object):
|
|
|
|
def __init__(self):
|
|
|
|
self.sets = tuple(set() for i in range(21))
|
|
|
|
|
|
|
|
def clone(self):
|
|
|
|
new_agenda = Agenda()
|
|
|
|
set_list = [s.copy() for s in self.sets]
|
|
|
|
|
|
|
|
new_allExs = set()
|
|
|
|
for allEx, _ in set_list[Categories.ALL]:
|
|
|
|
new_allEx = AllExpression(allEx.variable, allEx.term)
|
|
|
|
try:
|
|
|
|
new_allEx._used_vars = set(used for used in allEx._used_vars)
|
|
|
|
except AttributeError:
|
|
|
|
new_allEx._used_vars = set()
|
|
|
|
new_allExs.add((new_allEx, None))
|
|
|
|
set_list[Categories.ALL] = new_allExs
|
|
|
|
|
|
|
|
set_list[Categories.N_EQ] = set(
|
|
|
|
(NegatedExpression(n_eq.term), ctx)
|
|
|
|
for (n_eq, ctx) in set_list[Categories.N_EQ]
|
|
|
|
)
|
|
|
|
|
|
|
|
new_agenda.sets = tuple(set_list)
|
|
|
|
return new_agenda
|
|
|
|
|
|
|
|
def __getitem__(self, index):
|
|
|
|
return self.sets[index]
|
|
|
|
|
|
|
|
def put(self, expression, context=None):
|
|
|
|
if isinstance(expression, AllExpression):
|
|
|
|
ex_to_add = AllExpression(expression.variable, expression.term)
|
|
|
|
try:
|
|
|
|
ex_to_add._used_vars = set(used for used in expression._used_vars)
|
|
|
|
except AttributeError:
|
|
|
|
ex_to_add._used_vars = set()
|
|
|
|
else:
|
|
|
|
ex_to_add = expression
|
|
|
|
self.sets[self._categorize_expression(ex_to_add)].add((ex_to_add, context))
|
|
|
|
|
|
|
|
def put_all(self, expressions):
|
|
|
|
for expression in expressions:
|
|
|
|
self.put(expression)
|
|
|
|
|
|
|
|
def put_atoms(self, atoms):
|
|
|
|
for atom, neg in atoms:
|
|
|
|
if neg:
|
|
|
|
self[Categories.N_ATOM].add((-atom, None))
|
|
|
|
else:
|
|
|
|
self[Categories.ATOM].add((atom, None))
|
|
|
|
|
|
|
|
def pop_first(self):
|
|
|
|
""" Pop the first expression that appears in the agenda """
|
|
|
|
for i, s in enumerate(self.sets):
|
|
|
|
if s:
|
|
|
|
if i in [Categories.N_EQ, Categories.ALL]:
|
|
|
|
for ex in s:
|
|
|
|
try:
|
|
|
|
if not ex[0]._exhausted:
|
|
|
|
s.remove(ex)
|
|
|
|
return (ex, i)
|
|
|
|
except AttributeError:
|
|
|
|
s.remove(ex)
|
|
|
|
return (ex, i)
|
|
|
|
else:
|
|
|
|
return (s.pop(), i)
|
|
|
|
return ((None, None), None)
|
|
|
|
|
|
|
|
def replace_all(self, old, new):
|
|
|
|
for s in self.sets:
|
|
|
|
for ex, ctx in s:
|
|
|
|
ex.replace(old.variable, new)
|
|
|
|
if ctx is not None:
|
|
|
|
ctx.replace(old.variable, new)
|
|
|
|
|
|
|
|
def mark_alls_fresh(self):
|
|
|
|
for u, _ in self.sets[Categories.ALL]:
|
|
|
|
u._exhausted = False
|
|
|
|
|
|
|
|
def mark_neqs_fresh(self):
|
|
|
|
for neq, _ in self.sets[Categories.N_EQ]:
|
|
|
|
neq._exhausted = False
|
|
|
|
|
|
|
|
def _categorize_expression(self, current):
|
|
|
|
if isinstance(current, NegatedExpression):
|
|
|
|
return self._categorize_NegatedExpression(current)
|
|
|
|
elif isinstance(current, FunctionVariableExpression):
|
|
|
|
return Categories.PROP
|
|
|
|
elif TableauProver.is_atom(current):
|
|
|
|
return Categories.ATOM
|
|
|
|
elif isinstance(current, AllExpression):
|
|
|
|
return Categories.ALL
|
|
|
|
elif isinstance(current, AndExpression):
|
|
|
|
return Categories.AND
|
|
|
|
elif isinstance(current, OrExpression):
|
|
|
|
return Categories.OR
|
|
|
|
elif isinstance(current, ImpExpression):
|
|
|
|
return Categories.IMP
|
|
|
|
elif isinstance(current, IffExpression):
|
|
|
|
return Categories.IFF
|
|
|
|
elif isinstance(current, EqualityExpression):
|
|
|
|
return Categories.EQ
|
|
|
|
elif isinstance(current, ExistsExpression):
|
|
|
|
return Categories.EXISTS
|
|
|
|
elif isinstance(current, ApplicationExpression):
|
|
|
|
return Categories.APP
|
|
|
|
else:
|
|
|
|
raise ProverParseError("cannot categorize %s" % current.__class__.__name__)
|
|
|
|
|
|
|
|
def _categorize_NegatedExpression(self, current):
|
|
|
|
negated = current.term
|
|
|
|
|
|
|
|
if isinstance(negated, NegatedExpression):
|
|
|
|
return Categories.D_NEG
|
|
|
|
elif isinstance(negated, FunctionVariableExpression):
|
|
|
|
return Categories.N_PROP
|
|
|
|
elif TableauProver.is_atom(negated):
|
|
|
|
return Categories.N_ATOM
|
|
|
|
elif isinstance(negated, AllExpression):
|
|
|
|
return Categories.N_ALL
|
|
|
|
elif isinstance(negated, AndExpression):
|
|
|
|
return Categories.N_AND
|
|
|
|
elif isinstance(negated, OrExpression):
|
|
|
|
return Categories.N_OR
|
|
|
|
elif isinstance(negated, ImpExpression):
|
|
|
|
return Categories.N_IMP
|
|
|
|
elif isinstance(negated, IffExpression):
|
|
|
|
return Categories.N_IFF
|
|
|
|
elif isinstance(negated, EqualityExpression):
|
|
|
|
return Categories.N_EQ
|
|
|
|
elif isinstance(negated, ExistsExpression):
|
|
|
|
return Categories.N_EXISTS
|
|
|
|
elif isinstance(negated, ApplicationExpression):
|
|
|
|
return Categories.N_APP
|
|
|
|
else:
|
|
|
|
raise ProverParseError("cannot categorize %s" % negated.__class__.__name__)
|
|
|
|
|
|
|
|
|
|
|
|
class Debug(object):
|
|
|
|
def __init__(self, verbose, indent=0, lines=None):
|
|
|
|
self.verbose = verbose
|
|
|
|
self.indent = indent
|
|
|
|
|
|
|
|
if not lines:
|
|
|
|
lines = []
|
|
|
|
self.lines = lines
|
|
|
|
|
|
|
|
def __add__(self, increment):
|
|
|
|
return Debug(self.verbose, self.indent + 1, self.lines)
|
|
|
|
|
|
|
|
def line(self, data, indent=0):
|
|
|
|
if isinstance(data, tuple):
|
|
|
|
ex, ctx = data
|
|
|
|
if ctx:
|
|
|
|
data = "%s, %s" % (ex, ctx)
|
|
|
|
else:
|
|
|
|
data = "%s" % ex
|
|
|
|
|
|
|
|
if isinstance(ex, AllExpression):
|
|
|
|
try:
|
|
|
|
used_vars = "[%s]" % (
|
|
|
|
",".join("%s" % ve.variable.name for ve in ex._used_vars)
|
|
|
|
)
|
|
|
|
data += ": %s" % used_vars
|
|
|
|
except AttributeError:
|
|
|
|
data += ": []"
|
|
|
|
|
|
|
|
newline = "%s%s" % (" " * (self.indent + indent), data)
|
|
|
|
self.lines.append(newline)
|
|
|
|
|
|
|
|
if self.verbose:
|
|
|
|
print(newline)
|
|
|
|
|
|
|
|
|
|
|
|
class Categories(object):
|
|
|
|
ATOM = 0
|
|
|
|
PROP = 1
|
|
|
|
N_ATOM = 2
|
|
|
|
N_PROP = 3
|
|
|
|
APP = 4
|
|
|
|
N_APP = 5
|
|
|
|
N_EQ = 6
|
|
|
|
D_NEG = 7
|
|
|
|
N_ALL = 8
|
|
|
|
N_EXISTS = 9
|
|
|
|
AND = 10
|
|
|
|
N_OR = 11
|
|
|
|
N_IMP = 12
|
|
|
|
OR = 13
|
|
|
|
IMP = 14
|
|
|
|
N_AND = 15
|
|
|
|
IFF = 16
|
|
|
|
N_IFF = 17
|
|
|
|
EQ = 18
|
|
|
|
EXISTS = 19
|
|
|
|
ALL = 20
|
|
|
|
|
|
|
|
|
|
|
|
def testTableauProver():
|
|
|
|
tableau_test("P | -P")
|
|
|
|
tableau_test("P & -P")
|
|
|
|
tableau_test("Q", ["P", "(P -> Q)"])
|
|
|
|
tableau_test("man(x)")
|
|
|
|
tableau_test("(man(x) -> man(x))")
|
|
|
|
tableau_test("(man(x) -> --man(x))")
|
|
|
|
tableau_test("-(man(x) and -man(x))")
|
|
|
|
tableau_test("(man(x) or -man(x))")
|
|
|
|
tableau_test("(man(x) -> man(x))")
|
|
|
|
tableau_test("-(man(x) and -man(x))")
|
|
|
|
tableau_test("(man(x) or -man(x))")
|
|
|
|
tableau_test("(man(x) -> man(x))")
|
|
|
|
tableau_test("(man(x) iff man(x))")
|
|
|
|
tableau_test("-(man(x) iff -man(x))")
|
|
|
|
tableau_test("all x.man(x)")
|
|
|
|
tableau_test("all x.all y.((x = y) -> (y = x))")
|
|
|
|
tableau_test("all x.all y.all z.(((x = y) & (y = z)) -> (x = z))")
|
|
|
|
# tableau_test('-all x.some y.F(x,y) & some x.all y.(-F(x,y))')
|
|
|
|
# tableau_test('some x.all y.sees(x,y)')
|
|
|
|
|
|
|
|
p1 = "all x.(man(x) -> mortal(x))"
|
|
|
|
p2 = "man(Socrates)"
|
|
|
|
c = "mortal(Socrates)"
|
|
|
|
tableau_test(c, [p1, p2])
|
|
|
|
|
|
|
|
p1 = "all x.(man(x) -> walks(x))"
|
|
|
|
p2 = "man(John)"
|
|
|
|
c = "some y.walks(y)"
|
|
|
|
tableau_test(c, [p1, p2])
|
|
|
|
|
|
|
|
p = "((x = y) & walks(y))"
|
|
|
|
c = "walks(x)"
|
|
|
|
tableau_test(c, [p])
|
|
|
|
|
|
|
|
p = "((x = y) & ((y = z) & (z = w)))"
|
|
|
|
c = "(x = w)"
|
|
|
|
tableau_test(c, [p])
|
|
|
|
|
|
|
|
p = "some e1.some e2.(believe(e1,john,e2) & walk(e2,mary))"
|
|
|
|
c = "some e0.walk(e0,mary)"
|
|
|
|
tableau_test(c, [p])
|
|
|
|
|
|
|
|
c = "(exists x.exists z3.((x = Mary) & ((z3 = John) & sees(z3,x))) <-> exists x.exists z4.((x = John) & ((z4 = Mary) & sees(x,z4))))"
|
|
|
|
tableau_test(c)
|
|
|
|
|
|
|
|
|
|
|
|
# p = 'some e1.some e2.((believe e1 john e2) and (walk e2 mary))'
|
|
|
|
# c = 'some x.some e3.some e4.((believe e3 x e4) and (walk e4 mary))'
|
|
|
|
# tableau_test(c, [p])
|
|
|
|
|
|
|
|
|
|
|
|
def testHigherOrderTableauProver():
|
|
|
|
tableau_test("believe(j, -lie(b))", ["believe(j, -lie(b) & -cheat(b))"])
|
|
|
|
tableau_test("believe(j, lie(b) & cheat(b))", ["believe(j, lie(b))"])
|
|
|
|
tableau_test(
|
|
|
|
"believe(j, lie(b))", ["lie(b)"]
|
|
|
|
) # how do we capture that John believes all things that are true
|
|
|
|
tableau_test(
|
|
|
|
"believe(j, know(b, cheat(b)))",
|
|
|
|
["believe(j, know(b, lie(b)) & know(b, steals(b) & cheat(b)))"],
|
|
|
|
)
|
|
|
|
tableau_test("P(Q(y), R(y) & R(z))", ["P(Q(x) & Q(y), R(y) & R(z))"])
|
|
|
|
|
|
|
|
tableau_test("believe(j, cheat(b) & lie(b))", ["believe(j, lie(b) & cheat(b))"])
|
|
|
|
tableau_test("believe(j, -cheat(b) & -lie(b))", ["believe(j, -lie(b) & -cheat(b))"])
|
|
|
|
|
|
|
|
|
|
|
|
def tableau_test(c, ps=None, verbose=False):
|
|
|
|
pc = Expression.fromstring(c)
|
|
|
|
pps = [Expression.fromstring(p) for p in ps] if ps else []
|
|
|
|
if not ps:
|
|
|
|
ps = []
|
|
|
|
print(
|
|
|
|
"%s |- %s: %s"
|
|
|
|
% (", ".join(ps), pc, TableauProver().prove(pc, pps, verbose=verbose))
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def demo():
|
|
|
|
testTableauProver()
|
|
|
|
testHigherOrderTableauProver()
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
demo()
|