diff --git a/wpimath/src/main/native/include/frc/estimator/ExtendedKalmanFilter.h b/wpimath/src/main/native/include/frc/estimator/ExtendedKalmanFilter.h index b42fbf07a7..4e40931dec 100644 --- a/wpimath/src/main/native/include/frc/estimator/ExtendedKalmanFilter.h +++ b/wpimath/src/main/native/include/frc/estimator/ExtendedKalmanFilter.h @@ -8,13 +8,7 @@ #include -#include "Eigen/Cholesky" #include "Eigen/Core" -#include "drake/math/discrete_algebraic_riccati_equation.h" -#include "frc/StateSpaceUtil.h" -#include "frc/system/Discretization.h" -#include "frc/system/NumericalIntegration.h" -#include "frc/system/NumericalJacobian.h" #include "units/time.h" namespace frc { @@ -67,42 +61,7 @@ class ExtendedKalmanFilter { h, const wpi::array& stateStdDevs, const wpi::array& measurementStdDevs, - units::second_t dt) - : m_f(f), m_h(h) { - m_contQ = MakeCovMatrix(stateStdDevs); - m_contR = MakeCovMatrix(measurementStdDevs); - m_residualFuncY = [](auto a, auto b) -> Eigen::Vector { - return a - b; - }; - m_addFuncX = [](auto a, auto b) -> Eigen::Vector { - return a + b; - }; - m_dt = dt; - - Reset(); - - Eigen::Matrix contA = - NumericalJacobianX( - m_f, m_xHat, Eigen::Vector::Zero()); - Eigen::Matrix C = - NumericalJacobianX( - m_h, m_xHat, Eigen::Vector::Zero()); - - Eigen::Matrix discA; - Eigen::Matrix discQ; - DiscretizeAQTaylor(contA, m_contQ, dt, &discA, &discQ); - - Eigen::Matrix discR = - DiscretizeR(m_contR, dt); - - if (IsDetectable(discA, C) && Outputs <= States) { - m_initP = drake::math::DiscreteAlgebraicRiccatiEquation( - discA.transpose(), C.transpose(), discQ, discR); - } else { - m_initP = Eigen::Matrix::Zero(); - } - m_P = m_initP; - } + units::second_t dt); /** * Constructs an extended Kalman filter. @@ -136,36 +95,7 @@ class ExtendedKalmanFilter { const Eigen::Vector&, const Eigen::Vector&)> addFuncX, - units::second_t dt) - : m_f(f), m_h(h), m_residualFuncY(residualFuncY), m_addFuncX(addFuncX) { - m_contQ = MakeCovMatrix(stateStdDevs); - m_contR = MakeCovMatrix(measurementStdDevs); - m_dt = dt; - - Reset(); - - Eigen::Matrix contA = - NumericalJacobianX( - m_f, m_xHat, Eigen::Vector::Zero()); - Eigen::Matrix C = - NumericalJacobianX( - m_h, m_xHat, Eigen::Vector::Zero()); - - Eigen::Matrix discA; - Eigen::Matrix discQ; - DiscretizeAQTaylor(contA, m_contQ, dt, &discA, &discQ); - - Eigen::Matrix discR = - DiscretizeR(m_contR, dt); - - if (IsDetectable(discA, C) && Outputs <= States) { - m_initP = drake::math::DiscreteAlgebraicRiccatiEquation( - discA.transpose(), C.transpose(), discQ, discR); - } else { - m_initP = Eigen::Matrix::Zero(); - } - m_P = m_initP; - } + units::second_t dt); /** * Returns the error covariance matrix P. @@ -228,23 +158,7 @@ class ExtendedKalmanFilter { * @param u New control input from controller. * @param dt Timestep for prediction. */ - void Predict(const Eigen::Vector& u, units::second_t dt) { - // Find continuous A - Eigen::Matrix contA = - NumericalJacobianX(m_f, m_xHat, u); - - // Find discrete A and Q - Eigen::Matrix discA; - Eigen::Matrix discQ; - DiscretizeAQTaylor(contA, m_contQ, dt, &discA, &discQ); - - m_xHat = RK4(m_f, m_xHat, u, dt); - - // Pₖ₊₁⁻ = APₖ⁻Aᵀ + Q - m_P = discA * m_P * discA.transpose() + discQ; - - m_dt = dt; - } + void Predict(const Eigen::Vector& u, units::second_t dt); /** * Correct the state estimate x-hat using the measurements in y. @@ -264,15 +178,7 @@ class ExtendedKalmanFilter { const Eigen::Vector&, const Eigen::Vector&)> h, - const Eigen::Matrix& R) { - auto residualFuncY = [](auto a, auto b) -> Eigen::Vector { - return a - b; - }; - auto addFuncX = [](auto a, auto b) -> Eigen::Vector { - return a + b; - }; - Correct(u, y, h, R, residualFuncY, addFuncX); - } + const Eigen::Matrix& R); /** * Correct the state estimate x-hat using the measurements in y. @@ -305,38 +211,7 @@ class ExtendedKalmanFilter { std::function( const Eigen::Vector&, const Eigen::Vector)> - addFuncX) { - const Eigen::Matrix C = - NumericalJacobianX(h, m_xHat, u); - const Eigen::Matrix discR = DiscretizeR(R, m_dt); - - Eigen::Matrix S = C * m_P * C.transpose() + discR; - - // We want to put K = PCᵀS⁻¹ into Ax = b form so we can solve it more - // efficiently. - // - // K = PCᵀS⁻¹ - // KS = PCᵀ - // (KS)ᵀ = (PCᵀ)ᵀ - // SᵀKᵀ = CPᵀ - // - // The solution of Ax = b can be found via x = A.solve(b). - // - // Kᵀ = Sᵀ.solve(CPᵀ) - // K = (Sᵀ.solve(CPᵀ))ᵀ - Eigen::Matrix K = - S.transpose().ldlt().solve(C * m_P.transpose()).transpose(); - - // x̂ₖ₊₁⁺ = x̂ₖ₊₁⁻ + Kₖ₊₁(y − h(x̂ₖ₊₁⁻, uₖ₊₁)) - m_xHat = addFuncX(m_xHat, K * residualFuncY(y, h(m_xHat, u))); - - // Pₖ₊₁⁺ = (I−Kₖ₊₁C)Pₖ₊₁⁻(I−Kₖ₊₁C)ᵀ + Kₖ₊₁RKₖ₊₁ᵀ - // Use Joseph form for numerical stability - m_P = (Eigen::Matrix::Identity() - K * C) * m_P * - (Eigen::Matrix::Identity() - K * C) - .transpose() + - K * discR * K.transpose(); - } + addFuncX); private: std::function( @@ -365,3 +240,5 @@ class ExtendedKalmanFilter { }; } // namespace frc + +#include "ExtendedKalmanFilter.inc" diff --git a/wpimath/src/main/native/include/frc/estimator/ExtendedKalmanFilter.inc b/wpimath/src/main/native/include/frc/estimator/ExtendedKalmanFilter.inc new file mode 100644 index 0000000000..5a50bb4c87 --- /dev/null +++ b/wpimath/src/main/native/include/frc/estimator/ExtendedKalmanFilter.inc @@ -0,0 +1,204 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include "Eigen/Cholesky" +#include "drake/math/discrete_algebraic_riccati_equation.h" +#include "frc/StateSpaceUtil.h" +#include "frc/estimator/ExtendedKalmanFilter.h" +#include "frc/system/Discretization.h" +#include "frc/system/NumericalIntegration.h" +#include "frc/system/NumericalJacobian.h" + +namespace frc { + +template +ExtendedKalmanFilter::ExtendedKalmanFilter( + std::function< + Eigen::Vector(const Eigen::Vector&, + const Eigen::Vector&)> + f, + std::function< + Eigen::Vector(const Eigen::Vector&, + const Eigen::Vector&)> + h, + const wpi::array& stateStdDevs, + const wpi::array& measurementStdDevs, units::second_t dt) + : m_f(f), m_h(h) { + m_contQ = MakeCovMatrix(stateStdDevs); + m_contR = MakeCovMatrix(measurementStdDevs); + m_residualFuncY = [](auto a, auto b) -> Eigen::Vector { + return a - b; + }; + m_addFuncX = [](auto a, auto b) -> Eigen::Vector { + return a + b; + }; + m_dt = dt; + + Reset(); + + Eigen::Matrix contA = + NumericalJacobianX( + m_f, m_xHat, Eigen::Vector::Zero()); + Eigen::Matrix C = + NumericalJacobianX( + m_h, m_xHat, Eigen::Vector::Zero()); + + Eigen::Matrix discA; + Eigen::Matrix discQ; + DiscretizeAQTaylor(contA, m_contQ, dt, &discA, &discQ); + + Eigen::Matrix discR = + DiscretizeR(m_contR, dt); + + if (IsDetectable(discA, C) && Outputs <= States) { + m_initP = drake::math::DiscreteAlgebraicRiccatiEquation( + discA.transpose(), C.transpose(), discQ, discR); + } else { + m_initP = Eigen::Matrix::Zero(); + } + m_P = m_initP; +} + +template +ExtendedKalmanFilter::ExtendedKalmanFilter( + std::function< + Eigen::Vector(const Eigen::Vector&, + const Eigen::Vector&)> + f, + std::function< + Eigen::Vector(const Eigen::Vector&, + const Eigen::Vector&)> + h, + const wpi::array& stateStdDevs, + const wpi::array& measurementStdDevs, + std::function< + Eigen::Vector(const Eigen::Vector&, + const Eigen::Vector&)> + residualFuncY, + std::function< + Eigen::Vector(const Eigen::Vector&, + const Eigen::Vector&)> + addFuncX, + units::second_t dt) + : m_f(f), m_h(h), m_residualFuncY(residualFuncY), m_addFuncX(addFuncX) { + m_contQ = MakeCovMatrix(stateStdDevs); + m_contR = MakeCovMatrix(measurementStdDevs); + m_dt = dt; + + Reset(); + + Eigen::Matrix contA = + NumericalJacobianX( + m_f, m_xHat, Eigen::Vector::Zero()); + Eigen::Matrix C = + NumericalJacobianX( + m_h, m_xHat, Eigen::Vector::Zero()); + + Eigen::Matrix discA; + Eigen::Matrix discQ; + DiscretizeAQTaylor(contA, m_contQ, dt, &discA, &discQ); + + Eigen::Matrix discR = + DiscretizeR(m_contR, dt); + + if (IsDetectable(discA, C) && Outputs <= States) { + m_initP = drake::math::DiscreteAlgebraicRiccatiEquation( + discA.transpose(), C.transpose(), discQ, discR); + } else { + m_initP = Eigen::Matrix::Zero(); + } + m_P = m_initP; +} + +template +void ExtendedKalmanFilter::Predict( + const Eigen::Vector& u, units::second_t dt) { + // Find continuous A + Eigen::Matrix contA = + NumericalJacobianX(m_f, m_xHat, u); + + // Find discrete A and Q + Eigen::Matrix discA; + Eigen::Matrix discQ; + DiscretizeAQTaylor(contA, m_contQ, dt, &discA, &discQ); + + m_xHat = RK4(m_f, m_xHat, u, dt); + + // Pₖ₊₁⁻ = APₖ⁻Aᵀ + Q + m_P = discA * m_P * discA.transpose() + discQ; + + m_dt = dt; +} + +template +template +void ExtendedKalmanFilter::Correct( + const Eigen::Vector& u, + const Eigen::Vector& y, + std::function< + Eigen::Vector(const Eigen::Vector&, + const Eigen::Vector&)> + h, + const Eigen::Matrix& R) { + auto residualFuncY = [](auto a, auto b) -> Eigen::Vector { + return a - b; + }; + auto addFuncX = [](auto a, auto b) -> Eigen::Vector { + return a + b; + }; + Correct(u, y, h, R, residualFuncY, addFuncX); +} + +template +template +void ExtendedKalmanFilter::Correct( + const Eigen::Vector& u, + const Eigen::Vector& y, + std::function< + Eigen::Vector(const Eigen::Vector&, + const Eigen::Vector&)> + h, + const Eigen::Matrix& R, + std::function( + const Eigen::Vector&, const Eigen::Vector&)> + residualFuncY, + std::function< + Eigen::Vector(const Eigen::Vector&, + const Eigen::Vector)> + addFuncX) { + const Eigen::Matrix C = + NumericalJacobianX(h, m_xHat, u); + const Eigen::Matrix discR = DiscretizeR(R, m_dt); + + Eigen::Matrix S = C * m_P * C.transpose() + discR; + + // We want to put K = PCᵀS⁻¹ into Ax = b form so we can solve it more + // efficiently. + // + // K = PCᵀS⁻¹ + // KS = PCᵀ + // (KS)ᵀ = (PCᵀ)ᵀ + // SᵀKᵀ = CPᵀ + // + // The solution of Ax = b can be found via x = A.solve(b). + // + // Kᵀ = Sᵀ.solve(CPᵀ) + // K = (Sᵀ.solve(CPᵀ))ᵀ + Eigen::Matrix K = + S.transpose().ldlt().solve(C * m_P.transpose()).transpose(); + + // x̂ₖ₊₁⁺ = x̂ₖ₊₁⁻ + Kₖ₊₁(y − h(x̂ₖ₊₁⁻, uₖ₊₁)) + m_xHat = addFuncX(m_xHat, K * residualFuncY(y, h(m_xHat, u))); + + // Pₖ₊₁⁺ = (I−Kₖ₊₁C)Pₖ₊₁⁻(I−Kₖ₊₁C)ᵀ + Kₖ₊₁RKₖ₊₁ᵀ + // Use Joseph form for numerical stability + m_P = (Eigen::Matrix::Identity() - K * C) * m_P * + (Eigen::Matrix::Identity() - K * C) + .transpose() + + K * discR * K.transpose(); +} + +} // namespace frc