Skip to content

Commit 02dd353

Browse files
authored
Merge pull request #786 from 0xJonas/cooley-tukey-lisp
Added Cooley-Tukey in Common Lisp
2 parents 0ca5ac6 + e631b57 commit 02dd353

File tree

3 files changed

+120
-0
lines changed

3 files changed

+120
-0
lines changed

CONTRIBUTORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,4 @@ This file lists everyone, who contributed to this repo and wanted to show up her
5454
- Amaras
5555
- Jonathan Dönszelmann
5656
- Ishaan Verma
57+
- Delphi1024
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
2+
(defun coefficient (time-index freq-index dft-len)
3+
"Calculates a single twiddle factor for the Fourier Transform."
4+
(exp (- (/ (* #c(0 1) 2.0 pi time-index freq-index)
5+
dft-len))))
6+
7+
(defun dft (data)
8+
"Performs the Discrete Fourier Transform"
9+
(let ((dft-len (length data)))
10+
(loop for freq-index from 0 below dft-len collect
11+
(loop for time-index from 0 below dft-len sum
12+
(* (coefficient time-index freq-index dft-len) (elt data time-index))))))
13+
14+
(defun merge-sub-ffts (evens odds)
15+
"Combines the FFTs of the even and odd indices."
16+
(let* ((fft-length (+ (length evens) (length odds)))
17+
;; Calculate coefficients for the odd indices.
18+
(twiddle-factors (loop for i from 0 below (length odds)
19+
collect (coefficient 1.0 i fft-length)))
20+
;; Multiply values with coefficients.
21+
(odd-terms (mapcar #'* odds twiddle-factors)))
22+
;; Combine the two FFTs.
23+
(concatenate 'list
24+
(mapcar #'+ evens odd-terms)
25+
(mapcar #'- evens odd-terms))))
26+
27+
(defun cooley-tukey-rec (data)
28+
"Performs the Fourier Transform using the recursive Cooley-Tukey method."
29+
(if (<= (length data) 1)
30+
data
31+
(loop
32+
for i from 0 below (length data)
33+
;; Split even and odd indexed elements into two seperate lists.
34+
if (evenp i)
35+
collect (elt data i) into evens
36+
else
37+
collect (elt data i) into odds
38+
finally
39+
;; Calculate the Fourier Transform for the two smaller lists and
40+
;; combine them into the Fourier Transform of the full input.
41+
(return (merge-sub-ffts (cooley-tukey-rec evens)
42+
(cooley-tukey-rec odds))))))
43+
44+
(defun reverse-bits (value num-bits)
45+
"Reverses the bits of a value"
46+
(if (= num-bits 1)
47+
value
48+
;; Split bits into two parts.
49+
(let* ((num-low-bits (floor (/ num-bits 2)))
50+
(num-high-bits (- num-bits num-low-bits))
51+
(bit-mask (- (expt 2 num-low-bits) 1))
52+
(lower-half (logand value bit-mask))
53+
(upper-half (ash value (- num-low-bits))))
54+
;; Reverse the bits of each part, then swap the results.
55+
(logior (ash (reverse-bits lower-half num-low-bits) num-high-bits)
56+
(reverse-bits upper-half num-high-bits)))))
57+
58+
(defun bit-shuffle-indices (data)
59+
"Rearanges the elements in a list according to their bit-reversed indices."
60+
(loop
61+
with num-bits = (floor (log (length data) 2))
62+
for i from 0 below (length data)
63+
collect (elt data (reverse-bits i num-bits))))
64+
65+
(defun butterfly (a b coeff)
66+
"Calculates a single butterfly."
67+
(values (+ a (* coeff b)) (- a (* coeff b))))
68+
69+
(defun butterfly-group (data start stride)
70+
"Calculates a single group of butterflies."
71+
(dotimes (i stride)
72+
;; Take two elements which are stride apart and perform a butterfly on them.
73+
(let* ((first-elt-index (+ start i))
74+
(second-elt-index (+ start i stride))
75+
(first-elt (elt data first-elt-index))
76+
(second-elt (elt data second-elt-index))
77+
(coeff (coefficient 1.0 i (* 2 stride))))
78+
(multiple-value-bind (sum difference) (butterfly first-elt second-elt coeff)
79+
;; Write results back into the list.
80+
(setf (elt data first-elt-index) sum)
81+
(setf (elt data second-elt-index) difference)))))
82+
83+
(defun cooley-tukey-iter (data)
84+
"Performs the Fourier Transform using the iterative Cooley-Tukey method."
85+
(loop
86+
;; Bit-shuffle indices.
87+
with shuffled-data = (bit-shuffle-indices data)
88+
for stride = 1 then (* 2 stride)
89+
while (< stride (length shuffled-data))
90+
do
91+
;; Compute butterfly groups for the current stride.
92+
(loop for i from 0 below (length shuffled-data) by (* 2 stride) do
93+
(butterfly-group shuffled-data i stride))
94+
finally (return shuffled-data)))
95+
96+
(defun approx-eql (list1 list2)
97+
(let ((diffs (mapcar #'(lambda (e1 e2) (abs (- e1 e2)))
98+
list1
99+
list2)))
100+
(loop for d in diffs always (< d 1e-9))))
101+
102+
(defun test-fft (data)
103+
(let ((dft-result (dft data))
104+
(rec-result (cooley-tukey-rec data))
105+
(iter-result (cooley-tukey-iter data)))
106+
(format T "~&DFT and recursive Cooley-Tukey approx. equal: ~a"
107+
(approx-eql dft-result rec-result))
108+
(format T "~&DFT and iterative Cooley-Tukey approx. equal: ~a"
109+
(approx-eql dft-result iter-result))
110+
(format T "~&Recursive Cooley-Tukey and iterative Cooley-Tukey approx. equal: ~a"
111+
(approx-eql rec-result iter-result))))
112+
113+
(test-fft '(0.0 0.25 0.5 0.75 0.0 -0.25 -0.5 -0.75))

contents/cooley_tukey/cooley_tukey.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ For some reason, though, putting code to this transformation really helped me fi
8989
[import:3-15, lang:"javascript"](code/javascript/fft.js)
9090
{% sample lang="rs" %}
9191
[import:24-37, lang:"rust"](code/rust/fft.rs)
92+
{% sample lang="lisp" %}
93+
[import:2-12, lang:"lisp"](code/clisp/fft.lisp)
9294
{% endmethod %}
9395

9496
In this function, we define `n` to be a set of integers from $$0 \rightarrow N-1$$ and arrange them to be a column.
@@ -142,6 +144,8 @@ In the end, the code looks like:
142144
[import:17-39, lang="javascript"](code/javascript/fft.js)
143145
{% sample lang="rs" %}
144146
[import:39-55, lang:"rust"](code/rust/fft.rs)
147+
{% sample lang="lisp" %}
148+
[import:14-42, lang:"lisp"](code/clisp/fft.lisp)
145149
{% endmethod %}
146150

147151
As a side note, we are enforcing that the array must be a power of 2 for the operation to work.
@@ -255,6 +259,8 @@ Some rather impressive scratch code was submitted by Jie and can be found here:
255259
[import, lang:"javascript"](code/javascript/fft.js)
256260
{% sample lang="rs" %}
257261
[import, lang:"rust"](code/rust/fft.rs)
262+
{% sample lang="lisp" %}
263+
[import, lang:"lisp"](code/clisp/fft.lisp)
258264
{% endmethod %}
259265

260266
<script>

0 commit comments

Comments
 (0)