/*
* Apache License
* Version 2.0, January 2004
* http://www.apache.org/licenses/
*
* Copyright 2013 Aurelian Tutuianu
* Copyright 2014 Aurelian Tutuianu
* Copyright 2015 Aurelian Tutuianu
* Copyright 2016 Aurelian Tutuianu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package rapaio.math.fourier;
import rapaio.data.Mapping;
import rapaio.data.Numeric;
import rapaio.data.Var;
import rapaio.util.Pair;
/**
* Fast Fourier Transform
*
* Created by <a href="mailto:padreati@yahoo.com">Aurelian Tutuianu</a> on 6/28/16.
*/
public class FFT {
// compute the FFT of x[], assuming its length is a power of 2
public static Pair<Var, Var> fft(Pair<Var, Var> x) {
int N = x._1.rowCount();
// base case
if (N == 1) return x;
// radix 2 Cooley-Tukey FFT
if (N % 2 != 0) {
throw new RuntimeException("N is not a power of 2");
}
// fft of even terms
Mapping evenMap = Mapping.empty();
for (int k = 0; k < N / 2; k++) {
evenMap.add(2 * k);
}
Pair<Var, Var> even = Pair.from(x._1.mapRows(evenMap), x._2.mapRows(evenMap));
Pair<Var, Var> q = fft(even);
// fft of odd terms
Mapping oddMap = Mapping.empty();
for (int k = 0; k < N / 2; k++) {
oddMap.add(2 * k + 1);
}
Pair<Var, Var> r = fft(Pair.from(x._1.mapRows(oddMap), x._2.mapRows(oddMap)));
// combine
Var rey = Numeric.fill(N, 0.0);
Var imy = Numeric.fill(N, 0.0);
for (int k = 0; k < N / 2; k++) {
double kth = -2 * k * Math.PI / N;
double coskth = Math.cos(kth);
double sinkth = Math.sin(kth);
rey.setValue(k, q._1.value(k) + coskth * r._1.value(k) - sinkth * r._2.value(k));
imy.setValue(k, q._2.value(k) + coskth * r._2.value(k) + sinkth * r._1.value(k));
rey.setValue(k + N / 2, q._1.value(k) - coskth * r._1.value(k) + sinkth * r._2.value(k));
imy.setValue(k + N / 2, q._2.value(k) - coskth * r._2.value(k) - sinkth * r._1.value(k));
}
return Pair.from(rey, imy);
}
// compute the inverse FFT of x[], assuming its length is a power of 2
public static Pair<Var, Var> ifft(Pair<Var, Var> x) {
int N = x._1.rowCount();
Var im2 = Numeric.from(N, row -> -x._2.value(row));
// compute forward FFT
Pair<Var, Var> y = fft(Pair.from(x._1, im2));
// take conjugate again and divide by N
Var re3 = Numeric.from(N, row -> y._1.value(row) / N);
Var im3 = Numeric.from(N, row -> -y._2.value(row) / N);
return Pair.from(re3, im3);
}
// compute the circular convolution of x and y
public static Pair<Var, Var> cconvolve(Pair<Var, Var> x, Pair<Var, Var> y) {
int len = x._1.rowCount();
// should probably pad x and y with 0s so that they have same length
// and are powers of 2
if ((x._2.rowCount() != len)) {
throw new RuntimeException("Dimensions don't agree");
}
int N = x._1.rowCount();
// compute FFT of each sequence
Pair<Var, Var> a = fft(x);
Pair<Var, Var> b = fft(y);
// point-wise multiply
Pair<Var, Var> c = Pair.from(Numeric.fill(len, 0.0), Numeric.fill(len, 0.0));
for (int i = 0; i < N; i++) {
c._1.setValue(i, a._1.value(i) * b._1.value(i) - a._2.value(i) * b._2.value(i));
c._2.setValue(i, a._1.value(i) * b._2.value(i) + a._1.value(i) * b._2.value(i));
}
// compute inverse FFT
return ifft(c);
}
// compute the linear convolution of x and y
public static Pair<Var, Var> convolve(Pair<Var, Var> x, Pair<Var, Var> y) {
Pair<Var, Var> a = Pair.from(x._1.solidCopy(), x._2.solidCopy());
Pair<Var, Var> b = Pair.from(y._1.solidCopy(), y._2.solidCopy());
for (int i = 0; i < x._1.rowCount(); i++) {
a._1.addValue(0.0);
a._2.addValue(0.0);
b._1.addValue(0.0);
b._2.addValue(0.0);
}
return cconvolve(a, b);
}
}