From 97c493241fb36d7cd025148a78da1ce20c1b80a5 Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Fri, 29 Apr 2022 20:31:03 -0700 Subject: [PATCH] [wpimath] UnscentedKalmanFilter: Move implementation out-of-line --- .../frc/estimator/UnscentedKalmanFilter.h | 134 +---------- .../frc/estimator/UnscentedKalmanFilter.inc | 219 ++++++++++++++++++ 2 files changed, 226 insertions(+), 127 deletions(-) create mode 100644 wpimath/src/main/native/include/frc/estimator/UnscentedKalmanFilter.inc diff --git a/wpimath/src/main/native/include/frc/estimator/UnscentedKalmanFilter.h b/wpimath/src/main/native/include/frc/estimator/UnscentedKalmanFilter.h index 48c94c269a..4b20d32a26 100644 --- a/wpimath/src/main/native/include/frc/estimator/UnscentedKalmanFilter.h +++ b/wpimath/src/main/native/include/frc/estimator/UnscentedKalmanFilter.h @@ -9,14 +9,8 @@ #include #include -#include "Eigen/Cholesky" #include "Eigen/Core" -#include "frc/StateSpaceUtil.h" #include "frc/estimator/MerweScaledSigmaPoints.h" -#include "frc/estimator/UnscentedTransform.h" -#include "frc/system/Discretization.h" -#include "frc/system/NumericalIntegration.h" -#include "frc/system/NumericalJacobian.h" #include "units/time.h" namespace frc { @@ -69,29 +63,7 @@ class UnscentedKalmanFilter { 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_meanFuncX = [](auto sigmas, auto Wm) -> Eigen::Vector { - return sigmas * Wm; - }; - m_meanFuncY = [](auto sigmas, auto Wc) -> Eigen::Vector { - return sigmas * Wc; - }; - m_residualFuncX = [](auto a, auto b) -> Eigen::Vector { - return a - b; - }; - 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(); - } + units::second_t dt); /** * Constructs an unscented Kalman filter with custom mean, residual, and @@ -148,20 +120,7 @@ class UnscentedKalmanFilter { Eigen::Vector(const Eigen::Vector&, const Eigen::Vector&)> addFuncX, - units::second_t dt) - : m_f(f), - m_h(h), - m_meanFuncX(meanFuncX), - m_meanFuncY(meanFuncY), - m_residualFuncX(residualFuncX), - m_residualFuncY(residualFuncY), - m_addFuncX(addFuncX) { - m_contQ = MakeCovMatrix(stateStdDevs); - m_contR = MakeCovMatrix(measurementStdDevs); - m_dt = dt; - - Reset(); - } + units::second_t dt); /** * Returns the error covariance matrix P. @@ -225,31 +184,7 @@ class UnscentedKalmanFilter { * @param u New control input from controller. * @param dt Timestep for prediction. */ - void Predict(const Eigen::Vector& u, units::second_t dt) { - m_dt = dt; - - // Discretize Q before projecting mean and covariance forward - Eigen::Matrix contA = - NumericalJacobianX(m_f, m_xHat, u); - Eigen::Matrix discA; - Eigen::Matrix discQ; - DiscretizeAQTaylor(contA, m_contQ, dt, &discA, &discQ); - - Eigen::Matrix sigmas = - m_pts.SigmaPoints(m_xHat, m_P); - - for (int i = 0; i < m_pts.NumSigmas(); ++i) { - Eigen::Vector x = sigmas.template block(0, i); - m_sigmasF.template block(0, i) = RK4(m_f, x, u, dt); - } - - auto ret = UnscentedTransform( - m_sigmasF, m_pts.Wm(), m_pts.Wc(), m_meanFuncX, m_residualFuncX); - m_xHat = std::get<0>(ret); - m_P = std::get<1>(ret); - - m_P += discQ; - } + void Predict(const Eigen::Vector& u, units::second_t dt); /** * Correct the state estimate x-hat using the measurements in y. @@ -283,22 +218,7 @@ class UnscentedKalmanFilter { const Eigen::Vector&, const Eigen::Vector&)> h, - const Eigen::Matrix& R) { - auto meanFuncY = [](auto sigmas, auto Wc) -> Eigen::Vector { - return sigmas * Wc; - }; - auto residualFuncX = [](auto a, auto b) -> Eigen::Vector { - return a - b; - }; - 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, meanFuncY, residualFuncY, residualFuncX, - addFuncX); - } + const Eigen::Matrix& R); /** * Correct the state estimate x-hat using the measurements in y. @@ -343,49 +263,7 @@ class UnscentedKalmanFilter { std::function( const Eigen::Vector&, const Eigen::Vector)> - addFuncX) { - const Eigen::Matrix discR = DiscretizeR(R, m_dt); - - // Transform sigma points into measurement space - Eigen::Matrix sigmasH; - Eigen::Matrix sigmas = - m_pts.SigmaPoints(m_xHat, m_P); - for (int i = 0; i < m_pts.NumSigmas(); ++i) { - sigmasH.template block(0, i) = - h(sigmas.template block(0, i), u); - } - - // Mean and covariance of prediction passed through UT - auto [yHat, Py] = UnscentedTransform( - sigmasH, m_pts.Wm(), m_pts.Wc(), meanFuncY, residualFuncY); - Py += discR; - - // Compute cross covariance of the state and the measurements - Eigen::Matrix Pxy; - Pxy.setZero(); - for (int i = 0; i < m_pts.NumSigmas(); ++i) { - // Pxy += (sigmas_f[:, i] - x̂)(sigmas_h[:, i] - ŷ)ᵀ W_c[i] - Pxy += - m_pts.Wc(i) * - (residualFuncX(m_sigmasF.template block(0, i), m_xHat)) * - (residualFuncY(sigmasH.template block(0, i), yHat)) - .transpose(); - } - - // K = P_{xy} P_y⁻¹ - // Kᵀ = P_yᵀ⁻¹ P_{xy}ᵀ - // P_yᵀKᵀ = P_{xy}ᵀ - // Kᵀ = P_yᵀ.solve(P_{xy}ᵀ) - // K = (P_yᵀ.solve(P_{xy}ᵀ)ᵀ - Eigen::Matrix K = - Py.transpose().ldlt().solve(Pxy.transpose()).transpose(); - - // x̂ₖ₊₁⁺ = x̂ₖ₊₁⁻ + K(y − ŷ) - m_xHat = addFuncX(m_xHat, K * residualFuncY(y, yHat)); - - // Pₖ₊₁⁺ = Pₖ₊₁⁻ − KP_yKᵀ - m_P -= K * Py * K.transpose(); - } + addFuncX); private: std::function( @@ -432,3 +310,5 @@ extern template class EXPORT_TEMPLATE_DECLARE(WPILIB_DLLEXPORT) UnscentedKalmanFilter<5, 3, 3>; } // namespace frc + +#include "UnscentedKalmanFilter.inc" diff --git a/wpimath/src/main/native/include/frc/estimator/UnscentedKalmanFilter.inc b/wpimath/src/main/native/include/frc/estimator/UnscentedKalmanFilter.inc new file mode 100644 index 0000000000..55ead68d8d --- /dev/null +++ b/wpimath/src/main/native/include/frc/estimator/UnscentedKalmanFilter.inc @@ -0,0 +1,219 @@ +// 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 "frc/StateSpaceUtil.h" +#include "frc/estimator/UnscentedKalmanFilter.h" +#include "frc/estimator/UnscentedTransform.h" +#include "frc/system/Discretization.h" +#include "frc/system/NumericalIntegration.h" +#include "frc/system/NumericalJacobian.h" + +namespace frc { + +template +UnscentedKalmanFilter::UnscentedKalmanFilter( + 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_meanFuncX = [](auto sigmas, auto Wm) -> Eigen::Vector { + return sigmas * Wm; + }; + m_meanFuncY = [](auto sigmas, auto Wc) -> Eigen::Vector { + return sigmas * Wc; + }; + m_residualFuncX = [](auto a, auto b) -> Eigen::Vector { + return a - b; + }; + 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(); +} + +template +UnscentedKalmanFilter::UnscentedKalmanFilter( + 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( + const Eigen::Matrix&, + const Eigen::Vector&)> + meanFuncX, + std::function( + const Eigen::Matrix&, + const Eigen::Vector&)> + meanFuncY, + std::function< + Eigen::Vector(const Eigen::Vector&, + const Eigen::Vector&)> + residualFuncX, + 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_meanFuncX(meanFuncX), + m_meanFuncY(meanFuncY), + m_residualFuncX(residualFuncX), + m_residualFuncY(residualFuncY), + m_addFuncX(addFuncX) { + m_contQ = MakeCovMatrix(stateStdDevs); + m_contR = MakeCovMatrix(measurementStdDevs); + m_dt = dt; + + Reset(); +} + +template +void UnscentedKalmanFilter::Predict( + const Eigen::Vector& u, units::second_t dt) { + m_dt = dt; + + // Discretize Q before projecting mean and covariance forward + Eigen::Matrix contA = + NumericalJacobianX(m_f, m_xHat, u); + Eigen::Matrix discA; + Eigen::Matrix discQ; + DiscretizeAQTaylor(contA, m_contQ, dt, &discA, &discQ); + + Eigen::Matrix sigmas = + m_pts.SigmaPoints(m_xHat, m_P); + + for (int i = 0; i < m_pts.NumSigmas(); ++i) { + Eigen::Vector x = sigmas.template block(0, i); + m_sigmasF.template block(0, i) = RK4(m_f, x, u, dt); + } + + auto ret = UnscentedTransform( + m_sigmasF, m_pts.Wm(), m_pts.Wc(), m_meanFuncX, m_residualFuncX); + m_xHat = std::get<0>(ret); + m_P = std::get<1>(ret); + + m_P += discQ; +} + +template +template +void UnscentedKalmanFilter::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 meanFuncY = [](auto sigmas, auto Wc) -> Eigen::Vector { + return sigmas * Wc; + }; + auto residualFuncX = [](auto a, auto b) -> Eigen::Vector { + return a - b; + }; + 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, meanFuncY, residualFuncY, residualFuncX, addFuncX); +} + +template +template +void UnscentedKalmanFilter::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::Matrix&, + const Eigen::Vector&)> + meanFuncY, + std::function( + const Eigen::Vector&, const Eigen::Vector&)> + residualFuncY, + std::function< + Eigen::Vector(const Eigen::Vector&, + const Eigen::Vector&)> + residualFuncX, + std::function< + Eigen::Vector(const Eigen::Vector&, + const Eigen::Vector)> + addFuncX) { + const Eigen::Matrix discR = DiscretizeR(R, m_dt); + + // Transform sigma points into measurement space + Eigen::Matrix sigmasH; + Eigen::Matrix sigmas = + m_pts.SigmaPoints(m_xHat, m_P); + for (int i = 0; i < m_pts.NumSigmas(); ++i) { + sigmasH.template block(0, i) = + h(sigmas.template block(0, i), u); + } + + // Mean and covariance of prediction passed through UT + auto [yHat, Py] = UnscentedTransform( + sigmasH, m_pts.Wm(), m_pts.Wc(), meanFuncY, residualFuncY); + Py += discR; + + // Compute cross covariance of the state and the measurements + Eigen::Matrix Pxy; + Pxy.setZero(); + for (int i = 0; i < m_pts.NumSigmas(); ++i) { + // Pxy += (sigmas_f[:, i] - x̂)(sigmas_h[:, i] - ŷ)ᵀ W_c[i] + Pxy += m_pts.Wc(i) * + (residualFuncX(m_sigmasF.template block(0, i), m_xHat)) * + (residualFuncY(sigmasH.template block(0, i), yHat)) + .transpose(); + } + + // K = P_{xy} P_y⁻¹ + // Kᵀ = P_yᵀ⁻¹ P_{xy}ᵀ + // P_yᵀKᵀ = P_{xy}ᵀ + // Kᵀ = P_yᵀ.solve(P_{xy}ᵀ) + // K = (P_yᵀ.solve(P_{xy}ᵀ)ᵀ + Eigen::Matrix K = + Py.transpose().ldlt().solve(Pxy.transpose()).transpose(); + + // x̂ₖ₊₁⁺ = x̂ₖ₊₁⁻ + K(y − ŷ) + m_xHat = addFuncX(m_xHat, K * residualFuncY(y, yHat)); + + // Pₖ₊₁⁺ = Pₖ₊₁⁻ − KP_yKᵀ + m_P -= K * Py * K.transpose(); +} + +} // namespace frc