• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 java.lang.reflect.Array;
21 
22 import org.apache.commons.math.FunctionEvaluationException;
23 import org.apache.commons.math.MathRuntimeException;
24 import org.apache.commons.math.analysis.UnivariateRealFunction;
25 import org.apache.commons.math.complex.Complex;
26 import org.apache.commons.math.exception.util.LocalizedFormats;
27 import org.apache.commons.math.util.FastMath;
28 
29 /**
30  * Implements the <a href="http://mathworld.wolfram.com/FastFourierTransform.html">
31  * Fast Fourier Transform</a> for transformation of one-dimensional data sets.
32  * For reference, see <b>Applied Numerical Linear Algebra</b>, ISBN 0898713897,
33  * chapter 6.
34  * <p>
35  * There are several conventions for the definition of FFT and inverse FFT,
36  * mainly on different coefficient and exponent. Here the equations are listed
37  * in the comments of the corresponding methods.</p>
38  * <p>
39  * We require the length of data set to be power of 2, this greatly simplifies
40  * and speeds up the code. Users can pad the data with zeros to meet this
41  * requirement. There are other flavors of FFT, for reference, see S. Winograd,
42  * <i>On computing the discrete Fourier transform</i>, Mathematics of Computation,
43  * 32 (1978), 175 - 199.</p>
44  *
45  * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
46  * @since 1.2
47  */
48 public class FastFourierTransformer implements Serializable {
49 
50     /** Serializable version identifier. */
51     static final long serialVersionUID = 5138259215438106000L;
52 
53     /** roots of unity */
54     private RootsOfUnity roots = new RootsOfUnity();
55 
56     /**
57      * Construct a default transformer.
58      */
FastFourierTransformer()59     public FastFourierTransformer() {
60         super();
61     }
62 
63     /**
64      * Transform the given real data set.
65      * <p>
66      * The formula is $ y_n = \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k $
67      * </p>
68      *
69      * @param f the real data array to be transformed
70      * @return the complex transformed array
71      * @throws IllegalArgumentException if any parameters are invalid
72      */
transform(double f[])73     public Complex[] transform(double f[])
74         throws IllegalArgumentException {
75         return fft(f, false);
76     }
77 
78     /**
79      * Transform the given real function, sampled on the given interval.
80      * <p>
81      * The formula is $ y_n = \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k $
82      * </p>
83      *
84      * @param f the function to be sampled and transformed
85      * @param min the lower bound for the interval
86      * @param max the upper bound for the interval
87      * @param n the number of sample points
88      * @return the complex transformed array
89      * @throws FunctionEvaluationException if function cannot be evaluated
90      * at some point
91      * @throws IllegalArgumentException if any parameters are invalid
92      */
transform(UnivariateRealFunction f, double min, double max, int n)93     public Complex[] transform(UnivariateRealFunction f,
94                                double min, double max, int n)
95         throws FunctionEvaluationException, IllegalArgumentException {
96         double data[] = sample(f, min, max, n);
97         return fft(data, false);
98     }
99 
100     /**
101      * Transform the given complex data set.
102      * <p>
103      * The formula is $ y_n = \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k $
104      * </p>
105      *
106      * @param f the complex data array to be transformed
107      * @return the complex transformed array
108      * @throws IllegalArgumentException if any parameters are invalid
109      */
transform(Complex f[])110     public Complex[] transform(Complex f[])
111         throws IllegalArgumentException {
112         roots.computeOmega(f.length);
113         return fft(f);
114     }
115 
116     /**
117      * Transform the given real data set.
118      * <p>
119      * The formula is $y_n = (1/\sqrt{N}) \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k$
120      * </p>
121      *
122      * @param f the real data array to be transformed
123      * @return the complex transformed array
124      * @throws IllegalArgumentException if any parameters are invalid
125      */
transform2(double f[])126     public Complex[] transform2(double f[])
127         throws IllegalArgumentException {
128 
129         double scaling_coefficient = 1.0 / FastMath.sqrt(f.length);
130         return scaleArray(fft(f, false), scaling_coefficient);
131     }
132 
133     /**
134      * Transform the given real function, sampled on the given interval.
135      * <p>
136      * The formula is $y_n = (1/\sqrt{N}) \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k$
137      * </p>
138      *
139      * @param f the function to be sampled and transformed
140      * @param min the lower bound for the interval
141      * @param max the upper bound for the interval
142      * @param n the number of sample points
143      * @return the complex transformed array
144      * @throws FunctionEvaluationException if function cannot be evaluated
145      * at some point
146      * @throws IllegalArgumentException if any parameters are invalid
147      */
transform2(UnivariateRealFunction f, double min, double max, int n)148     public Complex[] transform2(UnivariateRealFunction f,
149                                 double min, double max, int n)
150         throws FunctionEvaluationException, IllegalArgumentException {
151 
152         double data[] = sample(f, min, max, n);
153         double scaling_coefficient = 1.0 / FastMath.sqrt(n);
154         return scaleArray(fft(data, false), scaling_coefficient);
155     }
156 
157     /**
158      * Transform the given complex data set.
159      * <p>
160      * The formula is $y_n = (1/\sqrt{N}) \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k$
161      * </p>
162      *
163      * @param f the complex data array to be transformed
164      * @return the complex transformed array
165      * @throws IllegalArgumentException if any parameters are invalid
166      */
transform2(Complex f[])167     public Complex[] transform2(Complex f[])
168         throws IllegalArgumentException {
169 
170         roots.computeOmega(f.length);
171         double scaling_coefficient = 1.0 / FastMath.sqrt(f.length);
172         return scaleArray(fft(f), scaling_coefficient);
173     }
174 
175     /**
176      * Inversely transform the given real data set.
177      * <p>
178      * The formula is $ x_k = (1/N) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n $
179      * </p>
180      *
181      * @param f the real data array to be inversely transformed
182      * @return the complex inversely transformed array
183      * @throws IllegalArgumentException if any parameters are invalid
184      */
inversetransform(double f[])185     public Complex[] inversetransform(double f[])
186         throws IllegalArgumentException {
187 
188         double scaling_coefficient = 1.0 / f.length;
189         return scaleArray(fft(f, true), scaling_coefficient);
190     }
191 
192     /**
193      * Inversely transform the given real function, sampled on the given interval.
194      * <p>
195      * The formula is $ x_k = (1/N) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n $
196      * </p>
197      *
198      * @param f the function to be sampled and inversely transformed
199      * @param min the lower bound for the interval
200      * @param max the upper bound for the interval
201      * @param n the number of sample points
202      * @return the complex inversely transformed array
203      * @throws FunctionEvaluationException if function cannot be evaluated
204      * at some point
205      * @throws IllegalArgumentException if any parameters are invalid
206      */
inversetransform(UnivariateRealFunction f, double min, double max, int n)207     public Complex[] inversetransform(UnivariateRealFunction f,
208                                       double min, double max, int n)
209         throws FunctionEvaluationException, IllegalArgumentException {
210 
211         double data[] = sample(f, min, max, n);
212         double scaling_coefficient = 1.0 / n;
213         return scaleArray(fft(data, true), scaling_coefficient);
214     }
215 
216     /**
217      * Inversely transform the given complex data set.
218      * <p>
219      * The formula is $ x_k = (1/N) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n $
220      * </p>
221      *
222      * @param f the complex data array to be inversely transformed
223      * @return the complex inversely transformed array
224      * @throws IllegalArgumentException if any parameters are invalid
225      */
inversetransform(Complex f[])226     public Complex[] inversetransform(Complex f[])
227         throws IllegalArgumentException {
228 
229         roots.computeOmega(-f.length);    // pass negative argument
230         double scaling_coefficient = 1.0 / f.length;
231         return scaleArray(fft(f), scaling_coefficient);
232     }
233 
234     /**
235      * Inversely transform the given real data set.
236      * <p>
237      * The formula is $x_k = (1/\sqrt{N}) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n$
238      * </p>
239      *
240      * @param f the real data array to be inversely transformed
241      * @return the complex inversely transformed array
242      * @throws IllegalArgumentException if any parameters are invalid
243      */
inversetransform2(double f[])244     public Complex[] inversetransform2(double f[])
245         throws IllegalArgumentException {
246 
247         double scaling_coefficient = 1.0 / FastMath.sqrt(f.length);
248         return scaleArray(fft(f, true), scaling_coefficient);
249     }
250 
251     /**
252      * Inversely transform the given real function, sampled on the given interval.
253      * <p>
254      * The formula is $x_k = (1/\sqrt{N}) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n$
255      * </p>
256      *
257      * @param f the function to be sampled and inversely transformed
258      * @param min the lower bound for the interval
259      * @param max the upper bound for the interval
260      * @param n the number of sample points
261      * @return the complex inversely transformed array
262      * @throws FunctionEvaluationException if function cannot be evaluated
263      * at some point
264      * @throws IllegalArgumentException if any parameters are invalid
265      */
inversetransform2(UnivariateRealFunction f, double min, double max, int n)266     public Complex[] inversetransform2(UnivariateRealFunction f,
267                                        double min, double max, int n)
268         throws FunctionEvaluationException, IllegalArgumentException {
269 
270         double data[] = sample(f, min, max, n);
271         double scaling_coefficient = 1.0 / FastMath.sqrt(n);
272         return scaleArray(fft(data, true), scaling_coefficient);
273     }
274 
275     /**
276      * Inversely transform the given complex data set.
277      * <p>
278      * The formula is $x_k = (1/\sqrt{N}) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n$
279      * </p>
280      *
281      * @param f the complex data array to be inversely transformed
282      * @return the complex inversely transformed array
283      * @throws IllegalArgumentException if any parameters are invalid
284      */
inversetransform2(Complex f[])285     public Complex[] inversetransform2(Complex f[])
286         throws IllegalArgumentException {
287 
288         roots.computeOmega(-f.length);    // pass negative argument
289         double scaling_coefficient = 1.0 / FastMath.sqrt(f.length);
290         return scaleArray(fft(f), scaling_coefficient);
291     }
292 
293     /**
294      * Perform the base-4 Cooley-Tukey FFT algorithm (including inverse).
295      *
296      * @param f the real data array to be transformed
297      * @param isInverse the indicator of forward or inverse transform
298      * @return the complex transformed array
299      * @throws IllegalArgumentException if any parameters are invalid
300      */
fft(double f[], boolean isInverse)301     protected Complex[] fft(double f[], boolean isInverse)
302         throws IllegalArgumentException {
303 
304         verifyDataSet(f);
305         Complex F[] = new Complex[f.length];
306         if (f.length == 1) {
307             F[0] = new Complex(f[0], 0.0);
308             return F;
309         }
310 
311         // Rather than the naive real to complex conversion, pack 2N
312         // real numbers into N complex numbers for better performance.
313         int N = f.length >> 1;
314         Complex c[] = new Complex[N];
315         for (int i = 0; i < N; i++) {
316             c[i] = new Complex(f[2*i], f[2*i+1]);
317         }
318         roots.computeOmega(isInverse ? -N : N);
319         Complex z[] = fft(c);
320 
321         // reconstruct the FFT result for the original array
322         roots.computeOmega(isInverse ? -2*N : 2*N);
323         F[0] = new Complex(2 * (z[0].getReal() + z[0].getImaginary()), 0.0);
324         F[N] = new Complex(2 * (z[0].getReal() - z[0].getImaginary()), 0.0);
325         for (int i = 1; i < N; i++) {
326             Complex A = z[N-i].conjugate();
327             Complex B = z[i].add(A);
328             Complex C = z[i].subtract(A);
329             //Complex D = roots.getOmega(i).multiply(Complex.I);
330             Complex D = new Complex(-roots.getOmegaImaginary(i),
331                                     roots.getOmegaReal(i));
332             F[i] = B.subtract(C.multiply(D));
333             F[2*N-i] = F[i].conjugate();
334         }
335 
336         return scaleArray(F, 0.5);
337     }
338 
339     /**
340      * Perform the base-4 Cooley-Tukey FFT algorithm (including inverse).
341      *
342      * @param data the complex data array to be transformed
343      * @return the complex transformed array
344      * @throws IllegalArgumentException if any parameters are invalid
345      */
fft(Complex data[])346     protected Complex[] fft(Complex data[])
347         throws IllegalArgumentException {
348 
349         final int n = data.length;
350         final Complex f[] = new Complex[n];
351 
352         // initial simple cases
353         verifyDataSet(data);
354         if (n == 1) {
355             f[0] = data[0];
356             return f;
357         }
358         if (n == 2) {
359             f[0] = data[0].add(data[1]);
360             f[1] = data[0].subtract(data[1]);
361             return f;
362         }
363 
364         // permute original data array in bit-reversal order
365         int ii = 0;
366         for (int i = 0; i < n; i++) {
367             f[i] = data[ii];
368             int k = n >> 1;
369             while (ii >= k && k > 0) {
370                 ii -= k; k >>= 1;
371             }
372             ii += k;
373         }
374 
375         // the bottom base-4 round
376         for (int i = 0; i < n; i += 4) {
377             final Complex a = f[i].add(f[i+1]);
378             final Complex b = f[i+2].add(f[i+3]);
379             final Complex c = f[i].subtract(f[i+1]);
380             final Complex d = f[i+2].subtract(f[i+3]);
381             final Complex e1 = c.add(d.multiply(Complex.I));
382             final Complex e2 = c.subtract(d.multiply(Complex.I));
383             f[i] = a.add(b);
384             f[i+2] = a.subtract(b);
385             // omegaCount indicates forward or inverse transform
386             f[i+1] = roots.isForward() ? e2 : e1;
387             f[i+3] = roots.isForward() ? e1 : e2;
388         }
389 
390         // iterations from bottom to top take O(N*logN) time
391         for (int i = 4; i < n; i <<= 1) {
392             final int m = n / (i<<1);
393             for (int j = 0; j < n; j += i<<1) {
394                 for (int k = 0; k < i; k++) {
395                     //z = f[i+j+k].multiply(roots.getOmega(k*m));
396                     final int k_times_m = k*m;
397                     final double omega_k_times_m_real = roots.getOmegaReal(k_times_m);
398                     final double omega_k_times_m_imaginary = roots.getOmegaImaginary(k_times_m);
399                     //z = f[i+j+k].multiply(omega[k*m]);
400                     final Complex z = new Complex(
401                         f[i+j+k].getReal() * omega_k_times_m_real -
402                         f[i+j+k].getImaginary() * omega_k_times_m_imaginary,
403                         f[i+j+k].getReal() * omega_k_times_m_imaginary +
404                         f[i+j+k].getImaginary() * omega_k_times_m_real);
405 
406                     f[i+j+k] = f[j+k].subtract(z);
407                     f[j+k] = f[j+k].add(z);
408                 }
409             }
410         }
411         return f;
412     }
413 
414     /**
415      * Sample the given univariate real function on the given interval.
416      * <p>
417      * The interval is divided equally into N sections and sample points
418      * are taken from min to max-(max-min)/N. Usually f(x) is periodic
419      * such that f(min) = f(max) (note max is not sampled), but we don't
420      * require that.</p>
421      *
422      * @param f the function to be sampled
423      * @param min the lower bound for the interval
424      * @param max the upper bound for the interval
425      * @param n the number of sample points
426      * @return the samples array
427      * @throws FunctionEvaluationException if function cannot be evaluated at some point
428      * @throws IllegalArgumentException if any parameters are invalid
429      */
sample(UnivariateRealFunction f, double min, double max, int n)430     public static double[] sample(UnivariateRealFunction f, double min, double max, int n)
431         throws FunctionEvaluationException, IllegalArgumentException {
432 
433         if (n <= 0) {
434             throw MathRuntimeException.createIllegalArgumentException(
435                     LocalizedFormats.NOT_POSITIVE_NUMBER_OF_SAMPLES,
436                     n);
437         }
438         verifyInterval(min, max);
439 
440         double s[] = new double[n];
441         double h = (max - min) / n;
442         for (int i = 0; i < n; i++) {
443             s[i] = f.value(min + i * h);
444         }
445         return s;
446     }
447 
448     /**
449      * Multiply every component in the given real array by the
450      * given real number. The change is made in place.
451      *
452      * @param f the real array to be scaled
453      * @param d the real scaling coefficient
454      * @return a reference to the scaled array
455      */
scaleArray(double f[], double d)456     public static double[] scaleArray(double f[], double d) {
457         for (int i = 0; i < f.length; i++) {
458             f[i] *= d;
459         }
460         return f;
461     }
462 
463     /**
464      * Multiply every component in the given complex array by the
465      * given real number. The change is made in place.
466      *
467      * @param f the complex array to be scaled
468      * @param d the real scaling coefficient
469      * @return a reference to the scaled array
470      */
scaleArray(Complex f[], double d)471     public static Complex[] scaleArray(Complex f[], double d) {
472         for (int i = 0; i < f.length; i++) {
473             f[i] = new Complex(d * f[i].getReal(), d * f[i].getImaginary());
474         }
475         return f;
476     }
477 
478     /**
479      * Returns true if the argument is power of 2.
480      *
481      * @param n the number to test
482      * @return true if the argument is power of 2
483      */
isPowerOf2(long n)484     public static boolean isPowerOf2(long n) {
485         return (n > 0) && ((n & (n - 1)) == 0);
486     }
487 
488     /**
489      * Verifies that the data set has length of power of 2.
490      *
491      * @param d the data array
492      * @throws IllegalArgumentException if array length is not power of 2
493      */
verifyDataSet(double d[])494     public static void verifyDataSet(double d[]) throws IllegalArgumentException {
495         if (!isPowerOf2(d.length)) {
496             throw MathRuntimeException.createIllegalArgumentException(
497                     LocalizedFormats.NOT_POWER_OF_TWO_CONSIDER_PADDING, d.length);
498         }
499     }
500 
501     /**
502      * Verifies that the data set has length of power of 2.
503      *
504      * @param o the data array
505      * @throws IllegalArgumentException if array length is not power of 2
506      */
verifyDataSet(Object o[])507     public static void verifyDataSet(Object o[]) throws IllegalArgumentException {
508         if (!isPowerOf2(o.length)) {
509             throw MathRuntimeException.createIllegalArgumentException(
510                     LocalizedFormats.NOT_POWER_OF_TWO_CONSIDER_PADDING, o.length);
511         }
512     }
513 
514     /**
515      * Verifies that the endpoints specify an interval.
516      *
517      * @param lower lower endpoint
518      * @param upper upper endpoint
519      * @throws IllegalArgumentException if not interval
520      */
verifyInterval(double lower, double upper)521     public static void verifyInterval(double lower, double upper)
522         throws IllegalArgumentException {
523 
524         if (lower >= upper) {
525             throw MathRuntimeException.createIllegalArgumentException(
526                     LocalizedFormats.ENDPOINTS_NOT_AN_INTERVAL,
527                     lower, upper);
528         }
529     }
530 
531     /**
532      * Performs a multi-dimensional Fourier transform on a given array.
533      * Use {@link #inversetransform2(Complex[])} and
534      * {@link #transform2(Complex[])} in a row-column implementation
535      * in any number of dimensions with O(N&times;log(N)) complexity with
536      * N=n<sub>1</sub>&times;n<sub>2</sub>&times;n<sub>3</sub>&times;...&times;n<sub>d</sub>,
537      * n<sub>x</sub>=number of elements in dimension x,
538      * and d=total number of dimensions.
539      *
540      * @param mdca Multi-Dimensional Complex Array id est Complex[][][][]
541      * @param forward inverseTransform2 is preformed if this is false
542      * @return transform of mdca as a Multi-Dimensional Complex Array id est Complex[][][][]
543      * @throws IllegalArgumentException if any dimension is not a power of two
544      */
mdfft(Object mdca, boolean forward)545     public Object mdfft(Object mdca, boolean forward)
546         throws IllegalArgumentException {
547         MultiDimensionalComplexMatrix mdcm = (MultiDimensionalComplexMatrix)
548                 new MultiDimensionalComplexMatrix(mdca).clone();
549         int[] dimensionSize = mdcm.getDimensionSizes();
550         //cycle through each dimension
551         for (int i = 0; i < dimensionSize.length; i++) {
552             mdfft(mdcm, forward, i, new int[0]);
553         }
554         return mdcm.getArray();
555     }
556 
557     /**
558      * Performs one dimension of a multi-dimensional Fourier transform.
559      *
560      * @param mdcm input matrix
561      * @param forward inverseTransform2 is preformed if this is false
562      * @param d index of the dimension to process
563      * @param subVector recursion subvector
564      * @throws IllegalArgumentException if any dimension is not a power of two
565      */
mdfft(MultiDimensionalComplexMatrix mdcm, boolean forward, int d, int[] subVector)566     private void mdfft(MultiDimensionalComplexMatrix mdcm, boolean forward,
567                        int d, int[] subVector)
568         throws IllegalArgumentException {
569         int[] dimensionSize = mdcm.getDimensionSizes();
570         //if done
571         if (subVector.length == dimensionSize.length) {
572             Complex[] temp = new Complex[dimensionSize[d]];
573             for (int i = 0; i < dimensionSize[d]; i++) {
574                 //fft along dimension d
575                 subVector[d] = i;
576                 temp[i] = mdcm.get(subVector);
577             }
578 
579             if (forward)
580                 temp = transform2(temp);
581             else
582                 temp = inversetransform2(temp);
583 
584             for (int i = 0; i < dimensionSize[d]; i++) {
585                 subVector[d] = i;
586                 mdcm.set(temp[i], subVector);
587             }
588         } else {
589             int[] vector = new int[subVector.length + 1];
590             System.arraycopy(subVector, 0, vector, 0, subVector.length);
591             if (subVector.length == d) {
592                 //value is not important once the recursion is done.
593                 //then an fft will be applied along the dimension d.
594                 vector[d] = 0;
595                 mdfft(mdcm, forward, d, vector);
596             } else {
597                 for (int i = 0; i < dimensionSize[subVector.length]; i++) {
598                     vector[subVector.length] = i;
599                     //further split along the next dimension
600                     mdfft(mdcm, forward, d, vector);
601                 }
602             }
603         }
604         return;
605     }
606 
607     /**
608      * Complex matrix implementation.
609      * Not designed for synchronized access
610      * may eventually be replaced by jsr-83 of the java community process
611      * http://jcp.org/en/jsr/detail?id=83
612      * may require additional exception throws for other basic requirements.
613      */
614     private static class MultiDimensionalComplexMatrix
615         implements Cloneable {
616 
617         /** Size in all dimensions. */
618         protected int[] dimensionSize;
619 
620         /** Storage array. */
621         protected Object multiDimensionalComplexArray;
622 
623         /** Simple constructor.
624          * @param multiDimensionalComplexArray array containing the matrix elements
625          */
MultiDimensionalComplexMatrix(Object multiDimensionalComplexArray)626         public MultiDimensionalComplexMatrix(Object multiDimensionalComplexArray) {
627 
628             this.multiDimensionalComplexArray = multiDimensionalComplexArray;
629 
630             // count dimensions
631             int numOfDimensions = 0;
632             for (Object lastDimension = multiDimensionalComplexArray;
633                  lastDimension instanceof Object[];) {
634                 final Object[] array = (Object[]) lastDimension;
635                 numOfDimensions++;
636                 lastDimension = array[0];
637             }
638 
639             // allocate array with exact count
640             dimensionSize = new int[numOfDimensions];
641 
642             // fill array
643             numOfDimensions = 0;
644             for (Object lastDimension = multiDimensionalComplexArray;
645                  lastDimension instanceof Object[];) {
646                 final Object[] array = (Object[]) lastDimension;
647                 dimensionSize[numOfDimensions++] = array.length;
648                 lastDimension = array[0];
649             }
650 
651         }
652 
653         /**
654          * Get a matrix element.
655          * @param vector indices of the element
656          * @return matrix element
657          * @exception IllegalArgumentException if dimensions do not match
658          */
get(int... vector)659         public Complex get(int... vector)
660             throws IllegalArgumentException {
661             if (vector == null) {
662                 if (dimensionSize.length > 0) {
663                     throw MathRuntimeException.createIllegalArgumentException(
664                             LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, 0, dimensionSize.length);
665                 }
666                 return null;
667             }
668             if (vector.length != dimensionSize.length) {
669                 throw MathRuntimeException.createIllegalArgumentException(
670                         LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, vector.length, dimensionSize.length);
671             }
672 
673             Object lastDimension = multiDimensionalComplexArray;
674 
675             for (int i = 0; i < dimensionSize.length; i++) {
676                 lastDimension = ((Object[]) lastDimension)[vector[i]];
677             }
678             return (Complex) lastDimension;
679         }
680 
681         /**
682          * Set a matrix element.
683          * @param magnitude magnitude of the element
684          * @param vector indices of the element
685          * @return the previous value
686          * @exception IllegalArgumentException if dimensions do not match
687          */
set(Complex magnitude, int... vector)688         public Complex set(Complex magnitude, int... vector)
689             throws IllegalArgumentException {
690             if (vector == null) {
691                 if (dimensionSize.length > 0) {
692                     throw MathRuntimeException.createIllegalArgumentException(
693                             LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, 0, dimensionSize.length);
694                 }
695                 return null;
696             }
697             if (vector.length != dimensionSize.length) {
698                 throw MathRuntimeException.createIllegalArgumentException(
699                         LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, vector.length,dimensionSize.length);
700             }
701 
702             Object[] lastDimension = (Object[]) multiDimensionalComplexArray;
703             for (int i = 0; i < dimensionSize.length - 1; i++) {
704                 lastDimension = (Object[]) lastDimension[vector[i]];
705             }
706 
707             Complex lastValue = (Complex) lastDimension[vector[dimensionSize.length - 1]];
708             lastDimension[vector[dimensionSize.length - 1]] = magnitude;
709 
710             return lastValue;
711         }
712 
713         /**
714          * Get the size in all dimensions.
715          * @return size in all dimensions
716          */
getDimensionSizes()717         public int[] getDimensionSizes() {
718             return dimensionSize.clone();
719         }
720 
721         /**
722          * Get the underlying storage array
723          * @return underlying storage array
724          */
getArray()725         public Object getArray() {
726             return multiDimensionalComplexArray;
727         }
728 
729         /** {@inheritDoc} */
730         @Override
clone()731         public Object clone() {
732             MultiDimensionalComplexMatrix mdcm =
733                     new MultiDimensionalComplexMatrix(Array.newInstance(
734                     Complex.class, dimensionSize));
735             clone(mdcm);
736             return mdcm;
737         }
738 
739         /**
740          * Copy contents of current array into mdcm.
741          * @param mdcm array where to copy data
742          */
clone(MultiDimensionalComplexMatrix mdcm)743         private void clone(MultiDimensionalComplexMatrix mdcm) {
744             int[] vector = new int[dimensionSize.length];
745             int size = 1;
746             for (int i = 0; i < dimensionSize.length; i++) {
747                 size *= dimensionSize[i];
748             }
749             int[][] vectorList = new int[size][dimensionSize.length];
750             for (int[] nextVector: vectorList) {
751                 System.arraycopy(vector, 0, nextVector, 0,
752                                  dimensionSize.length);
753                 for (int i = 0; i < dimensionSize.length; i++) {
754                     vector[i]++;
755                     if (vector[i] < dimensionSize[i]) {
756                         break;
757                     } else {
758                         vector[i] = 0;
759                     }
760                 }
761             }
762 
763             for (int[] nextVector: vectorList) {
764                 mdcm.set(get(nextVector), nextVector);
765             }
766         }
767     }
768 
769 
770     /** Computes the n<sup>th</sup> roots of unity.
771      * A cache of already computed values is maintained.
772      */
773     private static class RootsOfUnity implements Serializable {
774 
775       /** Serializable version id. */
776       private static final long serialVersionUID = 6404784357747329667L;
777 
778       /** Number of roots of unity. */
779       private int      omegaCount;
780 
781       /** Real part of the roots. */
782       private double[] omegaReal;
783 
784       /** Imaginary part of the roots for forward transform. */
785       private double[] omegaImaginaryForward;
786 
787       /** Imaginary part of the roots for reverse transform. */
788       private double[] omegaImaginaryInverse;
789 
790       /** Forward/reverse indicator. */
791       private boolean  isForward;
792 
793       /**
794        * Build an engine for computing then <sup>th</sup> roots of unity
795        */
RootsOfUnity()796       public RootsOfUnity() {
797 
798         omegaCount = 0;
799         omegaReal = null;
800         omegaImaginaryForward = null;
801         omegaImaginaryInverse = null;
802         isForward = true;
803 
804       }
805 
806       /**
807        * Check if computation has been done for forward or reverse transform.
808        * @return true if computation has been done for forward transform
809        * @throws IllegalStateException if no roots of unity have been computed yet
810        */
isForward()811       public synchronized boolean isForward() throws IllegalStateException {
812 
813         if (omegaCount == 0) {
814           throw MathRuntimeException.createIllegalStateException(LocalizedFormats.ROOTS_OF_UNITY_NOT_COMPUTED_YET);
815         }
816         return isForward;
817 
818       }
819 
820       /** Computes the n<sup>th</sup> roots of unity.
821        * <p>The computed omega[] = { 1, w, w<sup>2</sup>, ... w<sup>(n-1)</sup> } where
822        * w = exp(-2 &pi; i / n), i = &sqrt;(-1).</p>
823        * <p>Note that n is positive for
824        * forward transform and negative for inverse transform.</p>
825        * @param n number of roots of unity to compute,
826        * positive for forward transform, negative for inverse transform
827        * @throws IllegalArgumentException if n = 0
828        */
computeOmega(int n)829       public synchronized void computeOmega(int n) throws IllegalArgumentException {
830 
831         if (n == 0) {
832           throw MathRuntimeException.createIllegalArgumentException(
833                   LocalizedFormats.CANNOT_COMPUTE_0TH_ROOT_OF_UNITY);
834         }
835 
836         isForward = n > 0;
837 
838         // avoid repetitive calculations
839         final int absN = FastMath.abs(n);
840 
841         if (absN == omegaCount) {
842             return;
843         }
844 
845         // calculate everything from scratch, for both forward and inverse versions
846         final double t    = 2.0 * FastMath.PI / absN;
847         final double cosT = FastMath.cos(t);
848         final double sinT = FastMath.sin(t);
849         omegaReal             = new double[absN];
850         omegaImaginaryForward = new double[absN];
851         omegaImaginaryInverse = new double[absN];
852         omegaReal[0]             = 1.0;
853         omegaImaginaryForward[0] = 0.0;
854         omegaImaginaryInverse[0] = 0.0;
855         for (int i = 1; i < absN; i++) {
856           omegaReal[i] =
857             omegaReal[i-1] * cosT + omegaImaginaryForward[i-1] * sinT;
858           omegaImaginaryForward[i] =
859              omegaImaginaryForward[i-1] * cosT - omegaReal[i-1] * sinT;
860           omegaImaginaryInverse[i] = -omegaImaginaryForward[i];
861         }
862         omegaCount = absN;
863 
864       }
865 
866       /**
867        * Get the real part of the k<sup>th</sup> n<sup>th</sup> root of unity
868        * @param k index of the n<sup>th</sup> root of unity
869        * @return real part of the k<sup>th</sup> n<sup>th</sup> root of unity
870        * @throws IllegalStateException if no roots of unity have been computed yet
871        * @throws IllegalArgumentException if k is out of range
872        */
getOmegaReal(int k)873       public synchronized double getOmegaReal(int k)
874         throws IllegalStateException, IllegalArgumentException {
875 
876         if (omegaCount == 0) {
877             throw MathRuntimeException.createIllegalStateException(LocalizedFormats.ROOTS_OF_UNITY_NOT_COMPUTED_YET);
878         }
879         if ((k < 0) || (k >= omegaCount)) {
880             throw MathRuntimeException.createIllegalArgumentException(
881                     LocalizedFormats.OUT_OF_RANGE_ROOT_OF_UNITY_INDEX, k, 0, omegaCount - 1);
882         }
883 
884         return omegaReal[k];
885 
886       }
887 
888       /**
889        * Get the imaginary part of the k<sup>th</sup> n<sup>th</sup> root of unity
890        * @param k index of the n<sup>th</sup> root of unity
891        * @return imaginary part of the k<sup>th</sup> n<sup>th</sup> root of unity
892        * @throws IllegalStateException if no roots of unity have been computed yet
893        * @throws IllegalArgumentException if k is out of range
894        */
getOmegaImaginary(int k)895       public synchronized double getOmegaImaginary(int k)
896         throws IllegalStateException, IllegalArgumentException {
897 
898         if (omegaCount == 0) {
899             throw MathRuntimeException.createIllegalStateException(LocalizedFormats.ROOTS_OF_UNITY_NOT_COMPUTED_YET);
900         }
901         if ((k < 0) || (k >= omegaCount)) {
902           throw MathRuntimeException.createIllegalArgumentException(
903                   LocalizedFormats.OUT_OF_RANGE_ROOT_OF_UNITY_INDEX, k, 0, omegaCount - 1);
904         }
905 
906         return isForward ? omegaImaginaryForward[k] : omegaImaginaryInverse[k];
907 
908       }
909 
910     }
911 
912 }
913