Imports
/- Copyright (c) 2026 Terence Rokop. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Terence Rokop -/ module public import Mathlib.Data.List.Nodup public import Batteries.Data.List.Perm public import Geb.Prototypes.Computability.BitTree.Encoding
set_option doc.verso true

Counting lower bound for lossless tree representations

For fixed fork count I and total payload length B, every injective binary representation has worst-case length at least B. A fixed comb shape already contains all 2^B choices for its last leaf's payload. There are only 2^B - 1 binary words shorter than B, even when codewords need not be prefix-free.

Main definitions

  • words enumerates fixed-length bitstrings using list sections.

  • payloadComb embeds arbitrary bitstrings into trees with a prescribed fork count.

Main statements

  • bitstring_representation_lower_bound proves the finite counting bound.

  • tree_representation_lower_bound transfers it to trees of fixed size.

Tags

binary tree, representation size, counting, information lower bound

@[expose] public sectionnamespace Geb.BitTree.Elias

All binary words of the given length.

def words (n : ) : List (List Bool) := (List.replicate n [false, true]).sections

Enumeration extends every shorter word by each possible next bit.

theorem words_succ (n : ) : words (n + 1) = (words n).flatMap fun w [false :: w, true :: w] := n:words (n + 1) = List.flatMap (fun w [false :: w, true :: w]) (words n) All goals completed! 🐙

Fixed-length enumeration contains exactly the words of that length.

theorem mem_words (n : ) : w, w words n w.length = n := n: (w : List Bool), w words n w.length = n n: (w : List Bool), w words Nat.zero w.length = Nat.zeron: (n : ), (∀ (w : List Bool), w words n w.length = n) (w : List Bool), w words n.succ w.length = n.succ n: (w : List Bool), w words Nat.zero w.length = Nat.zero n:w:List Boolw words Nat.zero w.length = Nat.zero All goals completed! 🐙 n: (n : ), (∀ (w : List Bool), w words n w.length = n) (w : List Bool), w words n.succ w.length = n.succ n✝:n:ih: (w : List Bool), w words n w.length = nw:List Boolw words n.succ w.length = n.succ n✝:n:ih: (w : List Bool), w words n w.length = nw:List Bool(∃ a words n, w [false :: a, true :: a]) w.length = n.succ n✝:n:ih: (w : List Bool), w words n w.length = nw:List Bool(∃ a words n, w [false :: a, true :: a]) w.length = n.succn✝:n:ih: (w : List Bool), w words n w.length = nw:List Boolw.length = n.succ a words n, w [false :: a, true :: a] n✝:n:ih: (w : List Bool), w words n w.length = nw:List Bool(∃ a words n, w [false :: a, true :: a]) w.length = n.succ n✝:n:ih: (w : List Bool), w words n w.length = nw:List Boolv:List Boolhv:v words nhw:w [false :: v, true :: v]w.length = n.succ n✝:n:ih: (w : List Bool), w words n w.length = nw:List Boolv:List Boolhv:v words nhw:w [false :: v, true :: v]hl:v.length = nw.length = n.succ n✝:n:ih: (w : List Bool), w words n w.length = nw:List Boolv:List Boolhv:v words nhl:v.length = nhw:w = false :: v w = true :: vw.length = n.succ n✝:n:ih: (w : List Bool), w words n w.length = nv:List Boolhv:v words nhl:v.length = n(false :: v).length = n.succn✝:n:ih: (w : List Bool), w words n w.length = nv:List Boolhv:v words nhl:v.length = n(true :: v).length = n.succ n✝:n:ih: (w : List Bool), w words n w.length = nv:List Boolhv:v words nhl:v.length = n(false :: v).length = n.succn✝:n:ih: (w : List Bool), w words n w.length = nv:List Boolhv:v words nhl:v.length = n(true :: v).length = n.succ All goals completed! 🐙 n✝:n:ih: (w : List Bool), w words n w.length = nw:List Boolw.length = n.succ a words n, w [false :: a, true :: a] n✝:n:ih: (w : List Bool), w words n w.length = nw:List Boolhw:w.length = n.succ a words n, w [false :: a, true :: a] cases w with n✝:n:ih: (w : List Bool), w words n w.length = nhw:[].length = n.succ a words n, [] [false :: a, true :: a] n✝:n:ih: (w : List Bool), w words n w.length = nhw:0 = n.succ a words n, [] [false :: a, true :: a]; All goals completed! 🐙 n✝:n:ih: (w : List Bool), w words n w.length = nb:Boolw:List Boolhw:(b :: w).length = n.succ a words n, b :: w [false :: a, true :: a] refine w, (ih w).mpr (n✝:n:ih: (w : List Bool), w words n w.length = nb:Boolw:List Boolhw:(b :: w).length = n.succw.length = n n✝:n:ih: (w : List Bool), w words n w.length = nb:Boolw:List Boolhw:w.length + 1 = n.succw.length = n; All goals completed! 🐙), ?_ n✝:n:ih: (w : List Bool), w words n w.length = nw:List Boolhw:(false :: w).length = n.succfalse :: w [false :: w, true :: w]n✝:n:ih: (w : List Bool), w words n w.length = nw:List Boolhw:(true :: w).length = n.succtrue :: w [false :: w, true :: w] n✝:n:ih: (w : List Bool), w words n w.length = nw:List Boolhw:(false :: w).length = n.succfalse :: w [false :: w, true :: w]n✝:n:ih: (w : List Bool), w words n w.length = nw:List Boolhw:(true :: w).length = n.succtrue :: w [false :: w, true :: w] All goals completed! 🐙

There are exactly two to the power of the length binary words.

theorem length_words : n, (words n).length = 2 ^ n := (n : ), (words n).length = 2 ^ n (n : ), (words n).length = 2 ^ n (words n.succ).length = 2 ^ n.succ n:ih:(words n).length = 2 ^ n(words n.succ).length = 2 ^ n.succ n:ih:(words n).length = 2 ^ n(List.map (fun a [false :: a, true :: a].length) (words n)).sum = 2 ^ n.succ All goals completed! 🐙

Fixed-length enumeration has no duplicate words.

theorem nodup_words : n, (words n).Nodup := (n : ), (words n).Nodup refine Nat.rec ((words Nat.zero).Nodup All goals completed! 🐙) ?_ n:ih:(words n).Nodup(words n.succ).Nodup n:ih:(words n).Nodup(∀ x words n, [false :: x, true :: x].Nodup) List.Pairwise (Function.onFun List.Disjoint fun w [false :: w, true :: w]) (words n) refine fun w _ n:ih:(words n).Nodupw:List Boolx✝:w words n[false :: w, true :: w].Nodup All goals completed! 🐙, ih.imp ?_ n:ih:(words n).Nodupa:List Boolb:List Boolhab:a bFunction.onFun List.Disjoint (fun w [false :: w, true :: w]) a b All goals completed! 🐙

All binary words strictly shorter than the given bound.

def shortWords (n : ) : List (List Bool) := (List.range n).flatMap words

Membership in the bounded enumeration is exactly the strict length bound.

theorem mem_shortWords (n : ) (w : List Bool) : w shortWords n w.length < n := n:w:List Boolw shortWords n w.length < n n:w:List Bool(∃ a < n, w.length = a) w.length < n All goals completed! 🐙

The number of words shorter than a bound is one less than its power of two.

theorem length_shortWords : n, (shortWords n).length + 1 = 2 ^ n := (n : ), (shortWords n).length + 1 = 2 ^ n (n : ), (shortWords n).length + 1 = 2 ^ n (shortWords n.succ).length + 1 = 2 ^ n.succ n:ih:(shortWords n).length + 1 = 2 ^ n(shortWords n.succ).length + 1 = 2 ^ n.succ n:ih:(shortWords n).length + 1 = 2 ^ nhe:shortWords (n + 1) = shortWords n ++ words n(shortWords n.succ).length + 1 = 2 ^ n.succ n:ih:(shortWords n).length + 1 = 2 ^ nhe:shortWords (n + 1) = shortWords n ++ words n(shortWords n).length + 2 ^ n + 1 = 2 ^ n * 2 All goals completed! 🐙

An injective encoding of all fixed-length words cannot shorten every word.

theorem bitstring_representation_lower_bound (code : List Bool List Bool) (hinj : Function.Injective code) (bits bound : ) (hbound : w, w.length = bits (code w).length bound) : bits bound := code:List Bool List Boolhinj:Function.Injective codebits:bound:hbound: (w : List Bool), w.length = bits (code w).length boundbits bound code:List Bool List Boolhinj:Function.Injective codebits:bound:hbound: (w : List Bool), w.length = bits (code w).length boundhsub:List.map code (words bits) shortWords (bound + 1)bits bound code:List Bool List Boolhinj:Function.Injective codebits:bound:hbound: (w : List Bool), w.length = bits (code w).length boundhsub:List.map code (words bits) shortWords (bound + 1)hlen:(List.map code (words bits)).length (shortWords (bound + 1)).lengthbits bound code:List Bool List Boolhinj:Function.Injective codebits:bound:hbound: (w : List Bool), w.length = bits (code w).length boundhsub:List.map code (words bits) shortWords (bound + 1)hlen:2 ^ bits (shortWords (bound + 1)).lengthbits bound code:List Bool List Boolhinj:Function.Injective codebits:bound:hbound: (w : List Bool), w.length = bits (code w).length boundhsub:List.map code (words bits) shortWords (bound + 1)hlen:2 ^ bits (shortWords (bound + 1)).lengthhs:(shortWords (bound + 1)).length + 1 = 2 ^ (bound + 1)bits bound code:List Bool List Boolhinj:Function.Injective codebits:bound:hbound: (w : List Bool), w.length = bits (code w).length boundhsub:List.map code (words bits) shortWords (bound + 1)hlen:2 ^ bits (shortWords (bound + 1)).lengthhs:(shortWords (bound + 1)).length + 1 = 2 ^ (bound + 1)h:bits boundbits boundcode:List Bool List Boolhinj:Function.Injective codebits:bound:hbound: (w : List Bool), w.length = bits (code w).length boundhsub:List.map code (words bits) shortWords (bound + 1)hlen:2 ^ bits (shortWords (bound + 1)).lengthhs:(shortWords (bound + 1)).length + 1 = 2 ^ (bound + 1)h:¬bits boundbits bound code:List Bool List Boolhinj:Function.Injective codebits:bound:hbound: (w : List Bool), w.length = bits (code w).length boundhsub:List.map code (words bits) shortWords (bound + 1)hlen:2 ^ bits (shortWords (bound + 1)).lengthhs:(shortWords (bound + 1)).length + 1 = 2 ^ (bound + 1)h:bits boundbits bound All goals completed! 🐙 code:List Bool List Boolhinj:Function.Injective codebits:bound:hbound: (w : List Bool), w.length = bits (code w).length boundhsub:List.map code (words bits) shortWords (bound + 1)hlen:2 ^ bits (shortWords (bound + 1)).lengthhs:(shortWords (bound + 1)).length + 1 = 2 ^ (bound + 1)h:¬bits boundbits bound code:List Bool List Boolhinj:Function.Injective codebits:bound:hbound: (w : List Bool), w.length = bits (code w).length boundhsub:List.map code (words bits) shortWords (bound + 1)hlen:2 ^ bits (shortWords (bound + 1)).lengthhs:(shortWords (bound + 1)).length + 1 = 2 ^ (bound + 1)h:¬bits boundhp:2 ^ (bound + 1) 2 ^ bitsbits bound All goals completed! 🐙

A fixed right comb with empty left leaves and one variable last payload.

def payloadComb (forks : ) (w : List Bool) : Tree := Nat.rec (leaf w) (fun _ t fork (leaf []) t) forks

The payload stored at the rightmost leaf.

def lastPayload : Tree List Bool := WType.elim (List Bool) fun x match x with | some s, _ => s | none, f => f true

Reading the last leaf recovers the word used to build a comb.

theorem lastPayload_payloadComb (w : List Bool) : forks, lastPayload (payloadComb forks w) = w := Nat.rec rfl (fun _ ih ih)

Varying the last payload gives distinct trees.

theorem payloadComb_injective (forks : ) : Function.Injective (payloadComb forks) := forks:Function.Injective (payloadComb forks) forks:v:List Boolw:List Boolh:payloadComb forks v = payloadComb forks wv = w forks:v:List Boolw:List Boolh:payloadComb forks v = payloadComb forks whe:lastPayload (payloadComb forks v) = lastPayload (payloadComb forks w)v = w All goals completed! 🐙

The comb fixes the fork count while retaining the full payload length.

theorem counts_payloadComb (w : List Bool) : forks, counts (payloadComb forks w) = (forks, forks + 1, w.length) := w:List Bool (forks : ), counts (payloadComb forks w) = (forks, forks + 1, w.length) w:List Bool (n : ), counts (payloadComb n w) = (n, n + 1, w.length) counts (payloadComb n.succ w) = (n.succ, n.succ + 1, w.length) w:List Boolforks:ih:counts (payloadComb forks w) = (forks, forks + 1, w.length)counts (payloadComb forks.succ w) = (forks.succ, forks.succ + 1, w.length) w:List Boolforks:ih:counts (payloadComb forks w) = (forks, forks + 1, w.length)counts (fork (leaf []) (payloadComb forks w)) = (forks.succ, forks.succ + 1, w.length) w:List Boolforks:ih:counts (payloadComb forks w) = (forks, forks + 1, w.length)((0, 1, [].length).1 + (forks, forks + 1, w.length).1 + 1, (0, 1, [].length).2.1 + (forks, forks + 1, w.length).2.1, (0, 1, [].length).2.2 + (forks, forks + 1, w.length).2.2) = (forks.succ, forks.succ + 1, w.length) All goals completed! 🐙

Any lossless binary code for trees needs at least the total payload length in the worst case.

theorem tree_representation_lower_bound (code : Tree List Bool) (hinj : Function.Injective code) (forks bits bound : ) (hbound : t, (counts t).1 = forks (counts t).2.2 = bits (code t).length bound) : bits bound := code:Tree List Boolhinj:Function.Injective codeforks:bits:bound:hbound: (t : Tree), (counts t).1 = forks (counts t).2.2 = bits (code t).length boundbits bound code:Tree List Boolhinj:Function.Injective codeforks:bits:bound:hbound: (t : Tree), (counts t).1 = forks (counts t).2.2 = bits (code t).length bound (w : List Bool), w.length = bits (code (payloadComb forks w)).length bound code:Tree List Boolhinj:Function.Injective codeforks:bits:bound:hbound: (t : Tree), (counts t).1 = forks (counts t).2.2 = bits (code t).length boundw:List Boolhw:w.length = bits(code (payloadComb forks w)).length bound code:Tree List Boolhinj:Function.Injective codeforks:bits:bound:hbound: (t : Tree), (counts t).1 = forks (counts t).2.2 = bits (code t).length boundw:List Boolhw:w.length = bits(counts (payloadComb forks w)).1 = forkscode:Tree List Boolhinj:Function.Injective codeforks:bits:bound:hbound: (t : Tree), (counts t).1 = forks (counts t).2.2 = bits (code t).length boundw:List Boolhw:w.length = bits(counts (payloadComb forks w)).2.2 = bits code:Tree List Boolhinj:Function.Injective codeforks:bits:bound:hbound: (t : Tree), (counts t).1 = forks (counts t).2.2 = bits (code t).length boundw:List Boolhw:w.length = bits(counts (payloadComb forks w)).1 = forks All goals completed! 🐙 code:Tree List Boolhinj:Function.Injective codeforks:bits:bound:hbound: (t : Tree), (counts t).1 = forks (counts t).2.2 = bits (code t).length boundw:List Boolhw:w.length = bits(counts (payloadComb forks w)).2.2 = bits All goals completed! 🐙
end Geb.BitTree.Elias