Last active
June 15, 2019 10:36
-
-
Save louisswarren/d4b508f057f9a36500e056b314f4fdd7 to your computer and use it in GitHub Desktop.
Decidability algorithm for the implicational fragment of minimal logic
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Simple implementation of proving/disproving implicational statements | |
from collections import namedtuple | |
# String constants | |
lBOT = '⊥' | |
lNEG = '¬' | |
lIMP = ' → ' | |
lMMP = ' ⇒ ' | |
lVDA = ' ⊢ ' | |
lNVD = ' ⊬ ' | |
# Utils | |
compose = lambda f: lambda g: lambda *a, **k: f(g(*a, **k)) | |
def csv(it): | |
return '{' + ', '.join(map(str, sorted(it, key=str))) + '}' | |
def log(n, *args): | |
print('\t' * n + str(args[0]), *args[1:]) | |
# Formulae | |
class Prop: | |
def is_atom(self): | |
return isinstance(self, Atom) | |
def is_arrow(self): | |
return isinstance(self, Arrow) | |
def is_simple(self): | |
return self.is_arrow() and self.premise.is_atom() | |
def is_left(self): | |
return self.is_arrow() and self.premise.is_arrow() | |
# Warning: not right-associative | |
def __rshift__(self, shift): | |
return Arrow(self, shift) | |
def __invert__(self): | |
return Arrow(self, bot) | |
def __hash__(self): | |
return hash(repr(self)) | |
class Atom(Prop, namedtuple('Atom', 'name')): | |
def paren_str(self): | |
return str(self) | |
def __eq__(self, other): | |
return repr(self) == repr(other) | |
def __hash__(self): | |
return hash(repr(self)) | |
def __str__(self): | |
return self.name | |
class Arrow(Prop, namedtuple('Arrow', 'premise conclusion')): | |
def paren_str(self): | |
if self.conclusion is bot: | |
return str(self) | |
else: | |
return f'({self})' | |
def __eq__(self, other): | |
return repr(self) == repr(other) | |
def __hash__(self): | |
return hash(repr(self)) | |
def __str__(self): | |
if self.conclusion is bot: | |
return lNEG + self.premise.paren_str() | |
else: | |
return self.premise.paren_str() + lIMP + str(self.conclusion) | |
bot = Atom(lBOT) | |
# Deductions | |
class Deduction: | |
def __str__(self): | |
return csv(self.assumptions) + lVDA + str(self.prop) | |
class Assume(Deduction): | |
def __init__(self, prop): | |
self.assumptions = frozenset({prop}) | |
self.prop = prop | |
self.parents = () | |
class Intro(Deduction): | |
def __init__(self, premise, d): | |
self.assumptions = d.assumptions - {premise} | |
self.prop = Arrow(premise, d.prop) | |
self.parents = (d, ) | |
class Elim(Deduction): | |
def __init__(self, d1, d2): | |
assert(d1.prop.premise == d2.prop) | |
self.assumptions = d1.assumptions | d2.assumptions | |
self.prop = d1.prop.conclusion | |
self.parents = (d1, d2) | |
# Kripke trees | |
class Tree: | |
def __init__(self, labels, branches=(), disproves=None): | |
assert(all(x in b.labels for x in labels for b in branches)) | |
self.labels = labels | |
self.branches = branches | |
self.disproves = disproves | |
print(self) | |
assert(disproves is None or not self.forces(disproves)) | |
def forces(self, x): | |
if x.is_atom(): | |
return x in self.labels | |
else: | |
return (self.forces(x.conclusion) | |
or (not self.forces(x.premise) | |
and all(b.forces(x) for b in self.branches))) | |
def __repr__(self): | |
return 'Tree({}, {}, {})'.format(repr(self.labels), repr(self.branches), repr(self.disproves)) | |
@compose('\n'.join) | |
def _indent(self, n=0): | |
for line in str(self).split('\n'): | |
yield ' ' * n + line | |
@compose('\n'.join) | |
def __str__(self): | |
yield '\- ' + csv(self.labels) + ' \t\t ' + str(self.disproves) | |
for branch in self.branches: | |
yield branch._indent(1) | |
# Algorithm | |
def reduced(partials, n=0): | |
'''Combine partial proofs to get the simplest set of conclusions.''' | |
log(n, "Simplifying", csv(partials)) | |
modified = True | |
while modified: | |
modified = False | |
new_partials = set() | |
for p in partials: | |
if p.prop.is_arrow(): | |
for q in partials: | |
if p.prop.premise == q.prop: | |
r = Elim(p, q) | |
log(n, "From", p.prop, "and", q.prop, ", get", r.prop) | |
new_partials.add(r) | |
modified = True | |
break | |
else: | |
new_partials.add(p) | |
else: | |
new_partials.add(p) | |
partials = new_partials | |
return partials | |
def examine(goal, partials=None, n=0): | |
'''Get a proof or a countermodel of a formula.''' | |
partials = partials or set() | |
log(n, "Trying to prove", csv(x.prop for x in partials), lVDA, goal) | |
original_goal = goal | |
new_assumptions = [] | |
# First, restate the problem by simplifying the right side | |
while goal.is_arrow(): | |
log(n, "Assume", goal.premise, "and prove", goal.conclusion) | |
new_assumptions.append(Assume(goal.premise)) | |
goal = goal.conclusion | |
partials |= set(new_assumptions) | |
while True: | |
partials = reduced(partials, n) | |
log(n, "Now trying to prove", csv(x.prop for x in partials), lVDA, goal) | |
# Check if the goal is already proved by an assumption | |
for p in partials: | |
if goal == p.prop: | |
log(n, "Which is trivial.") | |
log(n, "We have", csv(p.assumptions), lVDA, p.prop) | |
while new_assumptions: | |
p = Intro(new_assumptions.pop().prop, p) | |
log(n, "so", csv(p.assumptions), lVDA, p.prop) | |
return p, None | |
# Check if any of the left-nested premises can be proved | |
models = [] | |
for d in (x for x in partials if x.prop.is_left()): | |
log(n, "Perhaps", d.prop.premise, | |
"can be proved from", csv(x.prop for x in partials - {d}), "?") | |
proof, model = examine(d.prop.premise, partials - {d}, n + 1) | |
if model: | |
log(n, "Sadly not") | |
models.append(model) | |
else: | |
# Proved a left-nested premise. Continue | |
partials = (partials - {d}) | {Elim(d, proof)} | |
break | |
else: | |
# No left-nested premises are provable. | |
# Combine all countermodels together | |
log(n, "No provable left-nested premises found.") | |
model = Tree([x.prop for x in partials if x.prop.is_atom()], | |
models, | |
original_goal) | |
assert(not model.forces(goal)) | |
return None, model | |
A = Atom('A') | |
B = Atom('B') | |
C = Atom('C') | |
D = Atom('D') | |
P = Atom('P') | |
Q = Atom('Q') | |
R = Atom('R') | |
#print(examine(A >> (B >> A))) | |
#print() | |
#print(examine(A >> (B >> ~(~~A >> ~~~B)))) | |
# | |
#examine(~(A >> B) >> ~~(B >> A)) # 1 IMPL | |
#print() | |
#examine(A >> (~A >> B)) # 2 IMPL | |
#print() | |
#examine(A >> (~B >> ~~~B)) # 3 IMPL | |
#print() | |
#examine(~(A >> B) >> ~~~B) # 4 IMPL | |
#print() | |
#examine(~A >> (A >> B)) # 5 | |
#print() | |
#examine((~A >> A) >> A) # 6 | |
#print() | |
#examine((A >> (B >> C)) >> (~(A >> C) >> ~~(B >> C))) # 7 IMPL | |
#print() | |
#examine((A >> (B >> C)) >> ~~(~(A >> C) >> ~~~~(B >> C))) # 8 IMPL | |
#print() | |
#examine(~(A >> B) >> A) # 9a IMPL | |
#print() | |
#examine(~(A >> B) >> ~B) # 9b IMPL | |
#print() | |
#examine(((A >> B) >> A) >> A) # 10 (& 11 IMPL) | |
#print() | |
#examine((~~~A >> ~~A) >> A) # 12 IMPL | |
#print() | |
#examine(~A >> ~~(A >> B)) # 13 IMPL | |
#print() | |
#examine(~(~A >> ~B) >> ~~(~B >> ~A)) # 14 | |
#print() | |
examine((A >> (~B >> ~~C)) >> (~(A >> B) >> ~~(A >> C))) # 15 IMPL | |
#print() | |
#examine((A >> ~B) >> (~~A >> ~~~B)) # DM1 IMPL part a | |
#print() | |
#examine((~~A >> ~~~B) >> (A >> ~B)) # DM1 IMPL part b | |
#print() | |
#examine(~(~A >> ~~B) >> ~A) # DM2 IMPL part a | |
#print() | |
#examine(~(~A >> ~~B) >> ~B) # DM2 IMPL part b | |
#print() | |
#examine(~A >> (~B >> ~(~A >> ~~B))) # DM2 IMPL part c | |
#print() | |
#examine((~A >> ~~B) >> (~A >> ~~B)) # DM1' IMPL | |
#print() | |
#examine(~(~~A >> ~~~B) >> A) # DM2' IMPL part a | |
#print() | |
#examine(~(~~A >> ~~~B) >> B) # DM2' IMPL part b | |
#print() | |
#examine(A >> (B >> ~(~~A >> ~~~B))) # DM2' IMPL part c | |
#print() | |
# | |
#examine((A >> ~~B) >> ~~(A >> B)) # 17 | |
#print() | |
#examine(~~(A >> B) >> (A >> ~~B)) # 17 converse | |
#print() | |
#examine(~~(A >> B) >> (~~A >> B)) # 18 | |
#print() | |
#examine((~~A >> B) >> ~~(A >> B)) # 18 converse | |
#print() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment