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.filtering.kalman;
19  
20  import org.hipparchus.exception.MathIllegalArgumentException;
21  import org.hipparchus.linear.MatrixDecomposer;
22  import org.hipparchus.linear.RealMatrix;
23  import org.hipparchus.linear.RealVector;
24  
25  /**
26   * Shared parts between linear and non-linear Kalman filters.
27   * @param <T> the type of the measurements
28   * @since 1.3
29   */
30  public abstract class AbstractKalmanFilter<T extends Measurement> implements KalmanFilter<T> {
31  
32      /** Decomposer decomposer to use for the correction phase. */
33      private final MatrixDecomposer decomposer;
34  
35      /** Predicted state. */
36      private ProcessEstimate predicted;
37  
38      /** Corrected state. */
39      private ProcessEstimate corrected;
40  
41      /** Simple constructor.
42       * @param decomposer decomposer to use for the correction phase
43       * @param initialState initial state
44       */
45      protected AbstractKalmanFilter(final MatrixDecomposer decomposer, final ProcessEstimate initialState) {
46          this.decomposer = decomposer;
47          this.corrected  = initialState;
48      }
49  
50      /** Perform prediction step.
51       * @param time process time
52       * @param predictedState predicted state vector
53       * @param stm state transition matrix
54       * @param noise process noise covariance matrix
55       */
56      protected void predict(final double time, final RealVector predictedState, final RealMatrix stm, final RealMatrix noise) {
57          final RealMatrix predictedCovariance =
58                          stm.multiply(corrected.getCovariance().multiplyTransposed(stm)).add(noise);
59          predicted = new ProcessEstimate(time, predictedState, predictedCovariance);
60          corrected = null;
61      }
62  
63      /** Compute innovation covariance matrix.
64       * @param r measurement covariance
65       * @param h Jacobian of the measurement with respect to the state
66       * (may be null if measurement should be ignored)
67       * @return innovation covariance matrix, defined as \(h.P.h^T + r\), or
68       * null if h is null
69       */
70      protected RealMatrix computeInnovationCovarianceMatrix(final RealMatrix r, final RealMatrix h) {
71          if (h == null) {
72              return null;
73          }
74          final RealMatrix phT = predicted.getCovariance().multiplyTransposed(h);
75          return h.multiply(phT).add(r);
76      }
77  
78      /** Perform correction step.
79       * @param measurement single measurement to handle
80       * @param stm state transition matrix
81       * @param innovation innovation vector (i.e. residuals)
82       * (may be null if measurement should be ignored)
83       * @param h Jacobian of the measurement with respect to the state
84       * (may be null if measurement should be ignored)
85       * @param s innovation covariance matrix
86       * (may be null if measurement should be ignored)
87       * @exception MathIllegalArgumentException if matrix cannot be decomposed
88       */
89      protected void correct(final T measurement, final RealMatrix stm, final RealVector innovation,
90                             final RealMatrix h, final RealMatrix s)
91          throws MathIllegalArgumentException {
92  
93          if (innovation == null) {
94              // measurement should be ignored
95              corrected = predicted;
96              return;
97          }
98  
99          // compute Kalman gain k
100         // the following is equivalent to k = p.h^T * (h.p.h^T + r)^(-1)
101         // we don't want to compute the inverse of a matrix,
102         // we start by post-multiplying by h.p.h^T + r and get
103         // k.(h.p.h^T + r) = p.h^T
104         // then we transpose, knowing that both p and r are symmetric matrices
105         // (h.p.h^T + r).k^T = h.p
106         // then we can use linear system solving instead of matrix inversion
107         final RealMatrix k = decomposer.
108                              decompose(s).
109                              solve(h.multiply(predicted.getCovariance())).
110                              transpose();
111 
112         // correct state vector
113         final RealVector correctedState = predicted.getState().add(k.operate(innovation));
114 
115         // here we use the Joseph algorithm (see "Fundamentals of Astrodynamics and Applications,
116         // Vallado, Fourth Edition ยง10.6 eq.10-34) which is equivalent to
117         // the traditional Pest = (I - k.h) x Ppred expression but guarantees the output stays symmetric:
118         // Pest = (I -k.h) Ppred (I - k.h)^T + k.r.k^T
119         final RealMatrix idMkh = k.multiply(h);
120         for (int i = 0; i < idMkh.getRowDimension(); ++i) {
121             for (int j = 0; j < idMkh.getColumnDimension(); ++j) {
122                 idMkh.multiplyEntry(i, j, -1);
123             }
124             idMkh.addToEntry(i, i, 1.0);
125         }
126         final RealMatrix r = measurement.getCovariance();
127         final RealMatrix correctedCovariance =
128                         idMkh.multiply(predicted.getCovariance()).multiplyTransposed(idMkh).
129                         add(k.multiply(r).multiplyTransposed(k));
130 
131         corrected = new ProcessEstimate(measurement.getTime(), correctedState, correctedCovariance,
132                                         stm, h, s, k);
133 
134     }
135 
136     /** Get the predicted state.
137      * @return predicted state
138      */
139     @Override
140     public ProcessEstimate getPredicted() {
141         return predicted;
142     }
143 
144     /** Get the corrected state.
145      * @return corrected state
146      */
147     @Override
148     public ProcessEstimate getCorrected() {
149         return corrected;
150     }
151 
152 }