Implementação da DFT Matricial
Aluno: Alysson Machado de Oliveira Barbosa
# importação dos pacotes utilitários
import matplotlib.pyplot as plt
import numpy as np
import warnings
import time
# desabilita todos os warnings
warnings.filterwarnings("ignore")
Lembrando que se $x(n)$ é um sinal no tempo de discreto e aperiódico, sua Transformada de Fourier é dada por $$ X(e^{j\omega}) = \sum_{n=-\infty}^{\infty}x[n]e^{-j\omega n} $$ e a inversa $$ x(n) = \frac{1}{2\pi}\int_{2\pi}X(e^{j\omega})e^{j\omega n}d\omega $$ $X(e^{j\omega})$ é uma função contínua de $\omega$.
Exemplo:
Seja o sinal $$ x(n) = \left\{ \begin{array}{c c} 1, & 0 \leq n < L \\ 0, & c.c. \end{array} \right. $$ que é limitado no tempo com apenas $L$ valores constantes e não nulos. Calculando a Transformada de Fourier para $L=8$ obtemos $$ X(e^{j\omega}) = \frac{e^{-j8\omega} - 1}{e^{-j\omega} - 1} $$ cujo módulo está representado na Figura abaixo:
def x(w, L):
'''
Representação do sinal x[n] no domínio da frequência
Inputs:
w (int) -> frequência em radianos.
L (int) -> valor máximo de n cujo x[n] = 1, considerando que n > 0.
'''
#
numerator = np.exp(-1j * L * w) - 1
denominator = np.exp(-1j * w) - 1
return numerator / denominator
# definindo os valores no domínio da frequência (radianos)
w = np.linspace(0, 2 * np.pi, 200)
# configurações para a plotagem do gráfico
plt.plot(w, np.abs(x(w, L = 8)))
plt.xlabel('Frequência (radianos)')
plt.ylabel('$|x(e^{jw})|$')
plt.title('Módulo da função no domínio da frequência')
plt.grid(True)
plt.show()
O módulo representado na figura acima é uma função contínua de $\omega$, e para realizar a análise espectral usando um processador digital, devemos amostrá-la, o que nos leva a questão de quantas amostras devem ser obtidas.
Por exemplo, se escolhermos $N = 8$, vamos amostras tomar nas frequências $\omega_k = 2\pi k/N$ e obteremos os valores calculados abaixo.
Podemos escrever $$ X[k] = X(e^{j\omega})|_{\omega=\frac{2\pi k}{N}},\ k=0,1,2,..., N-1 $$ e como $X(e^{j\omega})$ é periódica, basta tomar amostras no intervalo de 0 a $2\pi$.
Estamos considerando que o sinal $x(n)$ tem $L$ valores ($x[n] = 0$ para $n < 0 $ e $N\geq L$), ao tomar $N$ amostas do espectro temos $$ X[k] = X(e^{j\frac{2\pi k}{N}}) = \sum\limits_{n=0}^{N-1}x[n]e^{-j\frac{2\pi k}{N}n} $$ ou ainda como $$ X[k] =\left\{ \begin{array}{c c} \sum\limits_{n=0}^{N-1}x[n]W_N^{kn} & k = 0,1, ..., N-1 \\ 0, & c.c \end{array} \right. $$ e $$ x[n] = \left\{ \begin{array}{c c} \frac{1}{N}\sum\limits_{k=0}^{N-1}X[k]W_{N}^{-kn}, & n = 0,1,...,N-1 \\ 0, & c.c. \end{array}\right. $$ sendo $W_N = e^{-j\frac{2\pi}{N}}$.
def dft_sum(time_function, N):
'''
Define a DFT usando procedimentos de repetição com somatórios
Inputs:
time_function (list) -> lista com os valores discretos x[n] da função.
N (int) -> define o espaçamento dos valores discretos x[k] obtidos com a DFT.
Outputs:
x_freq (list) -> valores da DFT x[k] para x[n].
'''
# inicializa uma lista para adicionar os valores de x[k]
x_freq = np.zeros((N))
# aplica o zero padding em x[n] para os casos em que N > len(x[n])
if len(time_function) < N:
time_function.extend(np.zeros(N - len(time_function)))
# somatório para x[0], ..., x[N-1], 0 <= k < N - 1
for k in range(0, N):
# intera as componentes de cada somatório de x[k], 0 <= n < N - 1
x_k_values = list()
for n in range(0, N):
x_k_values.append(np.multiply(time_function[n], np.exp(-1j * ((2 * np.pi) / N) * (k * n))))
x_freq[k] = np.sum(x_k_values)
return x_freq
def dft_matrix(time_function, N):
'''
Define a DFT usando procedimentos de multiplicação matricial.
Inputs:
time_function (list) -> lista com os valores discretos x[n] da função.
N (int) -> define o espaçamento dos valores discretos x[k] obtidos com a DFT.
Outputs:
x_freq (list) -> valores da DFT x[k] para x[n].
'''
# aplica o zero padding em x[n] para os casos em que N > len(x[n])
if len(time_function) < N:
time_function.extend(np.zeros(N - len(time_function)))
# define todos os valores possíveis de n, correspondentes a quantidade máxima de linhas da matriz
n = np.arange(N)
# define todos os valores possíveis de k, correspondentes a quantidade máxima de colunas da matriz
k = n.reshape((N, 1))
# define a matrix que mapeia os elementos de x[n] para x[k]
W = np.exp(-1j * ((2 * np.pi) / N) * (k * n))
# realiza o mapeamento da função x[n] para o domínio x[k]
x_freq = np.dot(W, time_function)
return x_freq
def visualize_dfts(N):
'''
Função para visualizar as DFT pelo método de somatórios e matricial.
Inputs:
N (int) -> define o espaçamento dos valores discretos x[k] obtidos com a DFT.
'''
# aplica a DFT por somatórios
discrete_signal1 = dft_sum([1.,1.,1.,1.,1.,1.,1.,1.], N)
# aplica a DFT por matrizes
discrete_signal2 = dft_matrix([1.,1.,1.,1.,1.,1.,1.,1.], N)
# criando os objetos para plotar duas imagens
fig, axs = plt.subplots(1, 2 , figsize=(16, 6))
# plotando o sinal x(e^(jw))
axs[0].plot(w, x(w, L = 8), label = 'FFT')
# define os valores no eixo x, igualmente espaçados para a resolução do sinal x(e^(jw))
dft_x_axis = np.arange(0, np.max(w), (np.max(w) / len(discrete_signal1)))
# plota os pontos obtidos com a DFT para o método de somatórios
axs[0].scatter(dft_x_axis, discrete_signal1, color = 'red', marker = 'o', label = 'DFT Somatórios')
# define as legendas, títulos e grades da imagem
axs[0].set_xlabel('Frequência (radianos)')
axs[0].set_ylabel('$X(e^{jw})$')
axs[0].set_title(f'Função no domínio da frequência')
axs[0].grid(True)
axs[0].legend()
# plotando o sinal x(e^(jw))
axs[1].plot(w, x(w, L = 8), label = 'FFT')
# define os valores no eixo x, igualmente espaçados para a resolução do sinal x(e^(jw))
dft_x_axis = np.arange(0, np.max(w), (np.max(w) / len(discrete_signal2)))
# plota os pontos obtidos com a DFT para o método de multiplicação matricial
axs[1].scatter(dft_x_axis, discrete_signal2, color = 'red', marker = 'o', label = 'DFT Matricial')
# define as legendas, títulos e grades da imagem
axs[1].set_xlabel('Frequência (radianos)')
axs[1].set_ylabel('$X(e^{jw})$')
axs[1].set_title(f'Função no domínio da frequência')
axs[1].grid(True)
axs[1].legend()
# adiciona um título geral para a figura
fig.suptitle(f'N = {N}')
# Ajustando o layout para evitar sobreposição
plt.tight_layout()
# Exibindo os gráficos
plt.show()
visualize_dfts(N = 8)
visualize_dfts(N = 32)
visualize_dfts(N = 128)
visualize_dfts(N = 512)
def x_impulse(w):
'''
Representação do sinal x[n] no domínio da frequência.
Inputs:
w (int) -> frequência em radianos.
'''
return np.exp(-1j * w * 0)
def x_impulse_shift(w, no):
'''
Representação do sinal x[n] no domínio da frequência.
Inputs:
w (int) -> frequência em radianos.
no (int) -> deslocamento do impulson.
'''
return np.exp(-1j * w * no)
# criando os objetos para plotar duas imagens
fig, axs = plt.subplots(1, 2 , figsize=(16, 6))
discrete_signal3 = dft_sum([1.], N = 16)
discrete_signal4 = dft_matrix([1.], N = 16)
# definindo os valores no domínio da frequência (radianos)
w = np.linspace(0, 2 * np.pi, 200)
# plotando o módulo do sinal x(e^(jw))
axs[0].plot(w, np.abs(x_impulse(w)), label = 'FFT')
# define os valores no eixo x, igualmente espaçados para a resolução do sinal x(e^(jw))
dft_x_axis = np.arange(0, np.max(w), (np.max(w) / len(discrete_signal3)))
# plota os pontos obtidos com a DFT para o método de somatórios
axs[0].scatter(dft_x_axis, np.abs(discrete_signal4), color = 'red', marker = 'o', label = 'DFT Matricial')
# define os valores no eixo x, igualmente espaçados para a resolução do sinal x(e^(jw))
axs[0].set_xlabel('Frequência (radianos)')
axs[0].set_ylabel('$|X(e^{jw})|$')
axs[0].set_title(f'Módulo da função FFT($\delta(n)$) no domínio da frequência')
axs[0].set_ylim([-2, 2])
axs[0].grid(True)
axs[0].legend()
# plotando o ângulo do sinal x(e^(jw))
axs[1].plot(w, np.angle(x_impulse(w)), label = 'FFT')
# define os valores no eixo x, igualmente espaçados para a resolução do sinal x(e^(jw))
dft_x_axis = np.arange(0, np.max(w), (np.max(w) / len(discrete_signal3)))
# plota os pontos obtidos com a DFT para o método de somatórios
axs[1].scatter(dft_x_axis, np.angle(discrete_signal4), color = 'red', marker = 'o', label = 'DFT Matricial')
# define os valores no eixo x, igualmente espaçados para a resolução do sinal x(e^(jw))
axs[1].set_xlabel('Frequência (radianos)')
axs[1].set_ylabel('$\phi$')
axs[1].set_title(f'Ângulo da função FFT($\delta(n)$) no domínio da frequência')
axs[1].set_ylim([-2, 2])
axs[1].grid(True)
axs[1].legend()
# adiciona um título geral para a figura
fig.suptitle(f'N = {64}')
# Ajustando o layout para evitar sobreposição
plt.tight_layout()
# Exibindo os gráficos
plt.show()
# criando os objetos para plotar duas imagens
fig, axs = plt.subplots(1, 2 , figsize=(16, 6))
discrete_signal3 = dft_sum([0, 1., 0, 0], N = 32)
discrete_signal4 = dft_matrix([0, 1., 0, 0], N = 32)
# definindo os valores no domínio da frequência (radianos)
w = np.linspace(0, 2 * np.pi, 200)
# plotando o módulo do sinal x(e^(jw))
axs[0].plot(w, np.abs(x_impulse_shift(w, no = 1)), label = 'FFT')
# define os valores no eixo x, igualmente espaçados para a resolução do sinal x(e^(jw))
dft_x_axis = np.arange(0, np.max(w), (np.max(w) / len(discrete_signal3)))
# plota os pontos obtidos com a DFT para o método de somatórios
axs[0].scatter(dft_x_axis, np.abs(discrete_signal4), color = 'red', marker = 'o', label = 'DFT Matricial')
# define os valores no eixo x, igualmente espaçados para a resolução do sinal x(e^(jw))
axs[0].set_xlabel('Frequência (radianos)')
axs[0].set_ylabel('$|X(e^{jw})|$')
axs[0].set_title(f'Módulo da função FFT($\delta(n-1)$) no domínio da frequência')
axs[0].grid(True)
axs[0].legend()
# plotando o ângulo do sinal x(e^(jw))
axs[1].plot(w, np.angle(x_impulse_shift(w, no = 1)), label = 'FFT')
# define os valores no eixo x, igualmente espaçados para a resolução do sinal x(e^(jw))
dft_x_axis = np.arange(0, np.max(w), (np.max(w) / len(discrete_signal3)))
# plota os pontos obtidos com a DFT para o método de somatórios
axs[1].scatter(dft_x_axis, np.angle(discrete_signal4), color = 'red', marker = 'o', label = 'DFT Matricial')
# define os valores no eixo x, igualmente espaçados para a resolução do sinal x(e^(jw))
axs[1].set_xlabel('Frequência (radianos)')
axs[1].set_ylabel('$\phi$')
axs[1].set_title(f'Ângulo da função FFT($\delta(n-1)$) no domínio da frequência')
axs[1].grid(True)
axs[1].legend()
# adiciona um título geral para a figura
fig.suptitle(f'N = {32}')
# Ajustando o layout para evitar sobreposição
plt.tight_layout()
# Exibindo os gráficos
plt.show()
# criando os objetos para plotar duas imagens
fig, axs = plt.subplots(1, 2 , figsize=(16, 6))
discrete_signal3 = dft_sum([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1., 0, 0, 0,], N = 128)
discrete_signal4 = dft_matrix([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1., 0, 0, 0], N = 128)
# definindo os valores no domínio da frequência (radianos)
w = np.linspace(0, 2 * np.pi, 200)
# plotando o módulo do sinal x(e^(jw))
axs[0].plot(w, np.abs(x_impulse_shift(w, no = 10)), label = 'FFT')
# define os valores no eixo x, igualmente espaçados para a resolução do sinal x(e^(jw))
dft_x_axis = np.arange(0, np.max(w), (np.max(w) / len(discrete_signal3)))
# plota os pontos obtidos com a DFT para o método de somatórios
axs[0].scatter(dft_x_axis, np.abs(discrete_signal4), color = 'red', marker = 'o', label = 'DFT Matricial')
# define os valores no eixo x, igualmente espaçados para a resolução do sinal x(e^(jw))
axs[0].set_xlabel('Frequência (radianos)')
axs[0].set_ylabel('$|X(e^{jw})|$')
axs[0].set_title(f'Módulo da função FFT($\delta(n-10)$) no domínio da frequência')
axs[0].grid(True)
axs[0].legend()
# plotando o ângulo do sinal x(e^(jw))
axs[1].plot(w, np.angle(x_impulse_shift(w, no = 10)), label = 'FFT')
# define os valores no eixo x, igualmente espaçados para a resolução do sinal x(e^(jw))
dft_x_axis = np.arange(0, np.max(w), (np.max(w) / len(discrete_signal3)))
# plota os pontos obtidos com a DFT para o método de somatórios
axs[1].scatter(dft_x_axis, np.angle(discrete_signal4), color = 'red', marker = 'o', label = 'DFT Matricial')
# define os valores no eixo x, igualmente espaçados para a resolução do sinal x(e^(jw))
axs[1].set_xlabel('Frequência (radianos)')
axs[1].set_ylabel('$\phi$')
axs[1].set_title(f'Ângulo da função FFT($\delta(n-10)$) no domínio da frequência')
axs[1].grid(True)
axs[1].legend()
# adiciona um título geral para a figura
fig.suptitle(f'N = {128}')
# Ajustando o layout para evitar sobreposição
plt.tight_layout()
# Exibindo os gráficos
plt.show()
signal = [1,1,1,1,1,1,1,1]
time_inference = list()
for increase in range(len(signal), 1000):
init_time = time.time()
result = dft_matrix(signal, increase)
final_time = time.time()
time_inference.append((final_time - init_time) * 1000)
x = np.arange(8, len(time_inference) + 8)
y = time_inference
grau = 3
coeficientes = np.polyfit(x, y, grau)
polinomio = np.poly1d(coeficientes)
y_plot = polinomio(x)
# Plotagem dos dados originais e da aproximação polinomial
plt.plot(x, y, 'o', label='Tempo de Inferência para N')
plt.plot(x, y_plot, label=f'Aproximação Polinomial (grau {grau})')
plt.xlabel('N')
plt.ylabel('Tempo (segundos)')
plt.title('Gráfico o tempo de execução em função do tamanho da DFT (N)')
plt.legend()
plt.grid(True)
plt.show()