001// Copyright (c) FIRST and other WPILib contributors. 002// Open Source Software; you can modify and/or share it under the terms of 003// the WPILib BSD license file in the root directory of this project. 004 005package edu.wpi.first.math.estimator; 006 007import edu.wpi.first.math.DARE; 008import edu.wpi.first.math.MathSharedStore; 009import edu.wpi.first.math.MathUsageId; 010import edu.wpi.first.math.Matrix; 011import edu.wpi.first.math.Nat; 012import edu.wpi.first.math.Num; 013import edu.wpi.first.math.StateSpaceUtil; 014import edu.wpi.first.math.numbers.N1; 015import edu.wpi.first.math.system.Discretization; 016import edu.wpi.first.math.system.LinearSystem; 017 018/** 019 * A Kalman filter combines predictions from a model and measurements to give an estimate of the 020 * true system state. This is useful because many states cannot be measured directly as a result of 021 * sensor noise, or because the state is "hidden". 022 * 023 * <p>Kalman filters use a K gain matrix to determine whether to trust the model or measurements 024 * more. Kalman filter theory uses statistics to compute an optimal K gain which minimizes the sum 025 * of squares error in the state estimate. This K gain is used to correct the state estimate by some 026 * amount of the difference between the actual measurements and the measurements predicted by the 027 * model. 028 * 029 * <p>For more on the underlying math, read <a 030 * href="https://file.tavsys.net/control/controls-engineering-in-frc.pdf">https://file.tavsys.net/control/controls-engineering-in-frc.pdf</a> 031 * chapter 9 "Stochastic control theory". 032 * 033 * @param <States> Number of states. 034 * @param <Inputs> Number of inputs. 035 * @param <Outputs> Number of outputs. 036 */ 037public class KalmanFilter<States extends Num, Inputs extends Num, Outputs extends Num> 038 implements KalmanTypeFilter<States, Inputs, Outputs> { 039 private final Nat<States> m_states; 040 041 private final LinearSystem<States, Inputs, Outputs> m_plant; 042 private Matrix<States, N1> m_xHat; 043 private Matrix<States, States> m_P; 044 private final Matrix<States, States> m_contQ; 045 private final Matrix<Outputs, Outputs> m_contR; 046 private double m_dtSeconds; 047 048 private final Matrix<States, States> m_initP; 049 050 /** 051 * Constructs a Kalman filter with the given plant. 052 * 053 * <p>See <a 054 * href="https://docs.wpilib.org/en/stable/docs/software/advanced-controls/state-space/state-space-observers.html#process-and-measurement-noise-covariance-matrices">https://docs.wpilib.org/en/stable/docs/software/advanced-controls/state-space/state-space-observers.html#process-and-measurement-noise-covariance-matrices</a> 055 * for how to select the standard deviations. 056 * 057 * @param states A Nat representing the states of the system. 058 * @param outputs A Nat representing the outputs of the system. 059 * @param plant The plant used for the prediction step. 060 * @param stateStdDevs Standard deviations of model states. 061 * @param measurementStdDevs Standard deviations of measurements. 062 * @param dtSeconds Nominal discretization timestep. 063 * @throws IllegalArgumentException If the system is undetectable. 064 */ 065 public KalmanFilter( 066 Nat<States> states, 067 Nat<Outputs> outputs, 068 LinearSystem<States, Inputs, Outputs> plant, 069 Matrix<States, N1> stateStdDevs, 070 Matrix<Outputs, N1> measurementStdDevs, 071 double dtSeconds) { 072 this.m_states = states; 073 074 this.m_plant = plant; 075 076 m_contQ = StateSpaceUtil.makeCovarianceMatrix(states, stateStdDevs); 077 m_contR = StateSpaceUtil.makeCovarianceMatrix(outputs, measurementStdDevs); 078 m_dtSeconds = dtSeconds; 079 080 // Find discrete A and Q 081 var pair = Discretization.discretizeAQ(plant.getA(), m_contQ, dtSeconds); 082 var discA = pair.getFirst(); 083 var discQ = pair.getSecond(); 084 085 var discR = Discretization.discretizeR(m_contR, dtSeconds); 086 087 var C = plant.getC(); 088 089 m_initP = new Matrix<>(DARE.dare(discA.transpose(), C.transpose(), discQ, discR)); 090 091 reset(); 092 093 MathSharedStore.getMathShared().reportUsage(MathUsageId.kEstimator_KalmanFilter, 1); 094 } 095 096 /** 097 * Returns the error covariance matrix P. 098 * 099 * @return the error covariance matrix P. 100 */ 101 @Override 102 public Matrix<States, States> getP() { 103 return m_P; 104 } 105 106 /** 107 * Returns an element of the error covariance matrix P. 108 * 109 * @param row Row of P. 110 * @param col Column of P. 111 * @return the value of the error covariance matrix P at (i, j). 112 */ 113 @Override 114 public double getP(int row, int col) { 115 return m_P.get(row, col); 116 } 117 118 /** 119 * Sets the entire error covariance matrix P. 120 * 121 * @param newP The new value of P to use. 122 */ 123 @Override 124 public void setP(Matrix<States, States> newP) { 125 m_P = newP; 126 } 127 128 /** 129 * Returns the state estimate x-hat. 130 * 131 * @return the state estimate x-hat. 132 */ 133 @Override 134 public Matrix<States, N1> getXhat() { 135 return m_xHat; 136 } 137 138 /** 139 * Returns an element of the state estimate x-hat. 140 * 141 * @param row Row of x-hat. 142 * @return the value of the state estimate x-hat at that row. 143 */ 144 @Override 145 public double getXhat(int row) { 146 return m_xHat.get(row, 0); 147 } 148 149 /** 150 * Set initial state estimate x-hat. 151 * 152 * @param xHat The state estimate x-hat. 153 */ 154 @Override 155 public void setXhat(Matrix<States, N1> xHat) { 156 m_xHat = xHat; 157 } 158 159 /** 160 * Set an element of the initial state estimate x-hat. 161 * 162 * @param row Row of x-hat. 163 * @param value Value for element of x-hat. 164 */ 165 @Override 166 public void setXhat(int row, double value) { 167 m_xHat.set(row, 0, value); 168 } 169 170 @Override 171 public final void reset() { 172 m_xHat = new Matrix<>(m_states, Nat.N1()); 173 m_P = m_initP; 174 } 175 176 /** 177 * Project the model into the future with a new control input u. 178 * 179 * @param u New control input from controller. 180 * @param dtSeconds Timestep for prediction. 181 */ 182 @Override 183 public void predict(Matrix<Inputs, N1> u, double dtSeconds) { 184 // Find discrete A and Q 185 final var discPair = Discretization.discretizeAQ(m_plant.getA(), m_contQ, dtSeconds); 186 final var discA = discPair.getFirst(); 187 final var discQ = discPair.getSecond(); 188 189 m_xHat = m_plant.calculateX(m_xHat, u, dtSeconds); 190 191 // Pₖ₊₁⁻ = APₖ⁻Aᵀ + Q 192 m_P = discA.times(m_P).times(discA.transpose()).plus(discQ); 193 194 m_dtSeconds = dtSeconds; 195 } 196 197 /** 198 * Correct the state estimate x-hat using the measurements in y. 199 * 200 * @param u Same control input used in the predict step. 201 * @param y Measurement vector. 202 */ 203 @Override 204 public void correct(Matrix<Inputs, N1> u, Matrix<Outputs, N1> y) { 205 correct(u, y, m_contR); 206 } 207 208 /** 209 * Correct the state estimate x-hat using the measurements in y. 210 * 211 * <p>This is useful for when the measurement noise covariances vary. 212 * 213 * @param u Same control input used in the predict step. 214 * @param y Measurement vector. 215 * @param R Continuous measurement noise covariance matrix. 216 */ 217 public void correct(Matrix<Inputs, N1> u, Matrix<Outputs, N1> y, Matrix<Outputs, Outputs> R) { 218 final var C = m_plant.getC(); 219 final var D = m_plant.getD(); 220 221 final var discR = Discretization.discretizeR(R, m_dtSeconds); 222 223 final var S = C.times(m_P).times(C.transpose()).plus(discR); 224 225 // We want to put K = PCᵀS⁻¹ into Ax = b form so we can solve it more 226 // efficiently. 227 // 228 // K = PCᵀS⁻¹ 229 // KS = PCᵀ 230 // (KS)ᵀ = (PCᵀ)ᵀ 231 // SᵀKᵀ = CPᵀ 232 // 233 // The solution of Ax = b can be found via x = A.solve(b). 234 // 235 // Kᵀ = Sᵀ.solve(CPᵀ) 236 // K = (Sᵀ.solve(CPᵀ))ᵀ 237 // 238 // Drop the transposes on symmetric matrices S and P. 239 // 240 // K = (S.solve(CP))ᵀ 241 final Matrix<States, Outputs> K = S.solve(C.times(m_P)).transpose(); 242 243 // x̂ₖ₊₁⁺ = x̂ₖ₊₁⁻ + K(y − (Cx̂ₖ₊₁⁻ + Duₖ₊₁)) 244 m_xHat = m_xHat.plus(K.times(y.minus(C.times(m_xHat).plus(D.times(u))))); 245 246 // Pₖ₊₁⁺ = (I−Kₖ₊₁C)Pₖ₊₁⁻(I−Kₖ₊₁C)ᵀ + Kₖ₊₁RKₖ₊₁ᵀ 247 // Use Joseph form for numerical stability 248 m_P = 249 Matrix.eye(m_states) 250 .minus(K.times(C)) 251 .times(m_P) 252 .times(Matrix.eye(m_states).minus(K.times(C)).transpose()) 253 .plus(K.times(discR).times(K.transpose())); 254 } 255}