Imports
import CLRSLean.FourthEdition.Chapter_30.Section_30_2_DFT_And_FFT.S3_InversionAndConvolution
import Mathlib.Data.List.GetD
import Mathlib.TacticChapter 30.2: Executable recursive radix-2 FFT
The execution below generates twiddles successively, reuses that same trace to obtain the child root, and records arithmetic operations in the value-producing recursion.
namespace CLRSnamespace Chapter30A successor power-of-two length splits into two equal halves.
private theorem powTwo_succ_split (k : Nat) :
2 ^ (k + 1) = 2 ^ k + 2 ^ k := by
rw [pow_succ]
omegaEmbed a half-size index as an even full-size coefficient index.
def evenIndex {k : Nat} (i : Fin (2 ^ k)) : Fin (2 ^ (k + 1)) :=
⟨2 * i.1, by
have := i.2
simp [pow_succ]
omega⟩Embed a half-size index as an odd full-size coefficient index.
def oddIndex {k : Nat} (i : Fin (2 ^ k)) : Fin (2 ^ (k + 1)) :=
⟨2 * i.1 + 1, by
have := i.2
simp [pow_succ]
omega⟩The natural value of an embedded even coefficient index.
@[simp] theorem evenIndex_val {k : Nat} (i : Fin (2 ^ k)) :
(evenIndex i).1 = 2 * i.1 := rflThe natural value of an embedded odd coefficient index.
@[simp] theorem oddIndex_val {k : Nat} (i : Fin (2 ^ k)) :
(oddIndex i).1 = 2 * i.1 + 1 := rflThe even-indexed coefficient half.
def evenCoeffs {K : Type*} {k : Nat} (a : PowTwoVec K (k + 1)) :
PowTwoVec K k := fun i => a (evenIndex i)The odd-indexed coefficient half.
def oddCoeffs {K : Type*} {k : Nat} (a : PowTwoVec K (k + 1)) :
PowTwoVec K k := fun i => a (oddIndex i)Reading the even coefficient half uses the corresponding even index.
@[simp] theorem evenCoeffs_apply {K : Type*} {k : Nat}
(a : PowTwoVec K (k + 1)) (i : Fin (2 ^ k)) :
evenCoeffs a i = a (evenIndex i) := rflReading the odd coefficient half uses the corresponding odd index.
@[simp] theorem oddCoeffs_apply {K : Type*} {k : Nat}
(a : PowTwoVec K (k + 1)) (i : Fin (2 ^ k)) :
oddCoeffs a i = a (oddIndex i) := rflReassociate the successor power-of-two index type into two halves.
def powTwoSuccEquiv (k : Nat) :
Fin (2 ^ (k + 1)) ≃ Fin (2 ^ k + 2 ^ k) :=
finCongr (powTwo_succ_split k)Index into the lower half of a successor power-of-two vector.
def lowerHalfIndex {k : Nat} (i : Fin (2 ^ k)) : Fin (2 ^ (k + 1)) :=
(powTwoSuccEquiv k).symm (Fin.castAdd (2 ^ k) i)Index into the upper half of a successor power-of-two vector.
def upperHalfIndex {k : Nat} (i : Fin (2 ^ k)) : Fin (2 ^ (k + 1)) :=
(powTwoSuccEquiv k).symm (Fin.natAdd (2 ^ k) i)The lower-half embedding preserves the natural index value.
@[simp] theorem lowerHalfIndex_val {k : Nat} (i : Fin (2 ^ k)) :
(lowerHalfIndex i).1 = i.1 := by
simp [lowerHalfIndex, powTwoSuccEquiv]The upper-half embedding offsets the natural index by the half length.
@[simp] theorem upperHalfIndex_val {k : Nat} (i : Fin (2 ^ k)) :
(upperHalfIndex i).1 = 2 ^ k + i.1 := by
simp [upperHalfIndex, powTwoSuccEquiv]
omegaJoin two equal power-of-two halves without leaking casts downstream.
def joinHalves {K : Type*} {k : Nat} (lower upper : PowTwoVec K k) :
PowTwoVec K (k + 1) :=
fun i => Fin.append lower upper (powTwoSuccEquiv k i)Reading the lower embedding of joined vectors returns the lower input.
@[simp] theorem joinHalves_lower {K : Type*} {k : Nat}
(lower upper : PowTwoVec K k) (i : Fin (2 ^ k)) :
joinHalves lower upper (lowerHalfIndex i) = lower i := by
simp [joinHalves, lowerHalfIndex, powTwoSuccEquiv]Reading the upper embedding of joined vectors returns the upper input.
@[simp] theorem joinHalves_upper {K : Type*} {k : Nat}
(lower upper : PowTwoVec K k) (i : Fin (2 ^ k)) :
joinHalves lower upper (upperHalfIndex i) = upper i := by
change Fin.append lower upper (Fin.natAdd (2 ^ k) i) = upper i
exact Fin.append_right lower upper i
A successive twiddle-generation execution. next is the accumulator
after the last charged multiplication.
structure TwiddleExecution (K : Type*) where
value : List K
next : K
multiplications : Nat
Generate n successive values, charging every accumulator update.
def twiddlePowersAuxExec [Monoid K] (omega : K) :
Nat → K → TwiddleExecution K
| 0, current => ⟨[], current, 0⟩
| n + 1, current =>
let child := twiddlePowersAuxExec omega n (current * omega)
⟨current :: child.value, child.next, child.multiplications + 1⟩Value projection of the successive twiddle generator.
def twiddlePowersAux [Monoid K] (omega : K) (n : Nat) (current : K) : List K :=
(twiddlePowersAuxExec omega n current).valueSuccessive twiddle generation returns exactly the requested number of values.
theorem twiddlePowersAuxExec_length [Monoid K]
(omega : K) (n : Nat) (current : K) :
(twiddlePowersAuxExec omega n current).value.length = n := by
induction n generalizing current with
| zero => rfl
| succ n ih => simp [twiddlePowersAuxExec, ih]The value-only twiddle generator has the requested length.
theorem twiddlePowersAux_length [Monoid K]
(omega : K) (n : Nat) (current : K) :
(twiddlePowersAux omega n current).length = n :=
twiddlePowersAuxExec_length omega n currentSuccessive twiddle generation charges one multiplication per output.
theorem twiddlePowersAuxExec_multiplications [Monoid K]
(omega : K) (n : Nat) (current : K) :
(twiddlePowersAuxExec omega n current).multiplications = n := by
induction n generalizing current with
| zero => rfl
| succ n ih => simp [twiddlePowersAuxExec, ih]
The final twiddle accumulator is the initial value times omega ^ n.
private theorem twiddlePowersAuxExec_next [Monoid K]
(omega : K) (n : Nat) (current : K) :
(twiddlePowersAuxExec omega n current).next = current * omega ^ n := by
induction n generalizing current with
| zero => simp [twiddlePowersAuxExec]
| succ n ih =>
simp [twiddlePowersAuxExec, ih, pow_succ', mul_assoc]Every generated twiddle equals the corresponding successive power.
private theorem twiddlePowersAuxExec_get [Monoid K]
(omega : K) (n : Nat) (current : K) (i : Nat) (hi : i < n) :
(twiddlePowersAuxExec omega n current).value.getD i current =
current * omega ^ i := by
induction n generalizing current i with
| zero => omega
| succ n ih =>
cases i with
| zero => simp [twiddlePowersAuxExec]
| succ i =>
simp only [twiddlePowersAuxExec, List.getD_cons_succ]
have hlen :
(twiddlePowersAuxExec omega n (current * omega)).value.length = n :=
twiddlePowersAuxExec_length omega n (current * omega)
have hi' :
i < (twiddlePowersAuxExec omega n (current * omega)).value.length := by
omega
rw [List.getD_eq_getElem _ _ hi']
rw [← List.getD_eq_getElem _ (current * omega) hi']
rw [ih (current * omega) i (by omega)]
simp [pow_succ', mul_assoc]Convert a checked twiddle trace to a fixed-capacity vector.
def twiddleVectorOfExecution {K : Type*} (run : TwiddleExecution K) {n : Nat}
(hlen : run.value.length = n) : CoeffVector K n :=
fun i => run.value.get (Fin.cast hlen.symm i)
The first n powers, obtained from one successive generator execution.
def twiddlePowers [Monoid K] (omega : K) (n : Nat) : CoeffVector K n :=
let run := twiddlePowersAuxExec omega n 1
twiddleVectorOfExecution run (twiddlePowersAuxExec_length omega n 1)
The checked twiddle vector contains the first successive powers of omega.
theorem twiddlePowers_eq_pow [Monoid K]
(omega : K) (n : Nat) (i : Fin n) :
twiddlePowers omega n i = omega ^ i.1 := by
change (twiddlePowersAuxExec omega n (1 : K)).value.get
(Fin.cast (twiddlePowersAuxExec_length omega n 1).symm i) = omega ^ i.1
rw [← List.getD_eq_get
(twiddlePowersAuxExec omega n (1 : K)).value 1
(Fin.cast (twiddlePowersAuxExec_length omega n 1).symm i)]
simpa using twiddlePowersAuxExec_get omega n (1 : K) i.1 i.2Recover the squared child root from the charged twiddle trace.
def twiddleChildRoot [One K] (k : Nat) (_omega : K)
(run : TwiddleExecution K) : K :=
if k = 0 then 1 else run.value.getD 2 run.nextA positive-size twiddle trace exposes the squared root used by child FFTs.
theorem twiddleChildRoot_eq_square [Monoid K] {k : Nat} (hk : 0 < k)
(omega : K) :
twiddleChildRoot k omega (twiddlePowersAuxExec omega (2 ^ k) 1) =
omega ^ 2 := by
rw [twiddleChildRoot, if_neg (Nat.ne_of_gt hk)]
by_cases hlen : 2 < 2 ^ k
· have hactual :
2 < (twiddlePowersAuxExec omega (2 ^ k) (1 : K)).value.length := by
simpa [twiddlePowersAuxExec_length] using hlen
rw [List.getD_eq_getElem _ _ hactual]
rw [← List.getD_eq_getElem _ (1 : K) hactual]
rw [twiddlePowersAuxExec_get omega (2 ^ k) (1 : K) 2 hlen]
simp
· have hk_le_one : k ≤ 1 := by
by_contra hnot
have htwo : 2 ≤ k := by omega
have hp : 2 ^ 2 ≤ 2 ^ k :=
Nat.pow_le_pow_right (by omega) htwo
norm_num at hp
omega
have hkone : k = 1 := by omega
subst k
simp [twiddlePowersAuxExec, pow_two]One radix-2 butterfly layer and its actual arithmetic counters.
structure ButterflyExecution (K : Type*) (k : Nat) where
value : PowTwoVec K (k + 1)
addSubtractions : Nat
multiplications : NatConsume a previously evaluated twiddle trace in a butterfly layer.
def butterflyLayerFromTwiddleExec [Ring K] {k : Nat} (omega : K)
(twiddleRun : TwiddleExecution K)
(hrun : twiddleRun = twiddlePowersAuxExec omega (2 ^ k) 1)
(u v : PowTwoVec K k) : ButterflyExecution K k :=
let w := twiddleVectorOfExecution twiddleRun
(by simpa [hrun] using twiddlePowersAuxExec_length omega (2 ^ k) 1)
⟨joinHalves
(fun j => u j + w j * v j)
(fun j => u j - w j * v j),
2 * 2 ^ k,
2 ^ k + twiddleRun.multiplications⟩Execute one standalone butterfly layer, including twiddle generation.
def butterflyLayerExec [Ring K] {k : Nat} (omega : K)
(u v : PowTwoVec K k) : ButterflyExecution K k :=
let twiddleRun := twiddlePowersAuxExec omega (2 ^ k) 1
butterflyLayerFromTwiddleExec omega twiddleRun rfl u vValue projection of one butterfly execution.
def butterflyLayer [Ring K] {k : Nat} (omega : K)
(u v : PowTwoVec K k) : PowTwoVec K (k + 1) :=
(butterflyLayerExec omega u v).valueThe canonical checked execution vector is the public twiddle vector.
private theorem twiddleVectorOfExecution_canonical [Monoid K]
(omega : K) (n : Nat) :
twiddleVectorOfExecution (twiddlePowersAuxExec omega n 1)
(twiddlePowersAuxExec_length omega n 1) = twiddlePowers omega n := rflThe lower butterfly output is the sum with its twiddle product.
@[simp] theorem butterflyLayer_lower [Ring K] {k : Nat} (omega : K)
(u v : PowTwoVec K k) (j : Fin (2 ^ k)) :
butterflyLayer omega u v (lowerHalfIndex j) =
u j + omega ^ j.1 * v j := by
simp [butterflyLayer, butterflyLayerExec, butterflyLayerFromTwiddleExec,
twiddleVectorOfExecution_canonical, twiddlePowers_eq_pow]The upper butterfly output is the difference with its twiddle product.
@[simp] theorem butterflyLayer_upper [Ring K] {k : Nat} (omega : K)
(u v : PowTwoVec K k) (j : Fin (2 ^ k)) :
butterflyLayer omega u v (upperHalfIndex j) =
u j - omega ^ j.1 * v j := by
simp [butterflyLayer, butterflyLayerExec, butterflyLayerFromTwiddleExec,
twiddleVectorOfExecution_canonical, twiddlePowers_eq_pow]A butterfly layer charges two additions/subtractions per local offset.
theorem butterflyLayerExec_addSubtractions [Ring K] {k : Nat} (omega : K)
(u v : PowTwoVec K k) :
(butterflyLayerExec omega u v).addSubtractions = 2 * 2 ^ k := rflA butterfly layer charges data products and successive twiddle updates.
theorem butterflyLayerExec_multiplications [Ring K] {k : Nat} (omega : K)
(u v : PowTwoVec K k) :
(butterflyLayerExec omega u v).multiplications = 2 * 2 ^ k := by
simp [butterflyLayerExec, butterflyLayerFromTwiddleExec,
twiddlePowersAuxExec_multiplications]
omegaResult and counters of the canonical recursive FFT.
structure FFTExecution (K : Type*) (k : Nat) where
value : PowTwoVec K k
addSubtractions : Nat
multiplications : NatTotal charged arithmetic operations.
def FFTExecution.work (r : FFTExecution K k) : Nat :=
r.addSubtractions + r.multiplicationsThe canonical radix-2 execution. Its child root is extracted from the same twiddle trace consumed by the butterfly.
def recursiveFFTExec [Ring K] :
{k : Nat} → K → PowTwoVec K k → FFTExecution K k
| 0, _, a => ⟨a, 0, 0⟩
| k + 1, omega, a =>
let twiddleRun := twiddlePowersAuxExec omega (2 ^ k) 1
let childRoot := twiddleChildRoot k omega twiddleRun
let evenRun := recursiveFFTExec childRoot (evenCoeffs a)
let oddRun := recursiveFFTExec childRoot (oddCoeffs a)
let layer := butterflyLayerFromTwiddleExec omega twiddleRun rfl
evenRun.value oddRun.value
⟨layer.value,
evenRun.addSubtractions + oddRun.addSubtractions + layer.addSubtractions,
evenRun.multiplications + oddRun.multiplications + layer.multiplications⟩Value projection of the canonical recursive execution.
def recursiveFFT [Ring K] {k : Nat} (omega : K) (a : PowTwoVec K k) :
PowTwoVec K k :=
(recursiveFFTExec omega a).valueErasing the counters of a recursive execution yields the public FFT value.
theorem recursiveFFTExec_value [Ring K] {k : Nat}
(omega : K) (a : PowTwoVec K k) :
(recursiveFFTExec omega a).value = recursiveFFT omega a := rflRecursive inverse FFT: inverse-root recursive FFT followed by scaling.
def recursiveIFFT [Field K] {k : Nat} (omega : K) (a : PowTwoVec K k) :
PowTwoVec K k :=
fun i => ((2 ^ k : Nat) : K)⁻¹ * recursiveFFT omega⁻¹ a iend Chapter30end CLRS