Verification of the Ordnode α
datatype #
This file proves the correctness of the operations in Data.Ordmap.Ordnode
.
The public facing version is the type Ordset α
, which is a wrapper around
Ordnode α
which includes the correctness invariant of the type, and it exposes
parallel operations like insert
as functions on Ordset
that do the same
thing but bundle the correctness proofs. The advantage is that it is possible
to, for example, prove that the result of find
on insert
will actually find
the element, while Ordnode
cannot guarantee this if the input tree did not
satisfy the type invariants.
Main definitions #
Ordset α
: A well formed set of values of typeα
Implementation notes #
The majority of this file is actually in the Ordnode
namespace, because we first
have to prove the correctness of all the operations (and defining what correctness
means here is actually somewhat subtle). So all the actual Ordset
operations are
at the very end, once we have all the theorems.
An Ordnode α
is an inductive type which describes a tree which stores the size
at
internal nodes. The correctness invariant of an Ordnode α
is:
Ordnode.Sized t
: All internalsize
fields must match the actual measured size of the tree. (This is not hard to satisfy.)Ordnode.Balanced t
: Unless the tree has the form()
or((a) b)
or(a (b))
(that is, nil or a single singleton subtree), the two subtrees must satisfysize l ≤ δ * size r
andsize r ≤ δ * size l
, whereδ := 3
is a global parameter of the data structure (and this property must hold recursively at subtrees). This is why we say this is a "size balanced tree" data structure.Ordnode.Bounded lo hi t
: The members of the tree must be in strictly increasing order, meaning that ifa
is in the left subtree andb
is the root, thena ≤ b
and¬ (b ≤ a)
. We enforce this usingOrdnode.Bounded
which includes also a global upper and lower bound.
Because the Ordnode
file was ported from Haskell, the correctness invariants of some
of the functions have not been spelled out, and some theorems like
Ordnode.Valid'.balanceL_aux
show very intricate assumptions on the sizes,
which may need to be revised if it turns out some operations violate these assumptions,
because there is a decent amount of slop in the actual data structure invariants, so the
theorem will go through with multiple choices of assumption.
Note: This file is incomplete, in the sense that the intent is to have verified
versions and lemmas about all the definitions in Ordnode.lean
, but at the moment only
a few operations are verified (the hard part should be out of the way, but still).
Contributors are encouraged to pick this up and finish the job, if it appeals to you.
Tags #
ordered map, ordered set, data structure, verified programming
delta and ratio #
The BalancedSz l r
asserts that a hypothetical tree with children of sizes l
and r
is
balanced: either l ≤ δ * r
and r ≤ δ * r
, or the tree is trivial with a singleton on one side
and nothing on the other.
Equations
- Ordnode.BalancedSz l r = (l + r ≤ 1 ∨ l ≤ Ordnode.delta * r ∧ r ≤ Ordnode.delta * l)
Instances For
Equations
- Ordnode.BalancedSz.dec x✝¹ x✝ = inferInstanceAs (Decidable (x✝¹ + x✝ ≤ 1 ∨ x✝¹ ≤ Ordnode.delta * x✝ ∧ x✝ ≤ Ordnode.delta * x✝¹))
The Balanced t
asserts that the tree t
satisfies the balance invariants
(at every level).
Equations
- Ordnode.nil.Balanced = True
- (Ordnode.node size l x_1 r).Balanced = (Ordnode.BalancedSz l.size r.size ∧ l.Balanced ∧ r.Balanced)
Instances For
Equations
- Ordnode.Balanced.dec Ordnode.nil = ⋯.mpr inferInstance
- Ordnode.Balanced.dec (Ordnode.node size l x_1 r) = ⋯.mpr inferInstance
Build a tree from three nodes, with a () b -> (a ()) b
and a (b c) d -> ((a b) (c d))
.
Equations
- x✝³.node4L x✝² (Ordnode.node size ml y mr) x✝¹ x✝ = (x✝³.node' x✝² ml).node' y (mr.node' x✝¹ x✝)
- x✝³.node4L x✝² Ordnode.nil x✝¹ x✝ = x✝³.node3L x✝² Ordnode.nil x✝¹ x✝
Instances For
Build a tree from three nodes, with a () b -> a (() b)
and a (b c) d -> ((a b) (c d))
.
Equations
- x✝³.node4R x✝² (Ordnode.node size ml y mr) x✝¹ x✝ = (x✝³.node' x✝² ml).node' y (mr.node' x✝¹ x✝)
- x✝³.node4R x✝² Ordnode.nil x✝¹ x✝ = x✝³.node3R x✝² Ordnode.nil x✝¹ x✝
Instances For
Concatenate two nodes, performing a left rotation x (y z) -> ((x y) z)
if balance is upset.
Equations
- x✝¹.rotateL x✝ (Ordnode.node size m y r) = if m.size < Ordnode.ratio * r.size then x✝¹.node3L x✝ m y r else x✝¹.node4L x✝ m y r
- x✝¹.rotateL x✝ Ordnode.nil = x✝¹.node' x✝ Ordnode.nil
Instances For
Concatenate two nodes, performing a right rotation (x y) z -> (x (y z))
if balance is upset.
Equations
- (Ordnode.node size l x_3 m).rotateR x✝¹ x✝ = if m.size < Ordnode.ratio * l.size then l.node3R x_3 m x✝¹ x✝ else l.node4R x_3 m x✝¹ x✝
- Ordnode.nil.rotateR x✝¹ x✝ = Ordnode.nil.node' x✝¹ x✝
Instances For
A left balance operation. This will rebalance a concatenation, assuming the original nodes are not too far from balanced.
Equations
Instances For
A right balance operation. This will rebalance a concatenation, assuming the original nodes are not too far from balanced.
Equations
Instances For
The full balance operation. This is the same as balance
, but with less manual inlining.
It is somewhat easier to work with this version in proofs.
Equations
- One or more equations did not get rendered due to their size.
Instances For
All
, Any
, Emem
, Amem
#
toList
#
(find/erase/split)(Min/Max)
#
bounded
#
Bounded t lo hi
says that every element x ∈ t
is in the range lo < x < hi
, and also this
property holds recursively in subtrees, making the full tree a BST. The bounds can be set to
lo = ⊥
and hi = ⊤
if we care only about the internal ordering constraints.
Equations
Instances For
The validity predicate for an Ordnode
subtree. This asserts that the size
fields are
correct, the tree is balanced, and the elements of the tree are organized according to the
ordering. This version of Valid
also puts all elements in the tree in the interval (lo, hi)
.
- ord : t.Bounded lo hi
- sz : t.Sized
- bal : t.Balanced
Instances For
An Ordset α
is a finite set of values, represented as a tree. The operations on this type
maintain that the tree is balanced and correctly stores subtree sizes at each level. The
correctness property of the tree is baked into the type, so all operations on this type are correct
by construction.
Instances For
O(1). Construct a singleton set containing value a
.
Equations
- Ordset.singleton a = ⟨{a}, ⋯⟩
Instances For
Equations
- Ordset.instEmptyCollection = { emptyCollection := Ordset.nil }
Equations
- Ordset.Empty.instDecidablePred x✝ = decidable_of_iff' ((↑x✝).empty = true) ⋯
O(log n). Insert an element into the set, preserving balance and the BST property. If an equivalent element is already in the set, this replaces it.
Equations
- Ordset.insert x s = ⟨Ordnode.insert x ↑s, ⋯⟩
Instances For
O(log n). Insert an element into the set, preserving balance and the BST property. If an equivalent element is already in the set, the set is returned as is.
Equations
- Ordset.insert' x s = ⟨Ordnode.insert' x ↑s, ⋯⟩
Instances For
O(log n). Does the set contain the element x
? That is,
is there an element that is equivalent to x
in the order?
Equations
- Ordset.mem x s = decide (x ∈ ↑s)
Instances For
O(log n). Retrieve an element in the set that is equivalent to x
in the order,
if it exists.
Equations
- Ordset.find x s = Ordnode.find x ↑s
Instances For
Equations
- Ordset.instMembership = { mem := fun (s : Ordset α) (x : α) => Ordset.mem x s = true }
Equations
- Ordset.mem.decidable x s = instDecidableEqBool (Ordset.mem x s) true
O(log n). Remove an element from the set equivalent to x
. Does nothing if there
is no such element.
Equations
- Ordset.erase x s = ⟨Ordnode.erase x ↑s, ⋯⟩
Instances For
O(n). Map a function across a tree, without changing the structure.
Equations
- Ordset.map f f_strict_mono s = ⟨Ordnode.map f ↑s, ⋯⟩