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.MathSharedStore; 008import edu.wpi.first.math.MathUsageId; 009import edu.wpi.first.math.Matrix; 010import edu.wpi.first.math.Nat; 011import edu.wpi.first.math.Num; 012import edu.wpi.first.math.Pair; 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; 019import org.ejml.dense.row.decomposition.qr.QRDecompositionHouseholder_DDRM; 020import org.ejml.simple.SimpleMatrix; 021 022/** 023 * A Kalman filter combines predictions from a model and measurements to give an estimate of the 024 * true system state. This is useful because many states cannot be measured directly as a result of 025 * sensor noise, or because the state is "hidden". 026 * 027 * <p>Kalman filters use a K gain matrix to determine whether to trust the model or measurements 028 * more. Kalman filter theory uses statistics to compute an optimal K gain which minimizes the sum 029 * of squares error in the state estimate. This K gain is used to correct the state estimate by some 030 * amount of the difference between the actual measurements and the measurements predicted by the 031 * model. 032 * 033 * <p>An unscented Kalman filter uses nonlinear state and measurement models. It propagates the 034 * error covariance using sigma points chosen to approximate the true probability distribution. 035 * 036 * <p>For more on the underlying math, read <a 037 * href="https://file.tavsys.net/control/controls-engineering-in-frc.pdf">https://file.tavsys.net/control/controls-engineering-in-frc.pdf</a> 038 * chapter 9 "Stochastic control theory". 039 * 040 * <p>This class implements a square-root-form unscented Kalman filter (SR-UKF). The main reason for 041 * this is to guarantee that the covariance matrix remains positive definite. For more information 042 * about the SR-UKF, see https://www.researchgate.net/publication/3908304. 043 * 044 * @param <States> Number of states. 045 * @param <Inputs> Number of inputs. 046 * @param <Outputs> Number of outputs. 047 */ 048public class UnscentedKalmanFilter<States extends Num, Inputs extends Num, Outputs extends Num> 049 implements KalmanTypeFilter<States, Inputs, Outputs> { 050 private final Nat<States> m_states; 051 private final Nat<Outputs> m_outputs; 052 053 private final BiFunction<Matrix<States, N1>, Matrix<Inputs, N1>, Matrix<States, N1>> m_f; 054 private final BiFunction<Matrix<States, N1>, Matrix<Inputs, N1>, Matrix<Outputs, N1>> m_h; 055 056 private BiFunction<Matrix<States, ?>, Matrix<?, N1>, Matrix<States, N1>> m_meanFuncX; 057 private BiFunction<Matrix<Outputs, ?>, Matrix<?, N1>, Matrix<Outputs, N1>> m_meanFuncY; 058 private BiFunction<Matrix<States, N1>, Matrix<States, N1>, Matrix<States, N1>> m_residualFuncX; 059 private BiFunction<Matrix<Outputs, N1>, Matrix<Outputs, N1>, Matrix<Outputs, N1>> m_residualFuncY; 060 private BiFunction<Matrix<States, N1>, Matrix<States, N1>, Matrix<States, N1>> m_addFuncX; 061 062 private Matrix<States, N1> m_xHat; 063 private Matrix<States, States> m_S; 064 private final Matrix<States, States> m_contQ; 065 private final Matrix<Outputs, Outputs> m_contR; 066 private Matrix<States, ?> m_sigmasF; 067 private double m_dtSeconds; 068 069 private final MerweScaledSigmaPoints<States> m_pts; 070 071 /** 072 * Constructs an Unscented Kalman Filter. 073 * 074 * <p>See <a 075 * 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> 076 * for how to select the standard deviations. 077 * 078 * @param states A Nat representing the number of states. 079 * @param outputs A Nat representing the number of outputs. 080 * @param f A vector-valued function of x and u that returns the derivative of the state vector. 081 * @param h A vector-valued function of x and u that returns the measurement vector. 082 * @param stateStdDevs Standard deviations of model states. 083 * @param measurementStdDevs Standard deviations of measurements. 084 * @param nominalDtSeconds Nominal discretization timestep. 085 */ 086 public UnscentedKalmanFilter( 087 Nat<States> states, 088 Nat<Outputs> outputs, 089 BiFunction<Matrix<States, N1>, Matrix<Inputs, N1>, Matrix<States, N1>> f, 090 BiFunction<Matrix<States, N1>, Matrix<Inputs, N1>, Matrix<Outputs, N1>> h, 091 Matrix<States, N1> stateStdDevs, 092 Matrix<Outputs, N1> measurementStdDevs, 093 double nominalDtSeconds) { 094 this( 095 states, 096 outputs, 097 f, 098 h, 099 stateStdDevs, 100 measurementStdDevs, 101 (sigmas, Wm) -> sigmas.times(Matrix.changeBoundsUnchecked(Wm)), 102 (sigmas, Wm) -> sigmas.times(Matrix.changeBoundsUnchecked(Wm)), 103 Matrix::minus, 104 Matrix::minus, 105 Matrix::plus, 106 nominalDtSeconds); 107 } 108 109 /** 110 * Constructs an Unscented Kalman filter with custom mean, residual, and addition functions. Using 111 * custom functions for arithmetic can be useful if you have angles in the state or measurements, 112 * because they allow you to correctly account for the modular nature of angle arithmetic. 113 * 114 * <p>See <a 115 * 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> 116 * for how to select the standard deviations. 117 * 118 * @param states A Nat representing the number of states. 119 * @param outputs A Nat representing the number of outputs. 120 * @param f A vector-valued function of x and u that returns the derivative of the state vector. 121 * @param h A vector-valued function of x and u that returns the measurement vector. 122 * @param stateStdDevs Standard deviations of model states. 123 * @param measurementStdDevs Standard deviations of measurements. 124 * @param meanFuncX A function that computes the mean of 2 * States + 1 state vectors using a 125 * given set of weights. 126 * @param meanFuncY A function that computes the mean of 2 * States + 1 measurement vectors using 127 * a given set of weights. 128 * @param residualFuncX A function that computes the residual of two state vectors (i.e. it 129 * subtracts them.) 130 * @param residualFuncY A function that computes the residual of two measurement vectors (i.e. it 131 * subtracts them.) 132 * @param addFuncX A function that adds two state vectors. 133 * @param nominalDtSeconds Nominal discretization timestep. 134 */ 135 public UnscentedKalmanFilter( 136 Nat<States> states, 137 Nat<Outputs> outputs, 138 BiFunction<Matrix<States, N1>, Matrix<Inputs, N1>, Matrix<States, N1>> f, 139 BiFunction<Matrix<States, N1>, Matrix<Inputs, N1>, Matrix<Outputs, N1>> h, 140 Matrix<States, N1> stateStdDevs, 141 Matrix<Outputs, N1> measurementStdDevs, 142 BiFunction<Matrix<States, ?>, Matrix<?, N1>, Matrix<States, N1>> meanFuncX, 143 BiFunction<Matrix<Outputs, ?>, Matrix<?, N1>, Matrix<Outputs, N1>> meanFuncY, 144 BiFunction<Matrix<States, N1>, Matrix<States, N1>, Matrix<States, N1>> residualFuncX, 145 BiFunction<Matrix<Outputs, N1>, Matrix<Outputs, N1>, Matrix<Outputs, N1>> residualFuncY, 146 BiFunction<Matrix<States, N1>, Matrix<States, N1>, Matrix<States, N1>> addFuncX, 147 double nominalDtSeconds) { 148 this.m_states = states; 149 this.m_outputs = outputs; 150 151 m_f = f; 152 m_h = h; 153 154 m_meanFuncX = meanFuncX; 155 m_meanFuncY = meanFuncY; 156 m_residualFuncX = residualFuncX; 157 m_residualFuncY = residualFuncY; 158 m_addFuncX = addFuncX; 159 160 m_dtSeconds = nominalDtSeconds; 161 162 m_contQ = StateSpaceUtil.makeCovarianceMatrix(states, stateStdDevs); 163 m_contR = StateSpaceUtil.makeCovarianceMatrix(outputs, measurementStdDevs); 164 165 m_pts = new MerweScaledSigmaPoints<>(states); 166 167 reset(); 168 MathSharedStore.getMathShared().reportUsage(MathUsageId.kEstimator_KalmanFilter, 3); 169 } 170 171 static <S extends Num, C extends Num> 172 Pair<Matrix<C, N1>, Matrix<C, C>> squareRootUnscentedTransform( 173 Nat<S> s, 174 Nat<C> dim, 175 Matrix<C, ?> sigmas, 176 Matrix<?, N1> Wm, 177 Matrix<?, N1> Wc, 178 BiFunction<Matrix<C, ?>, Matrix<?, N1>, Matrix<C, N1>> meanFunc, 179 BiFunction<Matrix<C, N1>, Matrix<C, N1>, Matrix<C, N1>> residualFunc, 180 Matrix<C, C> squareRootR) { 181 if (sigmas.getNumRows() != dim.getNum() || sigmas.getNumCols() != 2 * s.getNum() + 1) { 182 throw new IllegalArgumentException( 183 "Sigmas must be covDim by 2 * states + 1! Got " 184 + sigmas.getNumRows() 185 + " by " 186 + sigmas.getNumCols()); 187 } 188 189 if (Wm.getNumRows() != 2 * s.getNum() + 1 || Wm.getNumCols() != 1) { 190 throw new IllegalArgumentException( 191 "Wm must be 2 * states + 1 by 1! Got " + Wm.getNumRows() + " by " + Wm.getNumCols()); 192 } 193 194 if (Wc.getNumRows() != 2 * s.getNum() + 1 || Wc.getNumCols() != 1) { 195 throw new IllegalArgumentException( 196 "Wc must be 2 * states + 1 by 1! Got " + Wc.getNumRows() + " by " + Wc.getNumCols()); 197 } 198 199 // New mean is usually just the sum of the sigmas * weights: 200 // 201 // 2n 202 // x̂ = Σ Wᵢ⁽ᵐ⁾𝒳ᵢ 203 // i=0 204 // 205 // equations (19) and (23) in the paper show this, 206 // but we allow a custom function, usually for angle wrapping 207 Matrix<C, N1> x = meanFunc.apply(sigmas, Wm); 208 209 // Form an intermediate matrix S⁻ as: 210 // 211 // [√{W₁⁽ᶜ⁾}(𝒳_{1:2L} - x̂) √{Rᵛ}] 212 // 213 // the part of equations (20) and (24) within the "qr{}" 214 Matrix<C, ?> Sbar = new Matrix<>(new SimpleMatrix(dim.getNum(), 2 * s.getNum() + dim.getNum())); 215 for (int i = 0; i < 2 * s.getNum(); i++) { 216 Sbar.setColumn( 217 i, 218 residualFunc.apply(sigmas.extractColumnVector(1 + i), x).times(Math.sqrt(Wc.get(1, 0)))); 219 } 220 Sbar.assignBlock(0, 2 * s.getNum(), squareRootR); 221 222 QRDecompositionHouseholder_DDRM qr = new QRDecompositionHouseholder_DDRM(); 223 var qrStorage = Sbar.transpose().getStorage(); 224 225 if (!qr.decompose(qrStorage.getDDRM())) { 226 throw new RuntimeException("QR decomposition failed! Input matrix:\n" + qrStorage); 227 } 228 229 // Compute the square-root covariance of the sigma points 230 // 231 // We transpose S⁻ first because we formed it by horizontally 232 // concatenating each part; it should be vertical so we can take 233 // the QR decomposition as defined in the "QR Decomposition" passage 234 // of section 3. "EFFICIENT SQUARE-ROOT IMPLEMENTATION" 235 // 236 // The resulting matrix R is the square-root covariance S, but it 237 // is upper triangular, so we need to transpose it. 238 // 239 // equations (20) and (24) 240 Matrix<C, C> newS = new Matrix<>(new SimpleMatrix(qr.getR(null, true)).transpose()); 241 242 // Update or downdate the square-root covariance with (𝒳₀-x̂) 243 // depending on whether its weight (W₀⁽ᶜ⁾) is positive or negative. 244 // 245 // equations (21) and (25) 246 newS.rankUpdate(residualFunc.apply(sigmas.extractColumnVector(0), x), Wc.get(0, 0), true); 247 248 return new Pair<>(x, newS); 249 } 250 251 /** 252 * Returns the square-root error covariance matrix S. 253 * 254 * @return the square-root error covariance matrix S. 255 */ 256 public Matrix<States, States> getS() { 257 return m_S; 258 } 259 260 /** 261 * Returns an element of the square-root error covariance matrix S. 262 * 263 * @param row Row of S. 264 * @param col Column of S. 265 * @return the value of the square-root error covariance matrix S at (i, j). 266 */ 267 public double getS(int row, int col) { 268 return m_S.get(row, col); 269 } 270 271 /** 272 * Sets the entire square-root error covariance matrix S. 273 * 274 * @param newS The new value of S to use. 275 */ 276 public void setS(Matrix<States, States> newS) { 277 m_S = newS; 278 } 279 280 /** 281 * Returns the reconstructed error covariance matrix P. 282 * 283 * @return the error covariance matrix P. 284 */ 285 @Override 286 public Matrix<States, States> getP() { 287 return m_S.times(m_S.transpose()); 288 } 289 290 /** 291 * Returns an element of the error covariance matrix P. 292 * 293 * @param row Row of P. 294 * @param col Column of P. 295 * @return the value of the error covariance matrix P at (i, j). 296 * @throws UnsupportedOperationException indexing into the reconstructed P matrix is not supported 297 */ 298 @Override 299 public double getP(int row, int col) { 300 throw new UnsupportedOperationException( 301 "indexing into the reconstructed P matrix is not supported"); 302 } 303 304 /** 305 * Sets the entire error covariance matrix P. 306 * 307 * @param newP The new value of P to use. 308 */ 309 @Override 310 public void setP(Matrix<States, States> newP) { 311 m_S = newP.lltDecompose(true); 312 } 313 314 /** 315 * Returns the state estimate x-hat. 316 * 317 * @return the state estimate x-hat. 318 */ 319 @Override 320 public Matrix<States, N1> getXhat() { 321 return m_xHat; 322 } 323 324 /** 325 * Returns an element of the state estimate x-hat. 326 * 327 * @param row Row of x-hat. 328 * @return the value of the state estimate x-hat at 'i'. 329 */ 330 @Override 331 public double getXhat(int row) { 332 return m_xHat.get(row, 0); 333 } 334 335 /** 336 * Set initial state estimate x-hat. 337 * 338 * @param xHat The state estimate x-hat. 339 */ 340 @Override 341 public void setXhat(Matrix<States, N1> xHat) { 342 m_xHat = xHat; 343 } 344 345 /** 346 * Set an element of the initial state estimate x-hat. 347 * 348 * @param row Row of x-hat. 349 * @param value Value for element of x-hat. 350 */ 351 @Override 352 public void setXhat(int row, double value) { 353 m_xHat.set(row, 0, value); 354 } 355 356 /** Resets the observer. */ 357 @Override 358 public final void reset() { 359 m_xHat = new Matrix<>(m_states, Nat.N1()); 360 m_S = new Matrix<>(m_states, m_states); 361 m_sigmasF = new Matrix<>(new SimpleMatrix(m_states.getNum(), 2 * m_states.getNum() + 1)); 362 } 363 364 /** 365 * Project the model into the future with a new control input u. 366 * 367 * @param u New control input from controller. 368 * @param dtSeconds Timestep for prediction. 369 */ 370 @Override 371 public void predict(Matrix<Inputs, N1> u, double dtSeconds) { 372 // Discretize Q before projecting mean and covariance forward 373 Matrix<States, States> contA = 374 NumericalJacobian.numericalJacobianX(m_states, m_states, m_f, m_xHat, u); 375 var discQ = Discretization.discretizeAQ(contA, m_contQ, dtSeconds).getSecond(); 376 var squareRootDiscQ = discQ.lltDecompose(true); 377 378 // Generate sigma points around the state mean 379 // 380 // equation (17) 381 var sigmas = m_pts.squareRootSigmaPoints(m_xHat, m_S); 382 383 // Project each sigma point forward in time according to the 384 // dynamics f(x, u) 385 // 386 // sigmas = 𝒳ₖ₋₁ 387 // sigmasF = 𝒳ₖ,ₖ₋₁ or just 𝒳 for readability 388 // 389 // equation (18) 390 for (int i = 0; i < m_pts.getNumSigmas(); ++i) { 391 Matrix<States, N1> x = sigmas.extractColumnVector(i); 392 393 m_sigmasF.setColumn(i, NumericalIntegration.rk4(m_f, x, u, dtSeconds)); 394 } 395 396 // Pass the predicted sigmas (𝒳) through the Unscented Transform 397 // to compute the prior state mean and covariance 398 // 399 // equations (18) (19) and (20) 400 var ret = 401 squareRootUnscentedTransform( 402 m_states, 403 m_states, 404 m_sigmasF, 405 m_pts.getWm(), 406 m_pts.getWc(), 407 m_meanFuncX, 408 m_residualFuncX, 409 squareRootDiscQ); 410 411 m_xHat = ret.getFirst(); 412 m_S = ret.getSecond(); 413 m_dtSeconds = dtSeconds; 414 } 415 416 /** 417 * Correct the state estimate x-hat using the measurements in y. 418 * 419 * @param u Same control input used in the predict step. 420 * @param y Measurement vector. 421 */ 422 @Override 423 public void correct(Matrix<Inputs, N1> u, Matrix<Outputs, N1> y) { 424 correct( 425 m_outputs, u, y, m_h, m_contR, m_meanFuncY, m_residualFuncY, m_residualFuncX, m_addFuncX); 426 } 427 428 /** 429 * Correct the state estimate x-hat using the measurements in y. 430 * 431 * <p>This is useful for when the measurement noise covariances vary. 432 * 433 * @param u Same control input used in the predict step. 434 * @param y Measurement vector. 435 * @param R Continuous measurement noise covariance matrix. 436 */ 437 public void correct(Matrix<Inputs, N1> u, Matrix<Outputs, N1> y, Matrix<Outputs, Outputs> R) { 438 correct(m_outputs, u, y, m_h, R, m_meanFuncY, m_residualFuncY, m_residualFuncX, m_addFuncX); 439 } 440 441 /** 442 * Correct the state estimate x-hat using the measurements in y. 443 * 444 * <p>This is useful for when the measurements available during a timestep's Correct() call vary. 445 * The h(x, u) passed to the constructor is used if one is not provided (the two-argument version 446 * of this function). 447 * 448 * @param <R> Number of measurements in y. 449 * @param rows Number of rows in y. 450 * @param u Same control input used in the predict step. 451 * @param y Measurement vector. 452 * @param h A vector-valued function of x and u that returns the measurement vector. 453 * @param R Continuous measurement noise covariance matrix. 454 */ 455 public <R extends Num> void correct( 456 Nat<R> rows, 457 Matrix<Inputs, N1> u, 458 Matrix<R, N1> y, 459 BiFunction<Matrix<States, N1>, Matrix<Inputs, N1>, Matrix<R, N1>> h, 460 Matrix<R, R> R) { 461 BiFunction<Matrix<R, ?>, Matrix<?, N1>, Matrix<R, N1>> meanFuncY = 462 (sigmas, Wm) -> sigmas.times(Matrix.changeBoundsUnchecked(Wm)); 463 BiFunction<Matrix<States, N1>, Matrix<States, N1>, Matrix<States, N1>> residualFuncX = 464 Matrix::minus; 465 BiFunction<Matrix<R, N1>, Matrix<R, N1>, Matrix<R, N1>> residualFuncY = Matrix::minus; 466 BiFunction<Matrix<States, N1>, Matrix<States, N1>, Matrix<States, N1>> addFuncX = Matrix::plus; 467 correct(rows, u, y, h, R, meanFuncY, residualFuncY, residualFuncX, addFuncX); 468 } 469 470 /** 471 * Correct the state estimate x-hat using the measurements in y. 472 * 473 * <p>This is useful for when the measurements available during a timestep's Correct() call vary. 474 * The h(x, u) passed to the constructor is used if one is not provided (the two-argument version 475 * of this function). 476 * 477 * @param <R> Number of measurements in y. 478 * @param rows Number of rows in y. 479 * @param u Same control input used in the predict step. 480 * @param y Measurement vector. 481 * @param h A vector-valued function of x and u that returns the measurement vector. 482 * @param R Continuous measurement noise covariance matrix. 483 * @param meanFuncY A function that computes the mean of 2 * States + 1 measurement vectors using 484 * a given set of weights. 485 * @param residualFuncY A function that computes the residual of two measurement vectors (i.e. it 486 * subtracts them.) 487 * @param residualFuncX A function that computes the residual of two state vectors (i.e. it 488 * subtracts them.) 489 * @param addFuncX A function that adds two state vectors. 490 */ 491 public <R extends Num> void correct( 492 Nat<R> rows, 493 Matrix<Inputs, N1> u, 494 Matrix<R, N1> y, 495 BiFunction<Matrix<States, N1>, Matrix<Inputs, N1>, Matrix<R, N1>> h, 496 Matrix<R, R> R, 497 BiFunction<Matrix<R, ?>, Matrix<?, N1>, Matrix<R, N1>> meanFuncY, 498 BiFunction<Matrix<R, N1>, Matrix<R, N1>, Matrix<R, N1>> residualFuncY, 499 BiFunction<Matrix<States, N1>, Matrix<States, N1>, Matrix<States, N1>> residualFuncX, 500 BiFunction<Matrix<States, N1>, Matrix<States, N1>, Matrix<States, N1>> addFuncX) { 501 final var discR = Discretization.discretizeR(R, m_dtSeconds); 502 final var squareRootDiscR = discR.lltDecompose(true); 503 504 // Generate new sigma points from the prior mean and covariance 505 // and transform them into measurement space using h(x, u) 506 // 507 // sigmas = 𝒳 508 // sigmasH = 𝒴 509 // 510 // This differs from equation (22) which uses 511 // the prior sigma points, regenerating them allows 512 // multiple measurement updates per time update 513 Matrix<R, ?> sigmasH = new Matrix<>(new SimpleMatrix(rows.getNum(), 2 * m_states.getNum() + 1)); 514 var sigmas = m_pts.squareRootSigmaPoints(m_xHat, m_S); 515 for (int i = 0; i < m_pts.getNumSigmas(); i++) { 516 Matrix<R, N1> hRet = h.apply(sigmas.extractColumnVector(i), u); 517 sigmasH.setColumn(i, hRet); 518 } 519 520 // Pass the predicted measurement sigmas through the Unscented Transform 521 // to compute the mean predicted measurement and square-root innovation 522 // covariance. 523 // 524 // equations (23) (24) and (25) 525 var transRet = 526 squareRootUnscentedTransform( 527 m_states, 528 rows, 529 sigmasH, 530 m_pts.getWm(), 531 m_pts.getWc(), 532 meanFuncY, 533 residualFuncY, 534 squareRootDiscR); 535 var yHat = transRet.getFirst(); 536 var Sy = transRet.getSecond(); 537 538 // Compute cross covariance of the predicted state and measurement sigma 539 // points given as: 540 // 541 // 2n 542 // P_{xy} = Σ Wᵢ⁽ᶜ⁾[𝒳ᵢ - x̂][𝒴ᵢ - ŷ⁻]ᵀ 543 // i=0 544 // 545 // equation (26) 546 Matrix<States, R> Pxy = new Matrix<>(m_states, rows); 547 for (int i = 0; i < m_pts.getNumSigmas(); i++) { 548 var dx = residualFuncX.apply(m_sigmasF.extractColumnVector(i), m_xHat); 549 var dy = residualFuncY.apply(sigmasH.extractColumnVector(i), yHat).transpose(); 550 551 Pxy = Pxy.plus(dx.times(dy).times(m_pts.getWc(i))); 552 } 553 554 // Compute the Kalman gain. We use Eigen's QR decomposition to solve. This 555 // is equivalent to MATLAB's \ operator, so we need to rearrange to use 556 // that. 557 // 558 // K = (P_{xy} / S_{y}ᵀ) / S_{y} 559 // K = (S_{y} \ P_{xy})ᵀ / S_{y} 560 // K = (S_{y}ᵀ \ (S_{y} \ P_{xy}ᵀ))ᵀ 561 // 562 // equation (27) 563 Matrix<States, R> K = 564 Sy.transpose() 565 .solveFullPivHouseholderQr(Sy.solveFullPivHouseholderQr(Pxy.transpose())) 566 .transpose(); 567 568 // Compute the posterior state mean 569 // 570 // x̂ = x̂⁻ + K(y − ŷ⁻) 571 // 572 // second part of equation (27) 573 m_xHat = addFuncX.apply(m_xHat, K.times(residualFuncY.apply(y, yHat))); 574 575 // Compute the intermediate matrix U for downdating 576 // the square-root covariance 577 // 578 // equation (28) 579 Matrix<States, R> U = K.times(Sy); 580 581 // Downdate the posterior square-root state covariance 582 // 583 // equation (29) 584 for (int i = 0; i < rows.getNum(); i++) { 585 m_S.rankUpdate(U.extractColumnVector(i), -1, true); 586 } 587 } 588}