From 1cf57cbd784b5727c21bd20f8abaf69b0dc53cfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Carouge?= Date: Mon, 27 Jun 2022 18:49:58 -0700 Subject: [PATCH] [filter] extend filter with update parameters (#50) --- include/fcarouge/internal/kalman.hpp | 61 ++++-- include/fcarouge/kalman.hpp | 292 ++++++++++++++++++--------- include/fcarouge/kalman_eigen.hpp | 8 +- sample/rocket_altitude.cpp | 3 +- 4 files changed, 246 insertions(+), 118 deletions(-) diff --git a/include/fcarouge/internal/kalman.hpp b/include/fcarouge/internal/kalman.hpp index a0837c7f4..b405de73a 100644 --- a/include/fcarouge/internal/kalman.hpp +++ b/include/fcarouge/internal/kalman.hpp @@ -49,8 +49,16 @@ namespace fcarouge::internal { template + typename UpdateArguments, typename PredictionArguments> struct kalman { +}; + +template +struct kalman, + std::tuple> { //! @name Public Member Types //! @{ @@ -161,21 +169,28 @@ struct kalman { //! access it through k.x() when needed? Where does the practical/performance //! tradeoff leans toward? For the general case? For the specialized cases? //! Same question applies to other parameters. - std::function observation_state_h{ - [this](const state &x) -> output_model { - static_cast(x); - return h; - } - }; + std::function + observation_state_h{ + [this](const state &x, + const UpdateArguments &...arguments) -> output_model { + static_cast(x); + (static_cast(arguments), ...); + return h; + } + }; //! @brief Compute observation noise R matrix. - std::function - noise_observation_r{ [this](const state &x, - const output &z) -> output_uncertainty { - static_cast(x); - static_cast(z); - return r; - } }; + std::function + noise_observation_r{ + [this](const state &x, const output &z, + const UpdateArguments &...arguments) -> output_uncertainty { + static_cast(x); + static_cast(z); + (static_cast(arguments), ...); + return r; + } + }; //! @brief Compute the state transition F matrix. //! @@ -236,9 +251,12 @@ struct kalman { //! extended filter, the client implements a linearization of the observation //! function hand the state observation H matrix is the Jacobian of the state //! observation function. - std::function observation{ - [this](const state &x) -> output { return h * x; } - }; + std::function + observation{ [this](const state &x, + const UpdateArguments &...arguments) -> output { + (static_cast(arguments), ...); + return h * x; + } }; Transpose transpose; Divide divide; @@ -254,17 +272,18 @@ struct kalman { //! does the compiler/linker do it for us? //! @todo H would be the observe Jacobian(x) extended? //! @todo Would innovation y = z - extended_hh(x) be extended? - inline constexpr void update(const auto &...output_z) + inline constexpr void update(const UpdateArguments &...arguments, + const auto &...output_z) { const auto i{ identity.template operator()() }; z = output{ output_z... }; - h = observation_state_h(x); - r = noise_observation_r(x, z); + h = observation_state_h(x, arguments...); + r = noise_observation_r(x, z, arguments...); s = h * p * transpose(h) + r; k = divide(p * transpose(h), s); // Do we want to support custom y = output_difference(z, observation(x))? - y = z - observation(x); + y = z - observation(x, arguments...); x = x + k * y; p = symmetrize(estimate_uncertainty{ (i - k * h) * p * transpose(i - k * h) + k * r * transpose(k) }); diff --git a/include/fcarouge/kalman.hpp b/include/fcarouge/kalman.hpp index 9208abade..6b744836e 100644 --- a/include/fcarouge/kalman.hpp +++ b/include/fcarouge/kalman.hpp @@ -47,6 +47,7 @@ For more information, please refer to */ #include #include +#include #include #include @@ -110,12 +111,27 @@ namespace fcarouge //! @todo Support mux pipes https://github.com/joboccara/pipes operator filter? //! @todo Reproduce Ardupilot's inertial navigation EKF and comparison //! benchmarks in SITL (software in the loop simulation). +//! @todo Can we provide the operator[] for the vector characteristics +//! regardless of implementation? And for the matrix ones too? It could simplify +//! client code. template < typename Type = double, typename State = Type, typename Output = State, typename Input = State, typename Transpose = std::identity, typename Symmetrize = std::identity, typename Divide = std::divides, - typename Identity = internal::identity, typename... PredictionArguments> + typename Identity = internal::identity, + typename UpdateArguments = std::tuple<>, + typename PredictionArguments = std::tuple<>> class kalman +{ +}; + +template +class kalman, + std::tuple> { private: //! @name Private Member Types @@ -124,7 +140,8 @@ class kalman //! @brief Implementation details of the filter. using implementation = internal::kalman; + Identity, std::tuple, + std::tuple>; //! @} @@ -510,7 +527,7 @@ class kalman inline constexpr void r(const auto &callable) requires std::is_invocable_r_v < output_uncertainty, std::decay_t, - const state &, const output & > + const state &, const output &, const UpdateArguments &... > { filter.noise_observation_r = callable; } @@ -527,7 +544,7 @@ class kalman inline constexpr void r(auto &&callable) requires std::is_invocable_r_v < output_uncertainty, std::decay_t, - const state &, const output & > + const state &, const output &, const UpdateArguments &... > { filter.noise_observation_r = std::forward(callable); } @@ -666,7 +683,7 @@ class kalman inline constexpr void h(const auto &callable) requires std::is_invocable_r_v < output_model, std::decay_t, - const state & > + const state &, const UpdateArguments &... > { filter.observation_state_h = callable; } @@ -683,7 +700,7 @@ class kalman inline constexpr void h(auto &&callable) requires std::is_invocable_r_v < output_model, std::decay_t, - const state & > + const state &, const UpdateArguments &... > { filter.observation_state_h = std::forward(callable); } @@ -811,7 +828,7 @@ class kalman inline constexpr void observation(const auto &callable) requires std::is_invocable_r_v < output, std::decay_t, - const state & > + const state &, const UpdateArguments &... > { filter.observation = callable; } @@ -819,7 +836,7 @@ class kalman inline constexpr void observation(auto &&callable) requires std::is_invocable_r_v < output, std::decay_t, - const state & > + const state &, const UpdateArguments &... > { filter.observation = std::forward(callable); } @@ -878,7 +895,8 @@ class kalman //! @todo Consider if returning the state vector X would be preferrable? And //! if it would be compatible with an ES-EKF implementation? Or if a fluent //! interface would be preferrable? - inline constexpr void update(const auto &...output_z); + inline constexpr void update(const UpdateArguments &...arguments, + const auto &...output_z); //! @brief Produces estimates of the state variables and uncertainties. //! @@ -912,50 +930,61 @@ class kalman template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr auto kalman::x() const -> state + std::tuple, std::tuple>::x() + const -> state { return filter.x; } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::x(const state &value) + std::tuple, + std::tuple>::x(const state &value) { filter.x = value; } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::x(state &&value) + std::tuple, + std::tuple>::x(state &&value) { filter.x = std::move(value); } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::x(const auto &value, const auto &...values) + std::tuple, + std::tuple>::x(const auto &value, + const auto &...values) { filter.x = std::move(state{ value, values... }); } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::x(auto &&value, auto &&...values) + std::tuple, + std::tuple>::x(auto &&value, auto &&...values) { filter.x = std::move(state{ std::forward(value), std::forward(values)... }); @@ -963,60 +992,73 @@ kalman + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr auto kalman::z() const -> output + std::tuple, std::tuple>::z() + const -> output { return filter.z; } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr auto kalman::p() const -> estimate_uncertainty + std::tuple, std::tuple>::p() + const -> estimate_uncertainty { return filter.p; } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::p(const estimate_uncertainty &value) + std::tuple, + std::tuple>::p(const estimate_uncertainty &value) { filter.p = value; } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::p(estimate_uncertainty &&value) + std::tuple, + std::tuple>::p(estimate_uncertainty &&value) { filter.p = std::move(value); } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::p(const auto &value, const auto &...values) + std::tuple, + std::tuple>::p(const auto &value, + const auto &...values) { filter.p = std::move(estimate_uncertainty{ value, values... }); } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::p(auto &&value, auto &&...values) + std::tuple, + std::tuple>::p(auto &&value, auto &&...values) { filter.p = std::move( estimate_uncertainty{ std::forward(value), @@ -1025,10 +1067,12 @@ kalman + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr auto kalman::q() const -> process_uncertainty + std::tuple, std::tuple>::q() + const -> process_uncertainty { return filter.q; } @@ -1036,10 +1080,12 @@ kalman + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::q(const process_uncertainty &value) + std::tuple, + std::tuple>::q(const process_uncertainty &value) { filter.q = value; } @@ -1047,10 +1093,12 @@ kalman + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::q(process_uncertainty &&value) + std::tuple, + std::tuple>::q(process_uncertainty &&value) { filter.q = std::move(value); } @@ -1058,10 +1106,13 @@ kalman + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::q(const auto &value, const auto &...values) + std::tuple, + std::tuple>::q(const auto &value, + const auto &...values) { filter.q = std::move(process_uncertainty{ value, values... }); } @@ -1069,10 +1120,12 @@ kalman + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::q(auto &&value, auto &&...values) + std::tuple, + std::tuple>::q(auto &&value, auto &&...values) { filter.q = std::move( process_uncertainty{ std::forward(value), @@ -1081,50 +1134,61 @@ kalman + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr auto kalman::r() const -> output_uncertainty + std::tuple, std::tuple>::r() + const -> output_uncertainty { return filter.r; } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::r(const output_uncertainty &value) + std::tuple, + std::tuple>::r(const output_uncertainty &value) { filter.r = value; } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::r(output_uncertainty &&value) + std::tuple, + std::tuple>::r(output_uncertainty &&value) { filter.r = std::move(value); } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::r(const auto &value, const auto &...values) + std::tuple, + std::tuple>::r(const auto &value, + const auto &...values) { filter.r = output_uncertainty{ value, values... }; } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::r(auto &&value, auto &&...values) + std::tuple, + std::tuple>::r(auto &&value, auto &&...values) { filter.r = std::move( output_uncertainty{ std::forward(value), @@ -1133,50 +1197,61 @@ kalman + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr auto kalman::f() const -> state_transition + std::tuple, std::tuple>::f() + const -> state_transition { return filter.f; } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::f(const state_transition &value) + std::tuple, + std::tuple>::f(const state_transition &value) { filter.f = value; } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::f(state_transition &&value) + std::tuple, + std::tuple>::f(state_transition &&value) { filter.f = std::move(value); } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::f(const auto &value, const auto &...values) + std::tuple, + std::tuple>::f(const auto &value, + const auto &...values) { filter.f = state_transition{ value, values... }; } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::f(auto &&value, auto &&...values) + std::tuple, + std::tuple>::f(auto &&value, auto &&...values) { filter.f = std::move(state_transition{ std::forward(value), @@ -1185,50 +1260,61 @@ kalman + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr auto kalman::h() const -> output_model + std::tuple, std::tuple>::h() + const -> output_model { return filter.h; } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::h(const output_model &value) + std::tuple, + std::tuple>::h(const output_model &value) { filter.h = value; } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::h(output_model &&value) + std::tuple, + std::tuple>::h(output_model &&value) { filter.h = std::move(value); } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::h(const auto &value, const auto &...values) + std::tuple, + std::tuple>::h(const auto &value, + const auto &...values) { filter.h = output_model{ value, values... }; } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::h(auto &&value, auto &&...values) + std::tuple, + std::tuple>::h(auto &&value, auto &&...values) { filter.h = std::move(output_model{ std::forward(value), @@ -1237,50 +1323,61 @@ kalman + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr auto kalman::g() const -> input_control + std::tuple, std::tuple>::g() + const -> input_control { return filter.g; } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::g(const input_control &value) + std::tuple, + std::tuple>::g(const input_control &value) { filter.g = value; } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::g(input_control &&value) + std::tuple, + std::tuple>::g(input_control &&value) { filter.g = std::move(value); } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::g(const auto &value, const auto &...values) + std::tuple, + std::tuple>::g(const auto &value, + const auto &...values) { filter.g = input_control{ value, values... }; } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::g(auto &&value, auto &&...values) + std::tuple, + std::tuple>::g(auto &&value, auto &&...values) { filter.g = std::move(input_control{ std::forward(value), @@ -1289,51 +1386,60 @@ kalman + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr auto kalman::k() const -> gain + std::tuple, std::tuple>::k() + const -> gain { return filter.k; } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr auto kalman::y() const -> innovation + std::tuple, std::tuple>::y() + const -> innovation { return filter.y; } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr auto kalman::s() const -> innovation_uncertainty + std::tuple, std::tuple>::s() + const -> innovation_uncertainty { return filter.s; } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::update(const auto &...output_z) + std::tuple, std::tuple>:: + update(const UpdateArguments &...arguments, const auto &...output_z) { - filter.update(output_z...); + filter.update(arguments..., output_z...); } template + typename Identity, typename... UpdateArguments, + typename... PredictionArguments> inline constexpr void kalman::predict(const PredictionArguments &...arguments, - const auto &...input_u) + std::tuple, std::tuple>:: + predict(const PredictionArguments &...arguments, const auto &...input_u) { filter.predict(arguments..., input_u...); } diff --git a/include/fcarouge/kalman_eigen.hpp b/include/fcarouge/kalman_eigen.hpp index c2a752df2..5757db75f 100644 --- a/include/fcarouge/kalman_eigen.hpp +++ b/include/fcarouge/kalman_eigen.hpp @@ -49,6 +49,7 @@ For more information, please refer to */ #include #include +#include namespace fcarouge::eigen { @@ -65,12 +66,13 @@ namespace fcarouge::eigen //! @tparam PredictionArguments The variadic type template parameter for //! additional prediction function parameters. Time, or a delta thereof, is //! often a prediction parameter. -template +template , + typename PredictionArguments = std::tuple<>> using kalman = fcarouge::kalman< Type, Eigen::Vector, Eigen::Vector, Eigen::Vector, internal::transpose, internal::symmetrize, - internal::divide, internal::identity, PredictionArguments...>; + internal::divide, internal::identity, UpdateArguments, PredictionArguments>; } // namespace fcarouge::eigen diff --git a/sample/rocket_altitude.cpp b/sample/rocket_altitude.cpp index f8093b361..39cb6f75f 100644 --- a/sample/rocket_altitude.cpp +++ b/sample/rocket_altitude.cpp @@ -43,7 +43,8 @@ namespace //! @example rocket_altitude.cpp [[maybe_unused]] auto rocket_altitude{ [] { // A 2x1x1 filter, constant acceleration dynamic model, no control, step time. - using kalman = eigen::kalman; + using kalman = eigen::kalman, + std::tuple>; kalman k; // Initialization