BiDiagonalTransformer.java
/*
* 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
*
* https://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.
*/
/*
* This is not the original file distributed by the Apache Software Foundation
* It has been modified by the Hipparchus project
*/
package org.hipparchus.linear;
import org.hipparchus.util.FastMath;
/**
* Class transforming any matrix to bi-diagonal shape.
* <p>Any m × n matrix A can be written as the product of three matrices:
* A = U × B × V<sup>T</sup> with U an m × m orthogonal matrix,
* B an m × n bi-diagonal matrix (lower diagonal if m < n, upper diagonal
* otherwise), and V an n × n orthogonal matrix.</p>
* <p>Transformation to bi-diagonal shape is often not a goal by itself, but it is
* an intermediate step in more general decomposition algorithms like {@link
* SingularValueDecomposition Singular Value Decomposition}. This class is therefore
* intended for internal use by the library and is not public. As a consequence of
* this explicitly limited scope, many methods directly returns references to
* internal arrays, not copies.</p>
*/
class BiDiagonalTransformer {
/** Householder vectors. */
private final double[][] householderVectors;
/** Main diagonal. */
private final double[] main;
/** Secondary diagonal. */
private final double[] secondary;
/** Cached value of U. */
private RealMatrix cachedU;
/** Cached value of B. */
private RealMatrix cachedB;
/** Cached value of V. */
private RealMatrix cachedV;
/**
* Build the transformation to bi-diagonal shape of a matrix.
* @param matrix the matrix to transform.
*/
BiDiagonalTransformer(RealMatrix matrix) {
final int m = matrix.getRowDimension();
final int n = matrix.getColumnDimension();
final int p = FastMath.min(m, n);
householderVectors = matrix.getData();
main = new double[p];
secondary = new double[p - 1];
cachedU = null;
cachedB = null;
cachedV = null;
// transform matrix
if (m >= n) {
transformToUpperBiDiagonal();
} else {
transformToLowerBiDiagonal();
}
}
/**
* Returns the matrix U of the transform.
* <p>U is an orthogonal matrix, i.e. its transpose is also its inverse.</p>
* @return the U matrix
*/
public RealMatrix getU() {
if (cachedU == null) {
final int m = householderVectors.length;
final int n = householderVectors[0].length;
final int p = main.length;
final int diagOffset = (m >= n) ? 0 : 1;
final double[] diagonal = (m >= n) ? main : secondary;
double[][] ua = new double[m][m];
// fill up the part of the matrix not affected by Householder transforms
for (int k = m - 1; k >= p; --k) {
ua[k][k] = 1;
}
// build up first part of the matrix by applying Householder transforms
for (int k = p - 1; k >= diagOffset; --k) {
final double[] hK = householderVectors[k];
ua[k][k] = 1;
if (hK[k - diagOffset] != 0.0) {
for (int j = k; j < m; ++j) {
double alpha = 0;
for (int i = k; i < m; ++i) {
alpha -= ua[i][j] * householderVectors[i][k - diagOffset];
}
alpha /= diagonal[k - diagOffset] * hK[k - diagOffset];
for (int i = k; i < m; ++i) {
ua[i][j] += -alpha * householderVectors[i][k - diagOffset];
}
}
}
}
if (diagOffset > 0) {
ua[0][0] = 1;
}
cachedU = MatrixUtils.createRealMatrix(ua);
}
// return the cached matrix
return cachedU;
}
/**
* Returns the bi-diagonal matrix B of the transform.
* @return the B matrix
*/
public RealMatrix getB() {
if (cachedB == null) {
final int m = householderVectors.length;
final int n = householderVectors[0].length;
double[][] ba = new double[m][n];
for (int i = 0; i < main.length; ++i) {
ba[i][i] = main[i];
if (m < n) {
if (i > 0) {
ba[i][i-1] = secondary[i - 1];
}
} else {
if (i < main.length - 1) {
ba[i][i+1] = secondary[i];
}
}
}
cachedB = MatrixUtils.createRealMatrix(ba);
}
// return the cached matrix
return cachedB;
}
/**
* Returns the matrix V of the transform.
* <p>V is an orthogonal matrix, i.e. its transpose is also its inverse.</p>
* @return the V matrix
*/
public RealMatrix getV() {
if (cachedV == null) {
final int m = householderVectors.length;
final int n = householderVectors[0].length;
final int p = main.length;
final int diagOffset = (m >= n) ? 1 : 0;
final double[] diagonal = (m >= n) ? secondary : main;
double[][] va = new double[n][n];
// fill up the part of the matrix not affected by Householder transforms
for (int k = n - 1; k >= p; --k) {
va[k][k] = 1;
}
// build up first part of the matrix by applying Householder transforms
for (int k = p - 1; k >= diagOffset; --k) {
final double[] hK = householderVectors[k - diagOffset];
va[k][k] = 1;
if (hK[k] != 0.0) {
for (int j = k; j < n; ++j) {
double beta = 0;
for (int i = k; i < n; ++i) {
beta -= va[i][j] * hK[i];
}
beta /= diagonal[k - diagOffset] * hK[k];
for (int i = k; i < n; ++i) {
va[i][j] += -beta * hK[i];
}
}
}
}
if (diagOffset > 0) {
va[0][0] = 1;
}
cachedV = MatrixUtils.createRealMatrix(va);
}
// return the cached matrix
return cachedV;
}
/**
* Get the Householder vectors of the transform.
* <p>Note that since this class is only intended for internal use,
* it returns directly a reference to its internal arrays, not a copy.</p>
* @return the main diagonal elements of the B matrix
*/
double[][] getHouseholderVectorsRef() {
return householderVectors; // NOPMD - returning an internal array is intentional and documented here
}
/**
* Get the main diagonal elements of the matrix B of the transform.
* <p>Note that since this class is only intended for internal use,
* it returns directly a reference to its internal arrays, not a copy.</p>
* @return the main diagonal elements of the B matrix
*/
double[] getMainDiagonalRef() {
return main; // NOPMD - returning an internal array is intentional and documented here
}
/**
* Get the secondary diagonal elements of the matrix B of the transform.
* <p>Note that since this class is only intended for internal use,
* it returns directly a reference to its internal arrays, not a copy.</p>
* @return the secondary diagonal elements of the B matrix
*/
double[] getSecondaryDiagonalRef() {
return secondary; // NOPMD - returning an internal array is intentional and documented here
}
/**
* Check if the matrix is transformed to upper bi-diagonal.
* @return true if the matrix is transformed to upper bi-diagonal
*/
boolean isUpperBiDiagonal() {
return householderVectors.length >= householderVectors[0].length;
}
/**
* Transform original matrix to upper bi-diagonal form.
* <p>Transformation is done using alternate Householder transforms
* on columns and rows.</p>
*/
private void transformToUpperBiDiagonal() {
final int m = householderVectors.length;
final int n = householderVectors[0].length;
for (int k = 0; k < n; k++) {
//zero-out a column
double xNormSqr = 0;
for (int i = k; i < m; ++i) {
final double c = householderVectors[i][k];
xNormSqr += c * c;
}
final double[] hK = householderVectors[k];
final double a = (hK[k] > 0) ? -FastMath.sqrt(xNormSqr) : FastMath.sqrt(xNormSqr);
main[k] = a;
if (a != 0.0) {
hK[k] -= a;
for (int j = k + 1; j < n; ++j) {
double alpha = 0;
for (int i = k; i < m; ++i) {
final double[] hI = householderVectors[i];
alpha -= hI[j] * hI[k];
}
alpha /= a * householderVectors[k][k];
for (int i = k; i < m; ++i) {
final double[] hI = householderVectors[i];
hI[j] -= alpha * hI[k];
}
}
}
if (k < n - 1) {
//zero-out a row
xNormSqr = 0;
for (int j = k + 1; j < n; ++j) {
final double c = hK[j];
xNormSqr += c * c;
}
final double b = (hK[k + 1] > 0) ? -FastMath.sqrt(xNormSqr) : FastMath.sqrt(xNormSqr);
secondary[k] = b;
if (b != 0.0) {
hK[k + 1] -= b;
for (int i = k + 1; i < m; ++i) {
final double[] hI = householderVectors[i];
double beta = 0;
for (int j = k + 1; j < n; ++j) {
beta -= hI[j] * hK[j];
}
beta /= b * hK[k + 1];
for (int j = k + 1; j < n; ++j) {
hI[j] -= beta * hK[j];
}
}
}
}
}
}
/**
* Transform original matrix to lower bi-diagonal form.
* <p>Transformation is done using alternate Householder transforms
* on rows and columns.</p>
*/
private void transformToLowerBiDiagonal() {
final int m = householderVectors.length;
final int n = householderVectors[0].length;
for (int k = 0; k < m; k++) {
//zero-out a row
final double[] hK = householderVectors[k];
double xNormSqr = 0;
for (int j = k; j < n; ++j) {
final double c = hK[j];
xNormSqr += c * c;
}
final double a = (hK[k] > 0) ? -FastMath.sqrt(xNormSqr) : FastMath.sqrt(xNormSqr);
main[k] = a;
if (a != 0.0) {
hK[k] -= a;
for (int i = k + 1; i < m; ++i) {
final double[] hI = householderVectors[i];
double alpha = 0;
for (int j = k; j < n; ++j) {
alpha -= hI[j] * hK[j];
}
alpha /= a * householderVectors[k][k];
for (int j = k; j < n; ++j) {
hI[j] -= alpha * hK[j];
}
}
}
if (k < m - 1) {
//zero-out a column
final double[] hKp1 = householderVectors[k + 1];
xNormSqr = 0;
for (int i = k + 1; i < m; ++i) {
final double c = householderVectors[i][k];
xNormSqr += c * c;
}
final double b = (hKp1[k] > 0) ? -FastMath.sqrt(xNormSqr) : FastMath.sqrt(xNormSqr);
secondary[k] = b;
if (b != 0.0) {
hKp1[k] -= b;
for (int j = k + 1; j < n; ++j) {
double beta = 0;
for (int i = k + 1; i < m; ++i) {
final double[] hI = householderVectors[i];
beta -= hI[j] * hI[k];
}
beta /= b * hKp1[k];
for (int i = k + 1; i < m; ++i) {
final double[] hI = householderVectors[i];
hI[j] -= beta * hI[k];
}
}
}
}
}
}
}