FastHadamardTransform.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 org.apache.commons.numbers.core.ArithmeticUtils;

  20. /**
  21.  * <a href="http://www.archive.chipcenter.com/dsp/DSP000517F1.html">Fast Hadamard Transform</a> (FHT).
  22.  * <p>
  23.  * The FHT can also transform integer vectors into integer vectors.
  24.  * However, this transform cannot be inverted directly, due to a scaling
  25.  * factor that may lead to rational results (for example, the inverse
  26.  * transform of integer vector (0, 1, 0, 1) is vector (1/2, -1/2, 0, 0).
  27.  */
  28. public class FastHadamardTransform implements RealTransform {
  29.     /** Operation to be performed. */
  30.     private final UnaryOperator<double[]> op;

  31.     /**
  32.      * Default constructor.
  33.      */
  34.     public FastHadamardTransform() {
  35.         this(false);
  36.     }

  37.     /**
  38.      * @param inverse Whether to perform the inverse transform.
  39.      */
  40.     public FastHadamardTransform(final boolean inverse) {
  41.         op = create(inverse);
  42.     }

  43.     /**
  44.      * {@inheritDoc}
  45.      *
  46.      * @throws IllegalArgumentException if the length of the data array is
  47.      * not a power of two.
  48.      */
  49.     @Override
  50.     public double[] apply(final double[] f) {
  51.         return op.apply(f);
  52.     }

  53.     /**
  54.      * Returns the forward transform of the given data set.
  55.      * The integer transform cannot be inverted directly, due to a scaling
  56.      * factor which may lead to double results.
  57.      *
  58.      * @param f Data array to be transformed (signal).
  59.      * @return the transformed array (spectrum).
  60.      * @throws IllegalArgumentException if the length of the data array is
  61.      * not a power of two.
  62.      */
  63.     public int[] apply(final int[] f) {
  64.         return fht(f);
  65.     }

  66.     /**
  67.      * FHT uses only subtraction and addition.
  68.      * It requires {@code N * log2(N) = n * 2^n} additions.
  69.      *
  70.      * <h3>Short Table of manual calculation for N=8</h3>
  71.      * <ol>
  72.      * <li><b>x</b> is the input vector to be transformed,</li>
  73.      * <li><b>y</b> is the output vector (Fast Hadamard transform of <b>x</b>),</li>
  74.      * <li>a and b are helper rows.</li>
  75.      * </ol>
  76.      * <table style="text-align: center" border="">
  77.      * <caption>manual calculation for N=8</caption>
  78.      * <tbody style="text-align: center">
  79.      * <tr>
  80.      *     <th>x</th>
  81.      *     <th>a</th>
  82.      *     <th>b</th>
  83.      *     <th>y</th>
  84.      * </tr>
  85.      * <tr>
  86.      *     <th>x<sub>0</sub></th>
  87.      *     <td>a<sub>0</sub> = x<sub>0</sub> + x<sub>1</sub></td>
  88.      *     <td>b<sub>0</sub> = a<sub>0</sub> + a<sub>1</sub></td>
  89.      *     <td>y<sub>0</sub> = b<sub>0</sub >+ b<sub>1</sub></td>
  90.      * </tr>
  91.      * <tr>
  92.      *     <th>x<sub>1</sub></th>
  93.      *     <td>a<sub>1</sub> = x<sub>2</sub> + x<sub>3</sub></td>
  94.      *     <td>b<sub>0</sub> = a<sub>2</sub> + a<sub>3</sub></td>
  95.      *     <td>y<sub>0</sub> = b<sub>2</sub> + b<sub>3</sub></td>
  96.      * </tr>
  97.      * <tr>
  98.      *     <th>x<sub>2</sub></th>
  99.      *     <td>a<sub>2</sub> = x<sub>4</sub> + x<sub>5</sub></td>
  100.      *     <td>b<sub>0</sub> = a<sub>4</sub> + a<sub>5</sub></td>
  101.      *     <td>y<sub>0</sub> = b<sub>4</sub> + b<sub>5</sub></td>
  102.      * </tr>
  103.      * <tr>
  104.      *     <th>x<sub>3</sub></th>
  105.      *     <td>a<sub>3</sub> = x<sub>6</sub> + x<sub>7</sub></td>
  106.      *     <td>b<sub>0</sub> = a<sub>6</sub> + a<sub>7</sub></td>
  107.      *     <td>y<sub>0</sub> = b<sub>6</sub> + b<sub>7</sub></td>
  108.      * </tr>
  109.      * <tr>
  110.      *     <th>x<sub>4</sub></th>
  111.      *     <td>a<sub>0</sub> = x<sub>0</sub> - x<sub>1</sub></td>
  112.      *     <td>b<sub>0</sub> = a<sub>0</sub> - a<sub>1</sub></td>
  113.      *     <td>y<sub>0</sub> = b<sub>0</sub> - b<sub>1</sub></td>
  114.      * </tr>
  115.      * <tr>
  116.      *     <th>x<sub>5</sub></th>
  117.      *     <td>a<sub>1</sub> = x<sub>2</sub> - x<sub>3</sub></td>
  118.      *     <td>b<sub>0</sub> = a<sub>2</sub> - a<sub>3</sub></td>
  119.      *     <td>y<sub>0</sub> = b<sub>2</sub> - b<sub>3</sub></td>
  120.      * </tr>
  121.      * <tr>
  122.      *     <th>x<sub>6</sub></th>
  123.      *     <td>a<sub>2</sub> = x<sub>4</sub> - x<sub>5</sub></td>
  124.      *     <td>b<sub>0</sub> = a<sub>4</sub> - a<sub>5</sub></td>
  125.      *     <td>y<sub>0</sub> = b<sub>4</sub> - b<sub>5</sub></td>
  126.      * </tr>
  127.      * <tr>
  128.      *     <th>x<sub>7</sub></th>
  129.      *     <td>a<sub>3</sub> = x<sub>6</sub> - x<sub>7</sub></td>
  130.      *     <td>b<sub>0</sub> = a<sub>6</sub> - a<sub>7</sub></td>
  131.      *     <td>y<sub>0</sub> = b<sub>6</sub> - b<sub>7</sub></td>
  132.      * </tr>
  133.      * </tbody>
  134.      * </table>
  135.      *
  136.      * <h3>How it works</h3>
  137.      * <ol>
  138.      * <li>Construct a matrix with {@code N} rows and {@code n + 1} columns,
  139.      * {@code hadm[n+1][N]}.<br>
  140.      * <em>(If I use [x][y] it always means [row-offset][column-offset] of a
  141.      * Matrix with n rows and m columns. Its entries go from M[0][0]
  142.      * to M[n][N])</em></li>
  143.      * <li>Place the input vector {@code x[N]} in the first column of the
  144.      * matrix {@code hadm}.</li>
  145.      * <li>The entries of the submatrix {@code D_top} are calculated as follows
  146.      *     <ul>
  147.      *         <li>{@code D_top} goes from entry {@code [0][1]} to
  148.      *         {@code [N / 2 - 1][n + 1]},</li>
  149.      *         <li>the columns of {@code D_top} are the pairwise mutually
  150.      *         exclusive sums of the previous column.</li>
  151.      *     </ul>
  152.      * </li>
  153.      * <li>The entries of the submatrix {@code D_bottom} are calculated as
  154.      * follows
  155.      *     <ul>
  156.      *         <li>{@code D_bottom} goes from entry {@code [N / 2][1]} to
  157.      *         {@code [N][n + 1]},</li>
  158.      *         <li>the columns of {@code D_bottom} are the pairwise differences
  159.      *         of the previous column.</li>
  160.      *     </ul>
  161.      * </li>
  162.      * <li>The computation of {@code D_top} and {@code D_bottom} are best
  163.      * understood with the above example (for {@code N = 8}).
  164.      * <li>The output vector {@code y} is now in the last column of
  165.      * {@code hadm}.</li>
  166.      * <li><em>Algorithm from <a href="http://www.archive.chipcenter.com/dsp/DSP000517F1.html">chipcenter</a>.</em></li>
  167.      * </ol>
  168.      * <h3>Visually</h3>
  169.      * <table border="" style="text-align: center">
  170.      * <caption>chipcenter algorithm</caption>
  171.      * <tbody style="text-align: center">
  172.      * <tr>
  173.      *     <td></td><th>0</th><th>1</th><th>2</th><th>3</th>
  174.      *     <th>&hellip;</th>
  175.      *     <th>n + 1</th>
  176.      * </tr>
  177.      * <tr>
  178.      *     <th>0</th>
  179.      *     <td>x<sub>0</sub></td>
  180.      *     <td colspan="5" rowspan="5" align="center" valign="middle">
  181.      *         &uarr;<br>
  182.      *         &larr; D<sub>top</sub> &rarr;<br>
  183.      *         &darr;
  184.      *     </td>
  185.      * </tr>
  186.      * <tr><th>1</th><td>x<sub>1</sub></td></tr>
  187.      * <tr><th>2</th><td>x<sub>2</sub></td></tr>
  188.      * <tr><th>&hellip;</th><td>&hellip;</td></tr>
  189.      * <tr><th>N / 2 - 1</th><td>x<sub>N/2-1</sub></td></tr>
  190.      * <tr>
  191.      *     <th>N / 2</th>
  192.      *     <td>x<sub>N/2</sub></td>
  193.      *     <td colspan="5" rowspan="5" align="center" valign="middle">
  194.      *         &uarr;<br>
  195.      *         &larr; D<sub>bottom</sub> &rarr;<br>
  196.      *         &darr;
  197.      *     </td>
  198.      * </tr>
  199.      * <tr><th>N / 2 + 1</th><td>x<sub>N/2+1</sub></td></tr>
  200.      * <tr><th>N / 2 + 2</th><td>x<sub>N/2+2</sub></td></tr>
  201.      * <tr><th>&hellip;</th><td>&hellip;</td></tr>
  202.      * <tr><th>N</th><td>x<sub>N</sub></td></tr>
  203.      * </tbody>
  204.      * </table>
  205.      *
  206.      * @param x Data to be transformed.
  207.      * @return the transformed array.
  208.      * @throws IllegalArgumentException if the length of the data array is
  209.      * not a power of two.
  210.      */
  211.     private double[] fht(double[] x) {
  212.         final int n = x.length;

  213.         if (!ArithmeticUtils.isPowerOfTwo(n)) {
  214.             throw new TransformException(TransformException.NOT_POWER_OF_TWO,
  215.                                          n);
  216.         }

  217.         final int halfN = n / 2;

  218.         // Instead of creating a matrix with p+1 columns and n rows, we use two
  219.         // one dimension arrays which we are used in an alternating way.
  220.         double[] yPrevious = new double[n];
  221.         double[] yCurrent  = x.clone();

  222.         // iterate from left to right (column)
  223.         for (int j = 1; j < n; j <<= 1) {

  224.             // switch columns
  225.             final double[] yTmp = yCurrent;
  226.             yCurrent  = yPrevious;
  227.             yPrevious = yTmp;

  228.             // iterate from top to bottom (row)
  229.             for (int i = 0; i < halfN; ++i) {
  230.                 // Dtop: the top part works with addition
  231.                 final int twoI = 2 * i;
  232.                 yCurrent[i] = yPrevious[twoI] + yPrevious[twoI + 1];
  233.             }
  234.             for (int i = halfN; i < n; ++i) {
  235.                 // Dbottom: the bottom part works with subtraction
  236.                 final int twoI = 2 * i;
  237.                 yCurrent[i] = yPrevious[twoI - n] - yPrevious[twoI - n + 1];
  238.             }
  239.         }

  240.         return yCurrent;
  241.     }

  242.     /**
  243.      * Returns the forward transform of the specified integer data set.
  244.      * FHT only uses subtraction and addition.
  245.      *
  246.      * @param x Data to be transformed.
  247.      * @return the transformed array.
  248.      * @throws IllegalArgumentException if the length of the data array is
  249.      * not a power of two.
  250.      */
  251.     private int[] fht(int[] x) {
  252.         final int n = x.length;
  253.         if (!ArithmeticUtils.isPowerOfTwo(n)) {
  254.             throw new TransformException(TransformException.NOT_POWER_OF_TWO,
  255.                                          n);
  256.         }

  257.         final int halfN = n / 2;

  258.         // Instead of creating a matrix with p+1 columns and n rows, we use two
  259.         // one dimension arrays which we are used in an alternating way.

  260.         int[] yPrevious = new int[n];
  261.         int[] yCurrent  = x.clone();

  262.         // iterate from left to right (column)
  263.         for (int j = 1; j < n; j <<= 1) {
  264.             // switch columns
  265.             final int[] yTmp = yCurrent;
  266.             yCurrent  = yPrevious;
  267.             yPrevious = yTmp;

  268.             // iterate from top to bottom (row)
  269.             for (int i = 0; i < halfN; ++i) {
  270.                 // Dtop: the top part works with addition
  271.                 final int twoI = 2 * i;
  272.                 yCurrent[i] = yPrevious[twoI] + yPrevious[twoI + 1];
  273.             }
  274.             for (int i = halfN; i < n; ++i) {
  275.                 // Dbottom: the bottom part works with subtraction
  276.                 final int twoI = 2 * i;
  277.                 yCurrent[i] = yPrevious[twoI - n] - yPrevious[twoI - n + 1];
  278.             }
  279.         }

  280.         // return the last computed output vector y
  281.         return yCurrent;
  282.     }

  283.     /**
  284.      * Factory method.
  285.      *
  286.      * @param inverse Whether to perform the inverse transform.
  287.      * @return the transform operator.
  288.      */
  289.     private UnaryOperator<double[]> create(final boolean inverse) {
  290.         if (inverse) {
  291.             return f -> TransformUtils.scaleInPlace(fht(f), 1d / f.length);
  292.         } else {
  293.             return f -> fht(f);
  294.         }
  295.     }
  296. }