Imports
Complete heap-based Huffman execution
This module joins the verified binary heap to the existing Huffman recursion. The state invariant supplies the global frequency and stable-stamp facts needed to construct every newly merged entry inside the fixed heap bounds.
namespace CLRS.HuffmanV2Sum of root frequencies in an active heap forest.
theorem entryRootSum_eq_of_perm {as bs : List HeapEntry} (h : as.Perm bs) :
entryRootSum as = entryRootSum bs := by
unfold entryRootSum
exact (h.map (fun e => rootFreq e.tree)).sum_eq@[simp] theorem entryRootSum_cons (e : HeapEntry) (es : List HeapEntry) :
entryRootSum (e :: es) = rootFreq e.tree + entryRootSum es := by
simp [entryRootSum]
theorem entryRootSum_decorateFrom (base : Nat) (ts : List HuffTree) :
entryRootSum (decorateFrom base ts) = (ts.map rootFreq).sum := by
induction ts generalizing base with
| nil => rfl
| cons t ts ih =>
rw [decorateFrom, entryRootSum]
simp only [List.map_cons, List.sum_cons]
change rootFreq t + entryRootSum (decorateFrom (base + 1) ts) =
rootFreq t + (ts.map rootFreq).sum
rw [ih]theorem tableFreq_le_tableTotal_of_mem {p : Nat × Nat}
{xs : List (Nat × Nat)} (hp : p ∈ xs) : p.2 ≤ HeapParams.tableTotal xs := by
induction xs with
| nil => simp at hp
| cons q qs ih =>
simp only [List.mem_cons] at hp
rcases hp with hpq | hp
· subst p
simp [HeapParams.tableTotal]
· have htail := ih hp
exact htail.trans (by simp [HeapParams.tableTotal])All initial decorated leaves lie inside the fixed execution envelope.
theorem initialEntries_bounded (xs : List (Nat × Nat)) :
∀ e ∈ initialEntries (leavesOfFreqs xs),
(HeapParams.ofFreqs xs).Bounded e := by
intro e he
constructor
· have htree : e.tree ∈ leavesOfFreqs xs := by
have : e.tree ∈ (initialEntries (leavesOfFreqs xs)).map HeapEntry.tree :=
List.mem_map.mpr ⟨e, he, rfl⟩
simpa using this
rcases List.mem_map.mp htree with ⟨p, hp, htree⟩
rw [← htree]
simpa [HeapParams.ofFreqs, rootFreq] using
tableFreq_le_tableTotal_of_mem hp
· have hstamp := decorateFrom_stamp_lt
(base := (leavesOfFreqs xs).length) (ts := leavesOfFreqs xs) he
simp [HeapParams.ofFreqs, HeapParams.base,
leavesOfFreqs] at hstamp ⊢
omegaThe initial forest's aggregate root frequency is the table total.
theorem initialEntries_rootSum (xs : List (Nat × Nat)) :
entryRootSum (initialEntries (leavesOfFreqs xs)) =
HeapParams.tableTotal xs := by
rw [initialEntries, entryRootSum_decorateFrom]
unfold HeapParams.tableTotal
induction xs with
| nil => rfl
| cons p ps ih =>
change p.2 + (List.map rootFreq (leavesOfFreqs ps)).sum =
p.2 + (List.map Prod.snd ps).sum
rw [ih]Execution invariant maintained across every two-extract/one-insert merge.
structure HeapHuffmanState (params : HeapParams) where
heap : MinHeap params
stampsNodup : (heap.data.map HeapEntry.stamp).Nodup
stampFloor : ∀ e ∈ heap.data, heap.data.length - 1 ≤ e.stamp
rootSum : entryRootSum heap.data = params.totalFreq
size_le_input : heap.data.length ≤ params.inputSizenamespace HeapHuffmanStateInitialize the complete verified state from a raw frequency table.
def ofFreqs (xs : List (Nat × Nat)) : HeapHuffmanState (HeapParams.ofFreqs xs) :=
let entries := initialEntries (leavesOfFreqs xs)
let hbounded := initialEntries_bounded xs
let heap := MinHeap.build (HeapParams.ofFreqs xs) entries hbounded
{ heap := heap
stampsNodup := by
exact (map_stamp_perm_of_perm
(MinHeap.build_perm (HeapParams.ofFreqs xs) entries hbounded)).nodup_iff.mpr
(initialEntries_stamps_nodup (leavesOfFreqs xs))
stampFloor := by
intro e he
have he' : e ∈ entries :=
(List.Perm.mem_iff
(MinHeap.build_perm (HeapParams.ofFreqs xs) entries hbounded)).mp he
have hge := initialEntries_stamp_ge he'
rw [MinHeap.build_size]
simp [entries, leavesOfFreqs] at hge ⊢
omega
rootSum := by
rw [entryRootSum_eq_of_perm
(MinHeap.build_perm (HeapParams.ofFreqs xs) entries hbounded)]
exact initialEntries_rootSum xs
size_le_input := by
rw [MinHeap.build_size]
simp [entries, HeapParams.ofFreqs, leavesOfFreqs] }
@[simp] theorem ofFreqs_size (xs : List (Nat × Nat)) :
(ofFreqs xs).heap.data.length = xs.length := by
unfold ofFreqs
rw [MinHeap.build_size]
simp [leavesOfFreqs]
theorem ofFreqs_ordered_trees (xs : List (Nat × Nat)) :
(ofFreqs xs).heap.orderedView.map HeapEntry.tree =
sortForest (leavesOfFreqs xs) := by
unfold ofFreqs MinHeap.orderedView
let entries := initialEntries (leavesOfFreqs xs)
let hbounded := initialEntries_bounded xs
rw [orderedEntries_eq_of_perm
((map_stamp_perm_of_perm
(MinHeap.build_perm (HeapParams.ofFreqs xs) entries hbounded)).nodup_iff.mpr
(initialEntries_stamps_nodup (leavesOfFreqs xs)))
(MinHeap.build_perm (HeapParams.ofFreqs xs) entries hbounded)]
exact orderedEntries_initial_trees (leavesOfFreqs xs)One verified merge transition
Two successful extractions followed by insertion of their joined tree.
def mergeState {params : HeapParams} (s : HeapHuffmanState params)
{e₁ e₂ : HeapEntry} {h₁ h₂ : MinHeap params}
(hextract₁ : s.heap.extractMin = some (e₁, h₁))
(hextract₂ : h₁.extractMin = some (e₂, h₂)) :
HeapHuffmanState params := by
let oldSize := s.heap.data.length
let newEntry := mergedEntry oldSize e₁ e₂
have hspec₁ := MinHeap.extractMin_spec hextract₁
have hspec₂ := MinHeap.extractMin_spec hextract₂
have hsize₁ : h₁.data.length + 1 = oldSize := by simpa [oldSize] using hspec₁.2.1
have hsize₂ : h₂.data.length + 1 = h₁.data.length := hspec₂.2.1
have holdSize : oldSize = h₂.data.length + 2 := by omega
have hsum₁ : rootFreq e₁.tree + entryRootSum h₁.data = params.totalFreq := by
calc
rootFreq e₁.tree + entryRootSum h₁.data = entryRootSum (e₁ :: h₁.data) := by simp
_ = entryRootSum s.heap.data := entryRootSum_eq_of_perm hspec₁.1
_ = params.totalFreq := s.rootSum
have hsum₂ : rootFreq e₂.tree + entryRootSum h₂.data = entryRootSum h₁.data := by
calc
rootFreq e₂.tree + entryRootSum h₂.data = entryRootSum (e₂ :: h₂.data) := by simp
_ = entryRootSum h₁.data := entryRootSum_eq_of_perm hspec₂.1
have hboundedNew : params.Bounded newEntry := by
constructor
· change rootFreq e₁.tree + rootFreq e₂.tree ≤ params.totalFreq
omega
· change oldSize - 2 < params.base
have hsizeBound : oldSize ≤ params.inputSize := by
simpa [oldSize] using s.size_le_input
simp only [HeapParams.base]
omega
let heap' := h₂.insert newEntry hboundedNew
have hinsertPerm : heap'.data.Perm (newEntry :: h₂.data) :=
MinHeap.insert_perm h₂ newEntry hboundedNew
have hnodup₁ : ((e₁ :: h₁.data).map HeapEntry.stamp).Nodup :=
(map_stamp_perm_of_perm hspec₁.1).nodup_iff.mpr s.stampsNodup
have hnodupH₁ : (h₁.data.map HeapEntry.stamp).Nodup :=
(List.nodup_cons.mp hnodup₁).2
have hnodup₂ : ((e₂ :: h₂.data).map HeapEntry.stamp).Nodup :=
(map_stamp_perm_of_perm hspec₂.1).nodup_iff.mpr hnodupH₁
have hnodupH₂ : (h₂.data.map HeapEntry.stamp).Nodup :=
(List.nodup_cons.mp hnodup₂).2
have hnewFresh : ∀ u ∈ h₂.data, newEntry.stamp < u.stamp := by
intro u hu
have huH₁ : u ∈ h₁.data :=
(List.Perm.mem_iff hspec₂.1).mp (List.mem_cons_of_mem e₂ hu)
have huOld : u ∈ s.heap.data :=
(List.Perm.mem_iff hspec₁.1).mp (List.mem_cons_of_mem e₁ huH₁)
have hfloor := s.stampFloor u huOld
change oldSize - 2 < u.stamp
change oldSize - 1 ≤ u.stamp at hfloor
omega
refine
{ heap := heap'
stampsNodup := ?_
stampFloor := ?_
rootSum := ?_
size_le_input := ?_ }
· apply (map_stamp_perm_of_perm hinsertPerm).nodup_iff.mpr
rw [List.map_cons, List.nodup_cons]
refine ⟨?_, hnodupH₂⟩
intro hmem
rcases List.mem_map.mp hmem with ⟨u, hu, hstamp⟩
have hfresh := hnewFresh u hu
omega
· intro u hu
have hu' : u ∈ newEntry :: h₂.data :=
(List.Perm.mem_iff hinsertPerm).mp hu
simp only [List.mem_cons] at hu'
rcases hu' with hueq | huOld
· subst u
rw [MinHeap.insert_size]
change h₂.data.length + 1 - 1 ≤ oldSize - 2
omega
· have huH₁ : u ∈ h₁.data :=
(List.Perm.mem_iff hspec₂.1).mp (List.mem_cons_of_mem e₂ huOld)
have huSource : u ∈ s.heap.data :=
(List.Perm.mem_iff hspec₁.1).mp (List.mem_cons_of_mem e₁ huH₁)
have hfloor := s.stampFloor u huSource
rw [MinHeap.insert_size]
omega
· calc
entryRootSum heap'.data = entryRootSum (newEntry :: h₂.data) :=
entryRootSum_eq_of_perm hinsertPerm
_ = rootFreq e₁.tree + rootFreq e₂.tree + entryRootSum h₂.data := by
simp [newEntry, mergedEntry, unite, rootFreq]
_ = params.totalFreq := by omega
· rw [MinHeap.insert_size]
have holdBound : oldSize ≤ params.inputSize := by
simpa [oldSize] using s.size_le_input
omegatheorem stampsNodup_after_extract {params : HeapParams}
{h h' : MinHeap params} {e : HeapEntry}
(hnodup : (h.data.map HeapEntry.stamp).Nodup)
(hextract : h.extractMin = some (e, h')) :
(h'.data.map HeapEntry.stamp).Nodup := by
have hspec := MinHeap.extractMin_spec hextract
have hcons : ((e :: h'.data).map HeapEntry.stamp).Nodup :=
(map_stamp_perm_of_perm hspec.1).nodup_iff.mpr hnodup
exact (List.nodup_cons.mp hcons).2@[simp] theorem mergeState_size {params : HeapParams}
(s : HeapHuffmanState params) {e₁ e₂ : HeapEntry} {h₁ h₂ : MinHeap params}
(hextract₁ : s.heap.extractMin = some (e₁, h₁))
(hextract₂ : h₁.extractMin = some (e₂, h₂)) :
(mergeState s hextract₁ hextract₂).heap.data.length + 1 =
s.heap.data.length := by
have hspec₁ := MinHeap.extractMin_spec hextract₁
have hspec₂ := MinHeap.extractMin_spec hextract₂
unfold mergeState
simp only [MinHeap.insert_size]
omegaThe merge transition implements exactly the sorted-list reinsertion step.
theorem mergeState_ordered_trees {params : HeapParams}
(s : HeapHuffmanState params) {e₁ e₂ : HeapEntry} {h₁ h₂ : MinHeap params}
(hextract₁ : s.heap.extractMin = some (e₁, h₁))
(hextract₂ : h₁.extractMin = some (e₂, h₂)) :
(mergeState s hextract₁ hextract₂).heap.orderedView.map HeapEntry.tree =
insortTree (unite e₁.tree e₂.tree) (h₂.orderedView.map HeapEntry.tree) := by
let oldSize := s.heap.data.length
let newEntry := mergedEntry oldSize e₁ e₂
have hnodup₁ := stampsNodup_after_extract s.stampsNodup hextract₁
have hnodup₂ := stampsNodup_after_extract hnodup₁ hextract₂
have hspec₁ := MinHeap.extractMin_spec hextract₁
have hspec₂ := MinHeap.extractMin_spec hextract₂
have hsize₁ : h₁.data.length + 1 = oldSize := by simpa [oldSize] using hspec₁.2.1
have hsize₂ : h₂.data.length + 1 = h₁.data.length := hspec₂.2.1
have hboundedNew : params.Bounded newEntry := by
constructor
· change rootFreq e₁.tree + rootFreq e₂.tree ≤ params.totalFreq
have hsum₁ : rootFreq e₁.tree + entryRootSum h₁.data = params.totalFreq := by
calc
_ = entryRootSum (e₁ :: h₁.data) := by simp
_ = entryRootSum s.heap.data := entryRootSum_eq_of_perm hspec₁.1
_ = params.totalFreq := s.rootSum
have hsum₂ : rootFreq e₂.tree + entryRootSum h₂.data = entryRootSum h₁.data := by
calc
_ = entryRootSum (e₂ :: h₂.data) := by simp
_ = entryRootSum h₁.data := entryRootSum_eq_of_perm hspec₂.1
omega
· change oldSize - 2 < params.base
have holdBound : oldSize ≤ params.inputSize := by
simpa [oldSize] using s.size_le_input
simp only [HeapParams.base]
omega
have hconsNodup : ((newEntry :: h₂.data).map HeapEntry.stamp).Nodup := by
rw [List.map_cons, List.nodup_cons]
refine ⟨?_, hnodup₂⟩
intro hmem
rcases List.mem_map.mp hmem with ⟨u, hu, hstamp⟩
have huH₁ : u ∈ h₁.data :=
(List.Perm.mem_iff hspec₂.1).mp (List.mem_cons_of_mem e₂ hu)
have huOld : u ∈ s.heap.data :=
(List.Perm.mem_iff hspec₁.1).mp (List.mem_cons_of_mem e₁ huH₁)
have hfloor := s.stampFloor u huOld
have hlt : newEntry.stamp < u.stamp := by
change oldSize - 2 < u.stamp
change oldSize - 1 ≤ u.stamp at hfloor
omega
omega
have hviewInsert := MinHeap.orderedView_insert h₂ newEntry hboundedNew hconsNodup
have hstampLt : ∀ u ∈ h₂.orderedView, newEntry.stamp < u.stamp := by
intro u hu
have huData := (mem_orderedEntries u h₂.data).mp hu
have huH₁ : u ∈ h₁.data :=
(List.Perm.mem_iff hspec₂.1).mp (List.mem_cons_of_mem e₂ huData)
have huOld : u ∈ s.heap.data :=
(List.Perm.mem_iff hspec₁.1).mp (List.mem_cons_of_mem e₁ huH₁)
have hfloor := s.stampFloor u huOld
change oldSize - 2 < u.stamp
change oldSize - 1 ≤ u.stamp at hfloor
omega
unfold mergeState
rw [hviewInsert]
simpa [newEntry, mergedEntry] using
map_tree_insertEntry_of_stamp_lt newEntry h₂.orderedView hstampLtend HeapHuffmanStateTotal heap execution
Execute at most one merge per unit of fuel.
def heapHuffmanLoop {params : HeapParams} :
Nat → HeapHuffmanState params → HuffTree
| 0, _ => HuffTree.htLeaf 0 0
| fuel + 1, s =>
match hextract₁ : s.heap.extractMin with
| none => HuffTree.htLeaf 0 0
| some (e₁, h₁) =>
match hextract₂ : h₁.extractMin with
| none => e₁.tree
| some (_e₂, _h₂) =>
heapHuffmanLoop fuel
(HeapHuffmanState.mergeState s hextract₁ hextract₂)Textbook Huffman using the verified binary min-heap.
def heapHuffmanOfFreqs (xs : List (Nat × Nat)) : HuffTree :=
heapHuffmanLoop xs.length (HeapHuffmanState.ofFreqs xs)
theorem MinHeap.orderedView_eq_nil_iff {params : HeapParams}
(h : MinHeap params) : h.orderedView = [] ↔ h.data = [] := by
constructor
· intro hview
change h.ranked.data = []
apply List.eq_nil_of_length_eq_zero
rw [← orderedEntries_length h.ranked.data]
simpa [MinHeap.orderedView] using congrArg List.length hview
· intro hdata
change h.ranked.data = [] at hdata
change orderedEntries h.ranked.data = []
simp [hdata, orderedEntries]The complete heap loop refines the existing sorted-list recursion exactly.
theorem heapHuffmanLoop_eq_huffman {params : HeapParams}
(s : HeapHuffmanState params) :
heapHuffmanLoop s.heap.data.length s =
huffman (s.heap.orderedView.map HeapEntry.tree) := by
induction hlen : s.heap.data.length using Nat.strong_induction_on generalizing s with
| h n ih =>
cases n with
| zero =>
have hdata : s.heap.data = [] := List.eq_nil_of_length_eq_zero hlen
have hview : s.heap.orderedView = [] :=
(MinHeap.orderedView_eq_nil_iff s.heap).2 hdata
simp [heapHuffmanLoop, hview, huffman]
| succ n =>
simp only [heapHuffmanLoop]
split
next hextract₁ =>
have hdata : s.heap.data = [] :=
(MinHeap.extractMin_eq_none_iff s.heap).1 hextract₁
change s.heap.ranked.data = [] at hdata
change s.heap.ranked.data.length = n + 1 at hlen
simp [hdata] at hlen
next e₁ h₁ hextract₁ =>
have hview₁ := MinHeap.orderedView_extractMin
s.stampsNodup hextract₁
have hnodup₁ := HeapHuffmanState.stampsNodup_after_extract
s.stampsNodup hextract₁
split
next hextract₂ =>
have hdata₁ : h₁.data = [] :=
(MinHeap.extractMin_eq_none_iff h₁).1 hextract₂
have hviewEmpty : h₁.orderedView = [] :=
(MinHeap.orderedView_eq_nil_iff h₁).2 hdata₁
have htrees :
s.heap.orderedView.map HeapEntry.tree = [e₁.tree] := by
rw [hview₁, hviewEmpty]
rfl
rw [htrees]
simp [huffman]
next e₂ h₂ hextract₂ =>
let next := HeapHuffmanState.mergeState s hextract₁ hextract₂
have hnextSize : next.heap.ranked.data.length = n := by
have hdrop := HeapHuffmanState.mergeState_size
s hextract₁ hextract₂
change next.heap.ranked.data.length + 1 =
s.heap.ranked.data.length at hdrop
change s.heap.ranked.data.length = n + 1 at hlen
omega
have hnextLt : next.heap.ranked.data.length < n + 1 := by omega
have hrec := ih next.heap.ranked.data.length hnextLt next rfl
have hrec' :
heapHuffmanLoop n next =
huffman (next.heap.orderedView.map HeapEntry.tree) := by
rw [hnextSize] at hrec
exact hrec
have hview₂ := MinHeap.orderedView_extractMin hnodup₁ hextract₂
have holdTrees :
s.heap.orderedView.map HeapEntry.tree =
e₁.tree :: e₂.tree :: h₂.orderedView.map HeapEntry.tree := by
rw [hview₁, hview₂]
rfl
have hnextTrees := HeapHuffmanState.mergeState_ordered_trees
s hextract₁ hextract₂
calc
heapHuffmanLoop n next =
huffman (next.heap.orderedView.map HeapEntry.tree) := hrec'
_ = huffman
(insortTree (unite e₁.tree e₂.tree)
(h₂.orderedView.map HeapEntry.tree)) := by rw [hnextTrees]
_ = huffman (s.heap.orderedView.map HeapEntry.tree) := by
rw [holdTrees]
symm
simp [huffman]Exact erasure/refinement theorem for the public frequency-table program.
theorem heapHuffmanOfFreqs_eq (xs : List (Nat × Nat)) :
heapHuffmanOfFreqs xs = huffmanOfFreqs xs := by
let s := HeapHuffmanState.ofFreqs xs
calc
heapHuffmanOfFreqs xs = heapHuffmanLoop s.heap.data.length s := by
have hs := HeapHuffmanState.ofFreqs_size xs
change s.heap.ranked.data.length = xs.length at hs
unfold heapHuffmanOfFreqs
change heapHuffmanLoop xs.length s =
heapHuffmanLoop s.heap.ranked.data.length s
rw [hs]
_ = huffman (s.heap.orderedView.map HeapEntry.tree) :=
heapHuffmanLoop_eq_huffman s
_ = huffman (sortForest (leavesOfFreqs xs)) := by
rw [HeapHuffmanState.ofFreqs_ordered_trees]
_ = huffmanOfFreqs xs := rflFrequency semantics and optimality transported to the genuine heap run.
theorem heapHuffmanOfFreqs_semantic_correct (xs : List (Nat × Nat))
(h_nodup : (xs.map Prod.fst).Nodup)
(h_pos : ∀ p ∈ xs, p.2 > 0)
(h_nonempty : xs ≠ []) :
(∀ symbol, freqOf symbol (heapHuffmanOfFreqs xs) = tableFreq xs symbol) ∧
optimum (heapHuffmanOfFreqs xs) := by
rw [heapHuffmanOfFreqs_eq]
exact huffmanOfFreqs_correct xs h_nodup h_pos h_nonemptyend CLRS.HuffmanV2