12
INTERPOLACIÓN DE FUNCIONES BASADA EN LA TRANSFORMADA DE FOURIER GONZALO LUZARDO M. JUNIO 2009

INTERPOLACIÓN DE FUNCIONES BASADA EN LA …blog.espol.edu.ec/gluzardo/files/2009/06/fft.pdfhecho una interpolación de 1 a 2, esto es, que hemos obtenido el doble de muestras contenidas

  • Upload
    builiem

  • View
    218

  • Download
    0

Embed Size (px)

Citation preview

INTERPOLACIÓN DE FUNCIONES BASADA

EN LA TRANSFORMADA DE FOURIER

GONZALO LUZARDO M.

JUNIO 2009

1. Introducción

La interpolación es un método usado para conocer, de un modo aproximado, los valores que toma

cierta función de la cual sólo se conoce su imagen en un número finito de abscisas. A menudo, ni

siquiera se conocerá la expresión de la función y sólo se dispondrá de los valores que toma para

dichas abscisas.

El objetivo será hallar una función que cumpla lo antes mencionado y que permita hallar

aproximaciones de otros valores desconocidos para la función con una precisión deseable fijada.

Existen varios métodos que permiten hacer interpolación de funciones, unos tratan de buscar una

función aproximada a la función original, y otros en cambio tratan de buscar sólo los valores

interpolados sin interesarse en cómo luce la función original, entre los métodos más conocidos

tenemos:

• Interpolación lineal, donde la función original se aproxima a una recta.

• Interpolación cuadrática o de Lagrange, donde la función original se aproxima a una función

cuadrática.

• Interpolación polinómica, donde la función original se aproxima a un polinomio de grado N.

• Interpolación de Hermite, el cual consiste en buscar un polinomio cúbico por pedazos dentro

de la función a aproximar.

• Interpolación por series de Fourier, donde la función original es traspasada al dominio de la

frecuencia con el objetivo de buscar los valores a ser interpolados dentro del dominio del

tiempo.

El presente trabajo tiene el objetivo de, por un lado describir la técnica de interpolación de

funciones por medio de series de Fourier, y por otro lado, y debido a las cargas computacionales

incurridas dentro del cálculo de la serie de Fourier de una función, el maximizar la velocidad de

cálculo de los valores de la función interpolada.

2. Interpolación de funciones por medio de Series de Fourier

La interpolación de funciones por Series de Fourier, es un método que como ya se mencionó, no

trata de buscar la función que representa fielmente a la función original, sino más bien trata de

buscar los valores que representa dicha función.

Este tipo de interpolación tiene la ventaja de ser muy preciso con la penalización de ser complejo

computacionalmente hablando. Este método es especialmente indicado cuando es necesario

preservar no sólo el valor de la señal sino también su fase.

El método consiste en obtener la transformada de Fourier de la función que se desea interpolar, una

vez en el dominio de la frecuencia se agregan tantos ceros en las frecuencias bajas como se desee

interpolar la función, y luego se regresa al dominio del tiempo con los valores interpolados. Este

método, como veremos más tarde, es muy preciso para interpolar funciones simples sin saltos

bruscos que se traduzcan en frecuencias infinitas.

En resumen los pasos que debemos tomar para interpolar una función mediante series de Fourier es

el siguiente:

1. La función original la pasamos al dominio de la frecuencia usando la FFT. Con este primer

paso obtenemos la función original en el dominio de la frecuencia.

Fig. 2-1. Función original en el dominio del (a) tiempo y (b) frecuencia.

Como vemos en la Fig.2-1 al realizar la transformada de Fourier de una señal de N muestras

obtenemos la misma señal de N muestras en el dominio de la frecuencia.

2. Agregamos tantos ceros como queramos interpolar la función, por ejemplo en este caso

tenemos una función con 256 valores que queremos interpolar a una función de 512 valores,

te tal forma que agregaremos 256 ceros.

Fig. 2-2. Se colocan ceros en las frecuencias extremas localizadas en la función original.

Como vemos en la Fig.2-2, los ceros son agregados de tal forma que no afectemos a la

información contenida en el dominio de la frecuencia. Los ceros son agregados en los

extremos de la función que contiene información de las frecuencias extremas encontradas

en la función original.

3. Regresamos al dominio del tiempo usando la IFFT.

Como vemos en la Fig.2-3, la función contiene los valores interpolados, en este caso hemos

hecho una interpolación de 1 a 2, esto es, que hemos obtenido el doble de muestras

contenidas en la función original.

Fig. 2-3. Función interpolada.

3. Implementación

La implementación de la implementación por Series de Fourier fue desarrollada en el lenguaje C. Los

siguientes ficheros corresponden a la implementación del interpolador:

Interpolator.cpp Implementación de las funciones para realizar interpolación de funciones basada en la transformada de Fourier.

Utils.cpp Implementación de los métodos de creación de funciones de prueba, así como aquellas que nos ayudan a dar formato a las funciones de entrada al interpolador.

Interpolation.cpp Implementación de la interpolación de una recta, una función de impulso, y una función sinusoidal. Nos permite realizar las pruebas y obtener los resultados del interpolador. Crea tres ficheros de MatLab con los resultados numéricos y gráficos de la interpolación.

La transformada de Fourier implementada en Interpolator.cpp es la denominada decimation-

intime o Cooley-Tukey FFT presentada por N.M Brenner, la cual permite obtener la transformada

rápida de Fourier y su inversa utilizando la misma función con distinto parámetro de entrada.

La función que implementa la transformada de Fourier implementada en Interpolator.cpp

llamada four1 (float data[], unsigned long nn, int isign), propuesta en el texto de

Numerical Recipes: The Art of Scientific Computing1, recibe como parámetro el vector imaginario

que se desea aplicar la transformada de Fourier (data), el tamaño (nn) y el tipo de transformación

(isign) 1 para la FFT y -1 para la IFFT.

Tanto el vector complejo de entrada (que debe tener un tamaño de potencia de 2) como el de salida

es un vector que contiene datos reales e imaginarios alternados de forma consecutiva, vea Fig. 3-1.

Así mismo el vector imaginario de salida que contiene los datos de frecuencia del vector de entrada,

empieza con la frecuencia cero, hasta la frecuencia positiva más alta localizada. Luego contiene la

frecuencia más negativa hasta la menos negativa localizada justo antes de la frecuencia cero.

1 Numerical Recipes: The Art of Scientific Computing, Third Edition (2007), 1256 pp. Cambridge University

Press, ISBN-10: 0521880688, http://www.nr.com/

Fig. 3-1. Vectores de entrada y salida de la FFT. (a) El vector de entrada contiene N (potencia de 2) muestras complejas en

un vector real de tamaño 2N, alternando parte real e imaginaria de manera consecutiva. (b) El vector de salida contiene el

espectro complejo de Fourier de N valores de frecuencia, con la parte real e imaginaria se alterna consecutivamente. El

vector empieza con la frecuencia cero, hasta la frecuencia positiva más alta localizada. Luego contiene la frecuencia más

negativa hasta la menos negativa localizada justo antes de la frecuencia cero.

4. Primeros resultados

Utilizando el código descrito en Interpolation.cpp obtuvimos la interpolación de una recta, una

función de impulso y una función sinusoidal.

Los resultados para la interpolación de 256 a 512 muestras fueron los siguientes:

Parameters readed: NINPUT=256 NOUTPUT=512 Increment factor=2 *** INTERPOLATION ***

Setting parameters..

W=50, Bandwidth=50, Sample Period=0.01, Sample Frecuency=100 Increment factor=2 Setting data for interpolation... Setting data time ...

Time vector was succefully created... Setting perfect data to compare interpolation Setting data time ...

Time vector was succefully created... MAKING LINE INTERPOLATION

Setting function values ... Line was succefully created... Line was succefully created...

Starting interpolation... Make interpolation from:256 to:512 Interpolation completed in 0 seconds

Analyzing results... Data analyzed

Maximun error=4096 CME=10.1548

MAKING PULSE INTERPOLATION

Setting function values ... Starting interpolation... Make interpolation from:256 to:512

Interpolation completed in 0 seconds Analyzing results... Data analyzed

Maximun error=0.992203 CME=0.00318133

MAKING SINOSOIDAL INTERPOLATION

Setting function values ... Starting interpolation... Make interpolation from:256 to:512 Interpolation completed in 0 seconds

Analyzing results... Data analyzed Maximun error=0.0713742 CME=0.000178336

En la Fig.4-1, podemos observar las gráficas obtenidas para la función original y la interpolada en

cada caso:

Fig. 4-1. Gráficas obtenidas de para una interpolación 1 a 2.

Al observar los resultados obtenidos notamos cómo la interpolación es muy precisa, salvo el caso en aquellos lugares en que la función tiene saltos bruscos (zonas de frecuencia infinita), en donde la función interpolada muestra una especie de balanceos.

Una manera de corregir estos “balanceos” es tratar de evitar que la función original contenga saltos bruscos, realizando alguna transformación u ordenación previa de los datos que elimine dichos saltos, por ejemplo.

5. Optimización

Si analizamos los resultados obtenidos en el apartado anterior notamos como el tiempo tomado en

realizar la interpolación resulta casi despreciable al tratarse de una pequeña cantidad de muestras.

En cambio si queremos interpolar muestras de mayor tamaño, como por ejemplo la interpolación de

una función con 220 muestras a otra de 222, obtenemos los siguientes resultados:

Parameters readed: NINPUT=1048576 NOUTPUT=4194304 Increment factor=4

*** INTERPOLATION *** Setting parameters.. W=50, Bandwidth=50, Sample Period=0.01, Sample Frecuency=100 Increment factor=4

Setting data for interpolation... Setting data time ...

Time vector was succefully created... Setting perfect data to compare interpolation Setting data time ...

Time vector was succefully created...

MAKING LINE INTERPOLATION Setting function values ...

Line was succefully created... Line was succefully created... Starting interpolation...

Make interpolation from:1048576 to:4194304 Interpolation completed in 4.515 seconds Analyzing results...

Data analyzed Maximun error=1.67472e+011 CME=68748.4

MAKING PULSE INTERPOLATION Setting function values ... Starting interpolation...

Make interpolation from:1048576 to:4194304 Interpolation completed in 4.531 seconds

Analyzing results...

Data analyzed Maximun error=1.28829 CME=1.33643e-006

MAKING SINOSOIDAL INTERPOLATION Setting function values ... Starting interpolation... Make interpolation from:1048576 to:4194304

Interpolation completed in 4.561 seconds Analyzing results... Data analyzed

Maximun error=0.362624 CME=1.66753e-007

Como podemos observar, el tiempo tomado por hacer la interpolación de cada función es de 4.5

segundos aproximadamente. En este apartado trataremos de mejorar estos tiempos mediante la

optimización de código.

La estrategia que tomaremos para la optimización del código será la siguiente:

1. Utilizar el compilador de Intel.

2. Utilizar el optimizador de Intel VTunes para localizar los lugares dentro del código donde

se gasta la mayor parte del tiempo del proceso de interpolación.

3. Aplicar las recomendaciones hechas por VTunes para optimizar el código y analizar los

tiempos obtenidos.

4. Analizar las posibilidades de optimización por compilación y elegir la que nos da mejores

tiempos de ejecución.

5. Utilizar pragmas para tratar de mejorar aún más los tiempos.

Una vez seleccionado el compilador de Intel, realizamos un Quick Performance Analysis con VTunes

para analizar los lugares dentro del código en el cual se emplea la mayor parte del tiempo. Los

resultados pueden ser observados en la Fig. 5-1.

Fig. 5-1. Resultados obtenidos con el VTunes.

Al analizar los resultados obtenidos con el VTunes notamos que las recomendaciones hechas por

VTunes en su mayor parte se refieren a la vectorización y desenrollado de lazos.

El desenrollado no será aplicado ya que existe dependencia entre datos, y el esfuerzo en cambiar

dicha dependencia no hará que la velocidad sea incrementada de manera considerable. De igual

forma, la vectorización del lazo se tratará de hacer de manera automática por medio de pragmas y

directivas de compilación.

Lo siguiente que haremos será utilizar un conjunto de directivas de compilación y analizar los

resultados que iremos obteniendo. Los resultados obtenidos son reflejados en la Tabla 5-1.

Compilador Opciones de compilación Tiempos de ejecución para cada

interpolación (seg)

Intel -O2 4.5 – 4.469 – 4.469

Intel -G6 -QaxB -Qpc64 -Qvec_report3 -O2 4.422 – 4.391 – 4.485

Intel -QaxB -Qvec_report3 -O2 4.398 – 4.391 – 4.355

Tabla 5-1. Tabla de tiempos de ejecución, con diferentes parámetros de compilación.

Como podemos observar en la Tabla 5-1, la mejora es casi inapreciable. Al observar el reporte d

compilación, notamos que el compilador pudo vectorizar aquellos lazos que no toman mucho

tiempo de ejecución y que no están dentro de la función que realiza la transformada de Fourier, los

cuales toman la mayor cantidad de tiempo de proceso:

Compiling... Intel(R) C++ Compiler for 32-bit applications, Version 8.1 Build 20041119Z Package ID: W_CC_PC_8.1.022

Copyright (C) 1985-2004 Intel Corporation. All rights reserved. icl -Qvc6 "-Qlocation,link,D:\Archivos de programa\Microsoft Visual Studio\VC98\Bin" /MLd /Zi /O2 "/FoDebug 2/" "/FdDebug 2/" /QaxB /Qvec_report3 "AllCode.cpp"

AllCode.cpp AllCode.cpp(49): (col. 1) remark: _main has been targeted for automatic cpu dispatch. AllCode.cpp(318): (col. 2) remark: LOOP WAS VECTORIZED.

AllCode.cpp(317): (col. 57) remark: ?createSin@@YAXQAM0H@Z has been targeted for automatic cpu dispatch. AllCode.cpp(343): (col. 2) remark: loop was not vectorized: vectorization possible but seems

inefficient. AllCode.cpp(368): (col. 89) remark: ?insertZerosInTheMiddle@@YAXQAMH0H@Z has been targeted for automatic cpu dispatch.

AllCode.cpp(363): (col. 2) remark: LOOP WAS VECTORIZED.

AllCode.cpp(362): (col. 57) remark: ?insertZeros@@YAXQAMHH@Z has been targeted for automatic cpu dispatch.

AllCode.cpp(356): (col. 2) remark: LOOP WAS VECTORIZED. AllCode.cpp(355): (col. 100) remark: ?copyVectorData@@YAXQAM0HHH@Z has been targeted for automatic cpu dispatch. AllCode.cpp(469): (col. 2) remark: loop was not vectorized: not inner loop.

AllCode.cpp(476): (col. 3) remark: loop was not vectorized: unsupported loop structure. AllCode.cpp(485): (col. 2) remark: loop was not vectorized: not inner loop. AllCode.cpp(495): (col. 3) remark: loop was not vectorized: not inner loop.

AllCode.cpp(496): (col. 4) remark: loop was not vectorized: unsupported loop structure. AllCode.cpp(282): (col. 2) remark: loop was not vectorized: unsupported loop structure. AllCode.cpp(324): (col. 2) remark: LOOP WAS VECTORIZED.

AllCode.cpp(323): (col. 84) remark: ?multMatrixConstant@@YAXQAM0HM@Z has been targeted for automatic cpu dispatch.

AllCode.cpp(395): (col. 3) remark: loop was not vectorized: mixed data types. AllCode.cpp(289): (col. 3) remark: loop was not vectorized: nested conditional statements.

AllCode.cpp(305): (col. 2) remark: LOOP WAS VECTORIZED. AllCode.cpp(304): (col. 88) remark: ?createTimeVector@@YAXQAMHMM@Z has been targeted for automatic

cpu dispatch. AllCode.cpp(330): (col. 2) remark: LOOP WAS VECTORIZED.

AllCode.cpp(329): (col. 83) remark: ?addMatrixConstant@@YAXQAM0HM@Z has been targeted for automatic cpu dispatch.

D:\Archivos de programa\Microsoft Visual Studio\VC98\INCLUDE\streambuf(146) : (col. 3) remark: loop was not vectorized: unsupported loop structure. D:\Archivos de programa\Microsoft Visual Studio\VC98\INCLUDE\streambuf(159) : (col. 3) remark: loop

was not vectorized: unsupported loop structure. D:\Archivos de programa\Microsoft Visual Studio\VC98\INCLUDE\fstream(165) : (col. 4) remark: loop was not vectorized: unsupported loop structure. D:\Archivos de programa\Microsoft Visual Studio\VC98\INCLUDE\xlocnum(628) : (col. 4) remark: loop

was not vectorized: contains unvectorizable statement at line 629. D:\Archivos de programa\Microsoft Visual Studio\VC98\INCLUDE\xlocnum(598) : (col. 4) remark: loop was not vectorized: unsupported loop structure.

D:\Archivos de programa\Microsoft Visual Studio\VC98\INCLUDE\xlocnum(617) : (col. 4) remark: loop was not vectorized: unsupported loop structure. D:\Archivos de programa\Microsoft Visual Studio\VC98\INCLUDE\xlocnum(632) : (col. 4) remark: loop

was not vectorized: contains unvectorizable statement at line 633. D:\Archivos de programa\Microsoft Visual Studio\VC98\INCLUDE\xlocnum(636) : (col. 4) remark: loop was not vectorized: contains unvectorizable statement at line 637.

AllCode.cpp(227) : (col. 2) remark: loop was not vectorized: contains unvectorizable statement at line 228. AllCode.cpp(232) : (col. 2) remark: loop was not vectorized: contains unvectorizable statement at

line 233. AllCode.cpp(238) : (col. 2) remark: loop was not vectorized: contains unvectorizable statement at line 239.

AllCode.cpp(243) : (col. 2) remark: loop was not vectorized: contains unvectorizable statement at line 244. AllCode.cpp(248) : (col. 2) remark: loop was not vectorized: contains unvectorizable statement at line 249.

D:\Archivos de programa\Microsoft Visual Studio\VC98\INCLUDE\ostream(299) : (col. 4) remark: loop was not vectorized: unsupported loop structure. D:\Archivos de programa\Microsoft Visual Studio\VC98\INCLUDE\ostream(308) : (col. 4) remark: loop

was not vectorized: unsupported loop structure. AllCode.cpp(298) : (col. 2) remark: loop was not vectorized: contains unvectorizable statement at line 299.

AllCode.cpp(336) : (col. 2) remark: LOOP WAS VECTORIZED.

AllCode.cpp(335) : (col. 63) remark: ?copyVector@@YAXQAM0H@Z has been targeted for automatic cpu dispatch.

D:\Archivos de programa\Microsoft Visual Studio\VC98\INCLUDE\xlocale(219) : (col. 9) remark: loop was not vectorized: contains unvectorizable statement at line 220. D:\Archivos de programa\Microsoft Visual Studio\VC98\INCLUDE\fstream(49) : (col. 49) remark: loop

was not vectorized: unsupported loop structure. D:\Archivos de programa\Microsoft Visual Studio\VC98\INCLUDE\fstream(54) : (col. 4) remark: loop was not vectorized: contains unvectorizable statement at line 55.

Microsoft (R) Incremental Linker Version 6.00.8168 Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

-out:AllCode.exe -debug:notmapped,full -debugtype:cv -pdb:AllCode.pdb

"Debug 2\AllCode.obj"

AllCode.obj - 0 error(s), 0 warning(s)

El siguiente paso consiste en utilizar pragmas. En general utilizaremos dos pragmas:

• #pragma ivdep, que permite decirle al compilador que ignore la posible dependencia

entre datos.

• #pragma vector always, que permite decirle al compilador que vectorice el bucle

siempre que sea posible, ignorando posibles decisiones sobre rentabilidad o conveniencia.

Ambos pragmas fueron agregados dentro la función que realiza la transformada de Fourier, en los

siguientes lugares:

void four1(float data[], unsigned long nn, int isign) {

unsigned long n,mmax,m,j,istep,i; double wtemp,wr,wpr,wpi,wi,theta; float tempr,tempi;

n=nn << 1; j=1;

#pragma vector always for (i=1;i<n;i+=2) { if (j > i) {

SWAP(data[j],data[i]); SWAP(data[j+1],data[i+1]); } m=n >> 1;

while (m >= 2 && j > m) { j -= m;

m >>= 1; } j += m;

} //Herebegins the Daniel-Lanczos section of the routire, outer loop exectured log2(nn) times

mmax=2; #pragma ivdep

while (n > mmax) { istep=mmax << 1; // Trigonometric recurrence

theta=isign*(6.28318530717959/mmax); wtemp=sin(0.5*theta); wpr = -2.0*wtemp*wtemp; wpi=sin(theta);

wr=1.0; wi=0.0;

for (m=1;m<mmax;m+=2) { #pragma ivdep

for (i=m;i<=n;i+=istep) {

// Danielson-Lanczos Formula j=i+mmax;

tempr=wr*data[j]-wi*data[j+1]; tempi=wr*data[j+1]+wi*data[j]; data[j]=data[i]-tempr;

data[j+1]=data[i+1]-tempi; data[i] += tempr; data[i+1] += tempi;

} // Trigonometric recuerrence wr=(wtemp=wr)*wpr-wi*wpi+wr;

wi=wi*wpr+wtemp*wpi+wi; } mmax=istep; }

}

Los resultados en tiempos fueron 4.311, 4.356 y 4.322 segundos en cada caso respectivamente. Así

mismo se analizó los resultados para verificar que la especificación de no dependencia entre datos

en el código pudiera o no afectar el resultado del interpolador. Los resultados fueron correctos, de

tal forma que podemos decir que asumir dicha independencia fue lo correcto.

Si bien en cierto los tiempos mejoraron un poco, dicha mejora aún no es apreciable. Una posible

mejora sería utilizar instrucciones específicas del procesador (como MMX considerando que fuera un

procesador Intel) para incrementar la velocidad de procesamiento.

6. Conclusiones

Al finalizar el trabajo podemos obtener las siguientes conclusiones:

� El método de interpolación por series de Fourier es un método que a pesar de ser costoso

computacionalmente hablando, resulta ser muy preciso en la obtención de los valores

interpolados para funciones simples con un rango de frecuencias acotado.

� Debido a la gran cantidad de dependencia de datos, la implementación de la FFT no puede

ser optimizada aplicando vectorización y desenrollado de los lazos presentes.

7. Referencias

[1] Numerical Recipes-in C (The Art of Scientific Computing 2ª Ed.). William H. Press, Saul A.

Teukolsky, William T. Vetterling, Brian P. Flannery. Cambridge University Press. 1996. ISBN 0 521

43108 5.