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}