Salta al contenuto
Note per Studenti Trasformate notevoli ed esponenziazione di polinomi con la FFT

Trasformate notevoli ed esponenziazione di polinomi con la FFT

In questa pagina 4

Due esercitazioni sulla FFT. Nella prima si sfrutta la struttura del vettore per calcolare la DFT con meno operazioni della FFT generale (Fast Fourier Transform - algoritmo ricorsivo e trasformata inversala FFT calcola DFT_n (n potenza di due) con divide et impera: si separano i coefficienti di indice pari e dispari, p(x) = p0(x^2) + x p1(x^2), e per il lemma di dimezzamento bastano due DFT di ordine n/2; y_i = y0_i + w^i y1_i e y_{i+n/2} = y0_i - w^i y1_i. La ricorrenza T(n) = 2T(n/2) + 2n con T(1) = 0 ha soluzione esatta 2 n log2 n. L'inversa ha matrice F^{-1}_{ij} = w^{-ij}/n: si ottiene con la FFT sulle potenze negative (poi si divide per n fuori dalla ricorsione) oppure con DFT_n(y) rovesciato. LIN_CONV moltiplica due polinomi con n coefficienti in 12 n log2(2n) + 4n operazioni.Fast Fourier Transform - algoritmo ricorsivo e trasformata inversa →); nella seconda si usa la rappresentazione per valori per calcolare i coefficienti della potenza p(x)kp(x)^k di un polinomio. Il metodo è lo stesso del capitolo: si modifica la proprietà di sottostruttura della FFT con le informazioni aggiuntive sull'istanza, e si ricava la nuova ricorrenza. Una situazione analoga (struttura che abbassa il costo) è nelle matrici circolanti (Matrici circolanti e supercircolanti - algoritmi divide and conquerle matrici supercircolanti (quadranti alternati ricorsivi) hanno solo n valori distinti: somma in n operazioni, prodotto in 4n log2 n + n con il trucco (A1+A2)(B1+B2) e (A1-A2)(B1-B2) invece delle 4 chiamate (Theta(n^2)); gli autovalori sono la trasformata di Hadamard della prima riga (FHT, n log2 n) e il prodotto passa dalla base diagonalizzante; le matrici circolanti sono polinomi nella matrice di rotazione R, il loro prodotto è la convoluzione ciclica delle prime righe e si diagonalizzano con la matrice di Fourier: prodotto in O(n log n) con la FFT.Matrici circolanti e supercircolanti - algoritmi divide and conquer →).

Trasformate note a priori

La componente ii di y=Fna\mathbf y=F_n\mathbf a è il prodotto scalare della riga ii di FnF_n con a\mathbf a. La prima riga di FnF_n è fatta di soli 11.

Vettore costante. Se a=(a,a,…,a)∈Cn\mathbf a=(a,a,\dots,a)\in\mathbb C^n allora DFTn(a)=(n a, 0,…,0)\mathrm{DFT}_n(\mathbf a)=(n\,a,\,0,\dots,0).

Dimostrazione. y0=∑ja=nay_0=\sum_ja=na. Per 0<i≤n−10<i\le n-1, yi=a∑j=0n−1(ωni)j=0y_i=a\sum_{j=0}^{n-1}(\omega_n^i)^j=0 per il lemma di somma, perché ii non è un multiplo di nn. □\square

Vettore con la sola prima componente. Se a=(a,0,…,0)\mathbf a=(a,0,\dots,0) allora DFTn(a)=(a,a,…,a)\mathrm{DFT}_n(\mathbf a)=(a,a,\dots,a).

Dimostrazione. a=a e0\mathbf a=a\,\mathbf e_0, con e0\mathbf e_0 la prima colonna di InI_n, e Fne0F_n\mathbf e_0 è la prima colonna di FnF_n, tutta di 11. In termini di polinomio: p(x)=ap(x)=a vale aa in ogni punto. □\square

Più in generale la DFT di ej\mathbf e_j è la colonna jj di FnF_n, cioè (ωnij)i(\omega_n^{ij})_i.

Esempio (n=4n=4). DFT4(7,7,7,7)=(28,0,0,0)\mathrm{DFT}_4(7,7,7,7)=(28,0,0,0) e DFT4(7,0,0,0)=(7,7,7,7)\mathrm{DFT}_4(7,0,0,0)=(7,7,7,7) (controllati con numpy).

Vettori (n,k)(n,k)-sparsi

Definizione. Siano nn e kk potenze di due, 1≤k≤n1\le k\le n. Un vettore x∈Cn\mathbf x\in\mathbb C^n è (n,k)(n,k)-sparso se xi=0x_i=0 per ogni indice ii con i mod k≠0i\bmod k\ne0.

Le componenti possono essere diverse da zero solo agli indici multipli di kk (sono n/kn/k, separate da k−1k-1 zeri; non si esclude che anche quelle siano nulle). Esempio: un vettore (8,4)(8,4)-sparso ha la forma (x0,0,0,0,x4,0,0,0)(x_0,0,0,0,x_4,0,0,0). Casi estremi: ogni vettore è (n,1)(n,1)-sparso; un vettore (n,n)(n,n)-sparso ha al più la sola componente x0x_0 (la sua DFT è nota).

Obiettivo. Un algoritmo D&C che calcoli DFTn(x)\mathrm{DFT}_n(\mathbf x) per x\mathbf x (n,k)(n,k)-sparso con T(n,k)=O ⁣(nklog⁡nk)T(n,k)=O\!\left(\frac nk\log\frac nk\right) operazioni: per k=1k=1 si ritrova O(nlog⁡n)O(n\log n), per k=nk=n nessuna operazione.

Sottostruttura rinforzata. Siano x[0]\mathbf x^{[0]} e x[1]\mathbf x^{[1]} i vettori degli indici pari e dispari di x\mathbf x, come nella FFT. Se 1<k<n1<k<n (cioè k≥2k\ge2 pari):

  • x[0]\mathbf x^{[0]} è (n/2,k/2)(n/2,k/2)-sparso: xj[0]=x2jx^{[0]}_j=x_{2j} è nullo se 2j mod k≠02j\bmod k\ne0, cioè se j mod (k/2)≠0j\bmod(k/2)\ne0;
  • x[1]\mathbf x^{[1]} è il vettore nullo: xj[1]=x2j+1x^{[1]}_j=x_{2j+1} ha indice dispari, che non può essere un multiplo della potenza di due k≥2k\ge2.

Quindi y[1]=DFTn/2(0)=0\mathbf y^{[1]}=\mathrm{DFT}_{n/2}(\mathbf 0)=\mathbf 0 e le formule di ricombinazione della FFT diventano yi=yi[0]y_i=y^{[0]}_i e yi+n/2=yi[0]y_{i+n/2}=y^{[0]}_i: la DFT è la concatenazione di y[0]\mathbf y^{[0]} con se stessa, senza nessuna operazione.

SPARSE_FFT(x, k)
n <- x.len
if k = 1 then return FFT(x)
if k = n then return (x[0], x[0], ..., x[0])        { n copie }
x0 <- (x[0], x[2], ..., x[n-2])
y0 <- SPARSE_FFT(x0, k/2)
for i <- 0 to n/2 - 1
    y[i] <- y0[i];  y[i + n/2] <- y0[i]
return y

Correttezza. Segue dalla correttezza della FFT e dalla sottostruttura rinforzata, per induzione su kk.

Costo. Nel conquer ci sono solo assegnamenti, che non si contano come operazioni aritmetiche. La ricorrenza a due parametri è T(n,k)={2nlog⁡2nk=1 (FFT),0k=n,T ⁣(n2,k2)1<k<n.T(n,k)=\begin{cases}2n\log_2n&k=1\ (\text{FFT}),\\0&k=n,\\T\!\left(\frac n2,\frac k2\right)&1<k<n.\end{cases} Srotolando (Albero della ricorrenza e formula generale per le ricorrenzeL'albero della ricorrenza ha la forma dell'albero delle chiamate dell'istanza peggiore e T(n) è la somma dei lavori dei nodi. Per la famiglia T(n) = s(n)T(f(n)) + w(n) con base T0 per n <= n0, contando i nodi per livello con le iterate di f e il livello f*(n,n0) si ottiene la formula chiusa T(n) = somma_l [prodotto_j s(f^(j)(n))] w(f^(l)(n)) + [prodotto s] T0, verificata per induzione e applicata a esempi (merge sort, Hanoi, ricorrenze con radice).Albero della ricorrenza e formula generale per le ricorrenze →) per 1<k<n1<k<n: T(n,k)=T(n2,k2)=T(n4,k4)=⋯=T(nk,1)=2 nklog⁡2nk=Θ ⁣(nklog⁡nk)T(n,k)=T(\frac n2,\frac k2)=T(\frac n4,\frac k4)=\dots=T(\frac nk,1)=2\,\frac nk\log_2\frac nk=\Theta\!\left(\frac nk\log\frac nk\right). L'ordine di grandezza vale anche per k=1k=1 e per k=nk=n (log⁡1=0\log1=0). Se si contasse un'unità per ogni assegnamento, il lavoro sarebbe lineare e si otterrebbe O(nklog⁡nk+n)O(\frac nk\log\frac nk+n). Il conteggio 2nklog⁡2nk2\frac nk\log_2\frac nk è stato verificato con un'implementazione per tutte le coppie n=20,…,27n=2^0,\dots,2^7, k=20,…,nk=2^0,\dots,n.

Conseguenza. La DFT di un vettore (n,k)(n,k)-sparso è formata da kk copie della DFTn/k\mathrm{DFT}_{n/k} del vettore (x0,xk,x2k,…,xn−k)(x_0,x_k,x_{2k},\dots,x_{n-k}) che contiene le sue n/kn/k componenti non banali. Lo si vede anche direttamente: yi=∑m=0n/k−1xmk ωnimk=∑mxmk ωn/kimy_i=\sum_{m=0}^{n/k-1}x_{mk}\,\omega_n^{imk}=\sum_mx_{mk}\,\omega_{n/k}^{im} (cancellazione), che è periodica in ii con periodo n/kn/k.

Esempio (n=8n=8, k=4k=4). x=(2,0,0,0,1,0,0,0)\mathbf x=(2,0,0,0,1,0,0,0). Le componenti non nulle (2,1)(2,1) hanno DFT2(2,1)=(2+1, 2−1)=(3,1)\mathrm{DFT}_2(2,1)=(2+1,\,2-1)=(3,1), ripetuta 44 volte: DFT8(x)=(3,1,3,1,3,1,3,1)\mathrm{DFT}_8(\mathbf x)=(3,1,3,1,3,1,3,1).

Esempio d'esame. x=(5,3,3,3,4,3,3,3)\mathbf x=(5,3,3,3,4,3,3,3), y=DFT8(x)\mathbf y=\mathrm{DFT}_8(\mathbf x). Per linearità (FnF_n è lineare) si scompone x=(3,3,3,3,3,3,3,3)+(2,0,0,0,1,0,0,0)\mathbf x=(3,3,3,3,3,3,3,3)+(2,0,0,0,1,0,0,0). Il primo addendo è costante, con DFT (24,0,…,0)(24,0,\dots,0); il secondo è (8,4)(8,4)-sparso con DFT (3,1,3,1,3,1,3,1)(3,1,3,1,3,1,3,1). Somma: y=(27,1,3,1,3,1,3,1)\mathbf y=(27,1,3,1,3,1,3,1) (verificato con la DFT calcolata dalla definizione).

La strategia, ripetibile in esercizi simili, è: guardare la struttura del vettore, scomporlo (anche con la linearità) in pezzi con trasformata nota, oppure riscrivere la sottostruttura della FFT usando la struttura, e studiare la ricorrenza.

Esponenziazione di polinomi

Dato p(x)=∑i<naixi≡a∈Cnp(x)=\sum_{i<n}a_ix^i\equiv\mathbf a\in\mathbb C^n e un intero k≥1k\ge1, si vogliono i coefficienti di q(x)=p(x)kq(x)=p(x)^k, cioè a(k)=a⋆a⋆⋯⋆a\mathbf a^{(k)}=\mathbf a\star\mathbf a\star\dots\star\mathbf a (kk volte). Il limite di grado di qq è (n−1)k+1(n-1)k+1.

Primo approccio: quadrature successive con la convoluzione

La convoluzione è associativa, quindi si applica lo schema di Esponenziazione veloce per quadrature successivepotenza x^n con un operatore associativo con elemento neutro, costo = numero di applicazioni dell'operatore; STUPID_POWER costa n-1, la sottostruttura x^n = (x^floor(n/2))^2 (per n dispari per x) dà POWER con T(n) = floor(log2 n) + (numero di bit a 1 di n) - 1, Theta(log n) (caso peggiore 2 floor(log2 n), n = 2^t - 1); versioni iterative POWER2 e POWER3 dalla rappresentazione binaria di n; esempio con la matrice di Fibonacci; il modello a costo unitario vale solo se l'operatore non fa crescere gli operandi.Esponenziazione veloce per quadrature successive → con ⋆\star al posto del prodotto. Siano d=⌊log⁡2k⌋+1d=\lfloor\log_2k\rfloor+1 e kd−1…k0k_{d-1}\dots k_0 le cifre binarie di kk, k=∑iki2ik=\sum_ik_i2^i. Allora a(k)=a(k020)⋆a(k121)⋆⋯⋆a(kd−12d−1)\mathbf a^{(k)}=\mathbf a^{(k_02^0)}\star\mathbf a^{(k_12^1)}\star\dots\star\mathbf a^{(k_{d-1}2^{d-1})}.

EXPONENTIATE(a, k)
d <- floor(log2 k) + 1
h <- k                         { h = (k_{d-1} ... k_0) in base 2 }
y <- a                         { all'iterazione i: y = a^(2^i) }
z <- (1)                       { elemento neutro della convoluzione; accumula il prodotto }
for i <- 0 to d - 1
    if h è dispari then z <- LIN_CONV(z, y)       { cifra k_i = 1 }
    y <- LIN_CONV(y, y)                            { y = a^(2^(i+1)) }
    h <- floor(h / 2)
return z

LIN_CONV va nella versione per vettori di lunghezza qualsiasi (padding alla potenza di due). Analisi. All'inizio dell'iterazione ii, y ha lunghezza al più 2in2^in (la lunghezza di a(2i)\mathbf a^{(2^i)} è 2i(n−1)+12^i(n-1)+1) e z ha lunghezza minore di 2in2^in (è la convoluzione di fattori di lunghezze ≤2jn\le 2^jn, j<ij<i, e ∑j<i2j<2i\sum_{j<i}2^j<2^i). Quindi l'iterazione ii costa Ti=O(2inlog⁡(2in))=O(2inlog⁡n+2in i)T_i=O(2^in\log(2^in))=O(2^in\log n+2^in\,i), e poiché 2d≤2k2^d\le2k: T(n,k)=∑i=0d−1Ti=O(n2dlog⁡n+n2dd)=O(nklog⁡n+nklog⁡k)=O(nklog⁡(nk)).T(n,k)=\sum_{i=0}^{d-1}T_i=O\bigl(n2^d\log n+n2^dd\bigr)=O\bigl(nk\log n+nk\log k\bigr)=O\bigl(nk\log(nk)\bigr).

Secondo approccio: potenza dei valori

Se si ha una rappresentazione per valori di pp su una base estesa xE\mathbf x^E con h≥(n−1)k+1h\ge(n-1)k+1 punti, i valori di q=pkq=p^k sono le potenze dei valori di pp: q(xi)=(p(xi))kq(x_i)=\bigl(p(x_i)\bigr)^k, cioè yq=((yp,i)k)i\mathbf y_q=\bigl((y_{p,i})^k\bigr)_i. Basta quindi valutare, elevare e interpolare.

EXPONENTIATE2(a, k)
n <- a.len
h <- 2^ceil(log2((n-1)*k + 1))              { prima potenza di due >= (n-1)k+1 }
yp <- FFT((a | 0_{h-n}))
for i <- 0 to h - 1
    yq[i] <- POWER3(yp[i], k)                 { potenza scalare per quadrature, O(log k) }
return INV_FFT(yq)

La correttezza segue dalle considerazioni sopra: qq ha limite di grado ≤h\le h, quindi yq\mathbf y_q lo determina e INV_FFT restituisce i suoi coefficienti (seguiti da zeri di padding). Costo: FFT e INV_FFT su vettori di taglia h<2((n−1)k+1)≤2nkh<2((n-1)k+1)\le2nk, quindi Θ(nklog⁡(nk))\Theta(nk\log(nk)); più hh potenze scalari ciascuna di Θ(log⁡k)\Theta(\log k) prodotti, in tutto Θ(nklog⁡k)\Theta(nk\log k). Il totale è Θ(nklog⁡(nk)+nklog⁡k)=Θ(nklog⁡(nk))\Theta(nk\log(nk)+nk\log k)=\Theta(nk\log(nk)). Stesso ordine del primo approccio, ma l'algoritmo è più semplice e con costanti migliori: una trasformata diretta, una inversa e hh potenze, invece di O(log⁡k)O(\log k) convoluzioni di vettori sempre più lunghi.

Esempio (p(x)=1+xp(x)=1+x, k=4k=4). n=2n=2, il limite di grado di qq è (2−1)⋅4+1=5(2-1)\cdot4+1=5, quindi h=2⌈log⁡25⌉=8h=2^{\lceil\log_25\rceil}=8. a ∣ 0=(1,1,0,0,0,0,0,0)\mathbf a\,|\,0=(1,1,0,0,0,0,0,0) e yp,i=1+ω8iy_{p,i}=1+\omega_8^i. Le potenze quarte:

ii 0 1 2 3 4 5 6 7
(1+ω8i)4(1+\omega_8^i)^4 1616 (6+42) i(6+4\sqrt2)\,i −4-4 −(6−42) i-(6-4\sqrt2)\,i 00 (6−42) i(6-4\sqrt2)\,i −4-4 −(6+42) i-(6+4\sqrt2)\,i

(per esempio (1+ω8)2=1+2ω8+i=(1+2)(1+i)(1+\omega_8)^2=1+2\omega_8+i=(1+\sqrt2)(1+i) e quindi (1+ω8)4=(3+22)⋅2i=(6+42) i(1+\omega_8)^4=(3+2\sqrt2)\cdot2i=(6+4\sqrt2)\,i.) L'antitrasformata di questo vettore è (1,4,6,4,1,0,0,0)(1,4,6,4,1,0,0,0), cioè (1+x)4=1+4x+6x2+4x3+x4(1+x)^4=1+4x+6x^2+4x^3+x^4, con i tre zeri finali di padding. Verificato con numpy insieme a 2020 casi casuali per ciascun approccio.

Domande d'esame

  • Calcolare DFT8(5,3,3,3,4,3,3,3)\mathrm{DFT}_8(5,3,3,3,4,3,3,3) senza eseguire la FFT. Traccia: (3,…,3)+(2,0,0,0,1,0,0,0)(3,\dots,3)+(2,0,0,0,1,0,0,0), DFT (24,0,…,0)+(3,1,3,1,3,1,3,1)=(27,1,3,1,3,1,3,1)(24,0,\dots,0)+(3,1,3,1,3,1,3,1)=(27,1,3,1,3,1,3,1).
  • Quante operazioni esegue SPARSE_FFT su un vettore (n,k)(n,k)-sparso con 1<k<n1<k<n e perché? Traccia: nessuna nel conquer, T(n,k)=T(n/2,k/2)=⋯=T(n/k,1)T(n,k)=T(n/2,k/2)=\dots=T(n/k,1), quindi 2nklog⁡2nk2\frac nk\log_2\frac nk.
  • Per elevare alla sesta potenza un polinomio con 5 coefficienti usando la FFT, su quanti punti si valuta? Traccia: (n−1)k+1=4⋅6+1=25(n-1)k+1=4\cdot6+1=25, h=2⌈log⁡225⌉=32h=2^{\lceil\log_225\rceil}=32.

Esercizi svolti: Esercizio - polinomio con tutte le radici dell'unità tranne 1 e -1, valutazione e DFT dei coefficienti (compito del 26 giugno 2025, es. 1); Esercizio - domande di prima parte su polinomi, DFT e FFT (simulazioni d'esame); Esercizio - matrice di Fourier per una matrice generica a sinistra e a destra e per aI + bU (compito del 27 gennaio 2025, es. 2).

Versione ripasso

Esercizi su questo argomento