LCOV - code coverage report
Current view: top level - motion - generic_predict.hpp (source / functions) Coverage Total Hit
Test: lcov.info Lines: 100.0 % 47 47
Test Date: 2026-04-26 21:52:20 Functions: 100.0 % 8 8
Legend: Lines: hit not hit

            Line data    Source code
       1              : #ifndef A5CB3020_67E9_40A3_8F41_9346328EADA3
       2              : #define A5CB3020_67E9_40A3_8F41_9346328EADA3
       3              : 
       4              : #include "motion/generic_predict.h"
       5              : 
       6              : #include "filter/information_filter.hpp"     // IWYU pragma: keep
       7              : #include "filter/kalman_filter.hpp"          // IWYU pragma: keep
       8              : #include "math/linalg/square_matrix.h"       // IWYU pragma: keep
       9              : #include "motion/generic_predict_common.hpp" // IWYU pragma: keep
      10              : 
      11              : namespace tracking
      12              : {
      13              : namespace motion
      14              : {
      15              : namespace generic
      16              : {
      17              : 
      18              : template <typename MotionModel_, typename CovarianceMatrixPolicy_>
      19           40 : inline void Predict<MotionModel_, CovarianceMatrixPolicy_>::run(const value_type        dt,
      20              :                                                                 const KalmanFilterType& filter,
      21              :                                                                 const EgoMotionType&    egoMotion)
      22              : {
      23              :   // Access to MotionModel internals
      24           40 :   auto& underlying = static_cast<MotionModel_&>(*this);
      25           40 :   auto& P          = underlying.getCovForInternalUse();
      26              : 
      27           40 :   static typename BasePredictCommon::Storage data{};
      28           40 :   BasePredictCommon::run(data, dt, egoMotion);
      29              : 
      30              :   // Covariance prediction
      31              :   if constexpr (CovarianceMatrixPolicy_::is_factored)
      32              :   {
      33           20 :     if (egoMotion.getDisplacementCog().vec.isZeros())
      34              :     {
      35           10 :       filter.predictCovariance(P, data.AGo, data.G, data.Q);
      36              :     }
      37              :     else
      38              :     {
      39           10 :       filter.predictCovariance(P, data.AGo, data.Gstar, data.Qstar);
      40              :     }
      41              :   }
      42              :   else
      43              :   {
      44              :     // apply ego motion compensation on P
      45           20 :     P.apaT(data.Go);
      46           40 :     P += data.Ge * egoMotion.getDisplacementCog().cov * data.Ge.transpose();
      47           20 :     P.symmetrize();
      48              : 
      49              :     // apply time update
      50           20 :     filter.predictCovariance(P, data.A, data.G, data.Q);
      51              :   }
      52           40 : }
      53              : 
      54              : template <typename MotionModel_, typename CovarianceMatrixPolicy_>
      55           40 : inline void Predict<MotionModel_, CovarianceMatrixPolicy_>::run(const value_type             dt,
      56              :                                                                 const InformationFilterType& filter,
      57              :                                                                 const EgoMotionType&         egoMotion)
      58              : {
      59              :   // Access to MotionModel internals
      60           40 :   auto& underlying = static_cast<MotionModel_&>(*this);
      61           40 :   auto& Y          = underlying.getCovForInternalUse();
      62              : 
      63              :   // prepare covariance prediction based on egomotion compensated state
      64           40 :   static typename BasePredictCommon::Storage data{};
      65              : 
      66              :   // Step 1: Transform from information space to state space for state prediction
      67           40 :   underlying.convertStateVecIntoStateSpace();
      68              : 
      69              :   // Step 2: Run state prediction in state space (existing code)
      70           40 :   BasePredictCommon::run(data, dt, egoMotion);
      71              : 
      72              :   // Step 3: Covariance prediction (Y_k → Y_k+1)
      73              :   // NOTE: This MUST happen BEFORE convertStateVecIntoInformationSpace() because
      74              :   // the transformation needs the NEW information matrix Y_k+1
      75              :   if constexpr (CovarianceMatrixPolicy_::is_factored)
      76              :   {
      77           20 :     if (egoMotion.getDisplacementCog().vec.isZeros())
      78              :     {
      79           10 :       filter.predictCovariance(Y, data.AGo, data.G, data.Q);
      80              :     }
      81              :     else
      82              :     {
      83           10 :       filter.predictCovariance(Y, data.AGo, data.Gstar, data.Qstar);
      84              :     }
      85              :   }
      86              :   else
      87              :   {
      88           20 :     if (egoMotion.getDisplacementCog().vec.isZeros()) // TODO(matthias): is this really correct, Yes: because we are checking if
      89              :                                                       // the displacement vector is all zeros without tolerances!
      90              :     {
      91              :       // filter prediction step with zero ego motion
      92           10 :       filter.predictCovariance(Y, data.A, data.G, data.Q);
      93              :     }
      94              :     else
      95              :     {
      96              :       using EgoMotionCov = typename EgoMotionType::DisplacementCov;
      97              :       using StateCov     = typename MotionModel_::StateCov;
      98              :       using StateMatrix  = typename MotionModel_::StateMatrix;
      99              :       // Y = inv(Go*P*Go.T + Ge*Pe*Ge.T) with Po=inv(Y) can be solved directly in information space by
     100              :       // applying two steps
     101              :       // 1. Handle the State Transition
     102              :       //    Ys = inv(Go*P*Go.T) = inv(Go).T * Y * inv(Go)
     103              :       // 2. Handle the Additive Noise Q=Ge*Pe*Ge.T
     104              :       //    Y = inv(Ps + Q) applying the Woodbury Matrix Identity gives
     105              :       //    Y = Ys - Ys*Ge * inv( inv(Pe) + Ge.T*Ys*Ge ) * Ge.T*Ys
     106              : 
     107              :       // step 1: Handle the State Transition (using linear solver)
     108           10 :       StateMatrix Z_T = data.Go.transpose().qrSolve(Y);                                    // solve Go.T * Z_transpose = Y
     109           10 :       auto        Ytr = StateCov{std::move(data.Go.transpose().qrSolve(Z_T.transpose()))}; // solve Go.T * Y_tr = Z
     110           10 :       Ytr.symmetrize();
     111              : 
     112              :       // step 2: Handle the Additive Noise to be applied on the transformed Ytr (different to the one-step factored solution)!!!
     113              :       // apply Woodbury Matrix Identity
     114              :       // (Y^-1 + Ge Pe Ge')^-1 = Y - Y Ge (Pe^-1 + Ge' Y Ge)^-1 Ge' Y
     115              :       // note: if any of the inverses fails, we skip the ego motion compensation step
     116           10 :       const auto invPe = egoMotion.getDisplacementCog().cov.inverse();
     117           10 :       if (invPe.has_value())
     118              :       {
     119              :         // Compute Y Ge
     120           10 :         auto YGe =
     121              :             math::Matrix<value_type, MotionModel_::NUM_STATE_VARIABLES, EgoMotionType::DS_NUM_VARIABLES, true>{Ytr * data.Ge};
     122              : 
     123              :         // Compute Ge' Y
     124           10 :         auto GeTY = math::Matrix<value_type, EgoMotionType::DS_NUM_VARIABLES, MotionModel_::NUM_STATE_VARIABLES, false>{
     125              :             data.Ge.transpose() * Ytr};
     126              : 
     127              :         // Compute Ge' Y Ge
     128           10 :         auto GeTYGe = math::SquareMatrix<value_type, EgoMotionType::DS_NUM_VARIABLES, false>{GeTY * data.Ge};
     129              : 
     130              :         // Compute M = (Pe^-1 + Ge' Y Ge)^-1
     131           10 :         auto M = EgoMotionCov{typename EgoMotionCov::BaseSquareMatrix{invPe.value() + std::move(GeTYGe)}};
     132           10 :         M.symmetrize();
     133           10 :         const auto invM = M.inverse();
     134           10 :         if (invM.has_value())
     135              :         {
     136           10 :           const auto mat = std::move(YGe) * std::move(invM.value()) * std::move(GeTY);
     137              :           // Y^-1 + Ge Pe Ge')^-1 = Y - Y Ge (Pe^-1 + Ge' Y Ge)^-1 Ge' Y
     138           10 :           Ytr -= mat;
     139           10 :           Ytr.symmetrize();
     140              : 
     141              :           // final filter prediction step with compensated Ytr
     142           10 :           filter.predictCovariance(Ytr, data.A, data.G, data.Q);
     143              : 
     144              :           // prevent destroying the Information matrix, e.g. removing information from a zero Y matrix (no information)
     145           10 :           if (Ytr.isPositiveSemiDefinite())
     146              :           {
     147           10 :             Y = std::move(Ytr);
     148              :           }
     149           10 :         }
     150           10 :       }
     151           10 :     }
     152              :   }
     153              : 
     154              :   // Step 4: Transform state to information vector using the NEW information matrix Y_k+1
     155           40 :   underlying.convertStateVecIntoInformationSpace();
     156           40 : }
     157              : 
     158              : } // namespace generic
     159              : } // namespace motion
     160              : } // namespace tracking
     161              : 
     162              : #endif // A5CB3020_67E9_40A3_8F41_9346328EADA3
        

Generated by: LCOV version 2.0-1