Home | History | Annotate | Download | only in transform
      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 fvr. 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      */
     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      */
     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      */
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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          */
    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          */
    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          */
    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          */
    717         public int[] getDimensionSizes() {
    718             return dimensionSize.clone();
    719         }
    720 
    721         /**
    722          * Get the underlying storage array
    723          * @return underlying storage array
    724          */
    725         public Object getArray() {
    726             return multiDimensionalComplexArray;
    727         }
    728 
    729         /** {@inheritDoc} */
    730         @Override
    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          */
    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        */
    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        */
    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        */
    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        */
    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        */
    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