Skip to content

Commit 227b676

Browse files
committed
getting started
1 parent 4c0264b commit 227b676

File tree

5 files changed

+136
-6
lines changed

5 files changed

+136
-6
lines changed

README.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,106 @@ python3 -m pip install -e .
2424
./install.sh # install extra solvers
2525
```
2626

27+
## Getting Started
28+
29+
```python
30+
import kdrag as kd
31+
import kdrag.smt as smt # smt is literally a reexporting of z3
32+
33+
# Anything Z3 can do on it's own, we can "prove" with no extra work
34+
p,q = smt.Bools("p q")
35+
simple_taut = kd.lemma(smt.Implies(p, smt.Or(p, q)))
36+
37+
# The returned objects are `Proof`, not smt.ExprRef` formulas
38+
assert kd.kernel.is_proof(simple_taut)
39+
assert not isinstance(simple_taut, smt.ExprRef)
40+
41+
try:
42+
false_lemma = kd.lemma(smt.Implies(p, smt.And(p, q)))
43+
assert False # This will not be reached
44+
except kd.kernel.LemmaError as e:
45+
pass
46+
47+
# Z3 also supports things like Reals, Ints, BitVectors and strings
48+
x = smt.Real("x")
49+
real_trich = kd.lemma(smt.ForAll([x], smt.Or(x < 0, x == 0, 0 < x)))
50+
51+
x = smt.BitVec("x", 32)
52+
or_idem = kd.lemma(smt.ForAll([x], x | x == x))
53+
54+
################
55+
56+
# Knuckledragger also support algebraic datatypes and induction
57+
Nat = kd.Inductive("Nat", strict=False)
58+
Zero = Nat.declare("Zero")
59+
Succ = Nat.declare("Succ", ("pred", Nat))
60+
Nat = Nat.create()
61+
62+
# We can define an addition function
63+
n,m = smt.Consts("n m", Nat)
64+
add = smt.Function("add", Nat, Nat, Nat)
65+
add = kd.define("add", [n,m], kd.cond(
66+
(n.is_Zero, m),
67+
(n.is_Succ, Nat.Succ(add(n.pred, m)))
68+
))
69+
70+
# There is a notation overloading mechanism modelled after python's singledispatch
71+
kd.notation.add.register(Nat, add)
72+
73+
# The definitional lemma is not available to the solver unless you give it
74+
add_zero_x = kd.lemma(smt.ForAll([n], Nat.Zero + n == n), by=[add.defn])
75+
add_succ_x = kd.lemma(smt.ForAll([n,m], Nat.Succ(n) + m == Nat.Succ(n + m)), by=[add.defn])
76+
77+
# More involved proofs can be more easily done in an interactive tactic
78+
l = kd.Lemma(smt.ForAll([n], n + Nat.Zero == n))
79+
_n = l.fixes()
80+
l.induct(_n)
81+
l.auto(by=[add.defn])
82+
l.auto(by=[add.defn])
83+
add_x_zero = l.qed()
84+
85+
##############
86+
87+
# But we can also build our own sorts and axiomatic theories.
88+
# https://en.wikipedia.org/wiki/Group_(mathematics)
89+
G = smt.DeclareSort("G")
90+
mul = smt.Function("mul", G, G, G)
91+
e = smt.Const("e", G)
92+
inv = smt.Function("inv", G, G)
93+
94+
kd.notation.mul.register(G, mul)
95+
96+
x, y, z = smt.Consts("x y z", G)
97+
mul_assoc = kd.axiom(smt.ForAll([x, y, z], x * (y * z) == (x * y) * z))
98+
id_left = kd.axiom(smt.ForAll([x], e * x == x))
99+
inv_left = kd.axiom(smt.ForAll([x], inv(x) * x == e))
100+
101+
# The Calc tactic can allow one to write explicit equational proofs
102+
c = kd.Calc([x], x * inv(x))
103+
c.eq(e * (x * inv(x)), by=[id_left])
104+
c.eq((inv(inv(x)) * inv(x)) * (x * inv(x)), by=[inv_left])
105+
c.eq(inv(inv(x)) * ((inv(x) * x) * inv(x)), by=[mul_assoc])
106+
c.eq(inv(inv(x)) * (e * inv(x)), by=[inv_left])
107+
c.eq(inv(inv(x)) * inv(x), by=[id_left])
108+
c.eq(e, by=[inv_left])
109+
inv_right = c.qed()
110+
```
111+
112+
For more on using z3py
113+
114+
- <https://ericpony.github.io/z3py-tutorial/guide-examples.htm>
115+
- The z3 guide <https://microsoft.github.io/z3guide/>
116+
- The z3py [documentation](https://z3prover.github.io/api/html/namespacez3py.html)
117+
- <https://github.com/philzook58/z3_tutorial> ([video](https://www.youtube.com/watch?v=56IIrBZy9Rc&feature=youtu.be&ab_channel=BroadInstitute))
118+
119+
For more on interactive theorem proving (This is a lot to take in)
120+
121+
- [Software Foundations](https://softwarefoundations.cis.upenn.edu/) - Coq
122+
- [Theorem Proving in Lean 4](https://lean-lang.org/theorem_proving_in_lean4/title_page.html)
123+
- [Mathematics in Lean](https://leanprover-community.github.io/mathematics_in_lean/)
124+
- [Isabelle Tutorial](https://isabelle.in.tum.de/documentation.html)
125+
- [HOL Light Tutorial](https://hol-light.github.io/tutorial.pdf)
126+
27127
## Blog Posts
28128

29129
- ['Lean-style' Tactics in Knuckledragger](https://www.philipzucker.com/knuckle_lemma/)

kdrag/notation.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def datatype_call(self, *args):
184184
records = {}
185185

186186

187-
def Record(name, *fields, pred=None):
187+
def Record(name: str, *fields, pred=None) -> smt.DatatypeSortRef:
188188
"""
189189
Define a record datatype.
190190
The optional argument `pred` will add a well-formedness condition to the record
@@ -216,7 +216,7 @@ def Record(name, *fields, pred=None):
216216
return rec
217217

218218

219-
def NewType(name, sort, pred=None):
219+
def NewType(name: str, sort: smt.SortRef, pred=None) -> smt.DatatypeSortRef:
220220
"""Minimal wrapper around a sort for sort based overloading"""
221221
return Record(name, ("val", sort), pred=pred)
222222

@@ -254,7 +254,7 @@ def induct_inductive(DT: smt.DatatypeSortRef, x=None, P=None) -> kd.kernel.Proof
254254
)
255255

256256

257-
def Inductive(name, strict=True):
257+
def Inductive(name: str, strict=True) -> smt.DatatypeSortRef:
258258
"""Declare datatypes with auto generated induction principles. Wrapper around z3.Datatype"""
259259
if strict and name in records:
260260
raise Exception(

kdrag/tactics.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -475,13 +475,13 @@ def newgoal(self, newgoal: smt.BoolRef, **kwargs):
475475
self.goals[-1] = goalctx._replace(goal=newgoal)
476476
return self.top_goal()
477477

478-
def unfold(self, *decls: smt.FuncDeclRef):
478+
def unfold(self, *decls: smt.FuncDeclRef, at=None):
479479
"""
480480
Unfold the contents of a definition.
481481
"""
482482
for decl in decls:
483483
if hasattr(decl, "defn"):
484-
self.rewrite(decl.defn)
484+
self.rewrite(decl.defn, at=at)
485485
else:
486486
raise ValueError("Unfold failed. Not a defined function")
487487
return self.top_goal()

kdrag/theories/real/__init__.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,20 @@ def abstract_arith(t: smt.ExprRef) -> smt.ExprRef:
319319

320320
has_diff_at = smt.Function("has_diff_at", RFun, R, R, smt.BoolSort())
321321
diff_at = kd.define("diff_at", [f, x], smt.Exists([y], has_diff_at(f, x, y)))
322-
cont_at = smt.Function("cont_at", RFun, R, smt.BoolSort())
322+
cont_at = kd.define(
323+
"cont_at",
324+
[f, x],
325+
kd.QForAll(
326+
[eps],
327+
eps > 0,
328+
kd.QExists(
329+
[delta],
330+
delta > 0,
331+
kd.QForAll([y], abs(x - y) < delta, abs(f[x] - f[y]) < eps),
332+
),
333+
),
334+
)
335+
# smt.Function("cont_at", RFun, R, smt.BoolSort())
323336

324337
is_diff = kd.define("is_diff", [f], smt.ForAll([x], diff_at(f, x)))
325338
is_cont = kd.define("is_cont", [f], smt.ForAll([x], cont_at(f, x)))

tests/test_notebooks.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,20 @@ def test_notebook_execution(notebook_path):
1414
]
1515
client = NotebookClient(nb)
1616
client.execute()
17+
18+
19+
import re
20+
21+
22+
def test_readme():
23+
with open("README.md", "r") as file:
24+
content = file.read()
25+
26+
# Regular expression to match Python code blocks
27+
python_code_blocks = re.findall(r"```python\n(.*?)```", content, re.DOTALL)
28+
29+
if not python_code_blocks:
30+
raise ValueError("No Python code blocks found in the README.md file.")
31+
32+
for i, block in enumerate(python_code_blocks, 1):
33+
exec(block)

0 commit comments

Comments
 (0)