FastCosineTransform.java

  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.math4.transform;

  18. import java.util.function.UnaryOperator;
  19. import java.util.function.DoubleUnaryOperator;

  20. import org.apache.commons.numbers.complex.Complex;
  21. import org.apache.commons.numbers.core.ArithmeticUtils;

  22. /**
  23.  * Implements the Fast Cosine Transform for transformation of one-dimensional
  24.  * real data sets. For reference, see James S. Walker, <em>Fast Fourier
  25.  * Transforms</em>, chapter 3 (ISBN 0849371635).
  26.  * <p>
  27.  * There are several variants of the discrete cosine transform. The present
  28.  * implementation corresponds to DCT-I, with various normalization conventions,
  29.  * which are specified by the parameter {@link Norm}.
  30.  * <p>
  31.  * DCT-I is equivalent to DFT of an <em>even extension</em> of the data series.
  32.  * More precisely, if x<sub>0</sub>, &hellip;, x<sub>N-1</sub> is the data set
  33.  * to be cosine transformed, the extended data set
  34.  * x<sub>0</sub><sup>&#35;</sup>, &hellip;, x<sub>2N-3</sub><sup>&#35;</sup>
  35.  * is defined as follows
  36.  * <ul>
  37.  * <li>x<sub>k</sub><sup>&#35;</sup> = x<sub>k</sub> if 0 &le; k &lt; N,</li>
  38.  * <li>x<sub>k</sub><sup>&#35;</sup> = x<sub>2N-2-k</sub>
  39.  * if N &le; k &lt; 2N - 2.</li>
  40.  * </ul>
  41.  * <p>
  42.  * Then, the standard DCT-I y<sub>0</sub>, &hellip;, y<sub>N-1</sub> of the real
  43.  * data set x<sub>0</sub>, &hellip;, x<sub>N-1</sub> is equal to <em>half</em>
  44.  * of the N first elements of the DFT of the extended data set
  45.  * x<sub>0</sub><sup>&#35;</sup>, &hellip;, x<sub>2N-3</sub><sup>&#35;</sup>
  46.  * <br>
  47.  * y<sub>n</sub> = (1 / 2) &sum;<sub>k=0</sub><sup>2N-3</sup>
  48.  * x<sub>k</sub><sup>&#35;</sup> exp[-2&pi;i nk / (2N - 2)]
  49.  * &nbsp;&nbsp;&nbsp;&nbsp;k = 0, &hellip;, N-1.
  50.  * <p>
  51.  * The present implementation of the discrete cosine transform as a fast cosine
  52.  * transform requires the length of the data set to be a power of two plus one
  53.  * (N&nbsp;=&nbsp;2<sup>n</sup>&nbsp;+&nbsp;1). Besides, it implicitly assumes
  54.  * that the sampled function is even.
  55.  */
  56. public class FastCosineTransform implements RealTransform {
  57.     /** Operation to be performed. */
  58.     private final UnaryOperator<double[]> op;

  59.     /**
  60.      * @param normalization Normalization to be applied to the
  61.      * transformed data.
  62.      * @param inverse Whether to perform the inverse transform.
  63.      */
  64.     public FastCosineTransform(final Norm normalization,
  65.                                final boolean inverse) {
  66.         op = create(normalization, inverse);
  67.     }

  68.     /**
  69.      * @param normalization Normalization to be applied to the
  70.      * transformed data.
  71.      */
  72.     public FastCosineTransform(final Norm normalization) {
  73.         this(normalization, false);
  74.     }

  75.     /**
  76.      * {@inheritDoc}
  77.      *
  78.      * @throws IllegalArgumentException if the length of the data array is
  79.      * not a power of two plus one.
  80.      */
  81.     @Override
  82.     public double[] apply(final double[] f) {
  83.         return op.apply(f);
  84.     }

  85.     /**
  86.      * {@inheritDoc}
  87.      *
  88.      * @throws IllegalArgumentException if the number of sample points is
  89.      * not a power of two plus one, if the lower bound is greater than or
  90.      * equal to the upper bound, if the number of sample points is negative.
  91.      */
  92.     @Override
  93.     public double[] apply(final DoubleUnaryOperator f,
  94.                           final double min,
  95.                           final double max,
  96.                           final int n) {
  97.         return apply(TransformUtils.sample(f, min, max, n));
  98.     }

  99.     /**
  100.      * Perform the FCT algorithm (including inverse).
  101.      *
  102.      * @param f Data to be transformed.
  103.      * @return the transformed array.
  104.      * @throws IllegalArgumentException if the length of the data array is
  105.      * not a power of two plus one.
  106.      */
  107.     private double[] fct(double[] f) {
  108.         final int n = f.length - 1;
  109.         if (!ArithmeticUtils.isPowerOfTwo(n)) {
  110.             throw new TransformException(TransformException.NOT_POWER_OF_TWO_PLUS_ONE,
  111.                                          Integer.valueOf(f.length));
  112.         }

  113.         final double[] transformed = new double[f.length];

  114.         if (n == 1) {       // trivial case
  115.             transformed[0] = 0.5 * (f[0] + f[1]);
  116.             transformed[1] = 0.5 * (f[0] - f[1]);
  117.             return transformed;
  118.         }

  119.         // construct a new array and perform FFT on it
  120.         final double[] x = new double[n];
  121.         x[0] = 0.5 * (f[0] + f[n]);
  122.         final int nShifted = n >> 1;
  123.         x[nShifted] = f[nShifted];
  124.         // temporary variable for transformed[1]
  125.         double t1 = 0.5 * (f[0] - f[n]);
  126.         final double piOverN = Math.PI / n;
  127.         for (int i = 1; i < nShifted; i++) {
  128.             final int nMi = n - i;
  129.             final double fi = f[i];
  130.             final double fnMi = f[nMi];
  131.             final double a = 0.5 * (fi + fnMi);
  132.             final double arg = i * piOverN;
  133.             final double b = Math.sin(arg) * (fi - fnMi);
  134.             final double c = Math.cos(arg) * (fi - fnMi);
  135.             x[i] = a - b;
  136.             x[nMi] = a + b;
  137.             t1 += c;
  138.         }
  139.         final FastFourierTransform transformer = new FastFourierTransform(FastFourierTransform.Norm.STD,
  140.                                                                           false);
  141.         final Complex[] y = transformer.apply(x);

  142.         // reconstruct the FCT result for the original array
  143.         transformed[0] = y[0].getReal();
  144.         transformed[1] = t1;
  145.         for (int i = 1; i < nShifted; i++) {
  146.             final int i2 = 2 * i;
  147.             transformed[i2] = y[i].getReal();
  148.             transformed[i2 + 1] = transformed[i2 - 1] - y[i].getImaginary();
  149.         }
  150.         transformed[n] = y[nShifted].getReal();

  151.         return transformed;
  152.     }

  153.     /**
  154.      * Factory method.
  155.      *
  156.      * @param normalization Normalization to be applied to the
  157.      * transformed data.
  158.      * @param inverse Whether to perform the inverse transform.
  159.      * @return the transform operator.
  160.      */
  161.     private UnaryOperator<double[]> create(final Norm normalization,
  162.                                            final boolean inverse) {
  163.         if (inverse) {
  164.             return normalization == Norm.ORTHO ?
  165.                 f -> TransformUtils.scaleInPlace(fct(f), Math.sqrt(2d / (f.length - 1))) :
  166.                 f -> TransformUtils.scaleInPlace(fct(f), 2d / (f.length - 1));
  167.         } else {
  168.             return normalization == Norm.ORTHO ?
  169.                 f -> TransformUtils.scaleInPlace(fct(f), Math.sqrt(2d / (f.length - 1))) :
  170.                 f -> fct(f);
  171.         }
  172.     }

  173.     /**
  174.      * Normalization types.
  175.      */
  176.     public enum Norm {
  177.         /**
  178.          * Should be passed to the constructor of {@link FastCosineTransform}
  179.          * to use the <em>standard</em> normalization convention.  The standard
  180.          * DCT-I normalization convention is defined as follows
  181.          * <ul>
  182.          * <li>forward transform:
  183.          * y<sub>n</sub> = (1/2) [x<sub>0</sub> + (-1)<sup>n</sup>x<sub>N-1</sub>]
  184.          * + &sum;<sub>k=1</sub><sup>N-2</sup>
  185.          * x<sub>k</sub> cos[&pi; nk / (N - 1)],</li>
  186.          * <li>inverse transform:
  187.          * x<sub>k</sub> = [1 / (N - 1)] [y<sub>0</sub>
  188.          * + (-1)<sup>k</sup>y<sub>N-1</sub>]
  189.          * + [2 / (N - 1)] &sum;<sub>n=1</sub><sup>N-2</sup>
  190.          * y<sub>n</sub> cos[&pi; nk / (N - 1)],</li>
  191.          * </ul>
  192.          * where N is the size of the data sample.
  193.          */
  194.         STD,

  195.         /**
  196.          * Should be passed to the constructor of {@link FastCosineTransform}
  197.          * to use the <em>orthogonal</em> normalization convention. The orthogonal
  198.          * DCT-I normalization convention is defined as follows
  199.          * <ul>
  200.          * <li>forward transform:
  201.          * y<sub>n</sub> = [2(N - 1)]<sup>-1/2</sup> [x<sub>0</sub>
  202.          * + (-1)<sup>n</sup>x<sub>N-1</sub>]
  203.          * + [2 / (N - 1)]<sup>1/2</sup> &sum;<sub>k=1</sub><sup>N-2</sup>
  204.          * x<sub>k</sub> cos[&pi; nk / (N - 1)],</li>
  205.          * <li>inverse transform:
  206.          * x<sub>k</sub> = [2(N - 1)]<sup>-1/2</sup> [y<sub>0</sub>
  207.          * + (-1)<sup>k</sup>y<sub>N-1</sub>]
  208.          * + [2 / (N - 1)]<sup>1/2</sup> &sum;<sub>n=1</sub><sup>N-2</sup>
  209.          * y<sub>n</sub> cos[&pi; nk / (N - 1)],</li>
  210.          * </ul>
  211.          * which makes the transform orthogonal. N is the size of the data sample.
  212.          */
  213.         ORTHO;
  214.     }
  215. }