1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.commons.math.transform; 18 19 import java.io.Serializable; 20 import org.apache.commons.math.analysis.*; 21 import org.apache.commons.math.complex.*; 22 import org.apache.commons.math.MathException; 23 24 /** 25 * Implements the <a href="http://mathworld.wolfram.com/FastFourierTransform.html"> 26 * Fast Fourier Transform</a> for transformation of one-dimensional data sets. 27 * For reference, see <b>Applied Numerical Linear Algebra</b>, ISBN 0898713897, 28 * chapter 6. 29 * <p> 30 * There are several conventions for the definition of FFT and inverse FFT, 31 * mainly on different coefficient and exponent. Here the equations are listed 32 * in the comments of the corresponding methods.</p> 33 * <p> 34 * We require the length of data set to be power of 2, this greatly simplifies 35 * and speeds up the code. Users can pad the data with zeros to meet this 36 * requirement. There are other flavors of FFT, for reference, see S. Winograd, 37 * <i>On computing the discrete Fourier transform</i>, Mathematics of Computation, 38 * 32 (1978), 175 - 199.</p> 39 * 40 * @version $Revision: 620312 $ $Date: 2008-02-10 12:28:59 -0700 (Sun, 10 Feb 2008) $ 41 * @since 1.2 42 */ 43 public class FastFourierTransformer implements Serializable { 44 45 /** serializable version identifier */ 46 static final long serialVersionUID = 5138259215438106000L; 47 48 /** array of the roots of unity */ 49 private Complex omega[] = new Complex[0]; 50 51 /** 52 * |omegaCount| is the length of lasted computed omega[]. omegaCount 53 * is positive for forward transform and negative for inverse transform. 54 */ 55 private int omegaCount = 0; 56 57 /** 58 * Construct a default transformer. 59 */ 60 public FastFourierTransformer() { 61 super(); 62 } 63 64 /** 65 * Transform the given real data set. 66 * <p> 67 * The formula is $ y_n = \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k $ 68 * </p> 69 * 70 * @param f the real data array to be transformed 71 * @return the complex transformed array 72 * @throws MathException if any math-related errors occur 73 * @throws IllegalArgumentException if any parameters are invalid 74 */ 75 public Complex[] transform(double f[]) throws MathException, 76 IllegalArgumentException { 77 78 return fft(f, false); 79 } 80 81 /** 82 * Transform the given real function, sampled on the given interval. 83 * <p> 84 * The formula is $ y_n = \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k $ 85 * </p> 86 * 87 * @param f the function to be sampled and transformed 88 * @param min the lower bound for the interval 89 * @param max the upper bound for the interval 90 * @param n the number of sample points 91 * @return the complex transformed array 92 * @throws MathException if any math-related errors occur 93 * @throws IllegalArgumentException if any parameters are invalid 94 */ 95 public Complex[] transform( 96 UnivariateRealFunction f, double min, double max, int n) 97 throws MathException, IllegalArgumentException { 98 99 double data[] = sample(f, min, max, n); 100 return fft(data, false); 101 } 102 103 /** 104 * Transform the given complex data set. 105 * <p> 106 * The formula is $ y_n = \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k $ 107 * </p> 108 * 109 * @param f the complex data array to be transformed 110 * @return the complex transformed array 111 * @throws MathException if any math-related errors occur 112 * @throws IllegalArgumentException if any parameters are invalid 113 */ 114 public Complex[] transform(Complex f[]) throws MathException, 115 IllegalArgumentException { 116 117 computeOmega(f.length); 118 return fft(f); 119 } 120 121 /** 122 * Transform the given real data set. 123 * <p> 124 * The formula is $y_n = (1/\sqrt{N}) \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k$ 125 * </p> 126 * 127 * @param f the real data array to be transformed 128 * @return the complex transformed array 129 * @throws MathException if any math-related errors occur 130 * @throws IllegalArgumentException if any parameters are invalid 131 */ 132 public Complex[] transform2(double f[]) throws MathException, 133 IllegalArgumentException { 134 135 double scaling_coefficient = 1.0 / Math.sqrt(f.length); 136 return scaleArray(fft(f, false), scaling_coefficient); 137 } 138 139 /** 140 * Transform the given real function, sampled on the given interval. 141 * <p> 142 * The formula is $y_n = (1/\sqrt{N}) \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k$ 143 * </p> 144 * 145 * @param f the function to be sampled and transformed 146 * @param min the lower bound for the interval 147 * @param max the upper bound for the interval 148 * @param n the number of sample points 149 * @return the complex transformed array 150 * @throws MathException if any math-related errors occur 151 * @throws IllegalArgumentException if any parameters are invalid 152 */ 153 public Complex[] transform2( 154 UnivariateRealFunction f, double min, double max, int n) 155 throws MathException, IllegalArgumentException { 156 157 double data[] = sample(f, min, max, n); 158 double scaling_coefficient = 1.0 / Math.sqrt(n); 159 return scaleArray(fft(data, false), scaling_coefficient); 160 } 161 162 /** 163 * Transform the given complex data set. 164 * <p> 165 * The formula is $y_n = (1/\sqrt{N}) \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k$ 166 * </p> 167 * 168 * @param f the complex data array to be transformed 169 * @return the complex transformed array 170 * @throws MathException if any math-related errors occur 171 * @throws IllegalArgumentException if any parameters are invalid 172 */ 173 public Complex[] transform2(Complex f[]) throws MathException, 174 IllegalArgumentException { 175 176 computeOmega(f.length); 177 double scaling_coefficient = 1.0 / Math.sqrt(f.length); 178 return scaleArray(fft(f), scaling_coefficient); 179 } 180 181 /** 182 * Inversely transform the given real data set. 183 * <p> 184 * The formula is $ x_k = (1/N) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n $ 185 * </p> 186 * 187 * @param f the real data array to be inversely transformed 188 * @return the complex inversely transformed array 189 * @throws MathException if any math-related errors occur 190 * @throws IllegalArgumentException if any parameters are invalid 191 */ 192 public Complex[] inversetransform(double f[]) throws MathException, 193 IllegalArgumentException { 194 195 double scaling_coefficient = 1.0 / f.length; 196 return scaleArray(fft(f, true), scaling_coefficient); 197 } 198 199 /** 200 * Inversely transform the given real function, sampled on the given interval. 201 * <p> 202 * The formula is $ x_k = (1/N) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n $ 203 * </p> 204 * 205 * @param f the function to be sampled and inversely transformed 206 * @param min the lower bound for the interval 207 * @param max the upper bound for the interval 208 * @param n the number of sample points 209 * @return the complex inversely transformed array 210 * @throws MathException if any math-related errors occur 211 * @throws IllegalArgumentException if any parameters are invalid 212 */ 213 public Complex[] inversetransform( 214 UnivariateRealFunction f, double min, double max, int n) 215 throws MathException, IllegalArgumentException { 216 217 double data[] = sample(f, min, max, n); 218 double scaling_coefficient = 1.0 / n; 219 return scaleArray(fft(data, true), scaling_coefficient); 220 } 221 222 /** 223 * Inversely transform the given complex data set. 224 * <p> 225 * The formula is $ x_k = (1/N) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n $ 226 * </p> 227 * 228 * @param f the complex data array to be inversely transformed 229 * @return the complex inversely transformed array 230 * @throws MathException if any math-related errors occur 231 * @throws IllegalArgumentException if any parameters are invalid 232 */ 233 public Complex[] inversetransform(Complex f[]) throws MathException, 234 IllegalArgumentException { 235 236 computeOmega(-f.length); // pass negative argument 237 double scaling_coefficient = 1.0 / f.length; 238 return scaleArray(fft(f), scaling_coefficient); 239 } 240 241 /** 242 * Inversely transform the given real data set. 243 * <p> 244 * The formula is $x_k = (1/\sqrt{N}) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n$ 245 * </p> 246 * 247 * @param f the real data array to be inversely transformed 248 * @return the complex inversely transformed array 249 * @throws MathException if any math-related errors occur 250 * @throws IllegalArgumentException if any parameters are invalid 251 */ 252 public Complex[] inversetransform2(double f[]) throws MathException, 253 IllegalArgumentException { 254 255 double scaling_coefficient = 1.0 / Math.sqrt(f.length); 256 return scaleArray(fft(f, true), scaling_coefficient); 257 } 258 259 /** 260 * Inversely transform the given real function, sampled on the given interval. 261 * <p> 262 * The formula is $x_k = (1/\sqrt{N}) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n$ 263 * </p> 264 * 265 * @param f the function to be sampled and inversely transformed 266 * @param min the lower bound for the interval 267 * @param max the upper bound for the interval 268 * @param n the number of sample points 269 * @return the complex inversely transformed array 270 * @throws MathException if any math-related errors occur 271 * @throws IllegalArgumentException if any parameters are invalid 272 */ 273 public Complex[] inversetransform2( 274 UnivariateRealFunction f, double min, double max, int n) 275 throws MathException, IllegalArgumentException { 276 277 double data[] = sample(f, min, max, n); 278 double scaling_coefficient = 1.0 / Math.sqrt(n); 279 return scaleArray(fft(data, true), scaling_coefficient); 280 } 281 282 /** 283 * Inversely transform the given complex data set. 284 * <p> 285 * The formula is $x_k = (1/\sqrt{N}) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n$ 286 * </p> 287 * 288 * @param f the complex data array to be inversely transformed 289 * @return the complex inversely transformed array 290 * @throws MathException if any math-related errors occur 291 * @throws IllegalArgumentException if any parameters are invalid 292 */ 293 public Complex[] inversetransform2(Complex f[]) throws MathException, 294 IllegalArgumentException { 295 296 computeOmega(-f.length); // pass negative argument 297 double scaling_coefficient = 1.0 / Math.sqrt(f.length); 298 return scaleArray(fft(f), scaling_coefficient); 299 } 300 301 /** 302 * Perform the base-4 Cooley-Tukey FFT algorithm (including inverse). 303 * 304 * @param f the real data array to be transformed 305 * @param isInverse the indicator of forward or inverse transform 306 * @return the complex transformed array 307 * @throws MathException if any math-related errors occur 308 * @throws IllegalArgumentException if any parameters are invalid 309 */ 310 protected Complex[] fft(double f[], boolean isInverse) throws 311 MathException, IllegalArgumentException { 312 313 verifyDataSet(f); 314 Complex F[] = new Complex[f.length]; 315 if (f.length == 1) { 316 F[0] = new Complex(f[0], 0.0); 317 return F; 318 } 319 320 // Rather than the naive real to complex conversion, pack 2N 321 // real numbers into N complex numbers for better performance. 322 int N = f.length >> 1; 323 Complex c[] = new Complex[N]; 324 for (int i = 0; i < N; i++) { 325 c[i] = new Complex(f[2*i], f[2*i+1]); 326 } 327 computeOmega(isInverse ? -N : N); 328 Complex z[] = fft(c); 329 330 // reconstruct the FFT result for the original array 331 computeOmega(isInverse ? -2*N : 2*N); 332 F[0] = new Complex(2 * (z[0].getReal() + z[0].getImaginary()), 0.0); 333 F[N] = new Complex(2 * (z[0].getReal() - z[0].getImaginary()), 0.0); 334 for (int i = 1; i < N; i++) { 335 Complex A = z[N-i].conjugate(); 336 Complex B = z[i].add(A); 337 Complex C = z[i].subtract(A); 338 Complex D = omega[i].multiply(Complex.I); 339 F[i] = B.subtract(C.multiply(D)); 340 F[2*N-i] = F[i].conjugate(); 341 } 342 343 return scaleArray(F, 0.5); 344 } 345 346 /** 347 * Perform the base-4 Cooley-Tukey FFT algorithm (including inverse). 348 * 349 * @param data the complex data array to be transformed 350 * @return the complex transformed array 351 * @throws MathException if any math-related errors occur 352 * @throws IllegalArgumentException if any parameters are invalid 353 */ 354 protected Complex[] fft(Complex data[]) throws MathException, 355 IllegalArgumentException { 356 357 int i, j, k, m, N = data.length; 358 Complex A, B, C, D, E, F, z, f[] = new Complex[N]; 359 360 // initial simple cases 361 verifyDataSet(data); 362 if (N == 1) { 363 f[0] = data[0]; 364 return f; 365 } 366 if (N == 2) { 367 f[0] = data[0].add(data[1]); 368 f[1] = data[0].subtract(data[1]); 369 return f; 370 } 371 372 // permute original data array in bit-reversal order 373 j = 0; 374 for (i = 0; i < N; i++) { 375 f[i] = data[j]; 376 k = N >> 1; 377 while (j >= k && k > 0) { 378 j -= k; k >>= 1; 379 } 380 j += k; 381 } 382 383 // the bottom base-4 round 384 for (i = 0; i < N; i += 4) { 385 A = f[i].add(f[i+1]); 386 B = f[i+2].add(f[i+3]); 387 C = f[i].subtract(f[i+1]); 388 D = f[i+2].subtract(f[i+3]); 389 E = C.add(D.multiply(Complex.I)); 390 F = C.subtract(D.multiply(Complex.I)); 391 f[i] = A.add(B); 392 f[i+2] = A.subtract(B); 393 // omegaCount indicates forward or inverse transform 394 f[i+1] = omegaCount < 0 ? E : F; 395 f[i+3] = omegaCount > 0 ? E : F; 396 } 397 398 // iterations from bottom to top take O(N*logN) time 399 for (i = 4; i < N; i <<= 1) { 400 m = N / (i<<1); 401 for (j = 0; j < N; j += i<<1) { 402 for (k = 0; k < i; k++) { 403 z = f[i+j+k].multiply(omega[k*m]); 404 f[i+j+k] = f[j+k].subtract(z); 405 f[j+k] = f[j+k].add(z); 406 } 407 } 408 } 409 return f; 410 } 411 412 /** 413 * Calculate the n-th roots of unity. 414 * <p> 415 * The computed omega[] = { 1, w, w^2, ... w^(n-1) } where 416 * w = exp(-2 \pi i / n), i = sqrt(-1). Note n is positive for 417 * forward transform and negative for inverse transform. </p> 418 * 419 * @param n the integer passed in 420 * @throws IllegalArgumentException if n = 0 421 */ 422 protected void computeOmega(int n) throws IllegalArgumentException { 423 if (n == 0) { 424 throw new IllegalArgumentException 425 ("Cannot compute 0-th root of unity, indefinite result."); 426 } 427 // avoid repetitive calculations 428 if (n == omegaCount) { return; } 429 if (n + omegaCount == 0) { 430 for (int i = 0; i < Math.abs(omegaCount); i++) { 431 omega[i] = omega[i].conjugate(); 432 } 433 omegaCount = n; 434 return; 435 } 436 // calculate everything from scratch 437 omega = new Complex[Math.abs(n)]; 438 double t = 2.0 * Math.PI / n; 439 double cost = Math.cos(t); 440 double sint = Math.sin(t); 441 omega[0] = new Complex(1.0, 0.0); 442 for (int i = 1; i < Math.abs(n); i++) { 443 omega[i] = new Complex( 444 omega[i-1].getReal() * cost + omega[i-1].getImaginary() * sint, 445 omega[i-1].getImaginary() * cost - omega[i-1].getReal() * sint); 446 } 447 omegaCount = n; 448 } 449 450 /** 451 * Sample the given univariate real function on the given interval. 452 * <p> 453 * The interval is divided equally into N sections and sample points 454 * are taken from min to max-(max-min)/N. Usually f(x) is periodic 455 * such that f(min) = f(max) (note max is not sampled), but we don't 456 * require that.</p> 457 * 458 * @param f the function to be sampled 459 * @param min the lower bound for the interval 460 * @param max the upper bound for the interval 461 * @param n the number of sample points 462 * @return the samples array 463 * @throws MathException if any math-related errors occur 464 * @throws IllegalArgumentException if any parameters are invalid 465 */ 466 public static double[] sample( 467 UnivariateRealFunction f, double min, double max, int n) 468 throws MathException, IllegalArgumentException { 469 470 if (n <= 0) { 471 throw new IllegalArgumentException("Number of samples not positive."); 472 } 473 verifyInterval(min, max); 474 475 double s[] = new double[n]; 476 double h = (max - min) / n; 477 for (int i = 0; i < n; i++) { 478 s[i] = f.value(min + i * h); 479 } 480 return s; 481 } 482 483 /** 484 * Multiply every component in the given real array by the 485 * given real number. The change is made in place. 486 * 487 * @param f the real array to be scaled 488 * @param d the real scaling coefficient 489 * @return a reference to the scaled array 490 */ 491 public static double[] scaleArray(double f[], double d) { 492 for (int i = 0; i < f.length; i++) { 493 f[i] *= d; 494 } 495 return f; 496 } 497 498 /** 499 * Multiply every component in the given complex array by the 500 * given real number. The change is made in place. 501 * 502 * @param f the complex array to be scaled 503 * @param d the real scaling coefficient 504 * @return a reference to the scaled array 505 */ 506 public static Complex[] scaleArray(Complex f[], double d) { 507 for (int i = 0; i < f.length; i++) { 508 f[i] = new Complex(d * f[i].getReal(), d * f[i].getImaginary()); 509 } 510 return f; 511 } 512 513 /** 514 * Returns true if the argument is power of 2. 515 * 516 * @param n the number to test 517 * @return true if the argument is power of 2 518 */ 519 public static boolean isPowerOf2(long n) { 520 return (n > 0) && ((n & (n - 1)) == 0); 521 } 522 523 /** 524 * Verifies that the data set has length of power of 2. 525 * 526 * @param d the data array 527 * @throws IllegalArgumentException if array length is not power of 2 528 */ 529 public static void verifyDataSet(double d[]) throws IllegalArgumentException { 530 if (!isPowerOf2(d.length)) { 531 throw new IllegalArgumentException 532 ("Number of samples not power of 2, consider padding for fix."); 533 } 534 } 535 536 /** 537 * Verifies that the data set has length of power of 2. 538 * 539 * @param o the data array 540 * @throws IllegalArgumentException if array length is not power of 2 541 */ 542 public static void verifyDataSet(Object o[]) throws IllegalArgumentException { 543 if (!isPowerOf2(o.length)) { 544 throw new IllegalArgumentException 545 ("Number of samples not power of 2, consider padding for fix."); 546 } 547 } 548 549 /** 550 * Verifies that the endpoints specify an interval. 551 * 552 * @param lower lower endpoint 553 * @param upper upper endpoint 554 * @throws IllegalArgumentException if not interval 555 */ 556 public static void verifyInterval(double lower, double upper) throws 557 IllegalArgumentException { 558 559 if (lower >= upper) { 560 throw new IllegalArgumentException 561 ("Endpoints do not specify an interval: [" + lower + 562 ", " + upper + "]"); 563 } 564 } 565 }