BiDiagonalTransformer.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.legacy.linear;

  18. import org.apache.commons.math4.core.jdkmath.JdkMath;


  19. /**
  20.  * Class transforming any matrix to bi-diagonal shape.
  21.  * <p>Any m &times; n matrix A can be written as the product of three matrices:
  22.  * A = U &times; B &times; V<sup>T</sup> with U an m &times; m orthogonal matrix,
  23.  * B an m &times; n bi-diagonal matrix (lower diagonal if m &lt; n, upper diagonal
  24.  * otherwise), and V an n &times; n orthogonal matrix.</p>
  25.  * <p>Transformation to bi-diagonal shape is often not a goal by itself, but it is
  26.  * an intermediate step in more general decomposition algorithms like {@link
  27.  * SingularValueDecomposition Singular Value Decomposition}. This class is therefore
  28.  * intended for internal use by the library and is not public. As a consequence of
  29.  * this explicitly limited scope, many methods directly returns references to
  30.  * internal arrays, not copies.</p>
  31.  * @since 2.0
  32.  */
  33. class BiDiagonalTransformer {

  34.     /** Householder vectors. */
  35.     private final double[][] householderVectors;

  36.     /** Main diagonal. */
  37.     private final double[] main;

  38.     /** Secondary diagonal. */
  39.     private final double[] secondary;

  40.     /** Cached value of U. */
  41.     private RealMatrix cachedU;

  42.     /** Cached value of B. */
  43.     private RealMatrix cachedB;

  44.     /** Cached value of V. */
  45.     private RealMatrix cachedV;

  46.     /**
  47.      * Build the transformation to bi-diagonal shape of a matrix.
  48.      * @param matrix the matrix to transform.
  49.      */
  50.     BiDiagonalTransformer(RealMatrix matrix) {

  51.         final int m = matrix.getRowDimension();
  52.         final int n = matrix.getColumnDimension();
  53.         final int p = JdkMath.min(m, n);
  54.         householderVectors = matrix.getData();
  55.         main      = new double[p];
  56.         secondary = new double[p - 1];
  57.         cachedU   = null;
  58.         cachedB   = null;
  59.         cachedV   = null;

  60.         // transform matrix
  61.         if (m >= n) {
  62.             transformToUpperBiDiagonal();
  63.         } else {
  64.             transformToLowerBiDiagonal();
  65.         }
  66.     }

  67.     /**
  68.      * Returns the matrix U of the transform.
  69.      * <p>U is an orthogonal matrix, i.e. its transpose is also its inverse.</p>
  70.      * @return the U matrix
  71.      */
  72.     public RealMatrix getU() {

  73.         if (cachedU == null) {

  74.             final int m = householderVectors.length;
  75.             final int n = householderVectors[0].length;
  76.             final int p = main.length;
  77.             final int diagOffset    = (m >= n) ? 0 : 1;
  78.             final double[] diagonal = (m >= n) ? main : secondary;
  79.             double[][] ua = new double[m][m];

  80.             // fill up the part of the matrix not affected by Householder transforms
  81.             for (int k = m - 1; k >= p; --k) {
  82.                 ua[k][k] = 1;
  83.             }

  84.             // build up first part of the matrix by applying Householder transforms
  85.             for (int k = p - 1; k >= diagOffset; --k) {
  86.                 final double[] hK = householderVectors[k];
  87.                 ua[k][k] = 1;
  88.                 if (hK[k - diagOffset] != 0.0) {
  89.                     for (int j = k; j < m; ++j) {
  90.                         double alpha = 0;
  91.                         for (int i = k; i < m; ++i) {
  92.                             alpha -= ua[i][j] * householderVectors[i][k - diagOffset];
  93.                         }
  94.                         alpha /= diagonal[k - diagOffset] * hK[k - diagOffset];

  95.                         for (int i = k; i < m; ++i) {
  96.                             ua[i][j] += -alpha * householderVectors[i][k - diagOffset];
  97.                         }
  98.                     }
  99.                 }
  100.             }
  101.             if (diagOffset > 0) {
  102.                 ua[0][0] = 1;
  103.             }
  104.             cachedU = MatrixUtils.createRealMatrix(ua);
  105.         }

  106.         // return the cached matrix
  107.         return cachedU;
  108.     }

  109.     /**
  110.      * Returns the bi-diagonal matrix B of the transform.
  111.      * @return the B matrix
  112.      */
  113.     public RealMatrix getB() {

  114.         if (cachedB == null) {

  115.             final int m = householderVectors.length;
  116.             final int n = householderVectors[0].length;
  117.             double[][] ba = new double[m][n];
  118.             for (int i = 0; i < main.length; ++i) {
  119.                 ba[i][i] = main[i];
  120.                 if (m < n) {
  121.                     if (i > 0) {
  122.                         ba[i][i-1] = secondary[i - 1];
  123.                     }
  124.                 } else {
  125.                     if (i < main.length - 1) {
  126.                         ba[i][i+1] = secondary[i];
  127.                     }
  128.                 }
  129.             }
  130.             cachedB = MatrixUtils.createRealMatrix(ba);
  131.         }

  132.         // return the cached matrix
  133.         return cachedB;
  134.     }

  135.     /**
  136.      * Returns the matrix V of the transform.
  137.      * <p>V is an orthogonal matrix, i.e. its transpose is also its inverse.</p>
  138.      * @return the V matrix
  139.      */
  140.     public RealMatrix getV() {

  141.         if (cachedV == null) {

  142.             final int m = householderVectors.length;
  143.             final int n = householderVectors[0].length;
  144.             final int p = main.length;
  145.             final int diagOffset    = (m >= n) ? 1 : 0;
  146.             final double[] diagonal = (m >= n) ? secondary : main;
  147.             double[][] va = new double[n][n];

  148.             // fill up the part of the matrix not affected by Householder transforms
  149.             for (int k = n - 1; k >= p; --k) {
  150.                 va[k][k] = 1;
  151.             }

  152.             // build up first part of the matrix by applying Householder transforms
  153.             for (int k = p - 1; k >= diagOffset; --k) {
  154.                 final double[] hK = householderVectors[k - diagOffset];
  155.                 va[k][k] = 1;
  156.                 if (hK[k] != 0.0) {
  157.                     for (int j = k; j < n; ++j) {
  158.                         double beta = 0;
  159.                         for (int i = k; i < n; ++i) {
  160.                             beta -= va[i][j] * hK[i];
  161.                         }
  162.                         beta /= diagonal[k - diagOffset] * hK[k];

  163.                         for (int i = k; i < n; ++i) {
  164.                             va[i][j] += -beta * hK[i];
  165.                         }
  166.                     }
  167.                 }
  168.             }
  169.             if (diagOffset > 0) {
  170.                 va[0][0] = 1;
  171.             }
  172.             cachedV = MatrixUtils.createRealMatrix(va);
  173.         }

  174.         // return the cached matrix
  175.         return cachedV;
  176.     }

  177.     /**
  178.      * Get the Householder vectors of the transform.
  179.      * <p>Note that since this class is only intended for internal use,
  180.      * it returns directly a reference to its internal arrays, not a copy.</p>
  181.      * @return the main diagonal elements of the B matrix
  182.      */
  183.     double[][] getHouseholderVectorsRef() {
  184.         return householderVectors;
  185.     }

  186.     /**
  187.      * Get the main diagonal elements of the matrix B of the transform.
  188.      * <p>Note that since this class is only intended for internal use,
  189.      * it returns directly a reference to its internal arrays, not a copy.</p>
  190.      * @return the main diagonal elements of the B matrix
  191.      */
  192.     double[] getMainDiagonalRef() {
  193.         return main;
  194.     }

  195.     /**
  196.      * Get the secondary diagonal elements of the matrix B of the transform.
  197.      * <p>Note that since this class is only intended for internal use,
  198.      * it returns directly a reference to its internal arrays, not a copy.</p>
  199.      * @return the secondary diagonal elements of the B matrix
  200.      */
  201.     double[] getSecondaryDiagonalRef() {
  202.         return secondary;
  203.     }

  204.     /**
  205.      * Check if the matrix is transformed to upper bi-diagonal.
  206.      * @return true if the matrix is transformed to upper bi-diagonal
  207.      */
  208.     boolean isUpperBiDiagonal() {
  209.         return householderVectors.length >=  householderVectors[0].length;
  210.     }

  211.     /**
  212.      * Transform original matrix to upper bi-diagonal form.
  213.      * <p>Transformation is done using alternate Householder transforms
  214.      * on columns and rows.</p>
  215.      */
  216.     private void transformToUpperBiDiagonal() {

  217.         final int m = householderVectors.length;
  218.         final int n = householderVectors[0].length;
  219.         for (int k = 0; k < n; k++) {

  220.             //zero-out a column
  221.             double xNormSqr = 0;
  222.             for (int i = k; i < m; ++i) {
  223.                 final double c = householderVectors[i][k];
  224.                 xNormSqr += c * c;
  225.             }
  226.             final double[] hK = householderVectors[k];
  227.             final double a = (hK[k] > 0) ? -JdkMath.sqrt(xNormSqr) : JdkMath.sqrt(xNormSqr);
  228.             main[k] = a;
  229.             if (a != 0.0) {
  230.                 hK[k] -= a;
  231.                 for (int j = k + 1; j < n; ++j) {
  232.                     double alpha = 0;
  233.                     for (int i = k; i < m; ++i) {
  234.                         final double[] hI = householderVectors[i];
  235.                         alpha -= hI[j] * hI[k];
  236.                     }
  237.                     alpha /= a * householderVectors[k][k];
  238.                     for (int i = k; i < m; ++i) {
  239.                         final double[] hI = householderVectors[i];
  240.                         hI[j] -= alpha * hI[k];
  241.                     }
  242.                 }
  243.             }

  244.             if (k < n - 1) {
  245.                 //zero-out a row
  246.                 xNormSqr = 0;
  247.                 for (int j = k + 1; j < n; ++j) {
  248.                     final double c = hK[j];
  249.                     xNormSqr += c * c;
  250.                 }
  251.                 final double b = (hK[k + 1] > 0) ? -JdkMath.sqrt(xNormSqr) : JdkMath.sqrt(xNormSqr);
  252.                 secondary[k] = b;
  253.                 if (b != 0.0) {
  254.                     hK[k + 1] -= b;
  255.                     for (int i = k + 1; i < m; ++i) {
  256.                         final double[] hI = householderVectors[i];
  257.                         double beta = 0;
  258.                         for (int j = k + 1; j < n; ++j) {
  259.                             beta -= hI[j] * hK[j];
  260.                         }
  261.                         beta /= b * hK[k + 1];
  262.                         for (int j = k + 1; j < n; ++j) {
  263.                             hI[j] -= beta * hK[j];
  264.                         }
  265.                     }
  266.                 }
  267.             }
  268.         }
  269.     }

  270.     /**
  271.      * Transform original matrix to lower bi-diagonal form.
  272.      * <p>Transformation is done using alternate Householder transforms
  273.      * on rows and columns.</p>
  274.      */
  275.     private void transformToLowerBiDiagonal() {

  276.         final int m = householderVectors.length;
  277.         final int n = householderVectors[0].length;
  278.         for (int k = 0; k < m; k++) {

  279.             //zero-out a row
  280.             final double[] hK = householderVectors[k];
  281.             double xNormSqr = 0;
  282.             for (int j = k; j < n; ++j) {
  283.                 final double c = hK[j];
  284.                 xNormSqr += c * c;
  285.             }
  286.             final double a = (hK[k] > 0) ? -JdkMath.sqrt(xNormSqr) : JdkMath.sqrt(xNormSqr);
  287.             main[k] = a;
  288.             if (a != 0.0) {
  289.                 hK[k] -= a;
  290.                 for (int i = k + 1; i < m; ++i) {
  291.                     final double[] hI = householderVectors[i];
  292.                     double alpha = 0;
  293.                     for (int j = k; j < n; ++j) {
  294.                         alpha -= hI[j] * hK[j];
  295.                     }
  296.                     alpha /= a * householderVectors[k][k];
  297.                     for (int j = k; j < n; ++j) {
  298.                         hI[j] -= alpha * hK[j];
  299.                     }
  300.                 }
  301.             }

  302.             if (k < m - 1) {
  303.                 //zero-out a column
  304.                 final double[] hKp1 = householderVectors[k + 1];
  305.                 xNormSqr = 0;
  306.                 for (int i = k + 1; i < m; ++i) {
  307.                     final double c = householderVectors[i][k];
  308.                     xNormSqr += c * c;
  309.                 }
  310.                 final double b = (hKp1[k] > 0) ? -JdkMath.sqrt(xNormSqr) : JdkMath.sqrt(xNormSqr);
  311.                 secondary[k] = b;
  312.                 if (b != 0.0) {
  313.                     hKp1[k] -= b;
  314.                     for (int j = k + 1; j < n; ++j) {
  315.                         double beta = 0;
  316.                         for (int i = k + 1; i < m; ++i) {
  317.                             final double[] hI = householderVectors[i];
  318.                             beta -= hI[j] * hI[k];
  319.                         }
  320.                         beta /= b * hKp1[k];
  321.                         for (int i = k + 1; i < m; ++i) {
  322.                             final double[] hI = householderVectors[i];
  323.                             hI[j] -= beta * hI[k];
  324.                         }
  325.                     }
  326.                 }
  327.             }
  328.         }
  329.     }
  330. }