Added Linear Convolution, FFT, Bluestein's FFT and Circular and Linear Convolution using FFT
This commit is contained in:
parent
f077c8d19e
commit
4264e0f045
58
Maths/CircularConvolutionFFT.java
Normal file
58
Maths/CircularConvolutionFFT.java
Normal file
@ -0,0 +1,58 @@
|
||||
package com.maths;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Class for circular convolution of two discrete signals using the convolution theorem.
|
||||
*
|
||||
* @author Ioannis Karavitsis
|
||||
* @version 1.0
|
||||
* */
|
||||
public class CircularConvolutionFFT
|
||||
{
|
||||
/**
|
||||
* This method pads the signal with zeros until it reaches the new size.
|
||||
*
|
||||
* @param x The signal to be padded.
|
||||
* @param newSize The new size of the signal.
|
||||
* */
|
||||
private static void padding(ArrayList<FFT.Complex> x, int newSize)
|
||||
{
|
||||
if(x.size() < newSize)
|
||||
{
|
||||
int diff = newSize - x.size();
|
||||
for(int i = 0; i < diff; i++)
|
||||
x.add(new FFT.Complex());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Discrete circular convolution function. It uses the convolution theorem for discrete signals: convolved = IDFT(DFT(a)*DFT(b)).
|
||||
* Then we use the FFT algorithm for faster calculations of the two DFTs and the final IDFT.
|
||||
*
|
||||
* More info:
|
||||
* https://en.wikipedia.org/wiki/Convolution_theorem
|
||||
*
|
||||
* @param a The first signal.
|
||||
* @param b The other signal.
|
||||
* @return The convolved signal.
|
||||
* */
|
||||
public static ArrayList<FFT.Complex> fftCircularConvolution(ArrayList<FFT.Complex> a, ArrayList<FFT.Complex> b)
|
||||
{
|
||||
int convolvedSize = Math.max(a.size(), b.size()); //The two signals must have the same size equal to the bigger one
|
||||
padding(a, convolvedSize); //Zero padding the smaller signal
|
||||
padding(b, convolvedSize);
|
||||
|
||||
/* Find the FFTs of both signal. Here we use the Bluestein algorithm because we want the FFT to have the same length with the signal and not bigger */
|
||||
FFTBluestein.fftBluestein(a, false);
|
||||
FFTBluestein.fftBluestein(b, false);
|
||||
ArrayList<FFT.Complex> convolved = new ArrayList<>();
|
||||
|
||||
for(int i = 0; i < a.size(); i++)
|
||||
convolved.add(a.get(i).multiply(b.get(i))); //FFT(a)*FFT(b)
|
||||
|
||||
FFTBluestein.fftBluestein(convolved, true); //IFFT
|
||||
|
||||
return convolved;
|
||||
}
|
||||
}
|
47
Maths/Convolution.java
Normal file
47
Maths/Convolution.java
Normal file
@ -0,0 +1,47 @@
|
||||
package com.maths;
|
||||
|
||||
/**
|
||||
* Class for linear convolution of two discrete signals
|
||||
*
|
||||
* @author Ioannis Karavitsis
|
||||
* @version 1.0
|
||||
* */
|
||||
public class Convolution
|
||||
{
|
||||
/**
|
||||
* Discrete linear convolution function. Both input signals and the output signal must start from 0.
|
||||
* If you have a signal that has values before 0 then shift it to start from 0.
|
||||
*
|
||||
* @param A The first discrete signal
|
||||
* @param B The second discrete signal
|
||||
* @return The convolved signal
|
||||
* */
|
||||
public static double[] convolution(double[] A, double[] B)
|
||||
{
|
||||
double[] convolved = new double[A.length + B.length - 1];
|
||||
|
||||
/*
|
||||
The discrete convolution of two signals A and B is defined as:
|
||||
|
||||
A.length
|
||||
C[i] = Σ (A[k]*B[i-k])
|
||||
k=0
|
||||
|
||||
It's obvious that: 0 <= k <= A.length , 0 <= i <= A.length + B.length - 2 and 0 <= i-k <= B.length - 1
|
||||
From the last inequality we get that: i - B.length + 1 <= k <= i and thus we get the conditions below.
|
||||
*/
|
||||
for(int i = 0; i < convolved.length; i++)
|
||||
{
|
||||
convolved[i] = 0;
|
||||
int k = Math.max(i - B.length + 1, 0);
|
||||
|
||||
while(k < i + 1 && k < A.length)
|
||||
{
|
||||
convolved[i] += A[k] * B[i - k];
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
return convolved;
|
||||
}
|
||||
}
|
61
Maths/ConvolutionFFT.java
Normal file
61
Maths/ConvolutionFFT.java
Normal file
@ -0,0 +1,61 @@
|
||||
package com.maths;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Class for linear convolution of two discrete signals using the convolution theorem.
|
||||
*
|
||||
* @author Ioannis Karavitsis
|
||||
* @version 1.0
|
||||
* */
|
||||
public class ConvolutionFFT
|
||||
{
|
||||
/**
|
||||
* This method pads the signal with zeros until it reaches the new size.
|
||||
*
|
||||
* @param x The signal to be padded.
|
||||
* @param newSize The new size of the signal.
|
||||
* */
|
||||
private static void padding(ArrayList<FFT.Complex> x, int newSize)
|
||||
{
|
||||
if(x.size() < newSize)
|
||||
{
|
||||
int diff = newSize - x.size();
|
||||
for(int i = 0; i < diff; i++)
|
||||
x.add(new FFT.Complex());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Discrete linear convolution function. It uses the convolution theorem for discrete signals convolved: = IDFT(DFT(a)*DFT(b)).
|
||||
* This is true for circular convolution. In order to get the linear convolution of the two signals we first pad the two signals to have the same size equal to the convolved signal (a.size() + b.size() - 1).
|
||||
* Then we use the FFT algorithm for faster calculations of the two DFTs and the final IDFT.
|
||||
*
|
||||
* More info:
|
||||
* https://en.wikipedia.org/wiki/Convolution_theorem
|
||||
* https://ccrma.stanford.edu/~jos/ReviewFourier/FFT_Convolution.html
|
||||
*
|
||||
* @param a The first signal.
|
||||
* @param b The other signal.
|
||||
* @return The convolved signal.
|
||||
* */
|
||||
public static ArrayList<FFT.Complex> convolutionFFT(ArrayList<FFT.Complex> a, ArrayList<FFT.Complex> b)
|
||||
{
|
||||
int convolvedSize = a.size() + b.size() - 1; //The size of the convolved signal
|
||||
padding(a, convolvedSize); //Zero padding both signals
|
||||
padding(b, convolvedSize);
|
||||
|
||||
/* Find the FFTs of both signals (Note that the size of the FFTs will be bigger than the convolvedSize because of the extra zero padding in FFT algorithm) */
|
||||
FFT.fft(a, false);
|
||||
FFT.fft(b, false);
|
||||
ArrayList<FFT.Complex> convolved = new ArrayList<>();
|
||||
|
||||
for(int i = 0; i < a.size(); i++)
|
||||
convolved.add(a.get(i).multiply(b.get(i))); //FFT(a)*FFT(b)
|
||||
|
||||
FFT.fft(convolved, true); //IFFT
|
||||
convolved.subList(convolvedSize, convolved.size()).clear(); //Remove the remaining zeros after the convolvedSize. These extra zeros came from paddingPowerOfTwo() method inside the fft() method.
|
||||
|
||||
return convolved;
|
||||
}
|
||||
}
|
281
Maths/FFT.java
Normal file
281
Maths/FFT.java
Normal file
@ -0,0 +1,281 @@
|
||||
package com.maths;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Class for calculating the Fast Fourier Transform (FFT) of a discrete signal using the Cooley-Tukey algorithm.
|
||||
*
|
||||
* @author Ioannis Karavitsis
|
||||
* @version 1.0
|
||||
* */
|
||||
public class FFT
|
||||
{
|
||||
/**
|
||||
* This class represents a complex number and has methods for basic operations.
|
||||
*
|
||||
* More info:
|
||||
* https://introcs.cs.princeton.edu/java/32class/Complex.java.html
|
||||
* */
|
||||
static class Complex
|
||||
{
|
||||
private double real, img;
|
||||
|
||||
/**
|
||||
* Default Constructor.
|
||||
* Creates the complex number 0.
|
||||
* */
|
||||
public Complex()
|
||||
{
|
||||
real = 0;
|
||||
img = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor. Creates a complex number.
|
||||
*
|
||||
* @param r The real part of the number.
|
||||
* @param i The imaginary part of the number.
|
||||
* */
|
||||
public Complex(double r, double i)
|
||||
{
|
||||
real = r;
|
||||
img = i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the real part of the complex number.
|
||||
*
|
||||
* @return The real part of the complex number.
|
||||
* */
|
||||
public double getReal()
|
||||
{
|
||||
return real;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the imaginary part of the complex number.
|
||||
*
|
||||
* @return The imaginary part of the complex number.
|
||||
* */
|
||||
public double getImaginary()
|
||||
{
|
||||
return img;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds this complex number to another.
|
||||
*
|
||||
* @param z The number to be added.
|
||||
* @return The sum.
|
||||
* */
|
||||
public Complex add(Complex z)
|
||||
{
|
||||
Complex temp = new Complex();
|
||||
temp.real = this.real + z.real;
|
||||
temp.img = this.img + z.img;
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtracts a number from this complex number.
|
||||
*
|
||||
* @param z The number to be subtracted.
|
||||
* @return The difference.
|
||||
* */
|
||||
public Complex subtract(Complex z)
|
||||
{
|
||||
Complex temp = new Complex();
|
||||
temp.real = this.real - z.real;
|
||||
temp.img = this.img - z.img;
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies this complex number by another.
|
||||
*
|
||||
* @param z The number to be multiplied.
|
||||
* @return The product.
|
||||
* */
|
||||
public Complex multiply(Complex z)
|
||||
{
|
||||
Complex temp = new Complex();
|
||||
temp.real = this.real*z.real - this.img*z.img;
|
||||
temp.img = this.real*z.img + this.img*z.real;
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies this complex number by a scalar.
|
||||
*
|
||||
* @param n The real number to be multiplied.
|
||||
* @return The product.
|
||||
* */
|
||||
public Complex multiply(double n)
|
||||
{
|
||||
Complex temp = new Complex();
|
||||
temp.real = this.real * n;
|
||||
temp.img = this.img * n;
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the conjugate of this complex number.
|
||||
*
|
||||
* @return The conjugate.
|
||||
* */
|
||||
public Complex conjugate()
|
||||
{
|
||||
Complex temp = new Complex();
|
||||
temp.real = this.real;
|
||||
temp.img = -this.img;
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the magnitude of the complex number.
|
||||
*
|
||||
* @return The magnitude.
|
||||
* */
|
||||
public double abs()
|
||||
{
|
||||
return Math.hypot(this.real, this.img);
|
||||
}
|
||||
|
||||
/**
|
||||
* Divides this complex number by another.
|
||||
*
|
||||
* @param z The divisor.
|
||||
* @return The quotient.
|
||||
* */
|
||||
public Complex divide(Complex z)
|
||||
{
|
||||
Complex temp = new Complex();
|
||||
temp.real = (this.real*z.real + this.img*z.img) / (z.abs()*z.abs());
|
||||
temp.img = (this.img*z.real - this.real*z.img) / (z.abs()*z.abs());
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Divides this complex number by a scalar.
|
||||
*
|
||||
* @param n The divisor which is a real number.
|
||||
* @return The quotient.
|
||||
* */
|
||||
public Complex divide(double n)
|
||||
{
|
||||
Complex temp = new Complex();
|
||||
temp.real = this.real / n;
|
||||
temp.img = this.img / n;
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterative In-Place Radix-2 Cooley-Tukey Fast Fourier Transform Algorithm with Bit-Reversal.
|
||||
* The size of the input signal must be a power of 2. If it isn't then it is padded with zeros and the output FFT will be bigger than the input signal.
|
||||
*
|
||||
* More info:
|
||||
* https://www.algorithm-archive.org/contents/cooley_tukey/cooley_tukey.html
|
||||
* https://www.geeksforgeeks.org/iterative-fast-fourier-transformation-polynomial-multiplication/
|
||||
* https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm
|
||||
* https://cp-algorithms.com/algebra/fft.html
|
||||
*
|
||||
* @param x The discrete signal which is then converted to the FFT or the IFFT of signal x.
|
||||
* @param inverse True if you want to find the inverse FFT.
|
||||
* */
|
||||
public static void fft(ArrayList<Complex> x, boolean inverse)
|
||||
{
|
||||
/* Pad the signal with zeros if necessary */
|
||||
paddingPowerOfTwo(x);
|
||||
int N = x.size();
|
||||
|
||||
/* Find the log2(N) */
|
||||
int log2N = 0;
|
||||
while((1 << log2N) < N)
|
||||
log2N++;
|
||||
|
||||
/* Swap the values of the signal with bit-reversal method */
|
||||
int reverse;
|
||||
for(int i = 0; i < N; i++)
|
||||
{
|
||||
reverse = reverseBits(i, log2N);
|
||||
if(i < reverse)
|
||||
Collections.swap(x, i, reverse);
|
||||
}
|
||||
|
||||
int direction = inverse ? -1 : 1;
|
||||
|
||||
/* Main loop of the algorithm */
|
||||
for(int len = 2; len <= N; len *= 2)
|
||||
{
|
||||
double angle = -2 * Math.PI / len * direction;
|
||||
Complex wlen = new Complex(Math.cos(angle), Math.sin(angle));
|
||||
for(int i = 0; i < N; i += len)
|
||||
{
|
||||
Complex w = new Complex(1, 0);
|
||||
for(int j = 0; j < len / 2; j++)
|
||||
{
|
||||
Complex u = x.get(i + j);
|
||||
Complex v = w.multiply(x.get(i + j + len/2));
|
||||
x.set(i + j, u.add(v));
|
||||
x.set(i + j + len/2, u.subtract(v));
|
||||
w = w.multiply(wlen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Divide by N if we want the inverse FFT */
|
||||
if(inverse)
|
||||
{
|
||||
for (int i = 0; i < x.size(); i++)
|
||||
{
|
||||
Complex z = x.get(i);
|
||||
x.set(i, z.divide(N));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function reverses the bits of a number.
|
||||
* It is used in Cooley-Tukey FFT algorithm.
|
||||
*
|
||||
* E.g.
|
||||
* num = 13 = 00001101 in binary
|
||||
* log2N = 8
|
||||
* Then reversed = 176 = 10110000 in binary
|
||||
*
|
||||
* More info:
|
||||
* https://cp-algorithms.com/algebra/fft.html
|
||||
* https://www.geeksforgeeks.org/write-an-efficient-c-program-to-reverse-bits-of-a-number/
|
||||
*
|
||||
* @param num The integer you want to reverse its bits.
|
||||
* @param log2N The number of bits you want to reverse.
|
||||
* @return The reversed number
|
||||
* */
|
||||
private static int reverseBits(int num, int log2N)
|
||||
{
|
||||
int reversed = 0;
|
||||
for(int i = 0; i < log2N; i++)
|
||||
{
|
||||
if((num & (1 << i)) != 0)
|
||||
reversed |= 1 << (log2N - 1 - i);
|
||||
}
|
||||
return reversed;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method pads an ArrayList with zeros in order to have a size equal to the next power of two of the previous size.
|
||||
*
|
||||
* @param x The ArrayList to be padded.
|
||||
* */
|
||||
private static void paddingPowerOfTwo(ArrayList<Complex> x)
|
||||
{
|
||||
int n = 1;
|
||||
int oldSize = x.size();
|
||||
while(n < oldSize)
|
||||
n *= 2;
|
||||
for(int i = 0; i < n - oldSize; i++)
|
||||
x.add(new Complex());
|
||||
}
|
||||
}
|
69
Maths/FFTBluestein.java
Normal file
69
Maths/FFTBluestein.java
Normal file
@ -0,0 +1,69 @@
|
||||
package com.maths;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Class for calculating the Fast Fourier Transform (FFT) of a discrete signal using the Bluestein's algorithm.
|
||||
*
|
||||
* @author Ioannis Karavitsis
|
||||
* @version 1.0
|
||||
* */
|
||||
public class FFTBluestein
|
||||
{
|
||||
/**
|
||||
* Bluestein's FFT Algorithm.
|
||||
*
|
||||
* More info:
|
||||
* https://en.wikipedia.org/wiki/Chirp_Z-transform#Bluestein.27s_algorithm
|
||||
* http://tka4.org/materials/lib/Articles-Books/Numerical%20Algorithms/Hartley_Trasform/Bluestein%27s%20FFT%20algorithm%20-%20Wikipedia,%20the%20free%20encyclopedia.htm
|
||||
*
|
||||
* @param x The discrete signal which is then converted to the FFT or the IFFT of signal x.
|
||||
* @param inverse True if you want to find the inverse FFT.
|
||||
* */
|
||||
public static void fftBluestein(ArrayList<FFT.Complex> x, boolean inverse)
|
||||
{
|
||||
int N = x.size();
|
||||
int bnSize = 2*N - 1;
|
||||
int direction = inverse ? -1 : 1;
|
||||
ArrayList<FFT.Complex> an = new ArrayList<>();
|
||||
ArrayList<FFT.Complex> bn = new ArrayList<>();
|
||||
|
||||
/* Initialization of the b(n) sequence (see Wikipedia's article above for the symbols used)*/
|
||||
for(int i = 0; i < bnSize; i++)
|
||||
bn.add(new FFT.Complex());
|
||||
|
||||
for(int i = 0; i < N; i++)
|
||||
{
|
||||
double angle = (i - N + 1) * (i - N + 1) * Math.PI / N * direction;
|
||||
bn.set(i, new FFT.Complex(Math.cos(angle), Math.sin(angle)));
|
||||
bn.set(bnSize - i - 1, new FFT.Complex(Math.cos(angle), Math.sin(angle)));
|
||||
}
|
||||
|
||||
/* Initialization of the a(n) sequence */
|
||||
for(int i = 0; i < N; i++)
|
||||
{
|
||||
double angle = -i * i * Math.PI / N * direction;
|
||||
an.add(x.get(i).multiply(new FFT.Complex(Math.cos(angle), Math.sin(angle))));
|
||||
}
|
||||
|
||||
ArrayList<FFT.Complex> convolution = ConvolutionFFT.convolutionFFT(an, bn);
|
||||
|
||||
/* The final multiplication of the convolution with the b*(k) factor */
|
||||
for(int i = 0; i < N; i++)
|
||||
{
|
||||
double angle = -1 * i * i * Math.PI / N * direction;
|
||||
FFT.Complex bk = new FFT.Complex(Math.cos(angle), Math.sin(angle));
|
||||
x.set(i, bk.multiply(convolution.get(i + N - 1)));
|
||||
}
|
||||
|
||||
/* Divide by N if we want the inverse FFT */
|
||||
if(inverse)
|
||||
{
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
FFT.Complex z = x.get(i);
|
||||
x.set(i, z.divide(N));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user