All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ojalgo.data.transform.DiscreteFourierTransform Maven / Gradle / Ivy

Go to download

oj! Algorithms - ojAlgo - is Open Source Java code that has to do with mathematics, linear algebra and optimisation.

There is a newer version: 55.1.0
Show newest version
/*
 * Copyright 1997-2025 Optimatika
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.ojalgo.data.transform;

import java.util.function.DoubleUnaryOperator;

import org.ojalgo.array.ArrayR064;
import org.ojalgo.function.PrimitiveFunction;
import org.ojalgo.function.constant.ComplexMath;
import org.ojalgo.function.constant.PrimitiveMath;
import org.ojalgo.function.series.PeriodicFunction;
import org.ojalgo.function.special.PowerOf2;
import org.ojalgo.matrix.MatrixC128;
import org.ojalgo.matrix.MatrixC128.DenseReceiver;
import org.ojalgo.matrix.store.GenericStore;
import org.ojalgo.matrix.store.MatrixStore;
import org.ojalgo.matrix.store.PhysicalStore;
import org.ojalgo.matrix.store.TransformableRegion;
import org.ojalgo.scalar.ComplexNumber;
import org.ojalgo.structure.Access1D;
import org.ojalgo.structure.Access2D.ColumnView;
import org.ojalgo.structure.Access2D.RowView;
import org.ojalgo.structure.Factory2D;
import org.ojalgo.structure.Mutate2D;
import org.ojalgo.structure.Structure1D;

/**
 * The discrete Fourier transform (DFT) converts a finite sequence of equally-spaced samples of a function
 * into a same-length sequence of equally-spaced samples of the discrete-time Fourier transform (DTFT), which
 * is a complex-valued function of frequency. The interval at which the DTFT is sampled is the reciprocal of
 * the duration of the input sequence. An inverse DFT is a Fourier series, using the DTFT samples as
 * coefficients of complex sinusoids at the corresponding DTFT frequencies. The DFT is therefore said to be a
 * frequency domain representation of the original input sequence.
 * 

* The fast Fourier transform (FFT) is an algorithm for computing the DFT; it achieves its high speed by * storing and reusing results of computations as it progresses. *

* Calling the factory method {@linkplain #newInstance(int)} will return an FFT implementation if the size is * a power of 2, and a full matrix implementation otherwise. */ public abstract class DiscreteFourierTransform implements DataTransform, MatrixStore> { public static final class Directive { /** * Assume input complex (if false, will assume NOT complex and run simpler code). */ public final boolean complex; /** * Conjugate input before transforming and the output after (before returning). This is how the * inverse transform is performed. */ public final boolean conjugate; /** * Scale the output by the number of elements. Either the transform or the inverse transform needs to * be scaled. Usually it's done on the inverse. */ public final boolean scale; public Directive(final boolean complex, final boolean conjugate, final boolean scale) { super(); this.complex = complex; this.conjugate = conjugate; this.scale = scale; } Directive withComplex(final boolean complex) { return new Directive(complex, conjugate, scale); } } static final class FFT extends DiscreteFourierTransform { /** * Perform the remaining stage calculations (stage>=1). This is essentially "the algorithm". */ private static void doStages(final int nbStages, final ComplexNumber[] roots, final double[] workRe, final double[] workIm) { int size = roots.length; int nbHalf = 2; // Half the number of lanes per set int nbLanesPerSet = 4; int nbSets = size / 4; int index1, index2; for (int stage = 2; stage < nbStages; stage++) { nbHalf = nbLanesPerSet; nbLanesPerSet *= 2; nbSets /= 2; for (int set = 0; set < nbSets; set++) { index1 = set * nbLanesPerSet; index2 = index1 + nbHalf; FFT.update(workRe, workIm, index1, index2); for (int lane = 1; lane < nbHalf; lane++) { index1++; index2++; FFT.update(workRe, workIm, index1, index2, roots[lane * nbSets]); } } } } private static void setup0(final Access1D input, final boolean complex, final boolean conjugate, final double[] workRe, final double[] workIm) { if (complex) { ComplexNumber value = ComplexNumber.valueOf(input.get(0)); if (conjugate) { workRe[0] = value.doubleValue(); workIm[0] = -value.i; } else { workRe[0] = value.doubleValue(); workIm[0] = value.i; } } else { workRe[0] = input.doubleValue(0); workIm[0] = PrimitiveMath.ZERO; } } private static void setup0(final double[] input, final double[] workRe, final double[] workIm) { workRe[0] = input[0]; workIm[0] = PrimitiveMath.ZERO; } private static void setup1(final Access1D input, final boolean complex, final boolean conjugate, final double[] workRe, final double[] workIm) { if (complex) { ComplexNumber value1 = ComplexNumber.valueOf(input.get(0)); ComplexNumber value2 = ComplexNumber.valueOf(input.get(1)); FFT.toWork(0, 1, value1.doubleValue(), conjugate ? -value1.i : value1.i, value2.doubleValue(), conjugate ? -value2.i : value2.i, workRe, workIm); } else { double re1 = input.doubleValue(0); double re2 = input.doubleValue(1); workRe[0] = re1 + re2; workRe[1] = re1 - re2; workIm[0] = PrimitiveMath.ZERO; workIm[1] = PrimitiveMath.ZERO; } } private static void setup1(final double[] input, final double[] workRe, final double[] workIm) { double re1 = input[0]; double re2 = input[1]; workRe[0] = re1 + re2; workRe[1] = re1 - re2; workIm[0] = PrimitiveMath.ZERO; workIm[1] = PrimitiveMath.ZERO; } private static void setup2(final Access1D input, final boolean complex, final boolean conjugate, final int[] reversed, final double[] workRe, final double[] workIm) { double re1, re2, re3, re4; double re01, re02, re03, re04; if (complex) { double im1, im2, im3, im4; double im01, im02, im03, im04; for (int index1 = 0, index2, index3, index4, size = reversed.length; index1 < size; index1 += 4) { index2 = index1 + 1; index3 = index1 + 2; index4 = index1 + 3; ComplexNumber value1 = ComplexNumber.valueOf(input.get(reversed[index1])); ComplexNumber value2 = ComplexNumber.valueOf(input.get(reversed[index2])); ComplexNumber value3 = ComplexNumber.valueOf(input.get(reversed[index3])); ComplexNumber value4 = ComplexNumber.valueOf(input.get(reversed[index4])); if (conjugate) { re1 = value1.doubleValue(); im1 = -value1.i; re2 = value2.doubleValue(); im2 = -value2.i; re3 = value3.doubleValue(); im3 = -value3.i; re4 = value4.doubleValue(); im4 = -value4.i; } else { re1 = value1.doubleValue(); im1 = value1.i; re2 = value2.doubleValue(); im2 = value2.i; re3 = value3.doubleValue(); im3 = value3.i; re4 = value4.doubleValue(); im4 = value4.i; } re01 = re1 + re2; im01 = im1 + im2; re02 = re1 - re2; im02 = im1 - im2; re03 = re3 + re4; im03 = im3 + im4; re04 = re3 - re4; im04 = im3 - im4; workRe[index1] = re01 + re03; workRe[index2] = re02 + im04; workRe[index3] = re01 - re03; workRe[index4] = re02 - im04; workIm[index1] = im01 + im03; workIm[index2] = im02 - re04; workIm[index3] = im01 - im03; workIm[index4] = im02 + re04; } } else { for (int index1 = 0, index2, index3, index4, size = reversed.length; index1 < size; index1 += 4) { index2 = index1 + 1; index3 = index1 + 2; index4 = index1 + 3; re1 = input.doubleValue(reversed[index1]); re2 = input.doubleValue(reversed[index2]); re3 = input.doubleValue(reversed[index3]); re4 = input.doubleValue(reversed[index4]); re01 = re1 + re2; re02 = re1 - re2; re03 = re3 + re4; re04 = re3 - re4; workRe[index1] = re01 + re03; workRe[index2] = re02; workRe[index3] = re01 - re03; workRe[index4] = re02; workIm[index1] = PrimitiveMath.ZERO; workIm[index2] = -re04; workIm[index3] = PrimitiveMath.ZERO; workIm[index4] = re04; } } } private static void setup2(final double[] input, final int[] reversed, final double[] workRe, final double[] workIm) { for (int index1 = 0, index2, index3, index4, size = reversed.length; index1 < size; index1 += 4) { index2 = index1 + 1; index3 = index1 + 2; index4 = index1 + 3; double re1 = input[reversed[index1]]; double re2 = input[reversed[index2]]; double re3 = input[reversed[index3]]; double re4 = input[reversed[index4]]; double re01 = re1 + re2; double re02 = re1 - re2; double re03 = re3 + re4; double re04 = re3 - re4; workRe[index1] = re01 + re03; workRe[index2] = re02; workRe[index3] = re01 - re03; workRe[index4] = re02; workIm[index1] = PrimitiveMath.ZERO; workIm[index2] = -re04; workIm[index3] = PrimitiveMath.ZERO; workIm[index4] = re04; } } /** * Copy the results to the output data structure. In the copy-process transformations are performed. */ private static void toOutput(final double[] workRe, final double[] workIm, final boolean conjugate, final boolean scale, final Mutate2D.ModifiableReceiver output) { int size = workRe.length; if (conjugate) { if (scale) { double divisor = size; for (int i = 0; i < size; i++) { output.set(i, ComplexNumber.of(workRe[i] / divisor, -workIm[i] / divisor)); } } else { for (int i = 0; i < size; i++) { output.set(i, ComplexNumber.of(workRe[i], -workIm[i])); } } } else { if (scale) { double divisor = size; for (int i = 0; i < size; i++) { output.set(i, ComplexNumber.of(workRe[i] / divisor, workIm[i] / divisor)); } } else { for (int i = 0; i < size; i++) { output.set(i, ComplexNumber.of(workRe[i], workIm[i])); } } } } private static void toWork(final int index1, final int index2, final double re1, final double im1, final double re2, final double im2, final double[] workRe, final double[] workIm) { workRe[index1] = re1 + re2; workRe[index2] = re1 - re2; workIm[index1] = im1 + im2; workIm[index2] = im1 - im2; } private static void update(final double[] workRe, final double[] workIm, final int index1, final int index2) { FFT.toWork(index1, index2, workRe[index1], workIm[index1], workRe[index2], workIm[index2], workRe, workIm); } private static void update(final double[] workRe, final double[] workIm, final int index1, final int index2, final ComplexNumber scalar) { double re2 = workRe[index2]; double im2 = workIm[index2]; FFT.toWork(index1, index2, workRe[index1], workIm[index1], scalar.multiplyRe(re2, im2), scalar.multiplyIm(re2, im2), workRe, workIm); } private final int[] myBitReversedIndices; private final int myStages; private final ComplexNumber[] myUnitRoots; private final double[] myWorkIm; private final double[] myWorkRe; FFT(final int size) { super(size); myStages = DiscreteFourierTransform.toPowerOf2Exponent(size); if (myStages < 0) { throw new IllegalArgumentException(); } myBitReversedIndices = DiscreteFourierTransform.lookupIndices(size); myUnitRoots = DiscreteFourierTransform.lookupRoots(size); myWorkRe = new double[size]; myWorkIm = new double[size]; } @Override public void transform(final Access1D input, final Directive directive, final Mutate2D.ModifiableReceiver output) { if (myStages == 0) { FFT.setup0(input, directive.complex, directive.conjugate, myWorkRe, myWorkIm); } else if (myStages == 1) { FFT.setup1(input, directive.complex, directive.conjugate, myWorkRe, myWorkIm); } else if (myStages == 2) { FFT.setup2(input, directive.complex, directive.conjugate, myBitReversedIndices, myWorkRe, myWorkIm); } else { FFT.setup2(input, directive.complex, directive.conjugate, myBitReversedIndices, myWorkRe, myWorkIm); FFT.doStages(myStages, myUnitRoots, myWorkRe, myWorkIm); } FFT.toOutput(myWorkRe, myWorkIm, directive.conjugate, directive.scale, output); } @Override public MatrixStore transform(final double... input) { if (myStages == 0) { FFT.setup0(input, myWorkRe, myWorkIm); } else if (myStages == 1) { FFT.setup1(input, myWorkRe, myWorkIm); } else if (myStages == 2) { FFT.setup2(input, myBitReversedIndices, myWorkRe, myWorkIm); } else { FFT.setup2(input, myBitReversedIndices, myWorkRe, myWorkIm); FFT.doStages(myStages, myUnitRoots, myWorkRe, myWorkIm); } PhysicalStore output = GenericStore.C128.make(input.length, 1); FFT.toOutput(myWorkRe, myWorkIm, DEFAULT.conjugate, DEFAULT.scale, output); return output; } } static final class FullMatrix extends DiscreteFourierTransform { private final ComplexNumber myDivisor; private final PhysicalStore myVandermondeMatrix; FullMatrix(final int size) { super(size); myVandermondeMatrix = GenericStore.C128.make(size, size); DiscreteFourierTransform.generate(myVandermondeMatrix); myDivisor = ComplexNumber.valueOf(size); } @Override public void transform(final Access1D input, final Directive directive, final Mutate2D.ModifiableReceiver output) { MatrixStore column = GenericStore.C128.makeWrapperColumn(input); if (directive.conjugate) { column = column.onAll(ComplexMath.CONJUGATE); } myVandermondeMatrix.multiply(column, TransformableRegion.cast(output)); if (directive.conjugate) { output.modifyAll(ComplexMath.CONJUGATE); } if (directive.scale) { output.modifyAll(ComplexMath.DIVIDE.by(myDivisor)); } } } static final class Single extends DiscreteFourierTransform { Single() { super(1); } @Override public void transform(final Access1D input, final Directive directive, final Mutate2D.ModifiableReceiver output) { output.fillMatching(input); } } private static final int[][] BIT_REVERSED_INDICES = new int[31][]; private static final ComplexNumber[][] UNIT_ROOTS = new ComplexNumber[31][]; static final Directive DEFAULT = new Directive(false, false, false); static final Directive INVERSE = new Directive(true, true, true); public static int[] getBitReversedIndices(final int size) { return DiscreteFourierTransform.lookupIndices(size).clone(); } public static Access1D getUnitRoots(final int size) { return Access1D.wrap(DiscreteFourierTransform.lookupRoots(size)); } public static MatrixStore inverse2D(final MatrixStore input) { PhysicalStore retVal = GenericStore.C128.make(input.getRowDim(), input.getColDim()); DiscreteFourierTransform.transform2D(input, INVERSE, retVal); return retVal; } /** * Will return a functioning instance for any size, but if you want fast transformation (FFT) it needs to * be a power of 2. */ public static DiscreteFourierTransform newInstance(final int size) { if (size < 1) { throw new IllegalArgumentException(); } else if (size == 1) { return new Single(); } else if (PowerOf2.isPowerOf2(size)) { return new FFT(size); } else { return new FullMatrix(size); } } public static M newVandermonde(final Factory2D factory, final int size) { M matrix = factory.make(size, size); DiscreteFourierTransform.generate(matrix); return matrix; } public static MatrixC128 newVandermondeMatrix(final int size) { DenseReceiver receiver = MatrixC128.FACTORY.newDenseBuilder(size, size); DiscreteFourierTransform.generate(receiver); return receiver.get(); } /** * Sample, and transform, a function using the Discrete Fourier Transform. */ public static MatrixStore sample(final DoubleUnaryOperator function, final PrimitiveFunction.SampleDomain sampleDomain) { double[] input = sampleDomain.arguments(); for (int i = 0; i < input.length; i++) { input[i] = function.applyAsDouble(input[i]); } return DiscreteFourierTransform.newInstance(input.length).transform(input); } /** * @see #sample(DoubleUnaryOperator, PrimitiveFunction.SampleDomain) */ public static MatrixStore sample(final PeriodicFunction function, final int nbSamples) { return DiscreteFourierTransform.sample(function, function.getSampleDomain(nbSamples)); } /** * There is a symmetry in the DFT matrix. The first half of the rows (and columns) are the complex * conjugates of the second half. Furthermore, the first half correspond to positive frequencies, and the * second half to negative frequencies. *

* Re-arranging the elements of a matrix, shifting the first and second halves of the rows (and columns), * puts the zero-frequency term in the middle, and the conjugate pairs at equal distances from the centre. *

* This is useful when displaying the 2D DFT matrix as an image. *

* To revert the shift, simply call {@linkplain #shift(MatrixStore)} again. However, this will not work * correctly if there's an odd number of rows or columns. In that case the second call will not correctly * revert the position of the zero-frequency term – it will end up in the last row/column instead of in * the first. */ public static > MatrixStore shift(final MatrixStore matrix) { MatrixStore retVal = matrix; int nbRows = matrix.getRowDim(); int nbCols = matrix.getColDim(); if (nbRows > 1) { int half = (nbRows + 1) / 2; MatrixStore first = retVal.limits(half, -1); MatrixStore second = retVal.offsets(half, 0); retVal = second.below(first); } if (nbCols > 1) { int half = (nbCols + 1) / 2; MatrixStore first = retVal.limits(-1, half); MatrixStore second = retVal.offsets(0, half); retVal = second.right(first); } return retVal; } /** * Perform a 2D Discrete Fourier Transform on the input matrix. The output will be a complex matrix of the * same size. */ public static MatrixStore transform2D(final MatrixStore input) { PhysicalStore retVal = GenericStore.C128.make(input.getRowDim(), input.getColDim()); DiscreteFourierTransform.transform2D(input, DEFAULT, retVal); return retVal; } public static void transform2D(final MatrixStore input, final Directive directive, final TransformableRegion output) { int nbRows = input.getRowDim(); int nbCols = input.getColDim(); DiscreteFourierTransform transformer = DiscreteFourierTransform.newInstance(nbRows); PhysicalStore workOutput = GenericStore.C128.make(nbRows, 1); Directive directive1 = directive; for (ColumnView view : input.columns()) { transformer.transform(view, directive1, workOutput); output.fillColumn(view.column(), workOutput); } if (nbRows != nbCols) { transformer = DiscreteFourierTransform.newInstance(nbCols); workOutput = GenericStore.C128.make(nbCols, 1); } Directive directive2 = directive.withComplex(true); for (RowView view : output.rows()) { transformer.transform(view, directive2, workOutput); output.fillRow(view.row(), workOutput); } } private static ComplexNumber[] lookupRootsExponent(final int exponent) { if (exponent >= UNIT_ROOTS.length) { throw new IllegalArgumentException(); } else if (UNIT_ROOTS[exponent] != null) { return UNIT_ROOTS[exponent]; } else { if (exponent == 0) { return UNIT_ROOTS[0] = new ComplexNumber[] { ComplexNumber.ONE }; } else if (exponent == 1) { return UNIT_ROOTS[1] = new ComplexNumber[] { ComplexNumber.ONE, ComplexNumber.NEG }; } else if (exponent == 2) { return UNIT_ROOTS[2] = new ComplexNumber[] { ComplexNumber.ONE, ComplexNumber.N, ComplexNumber.NEG, ComplexNumber.I }; } else { ComplexNumber[] half = DiscreteFourierTransform.lookupRootsExponent(exponent - 1); ComplexNumber[] full = UNIT_ROOTS[exponent] = new ComplexNumber[half.length + half.length]; ComplexNumber[] unitRoots = ComplexNumber.newUnitRoots(full.length); for (int i = 0; i < half.length; i++) { int ii = 2 * i; int ii1 = ii + 1; full[ii] = half[i]; full[ii1] = unitRoots[ii1]; } return full; } } } /** * Function to perform bit-reversal on indices */ private static void reverseBits(final int[] indices) { int n = indices.length; int bits = 31 - Integer.numberOfLeadingZeros(n); for (int i = 0; i < n; i++) { int reversedIndex = 0; for (int j = 0; j < bits; j++) { reversedIndex |= (i >> j & 1) << bits - 1 - j; } // Swap indices[i] with indices[reversedIndex] if (reversedIndex > i) { int temp = indices[i]; indices[i] = indices[reversedIndex]; indices[reversedIndex] = temp; } } } static void generate(final Mutate2D matrix) { int size = matrix.getMinDim(); double unitRootPhase = ComplexNumber.newUnitRoot(size).phase(); for (int j = 0; j < size; j++) { for (int i = 0; i < size; i++) { matrix.set(i, j, ComplexNumber.makePolar(PrimitiveMath.ONE, i * j * unitRootPhase)); } } } static int[] lookupIndices(final int size) { int exponent = DiscreteFourierTransform.toPowerOf2Exponent(size); int[] retVal = BIT_REVERSED_INDICES[exponent]; if (retVal == null) { BIT_REVERSED_INDICES[exponent] = retVal = Structure1D.newIncreasingRange(0, size); DiscreteFourierTransform.reverseBits(retVal); } return retVal; } static ComplexNumber[] lookupRoots(final int size) { int exponent = DiscreteFourierTransform.toPowerOf2Exponent(size); return DiscreteFourierTransform.lookupRootsExponent(exponent); } static int toPowerOf2Exponent(final int size) { int exponent = PowerOf2.find(size); if (exponent < 0) { throw new IllegalArgumentException("Needs to be power of 2!"); } return exponent; } private final int mySize; DiscreteFourierTransform(final int size) { super(); mySize = size; } public final void inverse(final Access1D input, final Mutate2D.ModifiableReceiver output) { this.transform(input, INVERSE, output); } public final MatrixStore inverse(final Access1D input) { PhysicalStore output = GenericStore.C128.make(input.size(), 1); this.transform(input, INVERSE, output); return output; } @Override public final MatrixStore transform(final Access1D input) { PhysicalStore output = GenericStore.C128.make(input.size(), 1); this.transform(input, DEFAULT, output); return output; } public abstract void transform(Access1D input, Directive directive, Mutate2D.ModifiableReceiver output); public final void transform(final Access1D input, final Mutate2D.ModifiableReceiver output) { this.transform(input, DEFAULT, output); } public MatrixStore transform(final double... input) { PhysicalStore output = GenericStore.C128.make(input.length, 1); this.transform(ArrayR064.wrap(input), DEFAULT, output); return output; } int size() { return mySize; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy