View Javadoc
1   /*
2    * Copyright (C) 2015 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.numerical.signal.processing;
17  
18  import com.irurueta.algebra.AlgebraException;
19  import com.irurueta.algebra.Matrix;
20  import com.irurueta.algebra.Utils;
21  import com.irurueta.algebra.WrongSizeException;
22  
23  import java.io.Serializable;
24  
25  /**
26   * Implementation of a Kalman filter.
27   * This class contains the state of a Kalman kilter.
28   * The state of a Kalman filter is updated by
29   * <code>predict</code> and <code>correct</code> functions.
30   * <p>
31   * The source code, notation and formulae below are borrowed from the JKalman
32   * tutorial <a href="http://www.cs.unc.edu/~welch/kalman/">[Welch95]</a>:
33   * <pre>
34   * {@code
35   * x<sub>k</sub>=A*x<sub>k-1</sub>+B*u<sub>k</sub>+w<sub>k</sub>
36   * z<sub>k</sub>=Hx<sub>k</sub>+v<sub>k</sub>,
37   * }
38   * </pre>
39   * <p>where:
40   * <pre>
41   * {@code x<sub>k</sub> (x<sub>k-1</sub>)} - state of the system at the moment k (k-1)
42   * {@code z<sub>k</sub>} - measurement of the system state at the moment k
43   * {@code u<sub>k</sub>} - external control applied at the moment k
44   * {@code w<sub>k</sub>} and {@code v<sub>k</sub>} are normally-distributed process and
45   * measurement noise, respectively:
46   * p(w) ~ N(0,Q)
47   * p(v) ~ N(0,R),
48   * that is,
49   * Q - process noise covariance matrix, constant or variable,
50   * R - measurement noise covariance matrix, constant or variable
51   * </pre>
52   * <p>
53   * In case of standard Kalman filter, all the matrices: A, B, H, Q and R
54   * are initialized once after Kalman structure is allocated via constructor.
55   * However, the same structure and the same functions may be used to simulate
56   * extended Kalman filter by linearizing extended Kalman filter equation in the
57   * current system state neighborhood, in this case A, B, H (and, probably,
58   * Q and R) should be updated on every step.
59   */
60  public class KalmanFilter implements Serializable {
61  
62      /**
63       * Independent process noise variance assumed when no process noise
64       * covariance matrix is provided.
65       * The lower the process variance the smoother the estimated state will
66       * typically be.
67       */
68      public static final double DEFAULT_PROCESS_NOISE_VARIANCE = 1e-6;
69  
70      /**
71       * Independent measurement noise variance assumed when no measurement noise
72       * covariance matrix is provided.
73       */
74      public static final double DEFAULT_MEASUREMENT_NOISE_VARIANCE = 1e-1;
75  
76      /**
77       * Number of measurement vector dimensions (measure parameters).
78       */
79      private int mp;
80  
81      /**
82       * Number of state vector dimensions (dynamic parameters).
83       */
84      private final int dp;
85  
86      /**
87       * Number of control vector dimensions (control parameters).
88       */
89      private final int cp;
90  
91      /**
92       * Predicted state (x'(k)): x(k)=A*x(k-1)+B*u(k)
93       */
94      private Matrix statePre;
95  
96      /**
97       * Corrected state (x(k)): x(k)=x'(k)+K(k)*(z(k)-H*x'(k))
98       */
99      private Matrix statePost;
100 
101     /**
102      * State transition matrix (A).
103      */
104     private Matrix transitionMatrix;
105 
106     /**
107      * Control matrix (B) (it is not used if there is no control).
108      */
109     private Matrix controlMatrix;
110 
111     /**
112      * Measurement matrix (H).
113      */
114     private Matrix measurementMatrix;
115 
116     /**
117      * Process noise covariance matrix (Q).
118      */
119     private Matrix processNoiseCov;
120 
121     /**
122      * Measurement noise covariance matrix (R).
123      */
124     private Matrix measurementNoiseCov;
125 
126     /**
127      * Priori error estimate covariance matrix (P'(k)): P'(k)=A*P(k-1)*At + Q)
128      */
129     private Matrix errorCovPre;
130 
131     /**
132      * Kalman gain matrix (K(k)): K(k)=P'(k)*Ht*inv(H*P'(k)*Ht+R)
133      */
134     private Matrix gain;
135 
136     /**
137      * Posteriori error estimate covariance matrix (P(k)): P(k)=(I-K(k)*H)*P'(k)
138      */
139     private Matrix errorCovPost;
140 
141     // temporary matrices to be reused to avoid unnecessary re-allocations
142 
143     /**
144      * Temporary matrix 1.
145      */
146     private final Matrix temp1;
147 
148     /**
149      * Temporary matrix 2.
150      */
151     private Matrix temp2;
152 
153     /**
154      * Temporary matrix 3.
155      */
156     private Matrix temp3;
157 
158     /**
159      * Temporary matrix 4.
160      */
161     private Matrix temp4;
162 
163     /**
164      * Temporary matrix 5.
165      */
166     private Matrix temp5;
167 
168     /**
169      * Temporary matrix 6.
170      */
171     private Matrix temp6;
172 
173     /**
174      * Temporary matrix 7.
175      */
176     private final Matrix temp7;
177 
178     /**
179      * Temporary matrix 8.
180      */
181     private Matrix temp8;
182 
183     /**
184      * Allocates a Kalman filter and all its matrices and initializes them.
185      *
186      * @param dynamParams   number of dynamic parameters (state vector dimensions).
187      * @param measureParams number of measurement parameters (measurement vector
188      *                      dimensions).
189      * @param controlParams number of control parameters (control vector.
190      *                      dimensions). If zero, no control parameters are used. If less than zero,
191      *                      it is assumed that this is equal to the number of dynamic parameters.
192      * @throws IllegalArgumentException  if either the number of dynamic or
193      *                                   measurement parameters is zero or negative.
194      * @throws SignalProcessingException if something else fails.
195      */
196     public KalmanFilter(final int dynamParams, final int measureParams, int controlParams)
197             throws SignalProcessingException {
198 
199         if (dynamParams <= 0 || measureParams <= 0) {
200             throw new IllegalArgumentException("Kalman filter: Illegal dimensions");
201         }
202 
203         if (controlParams < 0) {
204             controlParams = dynamParams;
205         }
206 
207         // init
208         dp = dynamParams;
209         mp = measureParams;
210         cp = controlParams;
211 
212         try {
213             statePre = new Matrix(dp, 1);
214 
215             // following variables must be initialized properly in advance
216             statePost = new Matrix(dp, 1);
217             transitionMatrix = Matrix.identity(dp, dp);
218 
219             processNoiseCov = Matrix.identity(dp, dp);
220             processNoiseCov.multiplyByScalar(DEFAULT_PROCESS_NOISE_VARIANCE);
221 
222             measurementMatrix = Matrix.identity(mp, dp);
223             measurementNoiseCov = Matrix.identity(mp, mp);
224             measurementNoiseCov.multiplyByScalar(DEFAULT_MEASUREMENT_NOISE_VARIANCE);
225 
226             errorCovPre = new Matrix(dp, dp);
227             errorCovPost = Matrix.identity(dp, dp);
228 
229             gain = new Matrix(dp, mp);
230 
231             if (cp > 0) {
232                 controlMatrix = new Matrix(dp, cp);
233             } else {
234                 // no control parameters
235                 controlMatrix = null;
236             }
237 
238             temp1 = new Matrix(dp, dp);
239             temp2 = new Matrix(mp, dp);
240             temp3 = new Matrix(mp, mp);
241             temp4 = new Matrix(mp, dp);
242             temp5 = new Matrix(mp, 1);
243 
244             if (cp > 0) {
245                 temp6 = new Matrix(dp, 1);
246             }
247 
248             temp7 = new Matrix(dp, dp);
249             temp8 = new Matrix(dp, mp);
250         } catch (final AlgebraException ex) {
251             throw new SignalProcessingException(ex);
252         }
253     }
254 
255     /**
256      * Constructor in case of no control parameters.
257      *
258      * @param dynamParams   number of dynamic parameters (state vector dimensions).
259      * @param measureParams number of measurement parameters (measurement vector
260      *                      dimensions).
261      * @throws IllegalArgumentException  if either the number of dynamic or
262      *                                   measurement parameters is zero or negative.
263      * @throws SignalProcessingException if something else fails.
264      */
265     public KalmanFilter(final int dynamParams, final int measureParams) throws SignalProcessingException {
266         this(dynamParams, measureParams, 0);
267     }
268 
269     /**
270      * Estimates subsequent model state without control parameters.
271      *
272      * @return estimated state.
273      * @throws SignalProcessingException if something fails.
274      * @see #predict(Matrix)
275      */
276     public Matrix predict() throws SignalProcessingException {
277         return predict(null);
278     }
279 
280     /**
281      * Estimates subsequent model state.
282      * The function estimates the subsequent stochastic model state by its
283      * current state and stores it at <code>statePre</code>:
284      * <pre>
285      * {@code
286      * x'<sub>k</sub>=A*x<sub>k</sub>+B*u<sub>k</sub>
287      * P'<sub>k</sub>=A*P<sub>k-1</sub>*A<sup>T</sup> + Q,
288      * where
289      * x'<sub>k</sub> is predicted state (statePre),
290      * x<sub>k-1</sub> is corrected state on the previous step (statePost)
291      *     (should be initialized somehow in the beginning, zero vector by
292      * default),
293      * u<sub>k</sub> is external control (<code>control</code> parameter),
294      * P'<sub>k</sub> is prior error covariance matrix (error_cov_pre)
295      * P<sub>k-1</sub> is posteriori error covariance matrix on the previous
296      * step (error_cov_post)
297      *     (should be initialized somehow in the beginning, identity matrix by
298      * default),
299      * }
300      * </pre>
301      *
302      * @param control control vector (u<sub>k</sub>), should be null if there is
303      *                no external control (<code>controlParams</code>=0). If provided and
304      *                filter uses control parameters, it must be a 1 column matrix having
305      *                cp rows (where cp = number of control parameters), otherwise a
306      *                SignalProcessingException will be raised.
307      * @return estimated state as a 1 column matrix having dp rows (where dp =
308      * number of dynamic parameters).
309      * @throws SignalProcessingException if something fails.
310      */
311     public Matrix predict(final Matrix control) throws SignalProcessingException {
312         try {
313             // (1) Project the state ahead
314             // update the state: x'(k) = A*x(k)
315             transitionMatrix.multiply(statePost, statePre);
316             if (control != null && cp > 0) {
317                 // x'(k) = x'(k) + B*u(k)
318                 controlMatrix.multiply(control, temp6);
319                 statePre.add(temp6);
320             }
321 
322             // (2) Project the error covariance ahead
323             // update error covariance matrices: temp1 = A * P(k)
324             transitionMatrix.multiply(errorCovPost, temp1);
325             // P'(k) = temp1 * At + Q
326             transitionMatrix.transpose(temp7);
327             temp1.multiply(temp7);
328             temp1.add(processNoiseCov);
329             errorCovPre = temp1;
330 
331             return statePre;
332         } catch (final AlgebraException e) {
333             throw new SignalProcessingException(e);
334         }
335     }
336 
337     /**
338      * Adjusts model state.
339      * This method adjusts stochastic model state on the basis of the given
340      * measurement of the model state:
341      * <pre>
342      * {@code
343      * K<sub>k</sub>=P'<sub>k</sub>*H<sup>T</sup>*(H*P'<sub>k</sub>*H<sup>T</sup>+R)<sup>-1</sup>
344      * x<sub>k</sub>=x'<sub>k</sub>+K<sub>k</sub>*(z<sub>k</sub>-H*x'<sub>k</sub>)
345      * P<sub>k</sub>=(I-K<sub>k</sub>*H)*P'<sub>k</sub>
346      * where
347      * z<sub>k</sub> - given measurement (<code>measurement</code> parameter)
348      * K<sub>k</sub> - Kalman "gain" matrix.
349      * }
350      * </pre>
351      * <p>
352      * The function stores adjusted state at <code>statePost</code> and returns
353      * it on output.
354      *
355      * @param measurement matrix containing the measurement vector. Matrix must
356      *                    have 1 column and mp rows (mp = measurement parameters).
357      * @return adjusted model state.
358      * @throws SignalProcessingException if something fails.
359      */
360     public Matrix correct(final Matrix measurement) throws SignalProcessingException {
361         try {
362             // (1) compute the Kalman gain
363             // temp2 = H*P'(k)
364             measurementMatrix.multiply(errorCovPre, temp2);
365 
366             // temp3 = temp2*Ht + R
367             measurementMatrix.transpose(temp8);
368             temp2.multiply(temp8, temp3);
369             temp3.add(measurementNoiseCov);
370 
371             // temp4 = inv(temp3)*temp2 = Kt(k)
372             // which is also equivalent to:
373             // temp4 = temp3.svd().getU().times(temp2)
374             Utils.solve(temp3, temp2, temp4);
375 
376             // K(k)
377             temp4.transpose();
378             gain = temp4;
379 
380             // (2) Update estimate with measurement z(k)
381             //temp5 = z(k) - H*x'(k)
382             measurementMatrix.multiply(statePre, temp5);
383             temp5.multiplyByScalar(-1.0);
384             temp5.add(measurement);
385 
386             // x(k) = x'(k) + K(k)*temp5
387             gain.multiply(temp5, statePost);
388             statePost.add(statePre);
389 
390             // (3) Update the error covariance
391             // P(x) = P'(k) - K(k)*temp2
392             gain.multiply(temp2, errorCovPost);
393             errorCovPost.multiplyByScalar(-1.0);
394             errorCovPost.add(errorCovPre);
395 
396             return statePost;
397         } catch (final AlgebraException e) {
398             throw new SignalProcessingException(e);
399         }
400     }
401 
402     /**
403      * Obtains the number of measurement vector dimensions (measure parameters).
404      *
405      * @return number of measurement vector dimensions (measure parameters)
406      */
407     public int getMeasureParameters() {
408         return mp;
409     }
410 
411     /**
412      * Sets the number of measurement vector dimensions (measure parameters).
413      *
414      * @param measureParameters number of measurement vector dimensions (measure
415      *                          parameters).
416      *                          NOTE: when resetting number of measure parameters, the measurement noise
417      *                          covariance matrix and the measurement matrix get reset to their default
418      *                          values having the required new size. Please, make sure those matrices
419      *                          are reset to their proper values after calling this method.
420      * @throws IllegalArgumentException  if provided value is zero or negative.
421      * @throws SignalProcessingException if something else fails
422      */
423     public void setMeasureParameters(final int measureParameters) throws SignalProcessingException {
424         if (measureParameters <= 0) {
425             throw new IllegalArgumentException("");
426         }
427         mp = measureParameters;
428 
429         try {
430             measurementMatrix = Matrix.identity(mp, dp);
431             measurementNoiseCov = Matrix.identity(mp, mp);
432             measurementNoiseCov.multiplyByScalar(DEFAULT_MEASUREMENT_NOISE_VARIANCE);
433 
434             gain = new Matrix(dp, mp);
435 
436             temp2 = new Matrix(mp, dp);
437             temp3 = new Matrix(mp, mp);
438             temp4 = new Matrix(mp, dp);
439             temp5 = new Matrix(mp, 1);
440 
441             temp8 = new Matrix(dp, mp);
442         } catch (final WrongSizeException e) {
443             throw new SignalProcessingException(e);
444         }
445     }
446 
447     /**
448      * Obtains the number of state vector dimensions (dynamic parameters).
449      *
450      * @return number of state vector dimensions (dynamic parameters)
451      */
452     public int getDynamicParameters() {
453         return dp;
454     }
455 
456     /**
457      * Obtains the number of control vector dimensions (control parameters).
458      *
459      * @return number of control vector dimensions (control parameters)
460      */
461     public int getControlParameters() {
462         return cp;
463     }
464 
465     /**
466      * Obtains predicted state (x'(k)): x(k)=A*x(k-1)+B*u(k).
467      * It is a column matrix having 1 column and dp rows, where dp is the
468      * number of dynamic parameters
469      *
470      * @return predicted state
471      */
472     public Matrix getStatePre() {
473         return statePre;
474     }
475 
476     /**
477      * Sets predicted state (x'(k)): x(k)=A*x(k-1)+B*u(k).
478      * Provided matrix must have 1 column and dp rows, where dp is the number
479      * of dynamic parameters set for this Kalman filter instance.
480      * This setter method can be used for initial setup purposes.
481      *
482      * @param statePre new predicted state.
483      * @throws IllegalArgumentException if provided matrix does not have 1
484      *                                  column and dp rows
485      */
486     public void setStatePre(final Matrix statePre) {
487         if (statePre.getColumns() != 1 || statePre.getRows() != dp) {
488             throw new IllegalArgumentException();
489         }
490         this.statePre = statePre;
491     }
492 
493     /**
494      * Obtains corrected state (x(k)): x(k)=x'(k)+K(k)*(z(k)-H*x'(k)).
495      * It is a column matrix having 1 column and dp rows, where dp is the
496      * number of dynamic parameters
497      *
498      * @return corrected state
499      */
500     public Matrix getStatePost() {
501         return statePost;
502     }
503 
504     /**
505      * Sets corrected state (x(k)): x(k)=x'(k)+K(k)*(z(k)-H*x'(k)).
506      * Provided matrix must have 1 column and dp rows, where dp is the number
507      * of dynamic parameters set for this Kalman filter instance.
508      * This setter method can be used for initial setup purposes.
509      *
510      * @param statePost new corrected state
511      * @throws IllegalArgumentException if provided matrix does not have 1
512      *                                  column and dp rows
513      */
514     public void setStatePost(final Matrix statePost) {
515         if (statePost.getColumns() != 1 || statePost.getRows() != dp) {
516             throw new IllegalArgumentException();
517         }
518         this.statePost = statePost;
519     }
520 
521     /**
522      * Obtains the state transition matrix (A).
523      * It is a square matrix having dp rows and columns, where dp is equal to
524      * the number of dynamic parameters.
525      * This matrix defines how the system transitions to a new state for a given
526      * previous state. It is used for prediction purposes
527      *
528      * @return state transition matrix
529      */
530     public Matrix getTransitionMatrix() {
531         return transitionMatrix;
532     }
533 
534     /**
535      * Sets the state transition matrix (A).
536      * It must be a square matrix having dp rows and columns, where dp is equal
537      * to the number of dynamic parameters set for this instance.
538      * This matrix defines how the system transitions to a new state for a given
539      * previous state. It is used for prediction purposes.
540      * This setter method can be used for initial setup purposes.
541      *
542      * @param transitionMatrix new state transition matrix
543      * @throws IllegalArgumentException if provided matrix does not have dp rows
544      *                                  and columns
545      */
546     public void setTransitionMatrix(final Matrix transitionMatrix) {
547         if (transitionMatrix.getRows() != dp || transitionMatrix.getColumns() != dp) {
548             throw new IllegalArgumentException();
549         }
550         this.transitionMatrix = transitionMatrix;
551     }
552 
553     /**
554      * Obtains the control matrix (B) (it is not used if there is no control).
555      * It's a matrix having dp rows and cp columns, where dp is the number of
556      * dynamic parameters and cp is the number of control parameters.
557      *
558      * @return control matrix
559      */
560     public Matrix getControlMatrix() {
561         return controlMatrix;
562     }
563 
564     /**
565      * Sets the control matrix (B) (it is not used if there is no control).
566      * Provided matrix must have dp rows and cp columns, where dp is the number
567      * of dynamic parameters and cp is the number of control parameters set for
568      * this Kalman filter instance.
569      * This setter method can be used for initial setup purposes.
570      *
571      * @param controlMatrix new control matrix to be set, or null if no control
572      *                      parameters are set
573      * @throws IllegalArgumentException if provided matrix does not have dp
574      *                                  rows and cp columns
575      */
576     public void setControlMatrix(final Matrix controlMatrix) {
577         if (cp > 0) {
578             if (controlMatrix == null || (controlMatrix.getRows() != dp || controlMatrix.getColumns() != cp)) {
579                 throw new IllegalArgumentException();
580             }
581         } else {
582             // control matrix cannot be set
583             throw new IllegalArgumentException();
584         }
585         this.controlMatrix = controlMatrix;
586     }
587 
588     /**
589      * Obtains measurement matrix (H).
590      * It's a matrix having mp rows and dp columns, where mp is the number
591      * of measurement parameters and dp is the number of dynamic parameters of
592      * the system state.
593      * This matrix relates obtained measures to the actual system state when a
594      * given model is known in advance. If no model is known and measures
595      * directly indicate the system state, then this matrix must be the
596      * identity.
597      *
598      * @return measurement matrix
599      */
600     public Matrix getMeasurementMatrix() {
601         return measurementMatrix;
602     }
603 
604     /**
605      * Sets measurement matrix (H).
606      * Provided matrix must have mp rows and dp columns, where mp is the number
607      * of measurement parameters and dp is the number of dynamic parameters of
608      * the system state.
609      * This matrix relates obtained measures to the actual system state when a
610      * given model is known in advance. If no model is known and measures
611      * directly indicate the system state, then this matrix must be the
612      * identity.
613      * This setter method can be used for initial setup purposes.
614      *
615      * @param measurementMatrix measurement matrix
616      * @throws IllegalArgumentException if provided matrix does not have mp rows
617      *                                  and dp columns.
618      */
619     public void setMeasurementMatrix(final Matrix measurementMatrix) {
620         if (measurementMatrix.getRows() != mp || measurementMatrix.getColumns() != dp) {
621             throw new IllegalArgumentException();
622         }
623         this.measurementMatrix = measurementMatrix;
624     }
625 
626     /**
627      * Obtains the process noise covariance matrix (Q).
628      * This is a covariance matrix indicating the correlations of the amount of
629      * error in the system state.
630      * It is a square symmetric matrix having dp rows and columns, where dp is
631      * the number of dynamic parameters containing the system state.
632      *
633      * @return the process noise covariance matrix
634      */
635     public Matrix getProcessNoiseCov() {
636         return processNoiseCov;
637     }
638 
639     /**
640      * Sets the process noise covariance matrix (Q).
641      * This is a covariance matrix indicating the correlations of the amount of
642      * error in the system state.
643      * It must be provided a square symmetric matrix having dp rows and columns,
644      * where dp is the number of dynamic parameters containing the system state
645      * for this instance of a Kalman filter.
646      * This setter method can be used for initial setup purposes, however
647      * typically the process noise is difficult to determine. This matrix is
648      * generally constructed intuitively so that un-modelled dynamics and
649      * parameter uncertainties are modeled as process noise generally. If
650      * the process noise is unknown, just leave the default value or provide
651      * a diagonal matrix with the desired level of variance Q, where a low Q
652      * variance indicates confidence that any unknown noise terms and/or
653      * modelling errors are small to negligible, and higher Q allows the tracker
654      * to follow the state despite unknown noise and/or model errors.
655      *
656      * @param processNoiseCov process noise covariance matrix
657      * @throws IllegalArgumentException if provided matrix does not have dp
658      *                                  rows and columns, or it is not symmetric
659      */
660     public void setProcessNoiseCov(final Matrix processNoiseCov) {
661         if (processNoiseCov.getRows() != dp || processNoiseCov.getColumns() != dp
662                 || !Utils.isSymmetric(processNoiseCov)) {
663             throw new IllegalArgumentException();
664         }
665 
666         this.processNoiseCov = processNoiseCov;
667     }
668 
669     /**
670      * Obtains the measurement noise covariance matrix (R).
671      * This is a covariance matrix indicating the correlations of the amount of
672      * error in the measures taken from the system.
673      * It is a square symmetric matrix having mp rows and columns, where mp is
674      * the number of measurement parameters.
675      * Typically, this matrix can be easily obtained by processing the
676      * measurements while the output of the system is held constant. In this
677      * case, only noise remains in the data after its mean is removed.
678      * The covariance can be calculated easily from the remaining portion of the
679      * data.
680      *
681      * @return the measurement noise covariance matrix
682      */
683     public Matrix getMeasurementNoiseCov() {
684         return measurementNoiseCov;
685     }
686 
687     /**
688      * Sets the measurement noise covariance matrix (R).
689      * This is a covariance matrix indicating the correlations of the amount of
690      * error in the measures taken from the system.
691      * Provided matrix must be a square symmetric matrix having mp rows and
692      * columns, where mp is the number of measurement parameters.
693      * Typically, this matrix can be easily obtained by processing the
694      * measurements while the output of the system is held constant. In this
695      * case, only noise remains in the data after its mean is removed.
696      * The covariance can be calculated easily from the remaining portion of the
697      * data.
698      * This setter method can be used for initial setup purposes.
699      *
700      * @param measurementNoiseCov new measurement noise covariance matrix
701      * @throws IllegalArgumentException if provided matrix does not have mp
702      *                                  rows and columns, or it is not symmetric
703      */
704     public void setMeasurementNoiseCov(final Matrix measurementNoiseCov) {
705         if (measurementNoiseCov.getRows() != mp || measurementNoiseCov.getColumns() != mp
706                 || !Utils.isSymmetric(measurementNoiseCov)) {
707             throw new IllegalArgumentException();
708         }
709 
710         this.measurementNoiseCov = measurementNoiseCov;
711     }
712 
713     /**
714      * Obtains the priori error estimate covariance matrix
715      * (P'(k)): P'(k)=A*P(k-1)*At + Q).
716      * It is a square symmetric matrix having dp rows and columns, where dp
717      * is the number of dynamic parameters of the system state
718      *
719      * @return the priori error estimate covariance matrix
720      */
721     public Matrix getErrorCovPre() {
722         return errorCovPre;
723     }
724 
725     /**
726      * Sets the priori error estimate covariance matrix
727      * (P'(k)): P'(k)=A*P(k-1)*At + Q).
728      * Provided matrix must be square and symmetric having dp rows and columns,
729      * where dp is the number of the dynamic parameters of the system state set
730      * for this Kalman filter instance.
731      * This setter method can be used for initial setup purposes, however this
732      * value will rarely need to be set, and instead the getter method will be
733      * used to obtain the error of the predicted system state once the filter
734      * converges
735      *
736      * @param errorCovPre new priori error estimate covariance matrix
737      * @throws IllegalArgumentException if provided matrix does not have dp rows
738      *                                  and columns, or it is not symmetric
739      */
740     public void setErrorCovPre(final Matrix errorCovPre) {
741         if (errorCovPre.getRows() != dp || errorCovPre.getColumns() != dp || !Utils.isSymmetric(errorCovPre)) {
742             throw new IllegalArgumentException();
743         }
744         this.errorCovPre = errorCovPre;
745     }
746 
747     /**
748      * Obtains the Kalman gain matrix (K(k)): K(k)=P'(k)*Ht*inv(H*P'(k)*Ht+R).
749      * This matrix is used to correct the predicted state, if the gain values
750      * are small then the filter is accurately tracking the system state and the
751      * prediction error remains small too.
752      * The gain matrix has dp rows and mp columns, where dp is the number of
753      * dynamic parameters and mp is the number of measure parameters.
754      *
755      * @return the Kalman gain matrix
756      */
757     public Matrix getGain() {
758         return gain;
759     }
760 
761     /**
762      * Sets the Kalman gain matrix (K(k)): K(k)=P'(k)*Ht*inv(H*P'(k)*Ht+R).
763      * This matrix is used to correct the predicted state, if the gain values
764      * are small then the filter is accurately tracking the system state and the
765      * prediction error remains small too.
766      * The gain matrix must have dp rows and mp columns, where dp is the number
767      * of dynamic parameters and mp is the number of measure parameters set for
768      * this Kalman filter instance.
769      * This setter method can be used for initial setup purposes, however this
770      * matrix rarely needs to be set, and instead it is better to let the filter
771      * converge to the actual system state.
772      *
773      * @param gain new gain matrix
774      * @throws IllegalArgumentException if provided matrix does not have dp rows
775      *                                  and mp columns
776      */
777     public void setGain(final Matrix gain) {
778         if (gain.getRows() != dp || gain.getColumns() != mp) {
779             throw new IllegalArgumentException("Wrong matrix size");
780         }
781         this.gain = gain;
782     }
783 
784     /**
785      * Obtains the posteriori error estimate covariance matrix
786      * (P(k)): P(k)=(I-K(k)*H)*P'(k).
787      * It is a square symmetric matrix having dp rows and columns, where dp
788      * is the number of dynamic parameters of the system state
789      *
790      * @return the priori error estimate covariance matrix
791      */
792     public Matrix getErrorCovPost() {
793         return errorCovPost;
794     }
795 
796     /**
797      * Sets the posteriori error estimate covariance matrix
798      * (P(k)): P(k)=(I-K(k)*H)*P'(k).
799      * Provided matrix must be square and symmetric having dp rows and columns,
800      * where dp is the number of the dynamic parameters of the system state set
801      * for this Kalman filter instance.
802      * This setter method can be used for initial setup purposes, however this
803      * value will rarely need to be set, and instead the getter method will be
804      * used to obtain the error of the posteriori system state once the filter
805      * converges
806      *
807      * @param errorCovPost new posteriori error estimate covariance matrix
808      * @throws IllegalArgumentException if provided matrix does not have dp rows
809      *                                  and columns, or it is not symmetric
810      */
811     public void setErrorCovPost(final Matrix errorCovPost) {
812         if (errorCovPost.getRows() != dp || errorCovPost.getColumns() != dp || !Utils.isSymmetric(errorCovPost)) {
813             throw new IllegalArgumentException();
814         }
815         this.errorCovPost = errorCovPost;
816     }
817 }