applied Cyril Briquet's patch (with slight changes) to improve FastFourierTransform efficiency
JIRA: MATH-216 git-svn-id: https://svn.apache.org/repos/asf/commons/proper/math/trunk@740400 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
aa0dd1db20
commit
304ae29268
3
pom.xml
3
pom.xml
|
@ -105,6 +105,9 @@
|
||||||
<contributor>
|
<contributor>
|
||||||
<name>Rémi Arntzen</name>
|
<name>Rémi Arntzen</name>
|
||||||
</contributor>
|
</contributor>
|
||||||
|
<contributor>
|
||||||
|
<name>Cyril Briquet</name>
|
||||||
|
</contributor>
|
||||||
<contributor>
|
<contributor>
|
||||||
<name>Paul Cowan</name>
|
<name>Paul Cowan</name>
|
||||||
</contributor>
|
</contributor>
|
||||||
|
|
|
@ -372,6 +372,10 @@ public class MessagesResources_fr
|
||||||
// org.apache.commons.math.transform.FastFourierTransformer
|
// org.apache.commons.math.transform.FastFourierTransformer
|
||||||
{ "cannot compute 0-th root of unity, indefinite result",
|
{ "cannot compute 0-th root of unity, indefinite result",
|
||||||
"impossible de calculer la racine z\u00e9roi\u00e8me de l''unit\u00e9, r\u00e9sultat ind\u00e9fini" },
|
"impossible de calculer la racine z\u00e9roi\u00e8me de l''unit\u00e9, r\u00e9sultat ind\u00e9fini" },
|
||||||
|
{ "roots of unity have not been computed yet",
|
||||||
|
"les racines de l''unit\u00e9 n''ont pas encore \u00e9t\u00e9 calcul\u00e9es" },
|
||||||
|
{ "out of range root of unity index {0} (must be in [{1};{2}])",
|
||||||
|
"index de racine de l''unit\u00e9 hors domaine (devrait \u00eatre dans [{1}; {2}])" },
|
||||||
{ "number of sample is not positive: {0}",
|
{ "number of sample is not positive: {0}",
|
||||||
"le nombre d''\u00e9chantillons n''est pas positif : {0}" },
|
"le nombre d''\u00e9chantillons n''est pas positif : {0}" },
|
||||||
{ "{0} is not a power of 2, consider padding for fix",
|
{ "{0} is not a power of 2, consider padding for fix",
|
||||||
|
|
|
@ -48,14 +48,8 @@ public class FastFourierTransformer implements Serializable {
|
||||||
/** Serializable version identifier. */
|
/** Serializable version identifier. */
|
||||||
static final long serialVersionUID = 5138259215438106000L;
|
static final long serialVersionUID = 5138259215438106000L;
|
||||||
|
|
||||||
/** array of the roots of unity */
|
/** roots of unity */
|
||||||
private Complex omega[] = new Complex[0];
|
private RootsOfUnity roots = new RootsOfUnity();
|
||||||
|
|
||||||
/**
|
|
||||||
* |omegaCount| is the length of lasted computed omega[]. omegaCount
|
|
||||||
* is positive for forward transform and negative for inverse transform.
|
|
||||||
*/
|
|
||||||
private int omegaCount = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a default transformer.
|
* Construct a default transformer.
|
||||||
|
@ -113,7 +107,7 @@ public class FastFourierTransformer implements Serializable {
|
||||||
*/
|
*/
|
||||||
public Complex[] transform(Complex f[])
|
public Complex[] transform(Complex f[])
|
||||||
throws IllegalArgumentException {
|
throws IllegalArgumentException {
|
||||||
computeOmega(f.length);
|
roots.computeOmega(f.length);
|
||||||
return fft(f);
|
return fft(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,7 +165,7 @@ public class FastFourierTransformer implements Serializable {
|
||||||
public Complex[] transform2(Complex f[])
|
public Complex[] transform2(Complex f[])
|
||||||
throws IllegalArgumentException {
|
throws IllegalArgumentException {
|
||||||
|
|
||||||
computeOmega(f.length);
|
roots.computeOmega(f.length);
|
||||||
double scaling_coefficient = 1.0 / Math.sqrt(f.length);
|
double scaling_coefficient = 1.0 / Math.sqrt(f.length);
|
||||||
return scaleArray(fft(f), scaling_coefficient);
|
return scaleArray(fft(f), scaling_coefficient);
|
||||||
}
|
}
|
||||||
|
@ -230,7 +224,7 @@ public class FastFourierTransformer implements Serializable {
|
||||||
public Complex[] inversetransform(Complex f[])
|
public Complex[] inversetransform(Complex f[])
|
||||||
throws IllegalArgumentException {
|
throws IllegalArgumentException {
|
||||||
|
|
||||||
computeOmega(-f.length); // pass negative argument
|
roots.computeOmega(-f.length); // pass negative argument
|
||||||
double scaling_coefficient = 1.0 / f.length;
|
double scaling_coefficient = 1.0 / f.length;
|
||||||
return scaleArray(fft(f), scaling_coefficient);
|
return scaleArray(fft(f), scaling_coefficient);
|
||||||
}
|
}
|
||||||
|
@ -289,7 +283,7 @@ public class FastFourierTransformer implements Serializable {
|
||||||
public Complex[] inversetransform2(Complex f[])
|
public Complex[] inversetransform2(Complex f[])
|
||||||
throws IllegalArgumentException {
|
throws IllegalArgumentException {
|
||||||
|
|
||||||
computeOmega(-f.length); // pass negative argument
|
roots.computeOmega(-f.length); // pass negative argument
|
||||||
double scaling_coefficient = 1.0 / Math.sqrt(f.length);
|
double scaling_coefficient = 1.0 / Math.sqrt(f.length);
|
||||||
return scaleArray(fft(f), scaling_coefficient);
|
return scaleArray(fft(f), scaling_coefficient);
|
||||||
}
|
}
|
||||||
|
@ -319,18 +313,20 @@ public class FastFourierTransformer implements Serializable {
|
||||||
for (int i = 0; i < N; i++) {
|
for (int i = 0; i < N; i++) {
|
||||||
c[i] = new Complex(f[2*i], f[2*i+1]);
|
c[i] = new Complex(f[2*i], f[2*i+1]);
|
||||||
}
|
}
|
||||||
computeOmega(isInverse ? -N : N);
|
roots.computeOmega(isInverse ? -N : N);
|
||||||
Complex z[] = fft(c);
|
Complex z[] = fft(c);
|
||||||
|
|
||||||
// reconstruct the FFT result for the original array
|
// reconstruct the FFT result for the original array
|
||||||
computeOmega(isInverse ? -2*N : 2*N);
|
roots.computeOmega(isInverse ? -2*N : 2*N);
|
||||||
F[0] = new Complex(2 * (z[0].getReal() + z[0].getImaginary()), 0.0);
|
F[0] = new Complex(2 * (z[0].getReal() + z[0].getImaginary()), 0.0);
|
||||||
F[N] = new Complex(2 * (z[0].getReal() - z[0].getImaginary()), 0.0);
|
F[N] = new Complex(2 * (z[0].getReal() - z[0].getImaginary()), 0.0);
|
||||||
for (int i = 1; i < N; i++) {
|
for (int i = 1; i < N; i++) {
|
||||||
Complex A = z[N-i].conjugate();
|
Complex A = z[N-i].conjugate();
|
||||||
Complex B = z[i].add(A);
|
Complex B = z[i].add(A);
|
||||||
Complex C = z[i].subtract(A);
|
Complex C = z[i].subtract(A);
|
||||||
Complex D = omega[i].multiply(Complex.I);
|
//Complex D = roots.getOmega(i).multiply(Complex.I);
|
||||||
|
Complex D = new Complex(-roots.getOmegaImaginary(i),
|
||||||
|
roots.getOmegaReal(i));
|
||||||
F[i] = B.subtract(C.multiply(D));
|
F[i] = B.subtract(C.multiply(D));
|
||||||
F[2*N-i] = F[i].conjugate();
|
F[2*N-i] = F[i].conjugate();
|
||||||
}
|
}
|
||||||
|
@ -385,8 +381,8 @@ public class FastFourierTransformer implements Serializable {
|
||||||
f[i] = A.add(B);
|
f[i] = A.add(B);
|
||||||
f[i+2] = A.subtract(B);
|
f[i+2] = A.subtract(B);
|
||||||
// omegaCount indicates forward or inverse transform
|
// omegaCount indicates forward or inverse transform
|
||||||
f[i+1] = omegaCount < 0 ? E : F;
|
f[i+1] = roots.isForward() ? F : E;
|
||||||
f[i+3] = omegaCount > 0 ? E : F;
|
f[i+3] = roots.isForward() ? E : F;
|
||||||
}
|
}
|
||||||
|
|
||||||
// iterations from bottom to top take O(N*logN) time
|
// iterations from bottom to top take O(N*logN) time
|
||||||
|
@ -394,7 +390,17 @@ public class FastFourierTransformer implements Serializable {
|
||||||
m = N / (i<<1);
|
m = N / (i<<1);
|
||||||
for (j = 0; j < N; j += i<<1) {
|
for (j = 0; j < N; j += i<<1) {
|
||||||
for (k = 0; k < i; k++) {
|
for (k = 0; k < i; k++) {
|
||||||
z = f[i+j+k].multiply(omega[k*m]);
|
//z = f[i+j+k].multiply(roots.getOmega(k*m));
|
||||||
|
final int k_times_m = k*m;
|
||||||
|
final double omega_k_times_m_real = roots.getOmegaReal(k_times_m);
|
||||||
|
final double omega_k_times_m_imaginary = roots.getOmegaImaginary(k_times_m);
|
||||||
|
//z = f[i+j+k].multiply(omega[k*m]);
|
||||||
|
z = new Complex(
|
||||||
|
f[i+j+k].getReal() * omega_k_times_m_real -
|
||||||
|
f[i+j+k].getImaginary() * omega_k_times_m_imaginary,
|
||||||
|
f[i+j+k].getReal() * omega_k_times_m_imaginary +
|
||||||
|
f[i+j+k].getImaginary() * omega_k_times_m_real);
|
||||||
|
|
||||||
f[i+j+k] = f[j+k].subtract(z);
|
f[i+j+k] = f[j+k].subtract(z);
|
||||||
f[j+k] = f[j+k].add(z);
|
f[j+k] = f[j+k].add(z);
|
||||||
}
|
}
|
||||||
|
@ -403,45 +409,6 @@ public class FastFourierTransformer implements Serializable {
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the n-th roots of unity.
|
|
||||||
* <p>
|
|
||||||
* The computed omega[] = { 1, w, w^2, ... w^(n-1) } where
|
|
||||||
* w = exp(-2 \pi i / n), i = sqrt(-1). Note n is positive for
|
|
||||||
* forward transform and negative for inverse transform. </p>
|
|
||||||
*
|
|
||||||
* @param n the integer passed in
|
|
||||||
* @throws IllegalArgumentException if n = 0
|
|
||||||
*/
|
|
||||||
protected void computeOmega(int n)
|
|
||||||
throws IllegalArgumentException {
|
|
||||||
if (n == 0) {
|
|
||||||
throw MathRuntimeException.createIllegalArgumentException("cannot compute 0-th root of unity, indefinite result",
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
// avoid repetitive calculations
|
|
||||||
if (n == omegaCount) { return; }
|
|
||||||
if (n + omegaCount == 0) {
|
|
||||||
for (int i = 0; i < Math.abs(omegaCount); i++) {
|
|
||||||
omega[i] = omega[i].conjugate();
|
|
||||||
}
|
|
||||||
omegaCount = n;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// calculate everything from scratch
|
|
||||||
omega = new Complex[Math.abs(n)];
|
|
||||||
double t = 2.0 * Math.PI / n;
|
|
||||||
double cost = Math.cos(t);
|
|
||||||
double sint = Math.sin(t);
|
|
||||||
omega[0] = new Complex(1.0, 0.0);
|
|
||||||
for (int i = 1; i < Math.abs(n); i++) {
|
|
||||||
omega[i] = new Complex(
|
|
||||||
omega[i-1].getReal() * cost + omega[i-1].getImaginary() * sint,
|
|
||||||
omega[i-1].getImaginary() * cost - omega[i-1].getReal() * sint);
|
|
||||||
}
|
|
||||||
omegaCount = n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sample the given univariate real function on the given interval.
|
* Sample the given univariate real function on the given interval.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -803,4 +770,143 @@ public class FastFourierTransformer implements Serializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Computes the n<sup>th</sup> roots of unity.
|
||||||
|
* A cache of already computed values is maintained.
|
||||||
|
*/
|
||||||
|
private static class RootsOfUnity {
|
||||||
|
|
||||||
|
private int omegaCount;
|
||||||
|
private double[] omegaReal;
|
||||||
|
private double[] omegaImaginaryForward;
|
||||||
|
private double[] omegaImaginaryInverse;
|
||||||
|
private boolean isForward;
|
||||||
|
|
||||||
|
public RootsOfUnity() {
|
||||||
|
|
||||||
|
omegaCount = 0;
|
||||||
|
omegaReal = null;
|
||||||
|
omegaImaginaryForward = null;
|
||||||
|
omegaImaginaryInverse = null;
|
||||||
|
isForward = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if computation has been done for forward or reverse transform.
|
||||||
|
* @return true if computation has been done for forward transform
|
||||||
|
* @throws IllegalStateException if no roots of unity have been computed yet
|
||||||
|
*/
|
||||||
|
public synchronized boolean isForward() throws IllegalStateException {
|
||||||
|
|
||||||
|
if (omegaCount == 0) {
|
||||||
|
throw MathRuntimeException.createIllegalStateException(
|
||||||
|
"roots of unity have not been computed yet",
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
return isForward;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Computes the n<sup>th</sup> roots of unity.
|
||||||
|
* <p>The computed omega[] = { 1, w, w<sup>2</sup>, ... w<sup>(n-1)</sup> } where
|
||||||
|
* w = exp(-2 π i / n), i = &sqrt;(-1).</p>
|
||||||
|
* <p>Note that n is positive for
|
||||||
|
* forward transform and negative for inverse transform.</p>
|
||||||
|
* @param n number of roots of unity to compute,
|
||||||
|
* positive for forward transform, negative for inverse transform
|
||||||
|
* @throws IllegalArgumentException if n = 0
|
||||||
|
*/
|
||||||
|
public synchronized void computeOmega(int n) throws IllegalArgumentException {
|
||||||
|
|
||||||
|
if (n == 0) {
|
||||||
|
throw MathRuntimeException.createIllegalArgumentException(
|
||||||
|
"cannot compute 0-th root of unity, indefinite result",
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
isForward = (n > 0);
|
||||||
|
|
||||||
|
// avoid repetitive calculations
|
||||||
|
final int absN = Math.abs(n);
|
||||||
|
|
||||||
|
if (absN == omegaCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate everything from scratch, for both forward and inverse versions
|
||||||
|
final double t = 2.0 * Math.PI / absN;
|
||||||
|
final double cosT = Math.cos(t);
|
||||||
|
final double sinT = Math.sin(t);
|
||||||
|
omegaReal = new double[absN];
|
||||||
|
omegaImaginaryForward = new double[absN];
|
||||||
|
omegaImaginaryInverse = new double[absN];
|
||||||
|
omegaReal[0] = 1.0;
|
||||||
|
omegaImaginaryForward[0] = 0.0;
|
||||||
|
omegaImaginaryInverse[0] = 0.0;
|
||||||
|
for (int i = 1; i < absN; i++) {
|
||||||
|
omegaReal[i] =
|
||||||
|
omegaReal[i-1] * cosT + omegaImaginaryForward[i-1] * sinT;
|
||||||
|
omegaImaginaryForward[i] =
|
||||||
|
omegaImaginaryForward[i-1] * cosT - omegaReal[i-1] * sinT;
|
||||||
|
omegaImaginaryInverse[i] = -omegaImaginaryForward[i];
|
||||||
|
}
|
||||||
|
omegaCount = absN;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the real part of the k<sup>th</sup> n<sup>th</sup> root of unity
|
||||||
|
* @param k index of the n<sup>th</sup> root of unity
|
||||||
|
* @return real part of the k<sup>th</sup> n<sup>th</sup> root of unity
|
||||||
|
* @throws IllegalStateException if no roots of unity have been computed yet
|
||||||
|
* @throws IllegalArgumentException if k is out of range
|
||||||
|
*/
|
||||||
|
public synchronized double getOmegaReal(int k)
|
||||||
|
throws IllegalStateException, IllegalArgumentException {
|
||||||
|
|
||||||
|
if (omegaCount == 0) {
|
||||||
|
throw MathRuntimeException.createIllegalStateException(
|
||||||
|
"roots of unity have not been computed yet",
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
if ((k < 0) || (k >= omegaCount)) {
|
||||||
|
throw MathRuntimeException.createIllegalArgumentException(
|
||||||
|
"out of range root of unity index {0} (must be in [{1};{2}])",
|
||||||
|
new Object[] { k, 0, omegaCount - 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return omegaReal[k];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the imaginary part of the k<sup>th</sup> n<sup>th</sup> root of unity
|
||||||
|
* @param k index of the n<sup>th</sup> root of unity
|
||||||
|
* @return imaginary part of the k<sup>th</sup> n<sup>th</sup> root of unity
|
||||||
|
* @throws IllegalStateException if no roots of unity have been computed yet
|
||||||
|
* @throws IllegalArgumentException if k is out of range
|
||||||
|
*/
|
||||||
|
public synchronized double getOmegaImaginary(int k)
|
||||||
|
throws IllegalStateException, IllegalArgumentException {
|
||||||
|
|
||||||
|
if (omegaCount == 0) {
|
||||||
|
throw MathRuntimeException.createIllegalStateException(
|
||||||
|
"roots of unity have not been computed yet",
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
if ((k < 0) || (k >= omegaCount)) {
|
||||||
|
throw MathRuntimeException.createIllegalArgumentException(
|
||||||
|
"out of range root of unity index {0} (must be in [{1};{2}])",
|
||||||
|
new Object[] { k, 0, omegaCount - 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return (isForward) ?
|
||||||
|
omegaImaginaryForward[k] : omegaImaginaryInverse[k];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,9 @@ The <action> type attribute can be add,update,fix,remove.
|
||||||
</properties>
|
</properties>
|
||||||
<body>
|
<body>
|
||||||
<release version="2.0" date="TBD" description="TBD">
|
<release version="2.0" date="TBD" description="TBD">
|
||||||
|
<action dev="luc" type="fix" issue="MATH-216" due-to="Cyril Briquet">
|
||||||
|
Improved fast Fourier transform efficiency.
|
||||||
|
</action>
|
||||||
<action dev="luc" type="add" >
|
<action dev="luc" type="add" >
|
||||||
Added factory methods to create Chebyshev, Hermite, Laguerre and Legendre polynomials.
|
Added factory methods to create Chebyshev, Hermite, Laguerre and Legendre polynomials.
|
||||||
</action>
|
</action>
|
||||||
|
|
Loading…
Reference in New Issue