Skip to content

Instantly share code, notes, and snippets.

@Luiz-Monad
Last active November 2, 2025 14:07
Show Gist options
  • Select an option

  • Save Luiz-Monad/55c62cffcdcb0d796e6fabeba686690a to your computer and use it in GitHub Desktop.

Select an option

Save Luiz-Monad/55c62cffcdcb0d796e6fabeba686690a to your computer and use it in GitHub Desktop.
FFT APL
Its kind of easy to implement DFT in APL!
FFT ← { ⍺ = 1 : ⍡
f ← * (⍟¯1) Γ— Β―2 Γ— kΓ·N
T ← X ⍴⍨ 2 ,⍨ NΓ·2
EvenOdd ← βˆ‡ Β¨ βˆͺ ⌿ T
Y1 ← {⍺ + f Γ— ⍡} / EvenOdd
Y2 ← {⍺ - f Γ— ⍡} / EvenOdd
(⍺-1)βˆ‡ ⍺⍺ ⍡
}
⍝ I loved APL!
⍝ The circular function is beautiful : Negative exponent in alpha (first parameter) gives you complex numbers! (http://www.jsoftware.com/papers/satn40.htm)
⍝ Generally, I hate working with arrays in C, it’s very cumbersome. But in APL, arrays are beautiful.
⍝ But the coolest part is the implementation of the diadic trigonometric functions.
⍝ This language is so good for simple mathematics, now I know why such a beautiful language didn’t work in the boring 'real life'.
⍝ Yesterday, I was studying the Fourier transform, then they mentioned APL, and then I had a dream.
⍝ Hum, let’s implement it!
⍝ Decimation-in-time radix-2 (Cooley–Tukey)
⍝ EqI:
⍝ $X_k = \sum_{n=0}^{N-1} x_n e^{ \frac{ -2 \pi i }{N} nk }$ (fig0)
⍝ I’m not going to include all the intermediary steps, watch this video ( https://www.youtube.com/watch?v=htCj9exbGo0 ), I ignored the 'C' nonsense because it didn’t help, also ( https://www.youtube.com/watch?v=1mVbZLHLaf0 ) helps too.
⍝ Separate into even and odd.
⍝ $X_k = \sum_{m=0}^{N/2-1} x_{2m} e^{ \frac{ -2 \pi i }{N} (2m)k } + \sum_{m=0}^{N/2-1} x_{2m+1} e^{ \frac{ -2 \pi i }{N} (2m+1)k }$ (fig1)
⍝ Extract common factor.
⍝ EqII:
⍝ $e^{ \frac{ -2 \pi i }{N} k }$ (fig2)
⍝ But first, let’s convert the common factor to APL. (APL is read from right to left and has no operator precedence, except for parentheses)
⍝ e^{ \frac{ -2 \pi i }{N} k }
* (⍟¯1) Γ— Β―2 Γ— kΓ·N
⍝ Let’s test with k=1 and N=32
0.980785JΒ―0.19509
⍝ Exactly what Wolfram tells us ( https://www.wolframalpha.com/input/?i=e%5E(+(-2+%5Cpi+i)+%2F(32)) )
⍝ ⍟¯1 is the same as βˆ’2Ο€i.
⍝ -2Ο€ is Β―2Γ—β—‹1.
⍝ ⍟ is the natural logarithm.
⍝ β—‹ is the circular trigonometric function. β—‹1 is Ο€.
⍝ Oh, I never noticed this before in Mathematics, makes totally sense! (the circular function is so beautiful and much more useful than the traditional sin/cos/arc functions in Math)
⍝ What we got so far.
⍝ $X_k = \sum_{m=0}^{N/2-1} x_{2m} e^{ \frac{ -2 \pi i }{N} (2m)k } + \sum_{m=0}^{N/2-1} x_{2m+1} e^{ \frac{ -2 \pi i }{N} (2m+1)k }$ (fig3)
⍝ Extracting common factor.
⍝ EqIII:
⍝ $X_k = \sum_{m=0}^{N/2-1} x_{2m} e^{ \frac{ -2 \pi i }{N/2} mk } + e^{ \frac{ -2 \pi i }{N} k } \sum_{m=0}^{N/2-1} x_{2m+1} e^{ \frac{ -2 \pi i }{N/2} mk }$ (fig4)
⍝ Let’s simplify (replacing from Eq I).
⍝ $X_k = Even_k + e^{ \frac{ -2 \pi i }{N} k } Odd_k $ (fig5)
⍝ Even_k and Odd_k are recursive FFTs themselves (recursion and FP, take that, divide and conquer!)
⍝ Now we only have simple equations and array accesses, nice!
⍝ As per Nyquist theorem, we know that we only need half of the periodicity as the function will repeat values.
⍝ $Even_{k+\frac{N}{2}} = Even_k$ (fig6) (Odd is exactly the same)
⍝ So N can⍝ be reduced to N/2.
⍝ The equations take the following form.
⍝ EqIV:
⍝ $\newline X_k = Even_k + factor * Odd_k \: for \, 1 <= k <= N/2
⍝ \newline X_k = Even_{k-N/2} + factor * Odd_{(k-N/2)} \: for \, N/2 <= k <= N $ (fig7)
⍝ $Even_k$ and $Odd_k$ are recursive calls in EqI, as they were replaced in EqIII.
⍝ Now let’s use symmetry ( https://youtu.be/1mVbZLHLaf0?t=516 )
⍝ So plugging $k+\frac{N}{2}$ into EqII and knowing the symmetry (tiddle factor).
⍝ $e^{ \frac{ -2 \pi i }{N} (k+\frac{N}{2}) }$ (fig8)
⍝ The equation becomes:
⍝ EqV:
⍝ $-e^{ \frac{ -2 \pi i }{N} k }$ (fig9)
⍝ Replacing EqV back into EqIV.
⍝ $\newline X_k = Even_k + factor * Odd_k
⍝ \newline X_k = Even_k - factor * Odd_k$ (fig10)
⍝ Now we can plug it into APL:
⍝ the factor
f ← * (⍟¯1) Γ— Β―2 Γ— kΓ·N
⍝ the recursion
T ← FFT k-NΓ·2
⍝ equation
⍝ O[k] ← T[k] + f Γ— T[k+NΓ·2] ⍝ even
⍝ O[k+NΓ·2] ← T[k] - f Γ— T[k+NΓ·2] ⍝ odd
⍝ Let’s test it.
X ← ⍳8
s ← 8
N ← 8
k ← 1
f ← * (⍟¯1) Γ— Β―2 Γ— kΓ·N
T[k] + f Γ— T[k+NΓ·2]
4.53553JΒ―3.53553
T[k] - f Γ— T[k+NΓ·2]
Β―2.53553J3.53553
⍝ Perfect! ( https://www.wolframalpha.com/input/?i=1%2B(e%5E(+(+-2+%5Cpi+i+)%2F(8)+1+))*5 )
⍝ Now let’s write to use APL’s array primitives.
⍝ First, we need to split the sequence into odd and even and then apply the function recursively.
⍝ Reshape X to be N÷2 rows per 2 cols matrix.
T ← X ⍴⍨ 2 ,⍨ NΓ·2
⍝ then apply columns into FFT.
FFT ← {⍡} ⍝ dummy
Even ← FFT 1⌷[2]T ⍝ or T[;1]
Odd ← FFT 2⌷[2]T ⍝ or T[;2]
⍝ combined
EvenOdd ← Even Odd
⍝ Then execute the summation of k.
⍝ ⍺ is even, ⍡ is odd
⍝ ⍺ ← T[k] β‹„ ⍡ ← T[k+NΓ·2]
⍝ for every row do the thing inside {}.
⍝ We just replaced the variables
Y1 ← {⍺ + f Γ— ⍡} / EvenOdd
Y2 ← {⍺ - f Γ— ⍡} / EvenOdd
⍝ The final result is Y1 Y2 combined.
Y ← Y1 Y2
⍝ The result is 4 complex numbers for Y1 and 4 more for Y2.
⍝ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
⍝ β”‚2.41421JΒ―1.41421 5.82843JΒ―2.82843 9.24264JΒ―4.24264 12.6569JΒ―5.65685β”‚
⍝ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
⍝ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
⍝ β”‚Β―0.414214J1.41421 0.171573J2.82843 0.757359J4.24264 1.34315J5.65685β”‚
⍝ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
⍝ Let’s create a shortcut now.
⍝ Apply reduce to union then map FFT with dieresis.
EvenOdd ← FFT Β¨ βˆͺ ⌿ T
⍝ Let’s fix a mistake, f should be the term from EqII and k is a parameter.
⍝ We also need the index given.
omg ← { * (⍟¯1) Γ— Β―2 Γ— ⍡÷N }
EvenOdd ← FFT Β¨ βˆͺ ⌿ T
Ix ← { ⍡ (⍳⍴⍡ ⍴⍨ ⍴⍡) } Β¨ EvenOdd
Y1 ← { ⍺ + ⍡ Γ— omg ⍡ } / {⍡ Ix} Β¨ EvenOdd
Y2 ← { ⍺ - ⍡ Γ— omg ⍡ } / {⍡ Ix} Β¨ EvenOdd
⍝ So far
X ← ⍳8
s ← 8
N ← 8
k ← 1
omg ← { * (⍟¯1) Γ— Β―2 Γ— ⍡÷N }
T ← X ⍴⍨ 2 ,⍨ NΓ·2
FFT ← {⍡}
EvenOdd ← FFT Β¨ βˆͺ ⌿ T
Y1 ← { ⍺ + ⍡ Γ— omg k } / EvenOdd
Y2 ← { ⍺ - ⍡ Γ— omg k } / EvenOdd
⍝ Now just to build the recursion.
FFT ← { ⍺ = 1 : ⍡
f ← * (⍟¯1) Γ— Β―2 Γ— kΓ·N
T ← X ⍴⍨ 2 ,⍨ NΓ·2
EvenOdd ← βˆ‡ Β¨ βˆͺ ⌿ T
Y1 ← {⍺ + f Γ— ⍡} / EvenOdd
Y2 ← {⍺ - f Γ— ⍡} / EvenOdd
(⍺-1)βˆ‡ ⍺⍺ ⍡
}
⍝ Todo: butterfly optimization.
⍝ Todo: using matrices instead of iteration ( https://youtu.be/1mVbZLHLaf0?t=3348 ).
⍝ Todo: more optimization after reading this ( http://cnx.org/contents/ulXtQbN7@15/Implementing-FFTs-in-Practice ).
[Math/APL] (warning: walls of text and equations)
I loved APL !
The circular function is beautiful : Exponential negativo em alpha (primeiro parametro) te dΓ‘ numeros complexos ! ( http://www.jsoftware.com/papers/satn40.htm )
Geralmente eu odeio trabalhar com arrays em C, Γ© muito cumbersome. Mas em APL arrays sΓ£o lindos.
Mas o mais legal mesmo Γ© a implementaΓ§Γ£o das funΓ§Γ΅es trigonometricas diadicas.
This language is so good for simple mathematics, now I know why such a beaultiful language didn't work in the boring 'real life'.
Ontem estava estudando a transformada de Fourier, dai falaram de APL, dai eu tive um sonho.
Hum, lets implement it !
⍝ Decimation-in-time radix-2 ( Cooley–Tukey )
⍝ EqI:
⍝ $X_k = \sum_{n=0}^{N-1} x_n e^{ \frac{ -2 \pi i }{N} nk }$ (fig0)
⍝ Eu não vou colocar todos os passos intermediarios, assista esse video ( https://www.youtube.com/watch?v=htCj9exbGo0 ), I ignored the 'C' bullshit because it didn't help, also ( https://www.youtube.com/watch?v=1mVbZLHLaf0 ) helps too.
⍝ Separar entre pares e impares.
⍝ $X_k = \sum_{m=0}^{N/2-1} x_{2m} e^{ \frac{ -2 \pi i }{N} (2m)k } + \sum_{m=0}^{N/2-1} x_{2m+1} e^{ \frac{ -2 \pi i }{N} (2m+1)k }$ (fig1)
⍝ Extrair fator comum.
⍝ EqII:
⍝ $e^{ \frac{ -2 \pi i }{N} k }$ (fig2)
⍝ Mas antes vamos converter o fator comum para APL. (APL se le da direita para esquerda e não tem precedencia de operador, exceto parens)
⍝ e^{ \frac{ -2 \pi i }{N} k }
* (⍟¯1) Γ— Β―2 Γ— kΓ·N
⍝ Lets test with k=1 and N=32
0.980785JΒ―0.19509
⍝ Exactly what Wolfram tell us ( https://www.wolframalpha.com/input/?i=e%5E(+(-2+%5Cpi+i)+%2F(32)) )
⍝ ⍟¯1 Γ© o mesmo que βˆ’2Ο€i.
⍝ -2Ο€ Γ© Β―2Γ—β—‹1.
⍝ ⍟ é o logaritimo natural.
⍝ β—‹ Γ© a funΓ§Γ£o circular trigonometrica. β—‹1 Γ© Ο€.
⍝ Oh, eu nunca tinha percebido isto antes na Matematica, makes totally sense! (proof left as an exercise for the reader)
⍝ What we got so far.
⍝ $X_k = \sum_{m=0}^{N/2-1} x_{2m} e^{ \frac{ -2 \pi i }{N} (2m)k } + \sum_{m=0}^{N/2-1} x_{2m+1} e^{ \frac{ -2 \pi i }{N} (2m+1)k }$ (fig3)
⍝ Extracting commom factor.
⍝ EqIII:
⍝ $X_k = \sum_{m=0}^{N/2-1} x_{2m} e^{ \frac{ -2 \pi i }{N/2} mk } + e^{ \frac{ -2 \pi i }{N} k } \sum_{m=0}^{N/2-1} x_{2m+1} e^{ \frac{ -2 \pi i }{N/2} mk }$ (fig4)
⍝ Lets simplify (replacing from Eq I).
⍝ $X_k = Even_k + e^{ \frac{ -2 \pi i }{N} k } Odd_k $ (fig5)
⍝ Even_k and Odd_k are recursive FFTs themselves (recursion and FP, take that, divide and conquer!)
⍝ Now we only have simple equations, and array accesses, nice!
⍝ As per niquist theorem we know that we only need half of the periodicity as the function will repeat values.
⍝ $Even_{k+\frac{N}{2}} = Even_k$ (fig6) (Odd is exactly the same)
⍝ Então o N pode ser reduzido para N/2.
⍝ As equaçáes ficam da seguinte forma.
⍝ EqIV:
⍝ $\newline X_k = Even_k + factor * Odd_k \: for \, 1 <= k <= N/2
⍝ \newline X_k = Even_{k-N/2} + factor * Odd_{(k-N/2)} \: for \, N/2 <= k <= N $ (fig7)
⍝ $Even_k$ e $Odd_k$ são recursive calls em EqI, pois foi substituidos em EqIII.
⍝ Agora vamos usar a simetria ( https://youtu.be/1mVbZLHLaf0?t=516 )
⍝ Então plugando $k+\frac{N}{2}$ na EqII e sabendo da simetria (tiddle factor).
⍝ $e^{ \frac{ -2 \pi i }{N} (k+\frac{N}{2}) }$ (fig8)
⍝ A equação fica:
⍝ EqV:
⍝ $-e^{ \frac{ -2 \pi i }{N} k }$ (fig9)
⍝ Substituindo devolta EqV em EqIV.
⍝ $\newline X_k = Even_k + factor * Odd_k
⍝ \newline X_k = Even_k - factor * Odd_k$ (fig10)
⍝ Agora podemos enfiar em APL:
⍝ the factor
f ← * (⍟¯1) Γ— Β―2 Γ— kΓ·N
⍝ the recursion
T ← FFT k-NΓ·2
⍝ equation
⍝ O[k] ← T[k] + f Γ— T[k+NΓ·2] ⍝ even
⍝ O[k+NΓ·2] ← T[k] - f Γ— T[k+NΓ·2] ⍝ odd
⍝ Lets test it.
X ← ⍳8
s ← 8
N ← 8
k ← 1
f ← * (⍟¯1) Γ— Β―2 Γ— kΓ·N
T[k] + f Γ— T[k+NΓ·2]
4.53553JΒ―3.53553
T[k] - f Γ— T[k+NΓ·2]
Β―2.53553J3.53553
⍝ Perfect! ( https://www.wolframalpha.com/input/?i=1%2B(e%5E(+(+-2+%5Cpi+i+)%2F(8)+1+))*5 )
Agora vamos escrever para usar os primitivos de array de APL.
⍝ Primeiro precisamos cortar a sequencia em impar e par e depois aplicar a function recursivamente.
⍝ Reshape X to be N÷2 rows per 2 cols matrix.
T ← X ⍴⍨ 2 ,⍨ NΓ·2
⍝ then apply columns into FFT.
FFT ← {⍡} ⍝ dummy
Even ← FFT 1⌷[2]T ⍝ or T[;1]
Odd ← FFT 2⌷[2]T ⍝ or T[;2]
⍝ combined
EvenOdd ← Even Odd
⍝ Dai executa a somatoria de k.
⍝ ⍺ is even, ⍡ is odd
⍝ ⍺ ← T[k] β‹„ ⍡ ← T[k+NΓ·2]
⍝ for every row do thing inside {}.
⍝ we just replaced the variables
Y1 ← {⍺ + f Γ— ⍡} / EvenOdd
Y2 ← {⍺ - f Γ— ⍡} / EvenOdd
⍝ The final result is Y1 Y2 combined.
Y ← Y1 Y2
⍝ O resultado são 4 numeros complexos para Y1 e mais 4 para Y2.
⍝ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
⍝ β”‚2.41421JΒ―1.41421 5.82843JΒ―2.82843 9.24264JΒ―4.24264 12.6569JΒ―5.65685β”‚
⍝ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
⍝ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
⍝ β”‚Β―0.414214J1.41421 0.171573J2.82843 0.757359J4.24264 1.34315J5.65685β”‚
⍝ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
⍝ Vamos fazer um curto circuito agora.
⍝ apply reduce to union then and map FFT with dieresis.
EvenOdd ← FFT Β¨ βˆͺ ⌿ T
⍝ Lets fix a mistake, f deveria ser o termo da EqII e o k é parametro.
⍝ We also need the index given
omg ← { * (⍟¯1) Γ— Β―2 Γ— ⍡÷N }
EvenOdd ← FFT Β¨ βˆͺ ⌿ T
Ix ← { ⍡ (⍳⍴⍡ ⍴⍨ ⍴⍡) } Β¨ EvenOdd
Y1 ← { ⍺ + ⍡ Γ— omg ⍡ } / {⍡ Ix} Β¨ EvenOdd
Y2 ← { ⍺ - ⍡ Γ— omg ⍡ } / {⍡ Ix} Β¨ EvenOdd
⍝ So far
X ← ⍳8
s ← 8
N ← 8
k ← 1
omg ← { * (⍟¯1) Γ— Β―2 Γ— ⍡÷N }
T ← X ⍴⍨ 2 ,⍨ NΓ·2
FFT ← {⍡}
EvenOdd ← FFT Β¨ βˆͺ ⌿ T
Y1 ← { ⍺ + ⍡ Γ— omg k } / EvenOdd
Y2 ← { ⍺ - ⍡ Γ— omg k } / EvenOdd
⍝ Agora só construir a recursao.
FFT ← { ⍺ = 1 : ⍡
f ← * (⍟¯1) Γ— Β―2 Γ— kΓ·N
T ← X ⍴⍨ 2 ,⍨ NΓ·2
EvenOdd ← βˆ‡ Β¨ βˆͺ ⌿ T
Y1 ← {⍺ + f Γ— ⍡} / EvenOdd
Y2 ← {⍺ - f Γ— ⍡} / EvenOdd
(⍺-1)βˆ‡ ⍺⍺ ⍡
}
⍝ Todo: butterfly optimization.
⍝ Todo: using matrices instead of iteration ( https://youtu.be/1mVbZLHLaf0?t=3348 ) .
⍝ Todo: more optimization after reading this ( http://cnx.org/contents/ulXtQbN7@15/Implementing-FFTs-in-Practice )
Tudo porque eu estava estudando Quantum Physics e precisei de reestudar Calculo, este video aqui foi a inspiraΓ§Γ£o ( https://www.youtube.com/watch?v=r18Gi8lSkfM ). (There's crazy people like me studying Calculus for fun) (Why study only one thing when you can study 2 or 3 things at the same time)
Proximo episodio: Eigenvalues ! just kidding
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment