| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.math.transform; |
| |
| import java.io.Serializable; |
| import java.lang.reflect.Array; |
| |
| import org.apache.commons.math.FunctionEvaluationException; |
| import org.apache.commons.math.MathRuntimeException; |
| import org.apache.commons.math.analysis.UnivariateRealFunction; |
| import org.apache.commons.math.complex.Complex; |
| import org.apache.commons.math.exception.util.LocalizedFormats; |
| import org.apache.commons.math.util.FastMath; |
| |
| /** |
| * Implements the <a href="http://mathworld.wolfram.com/FastFourierTransform.html"> |
| * Fast Fourier Transform</a> for transformation of one-dimensional data sets. |
| * For reference, see <b>Applied Numerical Linear Algebra</b>, ISBN 0898713897, |
| * chapter 6. |
| * <p> |
| * There are several conventions for the definition of FFT and inverse FFT, |
| * mainly on different coefficient and exponent. Here the equations are listed |
| * in the comments of the corresponding methods.</p> |
| * <p> |
| * We require the length of data set to be power of 2, this greatly simplifies |
| * and speeds up the code. Users can pad the data with zeros to meet this |
| * requirement. There are other flavors of FFT, for reference, see S. Winograd, |
| * <i>On computing the discrete Fourier transform</i>, Mathematics of Computation, |
| * 32 (1978), 175 - 199.</p> |
| * |
| * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $ |
| * @since 1.2 |
| */ |
| public class FastFourierTransformer implements Serializable { |
| |
| /** Serializable version identifier. */ |
| static final long serialVersionUID = 5138259215438106000L; |
| |
| /** roots of unity */ |
| private RootsOfUnity roots = new RootsOfUnity(); |
| |
| /** |
| * Construct a default transformer. |
| */ |
| public FastFourierTransformer() { |
| super(); |
| } |
| |
| /** |
| * Transform the given real data set. |
| * <p> |
| * The formula is $ y_n = \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k $ |
| * </p> |
| * |
| * @param f the real data array to be transformed |
| * @return the complex transformed array |
| * @throws IllegalArgumentException if any parameters are invalid |
| */ |
| public Complex[] transform(double f[]) |
| throws IllegalArgumentException { |
| return fft(f, false); |
| } |
| |
| /** |
| * Transform the given real function, sampled on the given interval. |
| * <p> |
| * The formula is $ y_n = \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k $ |
| * </p> |
| * |
| * @param f the function to be sampled and transformed |
| * @param min the lower bound for the interval |
| * @param max the upper bound for the interval |
| * @param n the number of sample points |
| * @return the complex transformed array |
| * @throws FunctionEvaluationException if function cannot be evaluated |
| * at some point |
| * @throws IllegalArgumentException if any parameters are invalid |
| */ |
| public Complex[] transform(UnivariateRealFunction f, |
| double min, double max, int n) |
| throws FunctionEvaluationException, IllegalArgumentException { |
| double data[] = sample(f, min, max, n); |
| return fft(data, false); |
| } |
| |
| /** |
| * Transform the given complex data set. |
| * <p> |
| * The formula is $ y_n = \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k $ |
| * </p> |
| * |
| * @param f the complex data array to be transformed |
| * @return the complex transformed array |
| * @throws IllegalArgumentException if any parameters are invalid |
| */ |
| public Complex[] transform(Complex f[]) |
| throws IllegalArgumentException { |
| roots.computeOmega(f.length); |
| return fft(f); |
| } |
| |
| /** |
| * Transform the given real data set. |
| * <p> |
| * The formula is $y_n = (1/\sqrt{N}) \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k$ |
| * </p> |
| * |
| * @param f the real data array to be transformed |
| * @return the complex transformed array |
| * @throws IllegalArgumentException if any parameters are invalid |
| */ |
| public Complex[] transform2(double f[]) |
| throws IllegalArgumentException { |
| |
| double scaling_coefficient = 1.0 / FastMath.sqrt(f.length); |
| return scaleArray(fft(f, false), scaling_coefficient); |
| } |
| |
| /** |
| * Transform the given real function, sampled on the given interval. |
| * <p> |
| * The formula is $y_n = (1/\sqrt{N}) \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k$ |
| * </p> |
| * |
| * @param f the function to be sampled and transformed |
| * @param min the lower bound for the interval |
| * @param max the upper bound for the interval |
| * @param n the number of sample points |
| * @return the complex transformed array |
| * @throws FunctionEvaluationException if function cannot be evaluated |
| * at some point |
| * @throws IllegalArgumentException if any parameters are invalid |
| */ |
| public Complex[] transform2(UnivariateRealFunction f, |
| double min, double max, int n) |
| throws FunctionEvaluationException, IllegalArgumentException { |
| |
| double data[] = sample(f, min, max, n); |
| double scaling_coefficient = 1.0 / FastMath.sqrt(n); |
| return scaleArray(fft(data, false), scaling_coefficient); |
| } |
| |
| /** |
| * Transform the given complex data set. |
| * <p> |
| * The formula is $y_n = (1/\sqrt{N}) \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k$ |
| * </p> |
| * |
| * @param f the complex data array to be transformed |
| * @return the complex transformed array |
| * @throws IllegalArgumentException if any parameters are invalid |
| */ |
| public Complex[] transform2(Complex f[]) |
| throws IllegalArgumentException { |
| |
| roots.computeOmega(f.length); |
| double scaling_coefficient = 1.0 / FastMath.sqrt(f.length); |
| return scaleArray(fft(f), scaling_coefficient); |
| } |
| |
| /** |
| * Inversely transform the given real data set. |
| * <p> |
| * The formula is $ x_k = (1/N) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n $ |
| * </p> |
| * |
| * @param f the real data array to be inversely transformed |
| * @return the complex inversely transformed array |
| * @throws IllegalArgumentException if any parameters are invalid |
| */ |
| public Complex[] inversetransform(double f[]) |
| throws IllegalArgumentException { |
| |
| double scaling_coefficient = 1.0 / f.length; |
| return scaleArray(fft(f, true), scaling_coefficient); |
| } |
| |
| /** |
| * Inversely transform the given real function, sampled on the given interval. |
| * <p> |
| * The formula is $ x_k = (1/N) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n $ |
| * </p> |
| * |
| * @param f the function to be sampled and inversely transformed |
| * @param min the lower bound for the interval |
| * @param max the upper bound for the interval |
| * @param n the number of sample points |
| * @return the complex inversely transformed array |
| * @throws FunctionEvaluationException if function cannot be evaluated |
| * at some point |
| * @throws IllegalArgumentException if any parameters are invalid |
| */ |
| public Complex[] inversetransform(UnivariateRealFunction f, |
| double min, double max, int n) |
| throws FunctionEvaluationException, IllegalArgumentException { |
| |
| double data[] = sample(f, min, max, n); |
| double scaling_coefficient = 1.0 / n; |
| return scaleArray(fft(data, true), scaling_coefficient); |
| } |
| |
| /** |
| * Inversely transform the given complex data set. |
| * <p> |
| * The formula is $ x_k = (1/N) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n $ |
| * </p> |
| * |
| * @param f the complex data array to be inversely transformed |
| * @return the complex inversely transformed array |
| * @throws IllegalArgumentException if any parameters are invalid |
| */ |
| public Complex[] inversetransform(Complex f[]) |
| throws IllegalArgumentException { |
| |
| roots.computeOmega(-f.length); // pass negative argument |
| double scaling_coefficient = 1.0 / f.length; |
| return scaleArray(fft(f), scaling_coefficient); |
| } |
| |
| /** |
| * Inversely transform the given real data set. |
| * <p> |
| * The formula is $x_k = (1/\sqrt{N}) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n$ |
| * </p> |
| * |
| * @param f the real data array to be inversely transformed |
| * @return the complex inversely transformed array |
| * @throws IllegalArgumentException if any parameters are invalid |
| */ |
| public Complex[] inversetransform2(double f[]) |
| throws IllegalArgumentException { |
| |
| double scaling_coefficient = 1.0 / FastMath.sqrt(f.length); |
| return scaleArray(fft(f, true), scaling_coefficient); |
| } |
| |
| /** |
| * Inversely transform the given real function, sampled on the given interval. |
| * <p> |
| * The formula is $x_k = (1/\sqrt{N}) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n$ |
| * </p> |
| * |
| * @param f the function to be sampled and inversely transformed |
| * @param min the lower bound for the interval |
| * @param max the upper bound for the interval |
| * @param n the number of sample points |
| * @return the complex inversely transformed array |
| * @throws FunctionEvaluationException if function cannot be evaluated |
| * at some point |
| * @throws IllegalArgumentException if any parameters are invalid |
| */ |
| public Complex[] inversetransform2(UnivariateRealFunction f, |
| double min, double max, int n) |
| throws FunctionEvaluationException, IllegalArgumentException { |
| |
| double data[] = sample(f, min, max, n); |
| double scaling_coefficient = 1.0 / FastMath.sqrt(n); |
| return scaleArray(fft(data, true), scaling_coefficient); |
| } |
| |
| /** |
| * Inversely transform the given complex data set. |
| * <p> |
| * The formula is $x_k = (1/\sqrt{N}) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n$ |
| * </p> |
| * |
| * @param f the complex data array to be inversely transformed |
| * @return the complex inversely transformed array |
| * @throws IllegalArgumentException if any parameters are invalid |
| */ |
| public Complex[] inversetransform2(Complex f[]) |
| throws IllegalArgumentException { |
| |
| roots.computeOmega(-f.length); // pass negative argument |
| double scaling_coefficient = 1.0 / FastMath.sqrt(f.length); |
| return scaleArray(fft(f), scaling_coefficient); |
| } |
| |
| /** |
| * Perform the base-4 Cooley-Tukey FFT algorithm (including inverse). |
| * |
| * @param f the real data array to be transformed |
| * @param isInverse the indicator of forward or inverse transform |
| * @return the complex transformed array |
| * @throws IllegalArgumentException if any parameters are invalid |
| */ |
| protected Complex[] fft(double f[], boolean isInverse) |
| throws IllegalArgumentException { |
| |
| verifyDataSet(f); |
| Complex F[] = new Complex[f.length]; |
| if (f.length == 1) { |
| F[0] = new Complex(f[0], 0.0); |
| return F; |
| } |
| |
| // Rather than the naive real to complex conversion, pack 2N |
| // real numbers into N complex numbers for better performance. |
| int N = f.length >> 1; |
| Complex c[] = new Complex[N]; |
| for (int i = 0; i < N; i++) { |
| c[i] = new Complex(f[2*i], f[2*i+1]); |
| } |
| roots.computeOmega(isInverse ? -N : N); |
| Complex z[] = fft(c); |
| |
| // reconstruct the FFT result for the original array |
| roots.computeOmega(isInverse ? -2*N : 2*N); |
| F[0] = new Complex(2 * (z[0].getReal() + z[0].getImaginary()), 0.0); |
| F[N] = new Complex(2 * (z[0].getReal() - z[0].getImaginary()), 0.0); |
| for (int i = 1; i < N; i++) { |
| Complex A = z[N-i].conjugate(); |
| Complex B = z[i].add(A); |
| Complex C = z[i].subtract(A); |
| //Complex D = roots.getOmega(i).multiply(Complex.I); |
| Complex D = new Complex(-roots.getOmegaImaginary(i), |
| roots.getOmegaReal(i)); |
| F[i] = B.subtract(C.multiply(D)); |
| F[2*N-i] = F[i].conjugate(); |
| } |
| |
| return scaleArray(F, 0.5); |
| } |
| |
| /** |
| * Perform the base-4 Cooley-Tukey FFT algorithm (including inverse). |
| * |
| * @param data the complex data array to be transformed |
| * @return the complex transformed array |
| * @throws IllegalArgumentException if any parameters are invalid |
| */ |
| protected Complex[] fft(Complex data[]) |
| throws IllegalArgumentException { |
| |
| final int n = data.length; |
| final Complex f[] = new Complex[n]; |
| |
| // initial simple cases |
| verifyDataSet(data); |
| if (n == 1) { |
| f[0] = data[0]; |
| return f; |
| } |
| if (n == 2) { |
| f[0] = data[0].add(data[1]); |
| f[1] = data[0].subtract(data[1]); |
| return f; |
| } |
| |
| // permute original data array in bit-reversal order |
| int ii = 0; |
| for (int i = 0; i < n; i++) { |
| f[i] = data[ii]; |
| int k = n >> 1; |
| while (ii >= k && k > 0) { |
| ii -= k; k >>= 1; |
| } |
| ii += k; |
| } |
| |
| // the bottom base-4 round |
| for (int i = 0; i < n; i += 4) { |
| final Complex a = f[i].add(f[i+1]); |
| final Complex b = f[i+2].add(f[i+3]); |
| final Complex c = f[i].subtract(f[i+1]); |
| final Complex d = f[i+2].subtract(f[i+3]); |
| final Complex e1 = c.add(d.multiply(Complex.I)); |
| final Complex e2 = c.subtract(d.multiply(Complex.I)); |
| f[i] = a.add(b); |
| f[i+2] = a.subtract(b); |
| // omegaCount indicates forward or inverse transform |
| f[i+1] = roots.isForward() ? e2 : e1; |
| f[i+3] = roots.isForward() ? e1 : e2; |
| } |
| |
| // iterations from bottom to top take O(N*logN) time |
| for (int i = 4; i < n; i <<= 1) { |
| final int m = n / (i<<1); |
| for (int j = 0; j < n; j += i<<1) { |
| for (int k = 0; k < i; k++) { |
| //z = f[i+j+k].multiply(roots.getOmega(k*m)); |
| final int k_times_m = k*m; |
| final double omega_k_times_m_real = roots.getOmegaReal(k_times_m); |
| final double omega_k_times_m_imaginary = roots.getOmegaImaginary(k_times_m); |
| //z = f[i+j+k].multiply(omega[k*m]); |
| final Complex z = new Complex( |
| f[i+j+k].getReal() * omega_k_times_m_real - |
| f[i+j+k].getImaginary() * omega_k_times_m_imaginary, |
| f[i+j+k].getReal() * omega_k_times_m_imaginary + |
| f[i+j+k].getImaginary() * omega_k_times_m_real); |
| |
| f[i+j+k] = f[j+k].subtract(z); |
| f[j+k] = f[j+k].add(z); |
| } |
| } |
| } |
| return f; |
| } |
| |
| /** |
| * Sample the given univariate real function on the given interval. |
| * <p> |
| * The interval is divided equally into N sections and sample points |
| * are taken from min to max-(max-min)/N. Usually f(x) is periodic |
| * such that f(min) = f(max) (note max is not sampled), but we don't |
| * require that.</p> |
| * |
| * @param f the function to be sampled |
| * @param min the lower bound for the interval |
| * @param max the upper bound for the interval |
| * @param n the number of sample points |
| * @return the samples array |
| * @throws FunctionEvaluationException if function cannot be evaluated at some point |
| * @throws IllegalArgumentException if any parameters are invalid |
| */ |
| public static double[] sample(UnivariateRealFunction f, double min, double max, int n) |
| throws FunctionEvaluationException, IllegalArgumentException { |
| |
| if (n <= 0) { |
| throw MathRuntimeException.createIllegalArgumentException( |
| LocalizedFormats.NOT_POSITIVE_NUMBER_OF_SAMPLES, |
| n); |
| } |
| verifyInterval(min, max); |
| |
| double s[] = new double[n]; |
| double h = (max - min) / n; |
| for (int i = 0; i < n; i++) { |
| s[i] = f.value(min + i * h); |
| } |
| return s; |
| } |
| |
| /** |
| * Multiply every component in the given real array by the |
| * given real number. The change is made in place. |
| * |
| * @param f the real array to be scaled |
| * @param d the real scaling coefficient |
| * @return a reference to the scaled array |
| */ |
| public static double[] scaleArray(double f[], double d) { |
| for (int i = 0; i < f.length; i++) { |
| f[i] *= d; |
| } |
| return f; |
| } |
| |
| /** |
| * Multiply every component in the given complex array by the |
| * given real number. The change is made in place. |
| * |
| * @param f the complex array to be scaled |
| * @param d the real scaling coefficient |
| * @return a reference to the scaled array |
| */ |
| public static Complex[] scaleArray(Complex f[], double d) { |
| for (int i = 0; i < f.length; i++) { |
| f[i] = new Complex(d * f[i].getReal(), d * f[i].getImaginary()); |
| } |
| return f; |
| } |
| |
| /** |
| * Returns true if the argument is power of 2. |
| * |
| * @param n the number to test |
| * @return true if the argument is power of 2 |
| */ |
| public static boolean isPowerOf2(long n) { |
| return (n > 0) && ((n & (n - 1)) == 0); |
| } |
| |
| /** |
| * Verifies that the data set has length of power of 2. |
| * |
| * @param d the data array |
| * @throws IllegalArgumentException if array length is not power of 2 |
| */ |
| public static void verifyDataSet(double d[]) throws IllegalArgumentException { |
| if (!isPowerOf2(d.length)) { |
| throw MathRuntimeException.createIllegalArgumentException( |
| LocalizedFormats.NOT_POWER_OF_TWO_CONSIDER_PADDING, d.length); |
| } |
| } |
| |
| /** |
| * Verifies that the data set has length of power of 2. |
| * |
| * @param o the data array |
| * @throws IllegalArgumentException if array length is not power of 2 |
| */ |
| public static void verifyDataSet(Object o[]) throws IllegalArgumentException { |
| if (!isPowerOf2(o.length)) { |
| throw MathRuntimeException.createIllegalArgumentException( |
| LocalizedFormats.NOT_POWER_OF_TWO_CONSIDER_PADDING, o.length); |
| } |
| } |
| |
| /** |
| * Verifies that the endpoints specify an interval. |
| * |
| * @param lower lower endpoint |
| * @param upper upper endpoint |
| * @throws IllegalArgumentException if not interval |
| */ |
| public static void verifyInterval(double lower, double upper) |
| throws IllegalArgumentException { |
| |
| if (lower >= upper) { |
| throw MathRuntimeException.createIllegalArgumentException( |
| LocalizedFormats.ENDPOINTS_NOT_AN_INTERVAL, |
| lower, upper); |
| } |
| } |
| |
| /** |
| * Performs a multi-dimensional Fourier transform on a given array. |
| * Use {@link #inversetransform2(Complex[])} and |
| * {@link #transform2(Complex[])} in a row-column implementation |
| * in any number of dimensions with O(N×log(N)) complexity with |
| * N=n<sub>1</sub>×n<sub>2</sub>×n<sub>3</sub>×...×n<sub>d</sub>, |
| * n<sub>x</sub>=number of elements in dimension x, |
| * and d=total number of dimensions. |
| * |
| * @param mdca Multi-Dimensional Complex Array id est Complex[][][][] |
| * @param forward inverseTransform2 is preformed if this is false |
| * @return transform of mdca as a Multi-Dimensional Complex Array id est Complex[][][][] |
| * @throws IllegalArgumentException if any dimension is not a power of two |
| */ |
| public Object mdfft(Object mdca, boolean forward) |
| throws IllegalArgumentException { |
| MultiDimensionalComplexMatrix mdcm = (MultiDimensionalComplexMatrix) |
| new MultiDimensionalComplexMatrix(mdca).clone(); |
| int[] dimensionSize = mdcm.getDimensionSizes(); |
| //cycle through each dimension |
| for (int i = 0; i < dimensionSize.length; i++) { |
| mdfft(mdcm, forward, i, new int[0]); |
| } |
| return mdcm.getArray(); |
| } |
| |
| /** |
| * Performs one dimension of a multi-dimensional Fourier transform. |
| * |
| * @param mdcm input matrix |
| * @param forward inverseTransform2 is preformed if this is false |
| * @param d index of the dimension to process |
| * @param subVector recursion subvector |
| * @throws IllegalArgumentException if any dimension is not a power of two |
| */ |
| private void mdfft(MultiDimensionalComplexMatrix mdcm, boolean forward, |
| int d, int[] subVector) |
| throws IllegalArgumentException { |
| int[] dimensionSize = mdcm.getDimensionSizes(); |
| //if done |
| if (subVector.length == dimensionSize.length) { |
| Complex[] temp = new Complex[dimensionSize[d]]; |
| for (int i = 0; i < dimensionSize[d]; i++) { |
| //fft along dimension d |
| subVector[d] = i; |
| temp[i] = mdcm.get(subVector); |
| } |
| |
| if (forward) |
| temp = transform2(temp); |
| else |
| temp = inversetransform2(temp); |
| |
| for (int i = 0; i < dimensionSize[d]; i++) { |
| subVector[d] = i; |
| mdcm.set(temp[i], subVector); |
| } |
| } else { |
| int[] vector = new int[subVector.length + 1]; |
| System.arraycopy(subVector, 0, vector, 0, subVector.length); |
| if (subVector.length == d) { |
| //value is not important once the recursion is done. |
| //then an fft will be applied along the dimension d. |
| vector[d] = 0; |
| mdfft(mdcm, forward, d, vector); |
| } else { |
| for (int i = 0; i < dimensionSize[subVector.length]; i++) { |
| vector[subVector.length] = i; |
| //further split along the next dimension |
| mdfft(mdcm, forward, d, vector); |
| } |
| } |
| } |
| return; |
| } |
| |
| /** |
| * Complex matrix implementation. |
| * Not designed for synchronized access |
| * may eventually be replaced by jsr-83 of the java community process |
| * http://jcp.org/en/jsr/detail?id=83 |
| * may require additional exception throws for other basic requirements. |
| */ |
| private static class MultiDimensionalComplexMatrix |
| implements Cloneable { |
| |
| /** Size in all dimensions. */ |
| protected int[] dimensionSize; |
| |
| /** Storage array. */ |
| protected Object multiDimensionalComplexArray; |
| |
| /** Simple constructor. |
| * @param multiDimensionalComplexArray array containing the matrix elements |
| */ |
| public MultiDimensionalComplexMatrix(Object multiDimensionalComplexArray) { |
| |
| this.multiDimensionalComplexArray = multiDimensionalComplexArray; |
| |
| // count dimensions |
| int numOfDimensions = 0; |
| for (Object lastDimension = multiDimensionalComplexArray; |
| lastDimension instanceof Object[];) { |
| final Object[] array = (Object[]) lastDimension; |
| numOfDimensions++; |
| lastDimension = array[0]; |
| } |
| |
| // allocate array with exact count |
| dimensionSize = new int[numOfDimensions]; |
| |
| // fill array |
| numOfDimensions = 0; |
| for (Object lastDimension = multiDimensionalComplexArray; |
| lastDimension instanceof Object[];) { |
| final Object[] array = (Object[]) lastDimension; |
| dimensionSize[numOfDimensions++] = array.length; |
| lastDimension = array[0]; |
| } |
| |
| } |
| |
| /** |
| * Get a matrix element. |
| * @param vector indices of the element |
| * @return matrix element |
| * @exception IllegalArgumentException if dimensions do not match |
| */ |
| public Complex get(int... vector) |
| throws IllegalArgumentException { |
| if (vector == null) { |
| if (dimensionSize.length > 0) { |
| throw MathRuntimeException.createIllegalArgumentException( |
| LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, 0, dimensionSize.length); |
| } |
| return null; |
| } |
| if (vector.length != dimensionSize.length) { |
| throw MathRuntimeException.createIllegalArgumentException( |
| LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, vector.length, dimensionSize.length); |
| } |
| |
| Object lastDimension = multiDimensionalComplexArray; |
| |
| for (int i = 0; i < dimensionSize.length; i++) { |
| lastDimension = ((Object[]) lastDimension)[vector[i]]; |
| } |
| return (Complex) lastDimension; |
| } |
| |
| /** |
| * Set a matrix element. |
| * @param magnitude magnitude of the element |
| * @param vector indices of the element |
| * @return the previous value |
| * @exception IllegalArgumentException if dimensions do not match |
| */ |
| public Complex set(Complex magnitude, int... vector) |
| throws IllegalArgumentException { |
| if (vector == null) { |
| if (dimensionSize.length > 0) { |
| throw MathRuntimeException.createIllegalArgumentException( |
| LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, 0, dimensionSize.length); |
| } |
| return null; |
| } |
| if (vector.length != dimensionSize.length) { |
| throw MathRuntimeException.createIllegalArgumentException( |
| LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, vector.length,dimensionSize.length); |
| } |
| |
| Object[] lastDimension = (Object[]) multiDimensionalComplexArray; |
| for (int i = 0; i < dimensionSize.length - 1; i++) { |
| lastDimension = (Object[]) lastDimension[vector[i]]; |
| } |
| |
| Complex lastValue = (Complex) lastDimension[vector[dimensionSize.length - 1]]; |
| lastDimension[vector[dimensionSize.length - 1]] = magnitude; |
| |
| return lastValue; |
| } |
| |
| /** |
| * Get the size in all dimensions. |
| * @return size in all dimensions |
| */ |
| public int[] getDimensionSizes() { |
| return dimensionSize.clone(); |
| } |
| |
| /** |
| * Get the underlying storage array |
| * @return underlying storage array |
| */ |
| public Object getArray() { |
| return multiDimensionalComplexArray; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public Object clone() { |
| MultiDimensionalComplexMatrix mdcm = |
| new MultiDimensionalComplexMatrix(Array.newInstance( |
| Complex.class, dimensionSize)); |
| clone(mdcm); |
| return mdcm; |
| } |
| |
| /** |
| * Copy contents of current array into mdcm. |
| * @param mdcm array where to copy data |
| */ |
| private void clone(MultiDimensionalComplexMatrix mdcm) { |
| int[] vector = new int[dimensionSize.length]; |
| int size = 1; |
| for (int i = 0; i < dimensionSize.length; i++) { |
| size *= dimensionSize[i]; |
| } |
| int[][] vectorList = new int[size][dimensionSize.length]; |
| for (int[] nextVector: vectorList) { |
| System.arraycopy(vector, 0, nextVector, 0, |
| dimensionSize.length); |
| for (int i = 0; i < dimensionSize.length; i++) { |
| vector[i]++; |
| if (vector[i] < dimensionSize[i]) { |
| break; |
| } else { |
| vector[i] = 0; |
| } |
| } |
| } |
| |
| for (int[] nextVector: vectorList) { |
| mdcm.set(get(nextVector), nextVector); |
| } |
| } |
| } |
| |
| |
| /** Computes the n<sup>th</sup> roots of unity. |
| * A cache of already computed values is maintained. |
| */ |
| private static class RootsOfUnity implements Serializable { |
| |
| /** Serializable version id. */ |
| private static final long serialVersionUID = 6404784357747329667L; |
| |
| /** Number of roots of unity. */ |
| private int omegaCount; |
| |
| /** Real part of the roots. */ |
| private double[] omegaReal; |
| |
| /** Imaginary part of the roots for forward transform. */ |
| private double[] omegaImaginaryForward; |
| |
| /** Imaginary part of the roots for reverse transform. */ |
| private double[] omegaImaginaryInverse; |
| |
| /** Forward/reverse indicator. */ |
| private boolean isForward; |
| |
| /** |
| * Build an engine for computing then <sup>th</sup> roots of unity |
| */ |
| public RootsOfUnity() { |
| |
| omegaCount = 0; |
| omegaReal = null; |
| omegaImaginaryForward = null; |
| omegaImaginaryInverse = null; |
| isForward = true; |
| |
| } |
| |
| /** |
| * Check if computation has been done for forward or reverse transform. |
| * @return true if computation has been done for forward transform |
| * @throws IllegalStateException if no roots of unity have been computed yet |
| */ |
| public synchronized boolean isForward() throws IllegalStateException { |
| |
| if (omegaCount == 0) { |
| throw MathRuntimeException.createIllegalStateException(LocalizedFormats.ROOTS_OF_UNITY_NOT_COMPUTED_YET); |
| } |
| return isForward; |
| |
| } |
| |
| /** Computes the n<sup>th</sup> roots of unity. |
| * <p>The computed omega[] = { 1, w, w<sup>2</sup>, ... w<sup>(n-1)</sup> } where |
| * w = exp(-2 π i / n), i = &sqrt;(-1).</p> |
| * <p>Note that n is positive for |
| * forward transform and negative for inverse transform.</p> |
| * @param n number of roots of unity to compute, |
| * positive for forward transform, negative for inverse transform |
| * @throws IllegalArgumentException if n = 0 |
| */ |
| public synchronized void computeOmega(int n) throws IllegalArgumentException { |
| |
| if (n == 0) { |
| throw MathRuntimeException.createIllegalArgumentException( |
| LocalizedFormats.CANNOT_COMPUTE_0TH_ROOT_OF_UNITY); |
| } |
| |
| isForward = n > 0; |
| |
| // avoid repetitive calculations |
| final int absN = FastMath.abs(n); |
| |
| if (absN == omegaCount) { |
| return; |
| } |
| |
| // calculate everything from scratch, for both forward and inverse versions |
| final double t = 2.0 * FastMath.PI / absN; |
| final double cosT = FastMath.cos(t); |
| final double sinT = FastMath.sin(t); |
| omegaReal = new double[absN]; |
| omegaImaginaryForward = new double[absN]; |
| omegaImaginaryInverse = new double[absN]; |
| omegaReal[0] = 1.0; |
| omegaImaginaryForward[0] = 0.0; |
| omegaImaginaryInverse[0] = 0.0; |
| for (int i = 1; i < absN; i++) { |
| omegaReal[i] = |
| omegaReal[i-1] * cosT + omegaImaginaryForward[i-1] * sinT; |
| omegaImaginaryForward[i] = |
| omegaImaginaryForward[i-1] * cosT - omegaReal[i-1] * sinT; |
| omegaImaginaryInverse[i] = -omegaImaginaryForward[i]; |
| } |
| omegaCount = absN; |
| |
| } |
| |
| /** |
| * Get the real part of the k<sup>th</sup> n<sup>th</sup> root of unity |
| * @param k index of the n<sup>th</sup> root of unity |
| * @return real part of the k<sup>th</sup> n<sup>th</sup> root of unity |
| * @throws IllegalStateException if no roots of unity have been computed yet |
| * @throws IllegalArgumentException if k is out of range |
| */ |
| public synchronized double getOmegaReal(int k) |
| throws IllegalStateException, IllegalArgumentException { |
| |
| if (omegaCount == 0) { |
| throw MathRuntimeException.createIllegalStateException(LocalizedFormats.ROOTS_OF_UNITY_NOT_COMPUTED_YET); |
| } |
| if ((k < 0) || (k >= omegaCount)) { |
| throw MathRuntimeException.createIllegalArgumentException( |
| LocalizedFormats.OUT_OF_RANGE_ROOT_OF_UNITY_INDEX, k, 0, omegaCount - 1); |
| } |
| |
| return omegaReal[k]; |
| |
| } |
| |
| /** |
| * Get the imaginary part of the k<sup>th</sup> n<sup>th</sup> root of unity |
| * @param k index of the n<sup>th</sup> root of unity |
| * @return imaginary part of the k<sup>th</sup> n<sup>th</sup> root of unity |
| * @throws IllegalStateException if no roots of unity have been computed yet |
| * @throws IllegalArgumentException if k is out of range |
| */ |
| public synchronized double getOmegaImaginary(int k) |
| throws IllegalStateException, IllegalArgumentException { |
| |
| if (omegaCount == 0) { |
| throw MathRuntimeException.createIllegalStateException(LocalizedFormats.ROOTS_OF_UNITY_NOT_COMPUTED_YET); |
| } |
| if ((k < 0) || (k >= omegaCount)) { |
| throw MathRuntimeException.createIllegalArgumentException( |
| LocalizedFormats.OUT_OF_RANGE_ROOT_OF_UNITY_INDEX, k, 0, omegaCount - 1); |
| } |
| |
| return isForward ? omegaImaginaryForward[k] : omegaImaginaryInverse[k]; |
| |
| } |
| |
| } |
| |
| } |