Skip to content

Instantly share code, notes, and snippets.

@mtensor
Last active July 6, 2021 15:50
Show Gist options
  • Save mtensor/d824b8462535d9b2c690008d2db243a0 to your computer and use it in GitHub Desktop.
Save mtensor/d824b8462535d9b2c690008d2db243a0 to your computer and use it in GitHub Desktop.
from z3 import *
class ParseError(Exception):
pass
class FancyDefaultDict(dict):
def __init__(self, fn):
super(FancyDefaultDict, self).__init__()
self.fn = fn
def __missing__(self, key):
res = self[key] = self.fn(key)
return res
class Env:
def __init__(self):
self.people = FancyDefaultDict( lambda personName: Const(personName, Person) )
self.s = Solver()
self.s.set(unsat_core=True)
Person = DeclareSort('Person')
self.Person = Person
# ancestor(x, y) -> ancestor of x is y
ancestor = Function('ancestor', Person, Person, BoolSort())
# anti-reflexitivity
x = Const('x', Person)
y = Const('y', Person)
z = Const('z', Person)
self.x = x
self.y = y
self.z = z
#new stuff
grand = Function('grand', Person, Person, BoolSort())
#grand is short for grandchild
inv_grand = Function('inv_grand', Person, Person, BoolSort())
child = Function('child', Person, Person, BoolSort())
inv_child = Function('inv_child', Person, Person, BoolSort())
sibling = Function('sibling', Person, Person, BoolSort())
in_law = Function('in_law', Person, Person, BoolSort())
inv_in_law = Function('inv_in_law', Person, Person, BoolSort())
#uncle
un = Function('un', Person, Person, BoolSort())
inv_un = Function('inv_un', Person, Person, BoolSort())
SO = Function('SO', Person, Person, BoolSort())
#relations:
self.s.add(ForAll([x, y, z], Implies(And(child(x, z), child(z, y)),
grand(x, y))))
self.s.add(ForAll([x, y, z], Implies(And(SO(x, z), grand(z, y)),
grand(x, y))))
self.s.add(ForAll([x, y, z], Implies(And(grand(x, z), sibling(z, y)),
grand(x, y))))
self.s.add(ForAll([x, y, z], Implies(And(inv_child(x, z), inv_child(z, y)),
inv_grand(x, y))))
self.s.add(ForAll([x, y, z], Implies(And(sibling(x, z), inv_grand(z, y)),
inv_grand(x, y))))
self.s.add(ForAll([x, y, z], Implies(And(child(x, z), sibling(z, y)),
child(x, y))))
self.s.add(ForAll([x, y, z], Implies(And(SO(x, z), child(z, y)),
child(x, y))))
self.s.add(ForAll([x, y, z], Implies(And(sibling(x, z), inv_child(z, y)),
inv_child(x, y))))
self.s.add(ForAll([x, y, z], Implies(And(child(x, z), inv_grand(z, y)),
inv_child(x, y))))
# self.s.add(ForAll([x, y, z], Implies(And(child(x, z), inv_un(z, y)),
# sibling(x, y))))
self.s.add(ForAll([x, y, z], Implies(And(x != y, inv_child(x, z), child(z, y)),
sibling(x, y))))
self.s.add(ForAll([x, y, z], Implies(And(child(x, z), SO(z, y)),
in_law(x, y))))
self.s.add(ForAll([x, y, z], Implies(And(SO(x, z), inv_child(z, y)),
inv_in_law(x, y))))
#terminology for uncle is backwards: un(x,y) -> uncle of y is x
self.s.add(ForAll([x, y, z], Implies(And(sibling(x, z), child(z, y)),
un(x, y))))
self.s.add(ForAll([x, y, z], Implies(And(inv_child(x, z), sibling(z, y)),
inv_un(x, y))))
#inverses:
self.s.add(ForAll([x, y], child(x, y) == inv_child(y, x) ))
self.s.add(ForAll([x, y], inv_in_law(x, y) == in_law(y,x)))
self.s.add(ForAll([x, y], inv_grand(x, y) == grand(y,x)))
self.s.add(ForAll([x, y], inv_un(x, y) == un(y, x)))
#symmetric ones:
#self.s.add(ForAll([x, y], Implies(And(sibling(x, y), x != y), sibling(y,x))))
self.s.add(ForAll([x, y], Implies(sibling(x, y), sibling(y, x))))
self.s.add(ForAll([x, y], SO(y, x) == SO(x, y)))
#what is an ancestor?
self.s.add(ForAll([x, y], Implies( child(x, y), ancestor(y,x) )))
self.s.add(ForAll([x, y], Implies( grand(x, y), ancestor(y,x) )))
#sibling is transitive:
self.s.add(ForAll([x, y, z], Implies(And(x!=z, sibling(x, y), sibling(y, z)),
sibling(x, z))))
#1 you can't be your own ancestor
self.s.add(ForAll([x, y], Implies(ancestor(x, y), Not(ancestor(y, x)))))
# ancestor transitivity
self.s.add(ForAll([x, y, z], Implies(And(ancestor(x, y), ancestor(y, z)),
ancestor(x, z)))) #fine
#2 you can't be your own sibling - need to change rule above
self.s.add(ForAll(x, Not(sibling(x, x))))
#inverse of uncle
self.s.add(ForAll([x, y], Implies(un(x, y), Exists(z, And(sibling(x, z), child(z, y)) ))))
#inverse of sibling
self.s.add(ForAll([x, y], Implies(sibling(x, y), Exists(z, And(child(z, x), child(z, y)) ))))
#parents cant be uncles:
self.s.add(ForAll([x, y], Implies(inv_un(x, y), Not(inv_child(x, y))) ))
self.s.add(ForAll([x, y], Implies(inv_child(x, y), Not(inv_un(x, y))) ))
self.s.push()
self.rels = {rel.name(): rel for rel in [ancestor,
grand, inv_grand, child, inv_child, sibling, in_law, inv_in_law, un, inv_un, SO]} #TODO
#extra names for use of parser
self.rels['spouse'] = SO
self.rels['husband'] = SO
self.rels['wife'] = SO
self.rels['parent'] = inv_child
self.rels['grandchild'] = grand
self.rels['granddaughter'] = grand
self.rels['grandson'] = grand
self.rels['grandparent'] = inv_grand
self.rels['grandmother'] = inv_grand
self.rels['grandfather'] = inv_grand
self.rels['father'] = self.rels['parent']
self.rels['mother'] = self.rels['parent']
self.rels['uncle'] = inv_un
self.rels['aunt'] = inv_un
self.rels['nephew'] = un
self.rels['niece'] = un
self.rels['sister'] = sibling
self.rels['brother'] = sibling
self.rels['friend'] = None
self.rels['dog'] = None
self.rels['pet'] = None
self.rels['cousin'] = None
self.rels['daughter'] = child
self.rels['son'] = child
self.rels['daughter in law'] = in_law
self.rels['son in law'] = in_law
self.rels['mother in law'] = inv_in_law
self.rels['father in law'] = inv_in_law
self.rels['mother-in-law'] = inv_in_law
self.rels['father-in-law'] = inv_in_law
self.rels['daughter-in-law'] = in_law
self.rels['son-in-law'] = in_law
def buildConstraint(self, string):
return constraint
def parseConstraint(self, string):
#for now, assuming string is rel(p1, p2)
if len(string.strip().split('(')) != 2: raise ParseError
command, rest = string.strip().split('(')
if command not in self.rels: return None
if self.rels[command] is None: return None
p1Name, p2Name = rest[:-1].split(', ')
fn = self.rels[command]
constraint = fn(self.people[p1Name], self.people[p2Name])
return constraint
def checkConstraint(self, const_string):
if const_string == "None": return self.s.check()
if "; " in const_string:
for constStr in const_string.split("; "):
if constStr is "None": continue
try:
c = self.parseConstraint(constStr)
except ParseError:
print("parse error")
continue
if c is not None: self.s.add(c)
return self.s.check()
try:
constraint = self.parseConstraint(const_string)
except ParseError:
print("parse error")
return self.s.check()
if constraint is None: return self.s.check()
self.s.add(constraint)
return self.s.check()
def removeLast(self):
self.s.pop()
def findRelations(self, p1Name, p2Name):
#for each relation, check if it CAN be SAT
d = {}
assert self.s.check().r == 1
for name, fn in self.rels.items():
self.s.push()
self.s.add(fn(self.people[p1Name], self.people[p2Name]))
d[name] = self.s.check().r == 1
self.s.pop()
return d
def findNecessaryRelations(self, p1Name, p2Name):
#for each relation, check if it MUST be SAT
d = {}
assert self.s.check().r == 1
for name, fn in self.rels.items():
self.s.push()
self.s.add(Not(fn(self.people[p1Name], self.people[p2Name])))
d[name] = self.s.check().r == -1
self.s.pop()
return d
def checkNecessaryConstraint(self, const_string):
#TODO idk about pushing and pulling or whatever
print("TODO: push/pull")
constraint = self.parseConstraint(const_string)
self.s.add(Not(constraint))
return self.s.check()
def convertFromNL(self, nl_string):
if nl_string == "None": return nl_string
assert nl_string[-1] == "."
nl_string = nl_string[:-1]
if ". " in nl_string:
cs = ""
for constStr in nl_string.split(". "):
c = self.convertFromNL(constStr+".")
if c == "None": continue
if cs is "": cs+= c #don't put semicolon for first one
else: cs += "; " + c
return cs
# const1str, const2str = nl_string.split(". ")
# c1 = self.convertFromNL(const1str+".")
# c2 = self.convertFromNL(const2str+".")
# if c2 == "None": return c1
# if c1 == "None": return c2
# return c1 + "; " + c2
if not " is " in nl_string:
print("error parsing")
return "None"
if not "'s " in nl_string:
print("error parsing")
return "None"
p1, rest = nl_string.split(" is ")
if not len(rest.split("'s ")) == 2: return "None"
p2, rel = rest.split("'s ")
return f"{rel}({p2}, {p1})"
if __name__ == '__main__':
e = Env()
print(e.s.check())
#for string in ['ancestor(bob, joe)', 'ancestor(joe, bob)','ancestor(joe, kevin)']:
#for string in ['child(bob, joe)', 'child(joe, bob)','child(joe, kevin)', 'child(kevin, bob)',
#for string in ['grand(m, m)','child(joe, kevin)', 'child(kevin, joe)', 'sibling(kevin, timmy)', 'inv_un(beth, tom)','sibling(beth, tom)', 'inv_un(jake, tom)', 'SO(jake, beth)', 'SO(jake, tom)', 'SO(jake, bob)']:
for string in ['uncle(Marie, Robert)', 'father(Marie, Robert)']:
sat = e.checkConstraint(string)
print(string)
if sat.r > 0:
print("SEEMS OKAY")
e.s.push()
else:
print(e.s.unsat_core())
assert FALSE, print("INCONSISTENT SET OF CONSTRAINTS")
e.removeLast()
e.s.push()
print()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment