View Javadoc
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  
18  package org.hipparchus.linear;
19  
20  import java.util.Arrays;
21  
22  import org.hipparchus.exception.LocalizedCoreFormats;
23  import org.hipparchus.exception.MathIllegalArgumentException;
24  import org.hipparchus.util.FastMath;
25  
26  /**
27   * Calculates the Cholesky decomposition of a positive semidefinite matrix.
28   * <p>
29   * The classic Cholesky decomposition ({@link CholeskyDecomposition}) applies to real
30   * symmetric positive-definite matrix. This class extends the Cholesky decomposition to
31   * positive semidefinite matrix. The main application is for estimation based on the
32   * Unscented Kalman Filter.
33   *
34   * @see "J. Hartikainen, A. Solin, and S. Särkkä. Optimal filtering with Kalman filters and smoothers,
35   *       Dept. of Biomedica Engineering and Computational Sciences, Aalto University School of Science, Aug. 2011."
36   *
37   * @since 2.2
38   */
39  public class SemiDefinitePositiveCholeskyDecomposition {
40  
41      /** Default threshold below which elements are not considered positive. */
42      public static final double POSITIVITY_THRESHOLD = 1.0e-15;
43      /** Cached value of L. */
44      private RealMatrix cachedL;
45      /** Cached value of LT. */
46      private RealMatrix cachedLT;
47  
48      /**
49       * Calculates the Cholesky decomposition of the given matrix.
50       * @param matrix the matrix to decompose
51       * @throws MathIllegalArgumentException if the matrix is not square.
52       * @see #SemiDefinitePositiveCholeskyDecomposition(RealMatrix, double)
53       * @see #POSITIVITY_THRESHOLD
54       */
55      public SemiDefinitePositiveCholeskyDecomposition(final RealMatrix matrix) {
56          this(matrix, POSITIVITY_THRESHOLD);
57      }
58  
59      /**
60       * Calculates the Cholesky decomposition of the given matrix.
61       * @param matrix the matrix to decompose
62       * @param positivityThreshold threshold below which elements are not considered positive
63       * @throws MathIllegalArgumentException if the matrix is not square.
64       */
65      public SemiDefinitePositiveCholeskyDecomposition(final RealMatrix matrix,
66                                                       final double positivityThreshold) {
67          if (!matrix.isSquare()) {
68              throw new MathIllegalArgumentException(LocalizedCoreFormats.NON_SQUARE_MATRIX,
69                                                     matrix.getRowDimension(), matrix.getColumnDimension());
70          }
71  
72          final int order = matrix.getRowDimension();
73          final double[][] lTData = matrix.getData();
74          cachedL  = MatrixUtils.createRealMatrix(lTData);
75          int def  = 1;
76  
77          final double[] zeroArray = new double[order];
78          Arrays.fill(zeroArray, 0.);
79  
80          for (int i = 0; i < order; ++i) {
81              cachedL.setColumn(i, zeroArray);
82          }
83  
84          cachedLT = cachedL.transpose();
85  
86          for (int i = 0; i < order; ++i) {
87              for (int j = 0; j < i + 1; j++) {
88                  double s = lTData[i][j];
89                  for (int k = 0; k < j; ++k) {
90                      s = s - cachedL.getEntry(i, k) * cachedL.getEntry(j, k);
91                  }
92                  if (j < i) {
93                      if (cachedL.getEntry(j, j) > FastMath.ulp(1.0)) {
94                          cachedL.setEntry(i, j, s / cachedL.getEntry(j, j));
95                      } else {
96                          cachedL.setEntry(i, j, 0.);
97                      }
98                  } else {
99                      if (s < -positivityThreshold) {
100                         s = 0;
101                         def = -1;
102                     } else if (s < positivityThreshold) {
103                         s = 0;
104                         def = FastMath.min(0, def);
105                     }
106                     cachedL.setEntry(j, j, FastMath.sqrt(s));
107                 }
108 
109             }
110         }
111 
112         if (def < 0) {
113             throw new MathIllegalArgumentException(LocalizedCoreFormats.NEGATIVE_DEFINITE_MATRIX);
114         }
115 
116     }
117 
118     /**
119      * Returns the matrix L of the decomposition.
120      * <p>L is an lower-triangular matrix</p>
121      * @return the L matrix
122      */
123     public RealMatrix getL() {
124         // return the cached matrix
125         return cachedL;
126     }
127 
128     /**
129      * Returns the transpose of the matrix L of the decomposition.
130      * <p>L<sup>T</sup> is an upper-triangular matrix</p>
131      * @return the transpose of the matrix L of the decomposition
132      */
133     public RealMatrix getLT() {
134         cachedLT = getL().transpose();
135         // return the cached matrix
136         return cachedLT;
137     }
138 
139 }