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.NumericalIntegration; 017import edu.wpi.first.math.system.NumericalJacobian; 018import java.util.function.BiFunction; 019 020/** 021 * A Kalman filter combines predictions from a model and measurements to give an estimate of the 022 * true system state. This is useful because many states cannot be measured directly as a result of 023 * sensor noise, or because the state is "hidden". 024 * 025 * <p>Kalman filters use a K gain matrix to determine whether to trust the model or measurements 026 * more. Kalman filter theory uses statistics to compute an optimal K gain which minimizes the sum 027 * of squares error in the state estimate. This K gain is used to correct the state estimate by some 028 * amount of the difference between the actual measurements and the measurements predicted by the 029 * model. 030 * 031 * <p>An extended Kalman filter supports nonlinear state and measurement models. It propagates the 032 * error covariance by linearizing the models around the state estimate, then applying the linear 033 * Kalman filter equations. 034 * 035 * <p>For more on the underlying math, read <a 036 * href="https://file.tavsys.net/control/controls-engineering-in-frc.pdf">https://file.tavsys.net/control/controls-engineering-in-frc.pdf</a> 037 * chapter 9 "Stochastic control theory". 038 * 039 * @param <States> Number of states. 040 * @param <Inputs> Number of inputs. 041 * @param <Outputs> Number of outputs. 042 */ 043public class ExtendedKalmanFilter<States extends Num, Inputs extends Num, Outputs extends Num> 044 implements KalmanTypeFilter<States, Inputs, Outputs> { 045 private final Nat<States> m_states; 046 private final Nat<Outputs> m_outputs; 047 048 private final BiFunction<Matrix<States, N1>, Matrix<Inputs, N1>, Matrix<States, N1>> m_f; 049 050 private final BiFunction<Matrix<States, N1>, Matrix<Inputs, N1>, Matrix<Outputs, N1>> m_h; 051 052 private BiFunction<Matrix<Outputs, N1>, Matrix<Outputs, N1>, Matrix<Outputs, N1>> m_residualFuncY; 053 private BiFunction<Matrix<States, N1>, Matrix<States, N1>, Matrix<States, N1>> m_addFuncX; 054 055 private final Matrix<States, States> m_contQ; 056 private final Matrix<States, States> m_initP; 057 private final Matrix<Outputs, Outputs> m_contR; 058 059 private Matrix<States, N1> m_xHat; 060 061 private Matrix<States, States> m_P; 062 063 private double m_dtSeconds; 064 065 /** 066 * Constructs an extended Kalman filter. 067 * 068 * <p>See <a 069 * 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> 070 * for how to select the standard deviations. 071 * 072 * @param states a Nat representing the number of states. 073 * @param inputs a Nat representing the number of inputs. 074 * @param outputs a Nat representing the number of outputs. 075 * @param f A vector-valued function of x and u that returns the derivative of the state vector. 076 * @param h A vector-valued function of x and u that returns the measurement vector. 077 * @param stateStdDevs Standard deviations of model states. 078 * @param measurementStdDevs Standard deviations of measurements. 079 * @param dtSeconds Nominal discretization timestep. 080 */ 081 public ExtendedKalmanFilter( 082 Nat<States> states, 083 Nat<Inputs> inputs, 084 Nat<Outputs> outputs, 085 BiFunction<Matrix<States, N1>, Matrix<Inputs, N1>, Matrix<States, N1>> f, 086 BiFunction<Matrix<States, N1>, Matrix<Inputs, N1>, Matrix<Outputs, N1>> h, 087 Matrix<States, N1> stateStdDevs, 088 Matrix<Outputs, N1> measurementStdDevs, 089 double dtSeconds) { 090 this( 091 states, 092 inputs, 093 outputs, 094 f, 095 h, 096 stateStdDevs, 097 measurementStdDevs, 098 Matrix::minus, 099 Matrix::plus, 100 dtSeconds); 101 } 102 103 /** 104 * Constructs an extended Kalman filter. 105 * 106 * <p>See <a 107 * 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> 108 * for how to select the standard deviations. 109 * 110 * @param states a Nat representing the number of states. 111 * @param inputs a Nat representing the number of inputs. 112 * @param outputs a Nat representing the number of outputs. 113 * @param f A vector-valued function of x and u that returns the derivative of the state vector. 114 * @param h A vector-valued function of x and u that returns the measurement vector. 115 * @param stateStdDevs Standard deviations of model states. 116 * @param measurementStdDevs Standard deviations of measurements. 117 * @param residualFuncY A function that computes the residual of two measurement vectors (i.e. it 118 * subtracts them.) 119 * @param addFuncX A function that adds two state vectors. 120 * @param dtSeconds Nominal discretization timestep. 121 */ 122 public ExtendedKalmanFilter( 123 Nat<States> states, 124 Nat<Inputs> inputs, 125 Nat<Outputs> outputs, 126 BiFunction<Matrix<States, N1>, Matrix<Inputs, N1>, Matrix<States, N1>> f, 127 BiFunction<Matrix<States, N1>, Matrix<Inputs, N1>, Matrix<Outputs, N1>> h, 128 Matrix<States, N1> stateStdDevs, 129 Matrix<Outputs, N1> measurementStdDevs, 130 BiFunction<Matrix<Outputs, N1>, Matrix<Outputs, N1>, Matrix<Outputs, N1>> residualFuncY, 131 BiFunction<Matrix<States, N1>, Matrix<States, N1>, Matrix<States, N1>> addFuncX, 132 double dtSeconds) { 133 m_states = states; 134 m_outputs = outputs; 135 136 m_f = f; 137 m_h = h; 138 139 m_residualFuncY = residualFuncY; 140 m_addFuncX = addFuncX; 141 142 m_contQ = StateSpaceUtil.makeCovarianceMatrix(states, stateStdDevs); 143 m_contR = StateSpaceUtil.makeCovarianceMatrix(outputs, measurementStdDevs); 144 m_dtSeconds = dtSeconds; 145 146 reset(); 147 148 final var contA = 149 NumericalJacobian.numericalJacobianX( 150 states, states, f, m_xHat, new Matrix<>(inputs, Nat.N1())); 151 final var C = 152 NumericalJacobian.numericalJacobianX( 153 outputs, states, h, m_xHat, new Matrix<>(inputs, Nat.N1())); 154 155 final var discPair = Discretization.discretizeAQ(contA, m_contQ, dtSeconds); 156 final var discA = discPair.getFirst(); 157 final var discQ = discPair.getSecond(); 158 159 final var discR = Discretization.discretizeR(m_contR, dtSeconds); 160 161 if (StateSpaceUtil.isDetectable(discA, C) && outputs.getNum() <= states.getNum()) { 162 m_initP = DARE.dare(discA.transpose(), C.transpose(), discQ, discR); 163 } else { 164 m_initP = new Matrix<>(states, states); 165 } 166 167 m_P = m_initP; 168 169 MathSharedStore.getMathShared().reportUsage(MathUsageId.kEstimator_KalmanFilter, 2); 170 } 171 172 /** 173 * Returns the error covariance matrix P. 174 * 175 * @return the error covariance matrix P. 176 */ 177 @Override 178 public Matrix<States, States> getP() { 179 return m_P; 180 } 181 182 /** 183 * Returns an element of the error covariance matrix P. 184 * 185 * @param row Row of P. 186 * @param col Column of P. 187 * @return the value of the error covariance matrix P at (i, j). 188 */ 189 @Override 190 public double getP(int row, int col) { 191 return m_P.get(row, col); 192 } 193 194 /** 195 * Sets the entire error covariance matrix P. 196 * 197 * @param newP The new value of P to use. 198 */ 199 @Override 200 public void setP(Matrix<States, States> newP) { 201 m_P = newP; 202 } 203 204 /** 205 * Returns the state estimate x-hat. 206 * 207 * @return the state estimate x-hat. 208 */ 209 @Override 210 public Matrix<States, N1> getXhat() { 211 return m_xHat; 212 } 213 214 /** 215 * Returns an element of the state estimate x-hat. 216 * 217 * @param row Row of x-hat. 218 * @return the value of the state estimate x-hat at that row. 219 */ 220 @Override 221 public double getXhat(int row) { 222 return m_xHat.get(row, 0); 223 } 224 225 /** 226 * Set initial state estimate x-hat. 227 * 228 * @param xHat The state estimate x-hat. 229 */ 230 @Override 231 public void setXhat(Matrix<States, N1> xHat) { 232 m_xHat = xHat; 233 } 234 235 /** 236 * Set an element of the initial state estimate x-hat. 237 * 238 * @param row Row of x-hat. 239 * @param value Value for element of x-hat. 240 */ 241 @Override 242 public void setXhat(int row, double value) { 243 m_xHat.set(row, 0, value); 244 } 245 246 @Override 247 public final void reset() { 248 m_xHat = new Matrix<>(m_states, Nat.N1()); 249 m_P = m_initP; 250 } 251 252 /** 253 * Project the model into the future with a new control input u. 254 * 255 * @param u New control input from controller. 256 * @param dtSeconds Timestep for prediction. 257 */ 258 @Override 259 public void predict(Matrix<Inputs, N1> u, double dtSeconds) { 260 predict(u, m_f, dtSeconds); 261 } 262 263 /** 264 * Project the model into the future with a new control input u. 265 * 266 * @param u New control input from controller. 267 * @param f The function used to linearize the model. 268 * @param dtSeconds Timestep for prediction. 269 */ 270 public void predict( 271 Matrix<Inputs, N1> u, 272 BiFunction<Matrix<States, N1>, Matrix<Inputs, N1>, Matrix<States, N1>> f, 273 double dtSeconds) { 274 // Find continuous A 275 final var contA = NumericalJacobian.numericalJacobianX(m_states, m_states, f, m_xHat, u); 276 277 // Find discrete A and Q 278 final var discPair = Discretization.discretizeAQ(contA, m_contQ, dtSeconds); 279 final var discA = discPair.getFirst(); 280 final var discQ = discPair.getSecond(); 281 282 m_xHat = NumericalIntegration.rk4(f, m_xHat, u, dtSeconds); 283 284 // Pₖ₊₁⁻ = APₖ⁻Aᵀ + Q 285 m_P = discA.times(m_P).times(discA.transpose()).plus(discQ); 286 287 m_dtSeconds = dtSeconds; 288 } 289 290 /** 291 * Correct the state estimate x-hat using the measurements in y. 292 * 293 * @param u Same control input used in the predict step. 294 * @param y Measurement vector. 295 */ 296 @Override 297 public void correct(Matrix<Inputs, N1> u, Matrix<Outputs, N1> y) { 298 correct(m_outputs, u, y, m_h, m_contR, m_residualFuncY, m_addFuncX); 299 } 300 301 /** 302 * Correct the state estimate x-hat using the measurements in y. 303 * 304 * <p>This is useful for when the measurement noise covariances vary. 305 * 306 * @param u Same control input used in the predict step. 307 * @param y Measurement vector. 308 * @param R Continuous measurement noise covariance matrix. 309 */ 310 public void correct(Matrix<Inputs, N1> u, Matrix<Outputs, N1> y, Matrix<Outputs, Outputs> R) { 311 correct(m_outputs, u, y, m_h, R, m_residualFuncY, m_addFuncX); 312 } 313 314 /** 315 * Correct the state estimate x-hat using the measurements in y. 316 * 317 * <p>This is useful for when the measurements available during a timestep's Correct() call vary. 318 * The h(x, u) passed to the constructor is used if one is not provided (the two-argument version 319 * of this function). 320 * 321 * @param <Rows> Number of rows in the result of f(x, u). 322 * @param rows Number of rows in the result of f(x, u). 323 * @param u Same control input used in the predict step. 324 * @param y Measurement vector. 325 * @param h A vector-valued function of x and u that returns the measurement vector. 326 * @param R Continuous measurement noise covariance matrix. 327 */ 328 public <Rows extends Num> void correct( 329 Nat<Rows> rows, 330 Matrix<Inputs, N1> u, 331 Matrix<Rows, N1> y, 332 BiFunction<Matrix<States, N1>, Matrix<Inputs, N1>, Matrix<Rows, N1>> h, 333 Matrix<Rows, Rows> R) { 334 correct(rows, u, y, h, R, Matrix::minus, Matrix::plus); 335 } 336 337 /** 338 * Correct the state estimate x-hat using the measurements in y. 339 * 340 * <p>This is useful for when the measurements available during a timestep's Correct() call vary. 341 * The h(x, u) passed to the constructor is used if one is not provided (the two-argument version 342 * of this function). 343 * 344 * @param <Rows> Number of rows in the result of f(x, u). 345 * @param rows Number of rows in the result of f(x, u). 346 * @param u Same control input used in the predict step. 347 * @param y Measurement vector. 348 * @param h A vector-valued function of x and u that returns the measurement vector. 349 * @param R Continuous measurement noise covariance matrix. 350 * @param residualFuncY A function that computes the residual of two measurement vectors (i.e. it 351 * subtracts them.) 352 * @param addFuncX A function that adds two state vectors. 353 */ 354 public <Rows extends Num> void correct( 355 Nat<Rows> rows, 356 Matrix<Inputs, N1> u, 357 Matrix<Rows, N1> y, 358 BiFunction<Matrix<States, N1>, Matrix<Inputs, N1>, Matrix<Rows, N1>> h, 359 Matrix<Rows, Rows> R, 360 BiFunction<Matrix<Rows, N1>, Matrix<Rows, N1>, Matrix<Rows, N1>> residualFuncY, 361 BiFunction<Matrix<States, N1>, Matrix<States, N1>, Matrix<States, N1>> addFuncX) { 362 final var C = NumericalJacobian.numericalJacobianX(rows, m_states, h, m_xHat, u); 363 final var discR = Discretization.discretizeR(R, m_dtSeconds); 364 365 final var S = C.times(m_P).times(C.transpose()).plus(discR); 366 367 // We want to put K = PCᵀS⁻¹ into Ax = b form so we can solve it more 368 // efficiently. 369 // 370 // K = PCᵀS⁻¹ 371 // KS = PCᵀ 372 // (KS)ᵀ = (PCᵀ)ᵀ 373 // SᵀKᵀ = CPᵀ 374 // 375 // The solution of Ax = b can be found via x = A.solve(b). 376 // 377 // Kᵀ = Sᵀ.solve(CPᵀ) 378 // K = (Sᵀ.solve(CPᵀ))ᵀ 379 // 380 // Drop the transposes on symmetric matrices S and P. 381 // 382 // K = (S.solve(CP))ᵀ 383 final Matrix<States, Rows> K = S.solve(C.times(m_P)).transpose(); 384 385 // x̂ₖ₊₁⁺ = x̂ₖ₊₁⁻ + K(y − h(x̂ₖ₊₁⁻, uₖ₊₁)) 386 m_xHat = addFuncX.apply(m_xHat, K.times(residualFuncY.apply(y, h.apply(m_xHat, u)))); 387 388 // Pₖ₊₁⁺ = (I−Kₖ₊₁C)Pₖ₊₁⁻(I−Kₖ₊₁C)ᵀ + Kₖ₊₁RKₖ₊₁ᵀ 389 // Use Joseph form for numerical stability 390 m_P = 391 Matrix.eye(m_states) 392 .minus(K.times(C)) 393 .times(m_P) 394 .times(Matrix.eye(m_states).minus(K.times(C)).transpose()) 395 .plus(K.times(discR).times(K.transpose())); 396 } 397}