Skip to content

Commit a854ff0

Browse files
committed
improved backwards tactic system
1 parent d089dc9 commit a854ff0

File tree

5 files changed

+114
-59
lines changed

5 files changed

+114
-59
lines changed

kdrag/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
NewType = notation.NewType
1818

1919
Calc = tactics.Calc
20+
Lemma = tactics.Lemma
2021

2122
R = smt.RealSort()
2223
Z = smt.IntSort()

kdrag/kernel.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,20 +225,22 @@ def instan2(ts: list[smt.ExprRef], thm: smt.BoolRef) -> Proof:
225225
def forget(ts: list[smt.ExprRef], pf: Proof) -> Proof:
226226
"""
227227
"Forget" a term using existentials. This is existential introduction.
228+
This could be derived from forget2
228229
"""
229230
assert is_proof(pf)
230-
vs = fresh_const(pf.thm)
231+
vs = [smt.FreshConst(t.sort()) for t in ts]
231232
return __Proof(smt.Exists(vs, smt.substitute(pf.thm, *zip(ts, vs))), reason=[pf])
232233

233234

234235
def forget2(ts: list[smt.ExprRef], thm: smt.BoolRef) -> Proof:
235236
"""
236237
"Forget" a term using existentials. This is existential introduction.
238+
`thm` is an existential formula, and `ts` are terms to substitute those variables with.
237239
forget easily follows.
238240
"""
239-
vs = fresh_const(thm)
241+
assert smt.is_quantifier(thm) and thm.is_exists()
240242
return __Proof(
241-
smt.Implies(thm, smt.Exists(vs, smt.substitute(thm, *zip(ts, vs)))),
243+
smt.Implies(smt.substitute_vars(thm.body(), *reversed(ts)), thm),
242244
reason="exists_intro",
243245
)
244246

kdrag/tactics.py

Lines changed: 68 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -198,84 +198,98 @@ def simp(t: smt.ExprRef, by: list[kd.kernel.Proof] = [], **kwargs) -> kd.kernel.
198198

199199

200200
class Lemma:
201-
# Isar style forward proof
202-
def __init__(self, goal):
203-
self.goal = goal
204-
self.lemmas = []
205-
self.vars = []
206-
self.hyps = []
207-
208-
def intro(self, vars): # fix
209-
self.vars.extend(vars)
210-
return self
211-
212-
def assume(self, hyps):
213-
self.hyps.extend(hyps)
214-
return self
215-
216-
def _wrap(self, form):
217-
return smt.ForAll(self.vars, smt.Implies(smt.And(self.hyps), form))
218-
219-
def have(self, conc, **kwargs):
220-
self.lemmas.append(lemma(self._wrap(conc), **kwargs))
221-
return self
222-
223-
def qed(self):
224-
return lemma(self.goal, by=self.lemmas)
225-
226-
227-
class Lemma2:
228201
# Isar style forward proof
229202
def __init__(self, goal: smt.BoolRef):
230203
# self.cur_goal = goal
231204
self.lemmas = []
232205
self.thm = goal
233-
self.goals = [goal]
206+
self.goals = [([], goal)]
234207

235208
def intros(self):
236-
goal = self.goals.pop()
237-
vs, herb_lemma = kd.kernel.herb(goal)
238-
self.lemmas.append(herb_lemma)
239-
self.goals.append(goal.thm.arg(0))
240-
return vs
209+
ctx, goal = self.goals.pop()
210+
if smt.is_quantifier(goal) and goal.is_forall():
211+
vs, herb_lemma = kd.kernel.herb(goal)
212+
self.lemmas.append(herb_lemma)
213+
self.goals.append((ctx, herb_lemma.thm.arg(0)))
214+
return vs
215+
elif smt.is_implies(goal):
216+
self.goals.append((ctx + [goal.arg(0)], goal.arg(1)))
217+
return self
218+
219+
def cases(self, t):
220+
ctx, goal = self.goals.pop()
221+
if t.sort() == smt.BoolSort():
222+
self.goals.append((ctx + [smt.Not(t)], goal))
223+
self.goals.append((ctx + [t], goal))
224+
elif isinstance(t, smt.DatatypeRef):
225+
dsort = t.sort()
226+
for i in reversed(range(dsort.num_constructors())):
227+
self.goals.append((ctx + [dsort.recognizer(i)(t)], goal))
228+
else:
229+
raise ValueError("Cases failed. Not a bool or datatype")
230+
return self
241231

242-
def cases(self):
243-
pass
232+
def auto(self):
233+
ctx, goal = self.goals[-1]
234+
self.lemmas.append(lemma(smt.Implies(smt.And(ctx), goal)))
235+
self.goals.pop()
236+
return self
244237

245238
def split(self):
246-
goal = self.goals.pop()
239+
ctx, goal = self.goals[-1]
247240
if smt.is_and(goal):
248-
goals.extend(goal.children())
241+
self.goals.pop()
242+
self.goals.extend([(ctx, c) for c in goal.children()])
249243
else:
250244
raise ValueError("Split failed. Not an and")
251245

252246
def exists(self, *ts):
253-
goal = self.goals.pop()
254-
# kd.kernel.forget(self.goal, t)
255-
self.goals.append(utils.instan(goal, *ts))
247+
ctx, goal = self.goals[-1]
248+
lemma = kd.kernel.forget2(ts, goal)
249+
self.lemmas.append(lemma)
250+
self.goals[-1] = (ctx, lemma.thm.arg(0))
251+
return self
256252

257253
def apply(self, pf: kd.kernel.Proof):
258-
goal = self.goals.pop()
259-
self.lemmas.append(pf)
260-
self.goals.append(utils.apply(goal, pf.thm))
261-
# TODO.
262-
# self.lemmas.append(pf)
263-
# self.cur_goal = pf.thm.arg(0)
264-
# return self
265-
266-
def assume(self, hyps):
267-
self.goal.arg(0)
254+
ctx, goal = self.goals.pop()
255+
thm = pf.thm
256+
if smt.is_quantifier(thm) and thm.is_forall():
257+
vs, thm = kd.utils.open_binder(thm)
258+
else:
259+
vs = []
260+
if not smt.is_implies(thm):
261+
head = thm
262+
body = smt.BoolVal(True)
263+
else:
264+
body, head = thm.children()
265+
subst = kd.utils.pmatch(vs, head, goal)
266+
if subst is None:
267+
raise ValueError(f"Apply tactic failed to goal {goal} lemma {pf}")
268+
else:
269+
pf1 = kd.kernel.instan([subst[v] for v in vs], pf)
270+
self.lemmas.append(pf1)
271+
if smt.is_implies(pf1.thm):
272+
self.goals.append((ctx, pf1.thm.arg(0)))
268273
return self
269274

270-
def _wrap(self, form):
271-
return smt.ForAll(self.vars, smt.Implies(smt.And(self.hyps), form))
275+
def assumption(self):
276+
ctx, goal = self.goals.pop()
277+
if any([goal.eq(h) for h in ctx]):
278+
return self
279+
else:
280+
raise ValueError("Assumption tactic failed", goal, ctx)
272281

273282
def have(self, conc, **kwargs):
274-
self.lemmas.append(lemma(self._wrap(conc), **kwargs))
283+
ctx, goal = self.goals.pop()
284+
self.lemmas.append(lemma(smt.Implies(smt.And(ctx), conc)), **kwargs)
285+
self.goals.append((ctx + [conc], conc))
275286
return self
276287

277288
def __repr__(self):
278-
return "?|- " + repr(self.goals[-1])
289+
if len(self.goals) == 0:
290+
return "Nothing to do. Hooray!"
291+
ctx, goal = self.goals[-1]
292+
return repr(ctx) + " ?|- " + repr(goal)
279293

280294
def qed(self):
281295
return lemma(self.thm, by=self.lemmas)

kdrag/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ def check_escape(x):
6565

6666
while todo:
6767
pat, t = todo.pop()
68-
print(todo, pat, t, subst)
6968
if is_var(pat): # regular pattern
7069
if pat in subst:
7170
if not alpha_eq(subst[pat], t):
@@ -497,6 +496,7 @@ def kbo(vs: list[smt.ExprRef], t1: smt.ExprRef, t2: smt.ExprRef) -> Order:
497496
"""
498497
Knuth Bendix Ordering, naive implementation.
499498
All weights are 1.
499+
Source: Term Rewriting and All That section 5.4.4
500500
"""
501501
if t1.eq(t2):
502502
return Order.EQ

tests/test_kernel.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,34 @@ def test_cond():
143143
def test_Lemma():
144144
x = smt.Int("x")
145145
l = kd.tactics.Lemma(x != x + 1)
146-
l.intro([smt.Int("x")])
147146
l.have(x != x + 1)
147+
l.assumption()
148+
l.qed()
149+
150+
p, q, r = smt.Bools("p q r")
151+
l = kd.tactics.Lemma(smt.Implies(p, p))
152+
l.qed()
153+
154+
l = kd.tactics.Lemma(kd.QForAll([p, q], p, q, p))
155+
p1, q1 = l.intros()
156+
l.intros()
157+
l.cases(p1)
158+
l.auto()
159+
l.auto()
160+
l.qed()
161+
162+
y = smt.Int("y")
163+
l = kd.tactics.Lemma(smt.Exists([x, y], smt.And(x == 2, y == 3)))
164+
l.exists(smt.IntVal(2), smt.IntVal(3))
165+
l.auto()
166+
l.qed()
167+
168+
x, y, z = smt.Ints("x y z")
169+
P = smt.Function("P", smt.IntSort(), smt.BoolSort())
170+
myax = kd.axiom(smt.ForAll([z], P(z)))
171+
l = kd.tactics.Lemma(kd.QForAll([x], P(x)))
172+
x1 = l.intros()
173+
l.apply(myax)
148174
l.qed()
149175

150176

@@ -200,3 +226,15 @@ def test_lambda_def():
200226

201227
def test_bv():
202228
bv8 = bitvec.BVTheory(8)
229+
230+
231+
def test_forget():
232+
x, y = smt.Ints("x y")
233+
assert kd.kernel.forget2(
234+
[smt.IntVal(2), smt.IntVal(3)], smt.Exists([x, y], smt.And(x == 2, y == 3))
235+
).thm.eq(
236+
smt.Implies(
237+
smt.And(smt.IntVal(2) == 2, smt.IntVal(3) == 3),
238+
smt.Exists([x, y], smt.And(x == 2, y == 3)),
239+
)
240+
)

0 commit comments

Comments
 (0)