SemiDefinitePositiveCholeskyDecomposition.java

  1. /*
  2.  * Licensed to the Hipparchus project 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 Hipparchus project 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.  *      https://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.hipparchus.linear;

  18. import java.util.Arrays;

  19. import org.hipparchus.exception.LocalizedCoreFormats;
  20. import org.hipparchus.exception.MathIllegalArgumentException;
  21. import org.hipparchus.util.FastMath;

  22. /**
  23.  * Calculates the Cholesky decomposition of a positive semidefinite matrix.
  24.  * <p>
  25.  * The classic Cholesky decomposition ({@link CholeskyDecomposition}) applies to real
  26.  * symmetric positive-definite matrix. This class extends the Cholesky decomposition to
  27.  * positive semidefinite matrix. The main application is for estimation based on the
  28.  * Unscented Kalman Filter.
  29.  *
  30.  * @see "J. Hartikainen, A. Solin, and S. Särkkä. Optimal filtering with Kalman filters and smoothers,
  31.  *       Dept. of Biomedica Engineering and Computational Sciences, Aalto University School of Science, Aug. 2011."
  32.  *
  33.  * @since 2.2
  34.  */
  35. public class SemiDefinitePositiveCholeskyDecomposition {

  36.     /** Default threshold below which elements are not considered positive. */
  37.     public static final double POSITIVITY_THRESHOLD = 1.0e-15;
  38.     /** Cached value of L. */
  39.     private RealMatrix cachedL;
  40.     /** Cached value of LT. */
  41.     private RealMatrix cachedLT;

  42.     /**
  43.      * Calculates the Cholesky decomposition of the given matrix.
  44.      * @param matrix the matrix to decompose
  45.      * @throws MathIllegalArgumentException if the matrix is not square.
  46.      * @see #SemiDefinitePositiveCholeskyDecomposition(RealMatrix, double)
  47.      * @see #POSITIVITY_THRESHOLD
  48.      */
  49.     public SemiDefinitePositiveCholeskyDecomposition(final RealMatrix matrix) {
  50.         this(matrix, POSITIVITY_THRESHOLD);
  51.     }

  52.     /**
  53.      * Calculates the Cholesky decomposition of the given matrix.
  54.      * @param matrix the matrix to decompose
  55.      * @param positivityThreshold threshold below which elements are not considered positive
  56.      * @throws MathIllegalArgumentException if the matrix is not square.
  57.      */
  58.     public SemiDefinitePositiveCholeskyDecomposition(final RealMatrix matrix,
  59.                                                      final double positivityThreshold) {
  60.         if (!matrix.isSquare()) {
  61.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NON_SQUARE_MATRIX,
  62.                                                    matrix.getRowDimension(), matrix.getColumnDimension());
  63.         }

  64.         final int order = matrix.getRowDimension();
  65.         final double[][] lTData = matrix.getData();
  66.         cachedL  = MatrixUtils.createRealMatrix(lTData);
  67.         int def  = 1;

  68.         final double[] zeroArray = new double[order];
  69.         Arrays.fill(zeroArray, 0.);

  70.         for (int i = 0; i < order; ++i) {
  71.             cachedL.setColumn(i, zeroArray);
  72.         }

  73.         for (int i = 0; i < order; ++i) {
  74.             for (int j = 0; j < i + 1; j++) {
  75.                 double s = lTData[i][j];
  76.                 for (int k = 0; k < j; ++k) {
  77.                     s = s - cachedL.getEntry(i, k) * cachedL.getEntry(j, k);
  78.                 }
  79.                 if (j < i) {
  80.                     if (cachedL.getEntry(j, j) > FastMath.ulp(1.0)) {
  81.                         cachedL.setEntry(i, j, s / cachedL.getEntry(j, j));
  82.                     } else {
  83.                         cachedL.setEntry(i, j, 0.);
  84.                     }
  85.                 } else {
  86.                     if (s < -positivityThreshold) {
  87.                         s = 0;
  88.                         def = -1;
  89.                     } else if (s < positivityThreshold) {
  90.                         s = 0;
  91.                         def = FastMath.min(0, def);
  92.                     }
  93.                     cachedL.setEntry(j, j, FastMath.sqrt(s));
  94.                 }

  95.             }
  96.         }

  97.         cachedLT = cachedL.transpose();

  98.         if (def < 0) {
  99.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NEGATIVE_DEFINITE_MATRIX);
  100.         }

  101.     }

  102.     /**
  103.      * Returns the matrix L of the decomposition.
  104.      * <p>L is an lower-triangular matrix</p>
  105.      * @return the L matrix
  106.      */
  107.     public RealMatrix getL() {
  108.         // return the cached matrix
  109.         return cachedL;
  110.     }

  111.     /**
  112.      * Returns the transpose of the matrix L of the decomposition.
  113.      * <p>L<sup>T</sup> is an upper-triangular matrix</p>
  114.      * @return the transpose of the matrix L of the decomposition
  115.      */
  116.     public RealMatrix getLT() {
  117.          // return the cached matrix
  118.         return cachedLT;
  119.     }

  120. }