El primer tema de interés desde la reapertura del semillero fueron las series de Fourier. En un principio se buscó información acerca de este tema y luego se encontró el siguiente video que sirvió para llamar el interés y despertar la curiosidad de todos, en este se puede enteder de una manera llamativa algunas de las propiedades y relaciones de las series de Fourier y la transformada de Fourier.
Luego de este video surgieron muchas dudas, pero se decidió enfocar en dos:
¿Cuál es la teoría detrás de esta aplicación?
La siguiente información se obtuvo del video : https://www.youtube.com/watch?v=spUNpyF58BY
Intuitivamente la transformada de Fourier es una máquina que permite tomar una señal y descomponerla en cada una de las ondas sinusoidales que la conforman.
Esta máquina tendrá la funcionalidad de transformar la señal original del dominio del tiempo al dominio de frecuencia (transformada de Fourier). luego, de hacer el análisis correspondiente en el dominio de frecuencia, se obtiene la descomposición de las señales sinusoidales asociada a cada frecuencia en el dominio del tiempo (transformada inversa de Fourier).
Para entender esta máquina matemática, inicialmente supóngase que se tiene una señal sinusoidal que se enrolla al rededor del origen en un plano complejo con una frecuencia de giros variante.
Cuando se realiza el enrollado de la función sinusoidal de la señal original, se juega con dos frecuencias distintas:
La frecuencia de la señal original que es F=1/T donde T es el periodo de la señal.
La frecuencia de enrollado de la función; es decir, el número de giros por segundo al rededor del origen.
Cuando estas dos frecuencias coinciden, se obtiene el valor en el dominio de frecuencia que describe una de las señales sinusoidales simples que componen la señal original.
Para obtener la información que se desea en el dominio de frecuencias, se puede suponer que la curva enrollada alrededor del origen tiene un centro de masa. Inicialmente, el valor de la abscisa de este centro será el valor de la ordenada correspondiente a la frecuencia.
La representación del movimiento del vector al rededor del origen se da por la expresión compleja que ofrece la fórmula de Euler.
Si se lleva la anterior expresión al cálculo diferencial, se tiene el valor del centro de masa en forma compleja. Obteniendo una casi-transformada de Fourier
Para obtener la expresión de la transformada de Fourier sobre un intervalo, se puede eliminar el divisor que corresponde a la longitud del tiempo. Esto significa que ya no se tendrá en cuenta el valor del centro de masa, sino una cantidad proporcional a la longitud de tiempo.
Transformada de Fourier (FT)
Relación entre la serie de Fourier y la Transformada de Fourier
A continuación, se va a hacer una asociación entre la transformada y la serie de Fourier, para ello se va a definir una serie de preliminares importantes:
Se va a definir el siguiente producto escalar entre funciones periódicas y finitamente discontinuas en un conjunto finito de puntos (de lo contrario no se tendría un periodo)
donde y*(t) es el conjugado de la función y(t), Luego, se define la serie de Fourier como:
Donde cada uno de sus coeficientes son de la forma:
donde h(t) es la función inicial sobre la que se va a aplicar la serie de Fourier.
ahora bien, nótese que la serie de Fourier se define para funciones de periodicidad t, con lo que tienen una frecuencia a la que se denomina w_0. Dicho esto, se pueden notar dos cosas, primero, que los coeficientes de la serie son el producto escalar entre la función inicial y la función exponencial compleja, y segundo, que el periodo es inversamente proporcional a la frecuencia, de modo que si se aplica la serie de Fourier a funciones no periódicas o de periodo infinito, entonces la frecuencia va a tender a cero, es decir, será continua. Sin embargo, surge un inconveniente, el producto escalar definido anteriormente no está definido para funciones de periodicidad infinita, de modo que se va a definir un nuevo producto escalar de la siguiente manera:
el siguiente paso consiste en redefinir la serie para funciones de periodicidad infinita, en este caso, sea f(t) una función con periodicidad infinita, se efectúa el siguiente producto interno:
Ahora bien, el discreto que se tenía anteriormente con la sumatoria, se convierte en un continuo definido como la siguiente integral:
En la serie de Fourier se sumaba variando la frecuencia, por lo que esta nueva integral será con respecto a ella, luego cada coeficiente de dicha integral será la proximidad que existe entre la nueva función y la función exponencial compleja, de dónde se obtiene que
Donde
Es así que se ha obtenido la transformada de fourier a partir de su serie.
¿Se puede encontrar alguna forma de computar este algoritmo para entender y manipularlo para así entender mejor sus implicaciones y utilidades?
El código original fue obtenido de: https://www.instructables.com/Drawing-With-Discrete-Fourier-Transform/
Al código original se le realizaron ciertas modificaciones para dibujar las iniciales del nombre del semillero al tocar la pantalla.
Y este es el resultado final
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.js"></script>
<link rel="stylesheet" type="text/css" href="style.css" />
<meta charset="utf-8" />
</head>
<body>
<script>
class Complex {
constructor(a, b) {
this.re = a;
this.im = b;
}
add(c) {
this.re += c.re;
this.im += c.im;
}
mult(c) {
const re = this.re * c.re - this.im * c.im;
const im = this.re * c.im + this.im * c.re;
return new Complex(re, im);
}
}
function dft(x) {
const X = [];
const N = x.length;
for (let k = 0; k < N; k++) {
let sum = new Complex(0, 0);
for (let n = 0; n < N; n++) {
const phi = (TWO_PI * k * n) / N;
const c = new Complex(cos(phi), -sin(phi));
sum.add(x[n].mult(c));
}
sum.re = sum.re / N;
sum.im = sum.im / N;
let freq = k;
let amp = sqrt(sum.re * sum.re + sum.im * sum.im);
let phase = atan2(sum.im, sum.re);
X[k] = { re: sum.re, im: sum.im, freq, amp, phase };
}
return X;
}
</script>
<script>
const USER = 0;
const FOURIER = 1;
let x = [];
let fourierX;
let time = 0;
let path = [];
let drawing = [];
let state = -1;
function mouseReleased() {
state = FOURIER;
const skip = 1;
let p=[]
p.push({x:56,y:124}, {x:56,y:45}, {x:47,y:33}, {x:39,y:37},
{x:41,y:122}, {x:91,y:179}, {x:111,y:180}, {x:111,y:34}, {x:151,y:4},
{x:201,y:32}, {x:164,y:38}, {x:148,y:38}, {x:149,y:158}, {x:149,y:288},
{x:164,y:288}, {x:164,y:158}, {x:164,y:37}, {x:201,y:32}, {x:225,y:32},
{x:243,y:5}, {x:317,y:5}, {x:279,y:36}, {x:270,y:31}, {x:263,y:41},
{x:265,y:139}, {x:280,y:130}, {x:279,y:35}, {x:317,y:5}, {x:318,y:70},
{x:318,y:115}, {x:338,y:116}, {x:339,y:23}, {x:335,y:5}, {x:403,y:5},
{x:416,y:16}, {x:432,y:5}, {x:499,y:5}, {x:496,y:21}, {x:495,y:294},
{x:497,y:316}, {x:450,y:317}, {x:452,y:292}, {x:453,y:41}, {x:437,y:41},
{x:436,y:257}, {x:445,y:284}, {x:417,y:314}, {x:386,y:285}, {x:397,y:260},
{x:397,y:41}, {x:382,y:41}, {x:380,y:292}, {x:386,y:316}, {x:340,y:350},
{x:338,y:144}, {x:338,y:116}, {x:318,y:115}, {x:318,y:315}, {x:280,y:315},
{x:280,y:176}, {x:263,y:188}, {x:263,y:316}, {x:225,y:349}, {x:216,y:349},
{x:226,y:321}, {x:225,y:165}, {x:225,y:32}, {x:201,y:31}, {x:202,y:289},
{x:159,y:321}, {x:111,y:288}, {x:111,y:180}, {x:91,y:179}, {x:91,y:297},
{x:47,y:321}, {x:4,y:293}, {x:2,y:170}, {x:34,y:198}, {x:36,y:272},
{x:43,y:284}, {x:54,y:281}, {x:54,y:196}, {x:3,y:142}, {x:3,y:31}, {x:42,y:0},
{x:85,y:25}, {x:88,y:151}, {x:55,y:124}, {x:56,y:45}, {x:47,y:33},
{x:38,y:37}, {x:41,y:121}, {x:91,y:179},{x:56,y:124}, {x:56,y:45}, {x:47,y:33}, {x:39,y:37},
{x:41,y:122}, {x:91,y:179}, {x:111,y:180}, {x:111,y:34}, {x:151,y:4},
{x:201,y:32}, {x:164,y:38}, {x:148,y:38}, {x:149,y:158}, {x:149,y:288},
{x:164,y:288}, {x:164,y:158}, {x:164,y:37}, {x:201,y:32}, {x:225,y:32},
{x:243,y:5}, {x:317,y:5}, {x:279,y:36}, {x:270,y:31}, {x:263,y:41},
{x:265,y:139}, {x:280,y:130}, {x:279,y:35}, {x:317,y:5}, {x:318,y:70},
{x:318,y:115}, {x:338,y:116}, {x:339,y:23}, {x:335,y:5}, {x:403,y:5},
{x:416,y:16}, {x:432,y:5}, {x:499,y:5}, {x:496,y:21}, {x:495,y:294},
{x:497,y:316}, {x:450,y:317}, {x:452,y:292}, {x:453,y:41}, {x:437,y:41},
{x:436,y:257}, {x:445,y:284}, {x:417,y:314}, {x:386,y:285}, {x:397,y:260},
{x:397,y:41}, {x:382,y:41}, {x:380,y:292}, {x:386,y:316}, {x:340,y:350},
{x:338,y:144}, {x:338,y:116}, {x:318,y:115}, {x:318,y:315}, {x:280,y:315},
{x:280,y:176}, {x:263,y:188}, {x:263,y:316}, {x:225,y:349}, {x:216,y:349},
{x:226,y:321}, {x:225,y:165}, {x:225,y:32}, {x:201,y:31}, {x:202,y:289},
{x:159,y:321}, {x:111,y:288}, {x:111,y:180}, {x:91,y:179}, {x:91,y:297},
{x:47,y:321}, {x:4,y:293}, {x:2,y:170}, {x:34,y:198}, {x:36,y:272},
{x:43,y:284}, {x:54,y:281}, {x:54,y:196}, {x:3,y:142}, {x:3,y:31}, {x:42,y:0},
{x:85,y:25}, {x:88,y:151}, {x:55,y:124}, {x:56,y:45}, {x:47,y:33},
{x:38,y:37}, {x:41,y:121}, {x:91,y:179})
for (let i = 0; i <p.length; i += skip) {
x.push(new Complex(p[i].x, p[i].y));
}
fourierX = dft(x);
fourierX.sort((a, b) => b.amp - a.amp);
}
function setup() {
createCanvas(windowWidth, windowHeight);
background(100);
fill(255);
textAlign(CENTER);
textSize(32);
text("SEMILLERO DE OPTIMIZACIÓN\n Y ANÁLISIS MATEMÁTICO\n SOAM", width/2, height/2);
strokeWeight(5);
}
function epicycles(x, y, rotation, fourier) {
for (let i = 0; i < fourier.length; i++) {
let prevx = x;
let prevy = y;
let freq = fourier[i].freq;
let radius = fourier[i].amp;
let phase = fourier[i].phase;
x += radius * cos(freq * time + phase + rotation);
y += radius * sin(freq * time + phase + rotation);
stroke(255, 100);
noFill();
ellipse(prevx, prevy, radius * 2);
stroke(255);
line(prevx, prevy, x, y);
}
return createVector(x, y);
}
function draw() {
if (state == USER) {
background(100);
let point = createVector(mouseX - width / 2, mouseY - height / 2);
drawing.push(point);
stroke(255);
noFill();
beginShape();
for (let v of drawing) {
vertex(v.x + width / 2, v.y + height / 2);
}
endShape();
} else if (state == FOURIER) {
background(100);
let v = epicycles(width / 2, height / 2, 0, fourierX);
path.unshift(v);
beginShape();
noFill();
strokeWeight(5);
stroke(0, 255,255);
for (let i = 0; i < path.length; i++) {
vertex(path[i].x, path[i].y);
}
endShape();
const dt = TWO_PI / fourierX.length;
time += dt;
if (time > TWO_PI) {
time = 0;
path = [];
}
}
}
</script>
</body>
</html>
Construcción del código desde cero.
se define por medio del código la forma de un número complejo, suma entre ellos y su multiplicación.
Se define una función con la transformada discreta de Fourier con la fórmula de Euler
se tomaron ciertos puntos de la imagen dentro del plano cartesiano, haciendo uso de la siguiente plataforma: https://www.mobilefish.com/services/record_mouse_coordinates/record_mouse_coordinates.php
Se modifica al vector de entrada con los puntos de cada epiciclo generado por los puntos obtenidos en el paso anterior y se recorre mediante el for dentro del código original.
luego con la función de dibujo ya definida en el código original se generan los epiciclos los cuales dibujan el logo del semillero.
Para entender mejor gráficamente lo que se está haciendo, se ve como la exponencial compleja A medida que crece t, el valor de la exponencial gira alrededor del círculo unitario en el plano complejo.
La deducción de esta fórmula es bastante sencilla de entender.