Implementación de la ecuación de Wikipedia para el DFT

10

Estaba escribiendo una implementación simple de transformación de Fourier y miré la ecuación DFT en Wikipedia como referencia , cuando me di cuenta de que estaba haciendo algo diferente, y después de pensarlo, sentí que la versión de Wikipedia debe estar equivocada porque es muy simple pensar en un señal que cuando Fourier transformado (con esa ecuación) devolverá un espectro incorrecto: porque la ecuación envuelve la señal alrededor del plano complejo solo una vez (debido an/N con 0<n<N1), cualquier señal que sea periódica un número par de veces (mientras se envuelve el plano complejo) no tendrá espectro, ya que los picos habituales (al girar alrededor del círculo unitario) que aparecerían durante un DFT se cancelarán entre sí (cuando un algunos de ellos aparecen).

Para verificar esto, escribí un código que produjo la siguiente imagen, que parece confirmar lo que pienso. ingrese la descripción de la imagen aquí

"Tiempo usando ecuación" usa la ecuación

Xf=n=0N1xn(cos(2πftn)isin(2πftn))
con t un vector de tiempo (entonces el tiempo tn en el cual xnfue muestreado por ejemplo). Se puede encontrar en la función a ftcontinuación.

La ecuación de Wikipedia, vinculada anteriormente, se copia aquí como referencia:

Xf=n=0N1xn(cos(2πfnN)isin(2πfnN))
Se puede encontrar en la función ft2.
import numpy as np
import matplotlib.pyplot as plt 
plt.style.use('ggplot')

def ft(t, s, fs):
    freq_step = fs / len(s)
    freqs = np.arange(0, fs/2 + freq_step, freq_step)
    S = []
    for freq in freqs:
        real = np.sum(s * np.cos(2*np.pi*freq * t)) 
        compl = np.sum(- s * np.sin(2*np.pi*freq * t)) 
        tmpsum = (real**2 + compl**2) ** 0.5 
        S.append(tmpsum)
    return S, freqs

def ft2(s, fs):  # Using wikipedia equation
    nump=len(s)
    freq_step = fs / nump
    freqs = np.arange(0, fs/2 + freq_step, freq_step)
    S = []
    for i, freq in enumerate(freqs):
        real = np.sum(s * np.cos(2*np.pi*freq * i/nump))
        compl = np.sum(- s * np.sin(2*np.pi*freq * i/nump))
        tmpsum = (real**2 + compl**2) ** 0.5 
        S.append(tmpsum)
    return S, freqs


def main():
    f = 5 
    fs = 100 
    t = np.linspace(0, 2, 200)
    y = np.sin(2*np.pi*f*t) + np.cos(2*np.pi*f*2*t)

    fig = plt.figure()
    ax = fig.add_subplot(311)
    ax.set_title('Signal in time domain')
    ax.set_xlabel('t')
    ax.plot(t, y)

    S, freqs = ft(t, y, fs) 

    ax = fig.add_subplot(312)
    ax.set_xticks(np.arange(0, freqs[-1], 2)) 
    ax.set_title('Time using equation')
    ax.set_xlabel('frequency')
    ax.plot(freqs, S)

    S, freqs = ft2(y, fs) 
    ax = fig.add_subplot(313)
    ax.set_title('Using Wiki equation')
    ax.set_xlabel('frequency')
    ax.set_xticks(np.arange(0, freqs[-1], 2)) 
    ax.plot(freqs, S)

    plt.tight_layout()
    plt.show()

main()

Obviamente, parece bastante improbable que haya encontrado un error al azar en una página wiki de tan alto perfil. ¿Pero no puedo ver un error en lo que he hecho?

Nimitz14
fuente
Para obtener una comprensión más profunda del significado de un DFT, le recomiendo que lea mis dos primeros artículos de blog: "La naturaleza exponencial del círculo de unidades complejas" ( dsprelated.com/showarticle/754.php ) y "Interpretación gráfica de DFT: Centroides of Weighted Roots of Unity "( dsprelated.com/showarticle/768.php ).
Cedron Dawg
Gracias echaré un vistazo. Sinceramente, estoy muy sorprendido por la atención que recibió cuando todo se debe a un error muy tonto en mi código.
Nimitz14
Yo también estoy sorprendido. Sin embargo, lo continuo frente a lo discreto es un gran problema. Mi blog trata sobre el caso discreto sin ninguna referencia al caso continuo, que es diferente de enseñar el caso discreto como una versión de muestra del caso continuo.
Cedron Dawg

Respuestas:

16

Tienes un error adentro ft2. Están incrementando i, y freqjuntos. No es así como quieres que funcione tu resumen. Me puse a arreglarlo, pero se puso complicado. Decidí reescribirlo desde una perspectiva discreta en lugar de tratar de usar la terminología continua. En el DFT, la frecuencia de muestreo es irrelevante. Lo que importa es cuántas muestras se usan ( N). Los números del contenedor ( k) corresponden a la frecuencia en unidades de ciclos por cuadro. Intenté mantener tu código lo más intacto posible para que te resulte fácilmente comprensible. También desplegué los bucles de cálculo de DFT para revelar un poco mejor su naturaleza.

Espero que esto ayude.

Sección de la economía

importar numpy como np
importar matplotlib.pyplot como plt 

def ft (t, s, fs):
    freq_step = fs / len (s)
    freqs = np.arange (0, fs / 2, freq_step)
    S = []
    para freq en freqs:
        real = np.sum (s * np.cos (2 * np.pi * freq * t)) 
        compl = np.sum (- s * np.sin (2 * np.pi * freq * t)) 
        tmpsum = (real ** 2 + compl ** 2) ** 0.5 
        S.append (tmpsum)
    retorno S, freqs

def ft3 (s, N): # Forma más eficiente de ecuación de wikipedia

    S = []

    rebanada = 0.0
    astilla = 2 * np.pi / float (N) 

    para k en rango (N / 2):

        sum_real = 0.0    
        sum_imag = 0.0
        ángulo = 0.0
        para n en rango (N):
            sum_real + = s [n] * np.cos (ángulo)
            sum_imag + = -s [n] * np.sin (ángulo)
            ángulo + = corte

        rebanada + = astilla
        tmpsum = (sum_real ** 2 + sum_imag ** 2) ** 0.5 
        S.append (tmpsum)

    devoluciones

def ft4 (s, N): # Usando la ecuación de wikipedia

    S = []

    para k en rango (N / 2):

        sum_real = 0.0    
        sum_imag = 0.0
        para n en rango (N):
            sum_real + = s [n] * np.cos (2 * np.pi * k * n / float (N))
            sum_imag + = -s [n] * np.sin (2 * np.pi * k * n / float (N))

        tmpsum = (sum_real ** 2 + sum_imag ** 2) ** 0.5 
        S.append (tmpsum)

    devoluciones

def ft5 (s, N): # Raíces de la suma ponderada de la unidad

    astilla = 2 * np.pi / float (N) 

    root_real = np.zeros (N)
    root_imag = np.zeros (N)

    ángulo = 0.0
    para r en el rango (N):
        root_real [r] = np.cos (ángulo)
        root_imag [r] = -np.sin (ángulo)
        ángulo + = astilla

    S = []

    para k en rango (N / 2):

        sum_real = 0.0    
        sum_imag = 0.0
        r = 0

        para n en rango (N):
            sum_real + = s [n] * root_real [r]
            sum_imag + = s [n] * root_imag [r]
            r + = k
            si r> = N: r - = N

        tmpsum = np.sqrt (sum_real * sum_real + sum_imag * sum_imag)
        S.append (tmpsum)

    devoluciones

def main ():

    N = 200
    fs = 100.0

    time_step = 1.0 / fs
    t = np.arange (0, N * time_step, time_step)

    f = 5.0
    y = np.sin (2 * np.pi * f * t) + np.cos (2 * np.pi * f * 2 * t)

    fig = plt.figure ()
    ax = fig.add_subplot (311)
    ax.set_title ('Señal en dominio de tiempo')
    ax.set_xlabel ('t')
    ax.plot (t, y)

    S, freqs = ft (t, y, fs) 

    ax = fig.add_subplot (312)
    ax.set_xticks (np.arange (0, freqs [-1], 2)) 
    ax.set_title ('Tiempo usando ecuación')
    ax.set_xlabel ('frecuencia')
    ax.plot (freqs, S)

    S = ft3 (y, N) 
    ax = fig.add_subplot (313)
    ax.set_title ('Usando la ecuación Wiki')
    ax.set_xlabel ('frecuencia')
    ax.set_xticks (np.arange (0, freqs [-1], 2)) 
    imprimir len (S), len (freqs)
    ax.plot (freqs, S)

    plt.tight_layout ()
    plt.show ()

principal()

ingrese la descripción de la imagen aquí

Cedron Dawg
fuente
por cierto, probablemente tenías problemas porque mi código asumía python3;)
Nimitz14
1
@ Nimitz14, no es gran cosa. Agregué "float ()" y un montón de ".0" en los números. Su código funcionó bien, lo único que tuve que eliminar fue la declaración "plt.style.use ('ggplot')".
Cedron Dawg
1
@ Nimitz14, olvidé mencionar, agregué una rutina ft5 al código que calcula previamente las raíces de los valores de unidad y realmente muestra cómo se calcula el DFT usando las mismas raíces para cada bin.
Cedron Dawg
4

No voy a mirar tu código. la página de wikipedia se ve bien, pero es un buen ejemplo de la "guerra de formatos" o "guerra de notación" o "guerra de estilo" entre matemáticos e ingenieros eléctricos. En parte, creo que la gente de matemáticas tiene razón. Los EE nunca deberían haber adoptado "j"para la unidad imaginaria. Dicho esto, esta es una mejor expresión de la DFT e inversa es:

DFT:

X[k]=n=0N1x[n]ej2πnk/N

iDFT:

x[n]=1Nk=0N1X[k]ej2πnk/N

porque a los ingenieros eléctricos que hacen DSP les gusta usar x[n] como una secuencia de muestras en "tiempo" y X[k]como la secuencia de muestras discretas en "frecuencia". los matemáticos podrían preferir esto:

DFT:

Xk=n=0N1xnei2πnk/N

iDFT:

xn=1Nk=0N1Xkei2πnk/N

y eso es lo mismo que la página de wikipedia.

es posible que deba prestar más atención al uso de + o en el exponente y cómo eso se traduce en + o en contra de sin() término.

robert bristow-johnson
fuente
3
Si usáramos i en lugar de j, no podríamos decir ELI, el hombre de ICE. ELJ el hombre JCE no tiene el mismo anillo. La civilización se vería amenazada
1
elijah el hombre de jugo?
robert bristow-johnson
@ user28715 Bueno, en ese caso es actual no es la raíz cuadrada de menos 1 .... youtube.com/watch?v=2yqjMiFUMlA
Peter K.
0

Volví a esto e intenté derivar la versión discreta que ayudó a que las cosas tuvieran más sentido:

De algun modo fktn=f(n,k,N)

fk=fsNk y tn=TNn

fs=NT

Entonces

fktn=fsNkTNn=NTNkTNn=knN

¡Hecho!

Nimitz14
fuente