Skip to content

Commit 24ef51b

Browse files
author
Ryan Lahfa
committed
docs: init first sketch
This is a first sketch of user documentation for Aeneas to prove things with Lean. Signed-off-by: Ryan Lahfa <[email protected]>
1 parent 486b1c7 commit 24ef51b

File tree

12 files changed

+347
-3
lines changed

12 files changed

+347
-3
lines changed

docs/user/book.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ language = "en"
44
multilingual = false
55
src = "src"
66
title = "Aeneas user documentation"
7+
8+
[output.html]
9+
mathjax-support = true

docs/user/src/SUMMARY.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@
66

77
# Proving with Lean
88

9-
## Tactics guide
10-
11-
- [Progress](./lean/tactics/progress.md)
9+
- [Getting started]()
10+
- [Defining Binary Search Trees in Rust](./rust/bst_def.md)
11+
- [Extracting to Lean]()
12+
- [Panic-freedomness]()
13+
- [Specifying Invariants in Lean]()
14+
- [Invariants of a binary search tree](./lean/bst/invariants.md)
15+
- [Picking mathematical representations](./lean/bst/math-repr.md)
16+
- [Verifying `Find` Operation]()
17+
- [What does it mean to be in a tree?](./lean/bst/set_of_values.md)
18+
- [Dealing with Loops](./lean/bst/loops.md)
19+
- [Applying Known Specifications: `progress` tactic](./lean/bst/progress.md)
20+
- [Verifying Insert Operation]()
21+
- [Dealing with Large Proofs]()
22+
- [Dealing with Symmetry]()
23+
- [Verifying Height Operation](./lean/bst/height.md)
24+
- [Dealing with Termination Issues](./lean/bst/termination.md)
25+
- [Dealing with Machine Integers: `scalar_tac` Tactic](./lean/bst/scalars.md)
26+
- [Refining the Translation](./lean/bst/refining.md)

docs/user/src/lean/bst/height.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Verifying Height Operation
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Invariants of a binary search tree
2+
3+
The binary search tree invariant \\( \textrm{BST}(T) \\) can be defined inductively:
4+
5+
- \\( \textrm{BST}(\emptyset) \\) where \\( \emptyset \\) is the empty binary tree
6+
- yadayada
7+
8+
Mirroring this mathematical definition in the Lean theorem prover yields to an inductive predicate:
9+
10+
```lean
11+
-- A helper inductive predicate over trees
12+
13+
inductive ForallNode (p: T -> Prop): Tree T -> Prop
14+
| none : ForallNode p none
15+
| some (a: T) (left: Tree T) (right: Tree T) : ForallNode p left -> p a -> ForallNode p right -> ForallNode p (some (Node.mk a left right))
16+
17+
--- To express inequalities between values of `T`, it is necessary to require an linear order (also known as total order) over `T`.
18+
variable [LinearOrder T]
19+
inductive Invariant: Tree T -> Prop
20+
| none : Invariant none
21+
| some (a: T) (left: Tree T) (right: Tree T) :
22+
ForallNode (fun v => v < a) left -> ForallNode (fun v => a < v) right
23+
-> Invariant left -> Invariant right -> Invariant (some (Node.mk a left right))
24+
```
25+
26+
Once those definitions are provided, helper theorems on `ForallNode` and `Invariant` will be useful.
27+
28+
As exercises, consider proving the following statements:
29+
30+
- `ForallNode.not_mem {a: T} (p: T -> Prop) (t: Option (Node T)): ¬ p a -> ForallNode p t -> a ∉ Tree.set t`: given an element \\( a \\) and a predicate \\( p \\), if \\( \lnot p(a) \\) but all the nodes in the tree \\( t \\) verifies the predicate \\( p \\), then \\( a \\) cannot be part of that tree.
31+
- `singleton_bst {a: T}: Invariant (some (Node.mk a none none))` : the "singleton" tree, i.e. a tree with no subtrees, is a binary search tree.
32+
- `left_pos {left right: Option (Node T)} {a x: T}: BST.Invariant (some (Node.mk a left right)) -> x ∈ Tree.set (Node.mk a left right) -> x < a -> x ∈ Tree.set left`, that is a sub-tree localisation theorem by comparing \\( x \\) to the root.

docs/user/src/lean/bst/loops.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Dealing with Loops
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# Picking mathematical representations
2+
3+
While defining binary search trees and its operations, we needed to assume that `T`, the type of elements, supported a comparison operation:
4+
5+
```rust,noplayground
6+
pub enum Ordering {
7+
Less,
8+
Equal,
9+
Greater,
10+
}
11+
12+
trait Ord {
13+
fn cmp(&self, other: &Self) -> Ordering;
14+
}
15+
16+
impl Ord for u32 {
17+
fn cmp(&self, other: &Self) -> Ordering {
18+
if *self < *other {
19+
Ordering::Less
20+
} else if *self == *other {
21+
Ordering::Equal
22+
} else {
23+
Ordering::Greater
24+
}
25+
}
26+
}
27+
```
28+
29+
To be able to verify that the binary search tree works the way we expect, we need two ingredients:
30+
31+
- figure out what is the mathematical representation of the order we need for the binary search tree: in the invariant section, we chose `LinearOrder T` which is a total order over `T`. It's sufficient to build a theory of *sound* binary search trees.
32+
33+
- ensure that actual trait implementations satisfy our mathematical refinement we need for our verification: prove that the `u32` implementation of `Ord` yields to a `LinearOrder U32` in Lean.
34+
35+
## Why is it necessary to care about `Ord` ?
36+
37+
Consider this implementation of `Ord` for `u32`:
38+
39+
```rust,noplayground
40+
impl Ord for u32 {
41+
fn cmp(&self, other: &Self) -> Ordering {
42+
if *self == 5924 { panic!(); }
43+
44+
if *self < *other {
45+
Ordering::Less
46+
} else if *self == *other {
47+
Ordering::Equal
48+
} else {
49+
Ordering::Greater
50+
}
51+
}
52+
}
53+
```
54+
55+
A `u32` tree based on that implementation will not have any issue as long as we never insert `5924` in there.
56+
57+
From this, trait implementations have load-bearing consequences on the correctness of more complicated structures such as a tree and the verification needs to take them into account to ensure a complete correctness.
58+
59+
Furthermore, this problem can arise if the mathematical representation chosen is too ideal: that is, no trait implementation can be written such that it fulfills the mathematical representation chosen, or, **worse**, there's no such mathematical object *at all*.
60+
61+
If a whole verification is constructed on the top of an *impossible-to-fulfill-in-practice* mathematical representation, large changes may be necessary to repair the representation.
62+
63+
## Scalar theory in Aeneas
64+
65+
Aeneas provides a generic `Scalar` type mirroring some of the Rust scalar theory, i.e. scalar operations such as additions and multiplications are mirrored faithfully.
66+
67+
In Rust, scalars does not form an ideal mathematical structure, that is: `U64` is not \(( \mathbb{N} \)).
68+
69+
Likewise, additions of `U64` is well-defined as long as the result is contained in a `U64`, which means that the addition is fallible.
70+
71+
In practice, Rust will panic if addition overflows unexpectedly (i.e. the code makes no use of explicit overflowing addition operators), the extraction reflects this behavior by having most operations returns a `Result (Scalar ScalarTy)` where `ScalarTy` are the word sizes, e.g. `.U32`.
72+
73+
Nonetheless, Rust scalars do enjoy bits of mathematical structure such as linear order definitions and panic-freedomness with their default trait implementations.
74+
75+
## A linear order over `Scalar .U32`
76+
77+
Here, we will give an example of a `LinearOrder` definition for the Aeneas scalars:
78+
79+
```lean
80+
instance ScalarU32DecidableLE : DecidableRel (· ≤ · : U32 -> U32 -> Prop) := by
81+
simp [instLEScalar]
82+
-- Lift this to the decidability of the Int version.
83+
infer_instance
84+
85+
instance : LinearOrder (Scalar .U32) where
86+
le_antisymm := fun a b Hab Hba => by
87+
apply (Scalar.eq_equiv a b).2; exact (Int.le_antisymm ((Scalar.le_equiv _ _).1 Hab) ((Scalar.le_equiv _ _).1 Hba))
88+
le_total := fun a b => by
89+
rcases (Int.le_total a b) with H | H
90+
left; exact (Scalar.le_equiv _ _).2 H
91+
right; exact (Scalar.le_equiv _ _).2 H
92+
decidableLE := ScalarU32DecidableLE
93+
```
94+
95+
This definition just exploits the fact that Aeneas' scalars can be injected in \(( \mathbb{Z} \)) and that various properties can be transferred back'n'forth via an equivalence theorem.
96+
97+
This definition is part of the Aeneas standard library, so you usually do not have to write your own definitions.
98+
99+
If you find a missing generic definition that could be useful in general, do not hesitate to send a pull request to the Aeneas project.
100+
101+
## An bundling of Rust orders in Lean world
102+
103+
Nonetheless, the previous definition is insufficient on its own, as a user can provide its own `Ord` implementation, we need to bundle a user-provided `Ord` implementation with a verification-provided `Ord` specification.
104+
105+
Consider the following:
106+
107+
```lean
108+
variable {T: Type} (H: outParam (verification.Ord T))
109+
110+
-- Panic-freedomness of the Rust `Ord` implementation `H`
111+
class OrdSpec [Ord T] where
112+
infallible: ∀ a b, ∃ (o: verification.Ordering), H.cmp a b = .ok o ∧ compare a b = o.toLeanOrdering
113+
114+
-- `a ≤ b <-> b ≥ a`
115+
class OrdSpecSymmetry [O: Ord T] extends OrdSpec H where
116+
symmetry: ∀ a b, O.compare a b = (O.opposite.compare a b).toDualOrdering
117+
118+
-- A generalized equality specification
119+
class OrdSpecRel [O: Ord T] (R: outParam (T -> T -> Prop)) extends OrdSpec H where
120+
equivalence: ∀ a b, H.cmp a b = .ok .Equal -> R a b
121+
122+
-- We specialize the previous specifications to the case of the equivalence relation `=`, equality.
123+
class OrdSpecLinearOrderEq [O: Ord T] extends OrdSpecSymmetry H, OrdSpecRel H Eq
124+
```
125+
126+
With all those pieces, we only need to prove that the extracted `OrdU32` implementation fulfills `OrdSpecLinearOrderEq` which is one of the pre-requisite to benefit from a formal verification of binary search trees over Rust `u32` scalars.
127+
128+
Here's a solution to that proof:
129+
130+
```lean
131+
instance : OrdSpecLinearOrderEq OrdU32 where
132+
infallible := fun a b => by
133+
unfold Ord.cmp
134+
unfold OrdU32
135+
unfold OrdU32.cmp
136+
rw [LinearOrder.compare_eq_compareOfLessAndEq, compareOfLessAndEq]
137+
if hlt : a < b then
138+
use .Less
139+
simp [hlt]
140+
else
141+
if heq: a = b
142+
then
143+
use .Equal
144+
simp [hlt]
145+
rw [heq]
146+
else
147+
use .Greater
148+
simp [hlt, heq]
149+
symmetry := fun a b => by
150+
rw [Ordering.toDualOrdering, LinearOrder.compare_eq_compareOfLessAndEq, compareOfLessAndEq]
151+
rw [compare, Ord.opposite]
152+
simp [LinearOrder.compare_eq_compareOfLessAndEq, compareOfLessAndEq]
153+
split_ifs with hab hba hba' hab' hba'' _ hba₃ _ <;> tauto
154+
exact lt_irrefl _ (lt_trans hab hba)
155+
rw [hba'] at hab; exact lt_irrefl _ hab
156+
rw [hab'] at hba''; exact lt_irrefl _ hba''
157+
-- The order is total, therefore, we have at least one case where we are comparing something.
158+
cases (lt_trichotomy a b) <;> tauto
159+
equivalence := fun a b => by
160+
unfold Ord.cmp
161+
unfold OrdU32
162+
unfold OrdU32.cmp
163+
simp only []
164+
split_ifs <;> simp only [Result.ok.injEq, not_false_eq_true, neq_imp, IsEmpty.forall_iff]; tauto; try assumption
165+
```
166+
167+
Proving panic-freedomness, symmetry and equality comes from definition unfolding and going through the Rust code which is equal to a 'canonical' definition of `compare` assuming the existence of an linear order.
168+
169+
Therefore, we just reuse the linear order properties to finish most of those proofs once all definitions are unfolded.

docs/user/src/lean/bst/progress.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# The Progress Tactic

docs/user/src/lean/bst/refining.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Refining the Translation

docs/user/src/lean/bst/scalars.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Dealing with Machine Integers: scalar_tac Tactic
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# What does it mean to be in a tree?

0 commit comments

Comments
 (0)