LCOV - code coverage report
Current view: top level - AP_NavEKF3 - AP_NavEKF3_AirDataFusion.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 286 309 92.6 %
Date: 2026-09-09 11:02:08 Functions: 5 5 100.0 %

          Line data    Source code
       1             : #include <AP_HAL/AP_HAL.h>
       2             : 
       3             : #include "AP_NavEKF3.h"
       4             : #include "AP_NavEKF3_core.h"
       5             : #include <AP_DAL/AP_DAL.h>
       6             : 
       7             : /********************************************************
       8             : *                   RESET FUNCTIONS                     *
       9             : ********************************************************/
      10             : 
      11             : /********************************************************
      12             : *                   FUSE MEASURED_DATA                  *
      13             : ********************************************************/
      14             : 
      15             : /*
      16             :  * Fuse true airspeed measurements using explicit algebraic equations generated with Matlab symbolic toolbox.
      17             :  * The script file used to generate these and other equations in this filter can be found here:
      18             :  * https://github.com/PX4/ecl/blob/master/matlab/scripts/Inertial%20Nav%20EKF/GenerateNavFilterEquations.m
      19             : */
      20      583622 : void NavEKF3_core::FuseAirspeed()
      21             : {
      22             :     // declarations
      23             :     ftype SH_TAS[3];
      24             :     ftype SK_TAS[2];
      25             :     Vector24 H_TAS = {};
      26             : 
      27             :     // copy required states to local variable names
      28      583622 :     const ftype vn = stateStruct.velocity.x;
      29      583622 :     const ftype ve = stateStruct.velocity.y;
      30      583622 :     const ftype vd = stateStruct.velocity.z;
      31      583622 :     const ftype vwn = stateStruct.wind_vel.x;
      32      583622 :     const ftype vwe = stateStruct.wind_vel.y;
      33             : 
      34             :     // calculate the predicted airspeed
      35      583622 :     const ftype VtasPred = norm((ve - vwe) , (vn - vwn) , vd);
      36             :     // perform fusion of True Airspeed measurement
      37      583622 :     if (VtasPred > 1.0f)
      38             :     {
      39             :         // calculate observation innovation and variance
      40      574689 :         innovVtas = VtasPred - tasDataDelayed.tas;
      41             : 
      42             :         // calculate observation jacobians
      43      574689 :         SH_TAS[0] = 1.0f/VtasPred;
      44      574689 :         SH_TAS[1] = (SH_TAS[0]*(2.0f*ve - 2.0f*vwe))*0.5f;
      45      574689 :         SH_TAS[2] = (SH_TAS[0]*(2.0f*vn - 2.0f*vwn))*0.5f;
      46             :         H_TAS[4] = SH_TAS[2];
      47             :         H_TAS[5] = SH_TAS[1];
      48      574689 :         H_TAS[6] = vd*SH_TAS[0];
      49             :         H_TAS[22] = -SH_TAS[2];
      50             :         H_TAS[23] = -SH_TAS[1];
      51             :         // calculate Kalman gains
      52      574689 :         ftype temp = (tasDataDelayed.tasVariance + SH_TAS[2]*(P[4][4]*SH_TAS[2] + P[5][4]*SH_TAS[1] - P[22][4]*SH_TAS[2] - P[23][4]*SH_TAS[1] + P[6][4]*vd*SH_TAS[0]) + SH_TAS[1]*(P[4][5]*SH_TAS[2] + P[5][5]*SH_TAS[1] - P[22][5]*SH_TAS[2] - P[23][5]*SH_TAS[1] + P[6][5]*vd*SH_TAS[0]) - SH_TAS[2]*(P[4][22]*SH_TAS[2] + P[5][22]*SH_TAS[1] - P[22][22]*SH_TAS[2] - P[23][22]*SH_TAS[1] + P[6][22]*vd*SH_TAS[0]) - SH_TAS[1]*(P[4][23]*SH_TAS[2] + P[5][23]*SH_TAS[1] - P[22][23]*SH_TAS[2] - P[23][23]*SH_TAS[1] + P[6][23]*vd*SH_TAS[0]) + vd*SH_TAS[0]*(P[4][6]*SH_TAS[2] + P[5][6]*SH_TAS[1] - P[22][6]*SH_TAS[2] - P[23][6]*SH_TAS[1] + P[6][6]*vd*SH_TAS[0]));
      53      574689 :         if (temp >= tasDataDelayed.tasVariance) {
      54      574689 :             SK_TAS[0] = 1.0f / temp;
      55      574689 :             faultStatus.bad_airspeed = false;
      56             :         } else {
      57             :             // the calculation is badly conditioned, so we cannot perform fusion on this step
      58             :             // we reset the covariance matrix and try again next measurement
      59           0 :             CovarianceInit();
      60           0 :             faultStatus.bad_airspeed = true;
      61             :             return;
      62             :         }
      63             :         SK_TAS[1] = SH_TAS[1];
      64             : 
      65             :         uint32_t kalman_mask = 0; // values to calculate in Kfusion (others are set to zero)
      66             : 
      67      574689 :         if (tasDataDelayed.allowFusion && !airDataFusionWindOnly) {
      68             :             kalman_mask = (1<<10)-1;
      69             :         }
      70             : 
      71      574689 :         if (tasDataDelayed.allowFusion && !inhibitDelAngBiasStates && !airDataFusionWindOnly) {
      72       11157 :             kalman_mask |= (1<<10) | (1<<11) | (1<<12);
      73             :         }
      74             : 
      75      574689 :         if (tasDataDelayed.allowFusion && !inhibitDelVelBiasStates && !airDataFusionWindOnly) {
      76       44628 :             for (uint8_t index = 0; index < 3; index++) {
      77       33471 :                 const uint8_t stateIndex = index + 13;
      78       33471 :                 if (!dvelBiasAxisInhibit[index]) {
      79       33471 :                     kalman_mask |= (1<<stateIndex);
      80             :                 }
      81             :             }
      82             :         }
      83             : 
      84             :         // zero Kalman gains to inhibit magnetic field state estimation
      85      574689 :         if (tasDataDelayed.allowFusion && !inhibitMagStates && !airDataFusionWindOnly) {
      86       11155 :             kalman_mask |= (1<<16) | (1<<17) | (1<<18) | (1<<19) | (1<<20) | (1<<21);
      87             :         }
      88             : 
      89      574689 :         if (tasDataDelayed.allowFusion && !inhibitWindStates && !treatWindStatesAsTruth) {
      90      564546 :             kalman_mask |= (1<<22) | (1<<23);
      91             :         }
      92             : 
      93    14367225 :         for (auto i=0; i<24; i++) {
      94             :             ftype res = 0;
      95    13792536 :             if (kalman_mask & (1<<i)) {
      96     1374534 :                 res = SK_TAS[0]*(P[i][4]*SH_TAS[2] - P[i][22]*SH_TAS[2] + P[i][5]*SK_TAS[1] - P[i][23]*SK_TAS[1] + P[i][6]*vd*SH_TAS[0]);
      97             :             }
      98    13792536 :             Kfusion[i] = res;
      99             :         }
     100             : 
     101             :         // calculate measurement innovation variance
     102      574689 :         varInnovVtas = 1.0f/SK_TAS[0];
     103             : 
     104             :         // calculate the innovation consistency test ratio
     105      574689 :         tasTestRatio = sq(innovVtas) / (sq(MAX(0.01f * (ftype)frontend->_tasInnovGate, 1.0f)) * varInnovVtas);
     106             : 
     107             :         // fail if the ratio is > 1, but don't fail if bad IMU data
     108      574689 :         const bool isConsistent = (tasTestRatio < 1.0f) || badIMUdata;
     109      574689 :         tasTimeout = (imuSampleTime_ms - lastTasPassTime_ms) > frontend->tasRetryTime_ms;
     110      574689 :         if (!isConsistent) {
     111        1427 :             lastTasFailTime_ms = imuSampleTime_ms;
     112             :         } else {
     113      573262 :             lastTasFailTime_ms = 0;
     114             :         }
     115             : 
     116             :         // test the ratio before fusing data, forcing fusion if airspeed and position are timed out as we have no choice but to try and use airspeed to constrain error growth
     117      574689 :         if (tasDataDelayed.allowFusion && (isConsistent || (tasTimeout && posTimeout))) {
     118             :             // restart the counter
     119      573263 :             lastTasPassTime_ms = imuSampleTime_ms;
     120             : 
     121             :             // correct the covariance P = (I - K*H)*P = P - K*H*P. take advantage of
     122             :             // the zero elements of H to reduce the number of operations.
     123    14331575 :             for (unsigned i = 0; i<=stateIndexLim; i++) {
     124             :                 // j as the inner loop allows the compiler to hoist the KH product
     125             :                 // to save computation, and do the inner indexing more efficiently.
     126   343957800 :                 for (unsigned j = 0; j<=stateIndexLim; j++) {
     127             :                     ftype res = 0;
     128   330199488 :                     res += (Kfusion[i] * H_TAS[4]) * P[4][j];
     129   330199488 :                     res += (Kfusion[i] * H_TAS[5]) * P[5][j];
     130   330199488 :                     res += (Kfusion[i] * H_TAS[6]) * P[6][j];
     131   330199488 :                     res += (Kfusion[i] * H_TAS[22]) * P[22][j];
     132   330199488 :                     res += (Kfusion[i] * H_TAS[23]) * P[23][j];
     133   330199488 :                     KHP[i][j] = res;
     134             :                 }
     135             :             }
     136             : 
     137             :             // finish fusion from KHP and Kfusion
     138      573263 :             FinishFusion(innovVtas, true); // forcing fusion is probably a bug
     139             :         }
     140             :     }
     141             : }
     142             : 
     143             : // select fusion of true airspeed measurements
     144    19965274 : void NavEKF3_core::SelectTasFusion()
     145             : {
     146             :     // Check if the magnetometer has been fused on that time step and the filter is running at faster than 200 Hz
     147             :     // If so, don't fuse measurements on this time step to reduce frame over-runs
     148             :     // Only allow one time slip to prevent high rate magnetometer data locking out fusion of other measurements
     149    19965274 :     if (magFusePerformed && dtIMUavg < 0.005f && !airSpdFusionDelayed) {
     150     1204036 :         airSpdFusionDelayed = true;
     151     1204036 :         return;
     152             :     } else {
     153    18761238 :         airSpdFusionDelayed = false;
     154             :     }
     155             : 
     156             :     // get true airspeed measurement
     157    18761238 :     readAirSpdData();
     158             : 
     159             :     // if the filter is initialised, wind states are not inhibited and we have data to fuse, then perform TAS fusion
     160             : 
     161    18761238 :     if (tasDataToFuse && statesInitialised && !inhibitWindStates) {
     162      583622 :         FuseAirspeed();
     163      583622 :         tasDataToFuse = false;
     164      583622 :         prevTasStep_ms = imuSampleTime_ms;
     165             :     }
     166             : }
     167             : 
     168             : 
     169             : // select fusion of synthetic sideslip measurements or body frame drag
     170             : // synthetic sidelip fusion only works for fixed wing aircraft and relies on the average sideslip being close to zero
     171             : // body frame drag only works for bluff body multi rotor vehices with thrust forces aligned with the Z axis
     172             : // it requires a stable wind for best results and should not be used for aerobatic flight
     173    19965274 : void NavEKF3_core::SelectBetaDragFusion()
     174             : {
     175             :     // Check if the magnetometer has been fused on that time step and the filter is running at faster than 200 Hz
     176             :     // If so, don't fuse measurements on this time step to reduce frame over-runs
     177             :     // Only allow one time slip to prevent high rate magnetometer data preventing fusion of other measurements
     178    19965274 :     if (magFusePerformed && dtIMUavg < 0.005f && !sideSlipFusionDelayed) {
     179     1204036 :         sideSlipFusionDelayed = true;
     180     1204036 :         return;
     181             :     } else {
     182    18761238 :         sideSlipFusionDelayed = false;
     183             :     }
     184             : 
     185             :     // set true when the fusion time interval has triggered
     186    18761238 :     bool f_timeTrigger = ((imuSampleTime_ms - prevBetaDragStep_ms) >= frontend->betaAvg_ms);
     187             : 
     188             :     // use of air data to constrain drift is necessary if we have limited sensor data or are doing inertial dead reckoning
     189    18761238 :     bool is_dead_reckoning = ((imuSampleTime_ms - lastGpsPosPassTime_ms) > frontend->deadReckonDeclare_ms) &&
     190      229081 :                              ((imuSampleTime_ms - lastVelPassTime_ms) > frontend->deadReckonDeclare_ms);
     191    18761238 :     const bool noYawSensor = !use_compass() && !using_noncompass_for_yaw();
     192    18761238 :     const bool f_required = (noYawSensor && (frontend->_betaMask & (1<<1))) || is_dead_reckoning;
     193             : 
     194             :     // set true when sideslip fusion is feasible (requires zero sideslip assumption to be valid and use of wind states)
     195    18761238 :     const bool f_beta_feasible = (assume_zero_sideslip() && !inhibitWindStates);
     196             : 
     197             :     // use synthetic sideslip fusion if feasible, required and enough time has lapsed since the last fusion
     198    18761238 :     if (f_beta_feasible && f_timeTrigger) {
     199             :         // unless air data is required to constrain drift, it is only used to update wind state estimates
     200      595859 :         if (f_required || (frontend->_betaMask & (1<<0))) {
     201             :             // we are required to correct all states
     202        5929 :             airDataFusionWindOnly = false;
     203             :         } else {
     204             :             // we are required to correct only wind states
     205      589930 :             airDataFusionWindOnly = true;
     206             :         }
     207      595859 :         FuseSideslip();
     208      595859 :         prevBetaDragStep_ms = imuSampleTime_ms;
     209             :     }
     210             : 
     211             : #if EK3_FEATURE_DRAG_FUSION
     212             :     // fusion of XY body frame aero specific forces is done at a slower rate and only if alternative methods of wind estimation are not available
     213    18761238 :     if (!inhibitWindStates && storedDrag.recall(dragSampleDelayed,imuDataDelayed.time_ms)) {
     214        5366 :         FuseDragForces();
     215             :     }
     216    18761238 :     dragTimeout = (imuSampleTime_ms - lastDragPassTime_ms) > frontend->dragFailTimeLimit_ms;
     217             : #endif
     218             : }
     219             : 
     220             : /*
     221             :  * Fuse sythetic sideslip measurement of zero using explicit algebraic equations generated with Matlab symbolic toolbox.
     222             :  * The script file used to generate these and other equations in this filter can be found here:
     223             :  * https://github.com/PX4/ecl/blob/master/matlab/scripts/Inertial%20Nav%20EKF/GenerateNavFilterEquations.m
     224             : */
     225      595859 : void NavEKF3_core::FuseSideslip()
     226             : {
     227             :     // declarations
     228             :     const ftype R_BETA = 0.03f; // assume a sideslip angle RMS of ~10 deg
     229             :     Vector13 SH_BETA;
     230             :     Vector8 SK_BETA;
     231      595859 :     Vector3F vel_rel_wind;
     232             :     Vector24 H_BETA;
     233             : 
     234             :     // copy required states to local variable names
     235      595859 :     const ftype q0 = stateStruct.quat[0];
     236      595859 :     const ftype q1 = stateStruct.quat[1];
     237      595859 :     const ftype q2 = stateStruct.quat[2];
     238      595859 :     const ftype q3 = stateStruct.quat[3];
     239      595859 :     const ftype vn = stateStruct.velocity.x;
     240      595859 :     const ftype ve = stateStruct.velocity.y;
     241      595859 :     const ftype vd = stateStruct.velocity.z;
     242      595859 :     const ftype vwn = stateStruct.wind_vel.x;
     243      595859 :     const ftype vwe = stateStruct.wind_vel.y;
     244             : 
     245             :     // calculate predicted wind relative velocity in NED
     246      595859 :     vel_rel_wind.x = vn - vwn;
     247      595859 :     vel_rel_wind.y = ve - vwe;
     248      595859 :     vel_rel_wind.z = vd;
     249             : 
     250             :     // rotate into body axes
     251      595859 :     vel_rel_wind = prevTnb * vel_rel_wind;
     252             : 
     253             :     // perform fusion of assumed sideslip  = 0
     254      595859 :     if (vel_rel_wind.x > 5.0f)
     255             :     {
     256             :         // Calculate observation jacobians
     257      575267 :         SH_BETA[0] = (vn - vwn)*(sq(q0) + sq(q1) - sq(q2) - sq(q3)) - vd*(2*q0*q2 - 2*q1*q3) + (ve - vwe)*(2*q0*q3 + 2*q1*q2);
     258      575267 :         if (fabsF(SH_BETA[0]) <= 1e-9f) {
     259           0 :             faultStatus.bad_sideslip = true;
     260           0 :             return;
     261             :         } else {
     262      575267 :             faultStatus.bad_sideslip = false;
     263             :         }
     264      575267 :         SH_BETA[1] = (ve - vwe)*(sq(q0) - sq(q1) + sq(q2) - sq(q3)) + vd*(2*q0*q1 + 2*q2*q3) - (vn - vwn)*(2*q0*q3 - 2*q1*q2);
     265             :         SH_BETA[2] = vn - vwn;
     266             :         SH_BETA[3] = ve - vwe;
     267      575267 :         SH_BETA[4] = 1/sq(SH_BETA[0]);
     268      575267 :         SH_BETA[5] = 1/SH_BETA[0];
     269      575267 :         SH_BETA[6] = SH_BETA[5]*(sq(q0) - sq(q1) + sq(q2) - sq(q3));
     270      575267 :         SH_BETA[7] = sq(q0) + sq(q1) - sq(q2) - sq(q3);
     271      575267 :         SH_BETA[8] = 2*q0*SH_BETA[3] - 2*q3*SH_BETA[2] + 2*q1*vd;
     272      575267 :         SH_BETA[9] = 2*q0*SH_BETA[2] + 2*q3*SH_BETA[3] - 2*q2*vd;
     273      575267 :         SH_BETA[10] = 2*q2*SH_BETA[2] - 2*q1*SH_BETA[3] + 2*q0*vd;
     274      575267 :         SH_BETA[11] = 2*q1*SH_BETA[2] + 2*q2*SH_BETA[3] + 2*q3*vd;
     275             :         SH_BETA[12] = 2*q0*q3;
     276             : 
     277      575267 :         H_BETA[0] = SH_BETA[5]*SH_BETA[8] - SH_BETA[1]*SH_BETA[4]*SH_BETA[9];
     278      575267 :         H_BETA[1] = SH_BETA[5]*SH_BETA[10] - SH_BETA[1]*SH_BETA[4]*SH_BETA[11];
     279      575267 :         H_BETA[2] = SH_BETA[5]*SH_BETA[11] + SH_BETA[1]*SH_BETA[4]*SH_BETA[10];
     280      575267 :         H_BETA[3] = - SH_BETA[5]*SH_BETA[9] - SH_BETA[1]*SH_BETA[4]*SH_BETA[8];
     281      575267 :         H_BETA[4] = - SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) - SH_BETA[1]*SH_BETA[4]*SH_BETA[7];
     282      575267 :         H_BETA[5] = SH_BETA[6] - SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2);
     283      575267 :         H_BETA[6] = SH_BETA[5]*(2*q0*q1 + 2*q2*q3) + SH_BETA[1]*SH_BETA[4]*(2*q0*q2 - 2*q1*q3);
     284             :         for (uint8_t i=7; i<=21; i++) {
     285             :             H_BETA[i] = 0.0f;
     286             :         }
     287      575267 :         H_BETA[22] = SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) + SH_BETA[1]*SH_BETA[4]*SH_BETA[7];
     288      575267 :         H_BETA[23] = SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2) - SH_BETA[6];
     289             : 
     290             :         // Calculate Kalman gains
     291      575267 :         ftype temp = (R_BETA - (SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) + SH_BETA[1]*SH_BETA[4]*SH_BETA[7])*(P[22][4]*(SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) + SH_BETA[1]*SH_BETA[4]*SH_BETA[7]) - P[4][4]*(SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) + SH_BETA[1]*SH_BETA[4]*SH_BETA[7]) + P[5][4]*(SH_BETA[6] - SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2)) - P[23][4]*(SH_BETA[6] - SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2)) + P[0][4]*(SH_BETA[5]*SH_BETA[8] - SH_BETA[1]*SH_BETA[4]*SH_BETA[9]) + P[1][4]*(SH_BETA[5]*SH_BETA[10] - SH_BETA[1]*SH_BETA[4]*SH_BETA[11]) + P[2][4]*(SH_BETA[5]*SH_BETA[11] + SH_BETA[1]*SH_BETA[4]*SH_BETA[10]) - P[3][4]*(SH_BETA[5]*SH_BETA[9] + SH_BETA[1]*SH_BETA[4]*SH_BETA[8]) + P[6][4]*(SH_BETA[5]*(2*q0*q1 + 2*q2*q3) + SH_BETA[1]*SH_BETA[4]*(2*q0*q2 - 2*q1*q3))) + (SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) + SH_BETA[1]*SH_BETA[4]*SH_BETA[7])*(P[22][22]*(SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) + SH_BETA[1]*SH_BETA[4]*SH_BETA[7]) - P[4][22]*(SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) + SH_BETA[1]*SH_BETA[4]*SH_BETA[7]) + P[5][22]*(SH_BETA[6] - SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2)) - P[23][22]*(SH_BETA[6] - SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2)) + P[0][22]*(SH_BETA[5]*SH_BETA[8] - SH_BETA[1]*SH_BETA[4]*SH_BETA[9]) + P[1][22]*(SH_BETA[5]*SH_BETA[10] - SH_BETA[1]*SH_BETA[4]*SH_BETA[11]) + P[2][22]*(SH_BETA[5]*SH_BETA[11] + SH_BETA[1]*SH_BETA[4]*SH_BETA[10]) - P[3][22]*(SH_BETA[5]*SH_BETA[9] + SH_BETA[1]*SH_BETA[4]*SH_BETA[8]) + P[6][22]*(SH_BETA[5]*(2*q0*q1 + 2*q2*q3) + SH_BETA[1]*SH_BETA[4]*(2*q0*q2 - 2*q1*q3))) + (SH_BETA[6] - SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2))*(P[22][5]*(SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) + SH_BETA[1]*SH_BETA[4]*SH_BETA[7]) - P[4][5]*(SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) + SH_BETA[1]*SH_BETA[4]*SH_BETA[7]) + P[5][5]*(SH_BETA[6] - SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2)) - P[23][5]*(SH_BETA[6] - SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2)) + P[0][5]*(SH_BETA[5]*SH_BETA[8] - SH_BETA[1]*SH_BETA[4]*SH_BETA[9]) + P[1][5]*(SH_BETA[5]*SH_BETA[10] - SH_BETA[1]*SH_BETA[4]*SH_BETA[11]) + P[2][5]*(SH_BETA[5]*SH_BETA[11] + SH_BETA[1]*SH_BETA[4]*SH_BETA[10]) - P[3][5]*(SH_BETA[5]*SH_BETA[9] + SH_BETA[1]*SH_BETA[4]*SH_BETA[8]) + P[6][5]*(SH_BETA[5]*(2*q0*q1 + 2*q2*q3) + SH_BETA[1]*SH_BETA[4]*(2*q0*q2 - 2*q1*q3))) - (SH_BETA[6] - SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2))*(P[22][23]*(SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) + SH_BETA[1]*SH_BETA[4]*SH_BETA[7]) - P[4][23]*(SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) + SH_BETA[1]*SH_BETA[4]*SH_BETA[7]) + P[5][23]*(SH_BETA[6] - SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2)) - P[23][23]*(SH_BETA[6] - SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2)) + P[0][23]*(SH_BETA[5]*SH_BETA[8] - SH_BETA[1]*SH_BETA[4]*SH_BETA[9]) + P[1][23]*(SH_BETA[5]*SH_BETA[10] - SH_BETA[1]*SH_BETA[4]*SH_BETA[11]) + P[2][23]*(SH_BETA[5]*SH_BETA[11] + SH_BETA[1]*SH_BETA[4]*SH_BETA[10]) - P[3][23]*(SH_BETA[5]*SH_BETA[9] + SH_BETA[1]*SH_BETA[4]*SH_BETA[8]) + P[6][23]*(SH_BETA[5]*(2*q0*q1 + 2*q2*q3) + SH_BETA[1]*SH_BETA[4]*(2*q0*q2 - 2*q1*q3))) + (SH_BETA[5]*SH_BETA[8] - SH_BETA[1]*SH_BETA[4]*SH_BETA[9])*(P[22][0]*(SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) + SH_BETA[1]*SH_BETA[4]*SH_BETA[7]) - P[4][0]*(SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) + SH_BETA[1]*SH_BETA[4]*SH_BETA[7]) + P[5][0]*(SH_BETA[6] - SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2)) - P[23][0]*(SH_BETA[6] - SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2)) + P[0][0]*(SH_BETA[5]*SH_BETA[8] - SH_BETA[1]*SH_BETA[4]*SH_BETA[9]) + P[1][0]*(SH_BETA[5]*SH_BETA[10] - SH_BETA[1]*SH_BETA[4]*SH_BETA[11]) + P[2][0]*(SH_BETA[5]*SH_BETA[11] + SH_BETA[1]*SH_BETA[4]*SH_BETA[10]) - P[3][0]*(SH_BETA[5]*SH_BETA[9] + SH_BETA[1]*SH_BETA[4]*SH_BETA[8]) + P[6][0]*(SH_BETA[5]*(2*q0*q1 + 2*q2*q3) + SH_BETA[1]*SH_BETA[4]*(2*q0*q2 - 2*q1*q3))) + (SH_BETA[5]*SH_BETA[10] - SH_BETA[1]*SH_BETA[4]*SH_BETA[11])*(P[22][1]*(SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) + SH_BETA[1]*SH_BETA[4]*SH_BETA[7]) - P[4][1]*(SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) + SH_BETA[1]*SH_BETA[4]*SH_BETA[7]) + P[5][1]*(SH_BETA[6] - SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2)) - P[23][1]*(SH_BETA[6] - SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2)) + P[0][1]*(SH_BETA[5]*SH_BETA[8] - SH_BETA[1]*SH_BETA[4]*SH_BETA[9]) + P[1][1]*(SH_BETA[5]*SH_BETA[10] - SH_BETA[1]*SH_BETA[4]*SH_BETA[11]) + P[2][1]*(SH_BETA[5]*SH_BETA[11] + SH_BETA[1]*SH_BETA[4]*SH_BETA[10]) - P[3][1]*(SH_BETA[5]*SH_BETA[9] + SH_BETA[1]*SH_BETA[4]*SH_BETA[8]) + P[6][1]*(SH_BETA[5]*(2*q0*q1 + 2*q2*q3) + SH_BETA[1]*SH_BETA[4]*(2*q0*q2 - 2*q1*q3))) + (SH_BETA[5]*SH_BETA[11] + SH_BETA[1]*SH_BETA[4]*SH_BETA[10])*(P[22][2]*(SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) + SH_BETA[1]*SH_BETA[4]*SH_BETA[7]) - P[4][2]*(SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) + SH_BETA[1]*SH_BETA[4]*SH_BETA[7]) + P[5][2]*(SH_BETA[6] - SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2)) - P[23][2]*(SH_BETA[6] - SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2)) + P[0][2]*(SH_BETA[5]*SH_BETA[8] - SH_BETA[1]*SH_BETA[4]*SH_BETA[9]) + P[1][2]*(SH_BETA[5]*SH_BETA[10] - SH_BETA[1]*SH_BETA[4]*SH_BETA[11]) + P[2][2]*(SH_BETA[5]*SH_BETA[11] + SH_BETA[1]*SH_BETA[4]*SH_BETA[10]) - P[3][2]*(SH_BETA[5]*SH_BETA[9] + SH_BETA[1]*SH_BETA[4]*SH_BETA[8]) + P[6][2]*(SH_BETA[5]*(2*q0*q1 + 2*q2*q3) + SH_BETA[1]*SH_BETA[4]*(2*q0*q2 - 2*q1*q3))) - (SH_BETA[5]*SH_BETA[9] + SH_BETA[1]*SH_BETA[4]*SH_BETA[8])*(P[22][3]*(SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) + SH_BETA[1]*SH_BETA[4]*SH_BETA[7]) - P[4][3]*(SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) + SH_BETA[1]*SH_BETA[4]*SH_BETA[7]) + P[5][3]*(SH_BETA[6] - SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2)) - P[23][3]*(SH_BETA[6] - SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2)) + P[0][3]*(SH_BETA[5]*SH_BETA[8] - SH_BETA[1]*SH_BETA[4]*SH_BETA[9]) + P[1][3]*(SH_BETA[5]*SH_BETA[10] - SH_BETA[1]*SH_BETA[4]*SH_BETA[11]) + P[2][3]*(SH_BETA[5]*SH_BETA[11] + SH_BETA[1]*SH_BETA[4]*SH_BETA[10]) - P[3][3]*(SH_BETA[5]*SH_BETA[9] + SH_BETA[1]*SH_BETA[4]*SH_BETA[8]) + P[6][3]*(SH_BETA[5]*(2*q0*q1 + 2*q2*q3) + SH_BETA[1]*SH_BETA[4]*(2*q0*q2 - 2*q1*q3))) + (SH_BETA[5]*(2*q0*q1 + 2*q2*q3) + SH_BETA[1]*SH_BETA[4]*(2*q0*q2 - 2*q1*q3))*(P[22][6]*(SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) + SH_BETA[1]*SH_BETA[4]*SH_BETA[7]) - P[4][6]*(SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) + SH_BETA[1]*SH_BETA[4]*SH_BETA[7]) + P[5][6]*(SH_BETA[6] - SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2)) - P[23][6]*(SH_BETA[6] - SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2)) + P[0][6]*(SH_BETA[5]*SH_BETA[8] - SH_BETA[1]*SH_BETA[4]*SH_BETA[9]) + P[1][6]*(SH_BETA[5]*SH_BETA[10] - SH_BETA[1]*SH_BETA[4]*SH_BETA[11]) + P[2][6]*(SH_BETA[5]*SH_BETA[11] + SH_BETA[1]*SH_BETA[4]*SH_BETA[10]) - P[3][6]*(SH_BETA[5]*SH_BETA[9] + SH_BETA[1]*SH_BETA[4]*SH_BETA[8]) + P[6][6]*(SH_BETA[5]*(2*q0*q1 + 2*q2*q3) + SH_BETA[1]*SH_BETA[4]*(2*q0*q2 - 2*q1*q3))));
     292      575267 :         if (temp >= R_BETA) {
     293      575267 :             SK_BETA[0] = 1.0f / temp;
     294      575267 :             faultStatus.bad_sideslip = false;
     295             :         } else {
     296             :             // the calculation is badly conditioned, so we cannot perform fusion on this step
     297             :             // we reset the covariance matrix and try again next measurement
     298           0 :             CovarianceInit();
     299           0 :             faultStatus.bad_sideslip = true;
     300           0 :             return;
     301             :         }
     302             :         SK_BETA[1] = SH_BETA[5]*(SH_BETA[12] - 2*q1*q2) + SH_BETA[1]*SH_BETA[4]*SH_BETA[7];
     303             :         SK_BETA[2] = SH_BETA[6] - SH_BETA[1]*SH_BETA[4]*(SH_BETA[12] + 2*q1*q2);
     304             :         SK_BETA[3] = SH_BETA[5]*(2*q0*q1 + 2*q2*q3) + SH_BETA[1]*SH_BETA[4]*(2*q0*q2 - 2*q1*q3);
     305             :         SK_BETA[4] = SH_BETA[5]*SH_BETA[10] - SH_BETA[1]*SH_BETA[4]*SH_BETA[11];
     306             :         SK_BETA[5] = SH_BETA[5]*SH_BETA[8] - SH_BETA[1]*SH_BETA[4]*SH_BETA[9];
     307             :         SK_BETA[6] = SH_BETA[5]*SH_BETA[11] + SH_BETA[1]*SH_BETA[4]*SH_BETA[10];
     308             :         SK_BETA[7] = SH_BETA[5]*SH_BETA[9] + SH_BETA[1]*SH_BETA[4]*SH_BETA[8];
     309             : 
     310             :         uint32_t kalman_mask = 0; // values to calculate in Kfusion (others are set to zero)
     311             : 
     312      575267 :         if (!airDataFusionWindOnly) {
     313             :             kalman_mask = (1<<10)-1;
     314             :         }
     315             : 
     316      575267 :         if (!inhibitDelAngBiasStates && !airDataFusionWindOnly) {
     317        5924 :             kalman_mask |= (1<<10) | (1<<11) | (1<<12);
     318             :         }
     319             : 
     320      575267 :         if (!inhibitDelVelBiasStates && !airDataFusionWindOnly) {
     321       23696 :             for (uint8_t index = 0; index < 3; index++) {
     322       17772 :                 const uint8_t stateIndex = index + 13;
     323       17772 :                 if (!dvelBiasAxisInhibit[index]) {
     324       17772 :                     kalman_mask |= (1<<stateIndex);
     325             :                 }
     326             :             }
     327             :         }
     328             : 
     329             :         // zero Kalman gains to inhibit magnetic field state estimation
     330      575267 :         if (!inhibitMagStates && !airDataFusionWindOnly) {
     331        5924 :             kalman_mask |= (1<<16) | (1<<17) | (1<<18) | (1<<19) | (1<<20) | (1<<21);
     332             :         }
     333             : 
     334      575267 :         if (!inhibitWindStates && !treatWindStatesAsTruth) {
     335      570573 :             kalman_mask |= (1<<22) | (1<<23);
     336             :         }
     337             : 
     338    14381675 :         for (auto i=0; i<24; i++) {
     339             :             ftype res = 0;
     340    13806408 :             if (kalman_mask & (1<<i)) {
     341     1271474 :                 res = SK_BETA[0]*(P[i][0]*SK_BETA[5] + P[i][1]*SK_BETA[4] - P[i][4]*SK_BETA[1] + P[i][5]*SK_BETA[2] + P[i][2]*SK_BETA[6] + P[i][6]*SK_BETA[3] - P[i][3]*SK_BETA[7] + P[i][22]*SK_BETA[1] - P[i][23]*SK_BETA[2]);
     342             :             }
     343    13806408 :             Kfusion[i] = res;
     344             :         }
     345             : 
     346             :         // calculate predicted sideslip angle and innovation using small angle approximation
     347      575267 :         innovBeta = constrain_ftype(vel_rel_wind.y / vel_rel_wind.x, -0.5f, 0.5f);
     348             : 
     349             :         // correct the covariance P = (I - K*H)*P = P - K*H*P. take advantage of
     350             :         // the zero elements of H to reduce the number of operations.
     351    14381675 :         for (unsigned i = 0; i<=stateIndexLim; i++) {
     352             :             // j as the inner loop allows the compiler to hoist the KH product
     353             :             // to save computation, and do the inner indexing more efficiently.
     354   345160200 :             for (unsigned j = 0; j<=stateIndexLim; j++) {
     355             :                 ftype res = 0;
     356   331353792 :                 res += (Kfusion[i] * H_BETA[0]) * P[0][j];
     357   331353792 :                 res += (Kfusion[i] * H_BETA[1]) * P[1][j];
     358   331353792 :                 res += (Kfusion[i] * H_BETA[2]) * P[2][j];
     359   331353792 :                 res += (Kfusion[i] * H_BETA[3]) * P[3][j];
     360   331353792 :                 res += (Kfusion[i] * H_BETA[4]) * P[4][j];
     361   331353792 :                 res += (Kfusion[i] * H_BETA[5]) * P[5][j];
     362   331353792 :                 res += (Kfusion[i] * H_BETA[6]) * P[6][j];
     363   331353792 :                 res += (Kfusion[i] * H_BETA[22]) * P[22][j];
     364   331353792 :                 res += (Kfusion[i] * H_BETA[23]) * P[23][j];
     365   331353792 :                 KHP[i][j] = res;
     366             :             }
     367             :         }
     368             : 
     369             :         // finish fusion from KHP and Kfusion
     370      575267 :         FinishFusion(innovBeta, true); // forcing fusion is probably a bug
     371             :     }
     372             : }
     373             : 
     374             : #if EK3_FEATURE_DRAG_FUSION
     375             : /*
     376             :  * Fuse X and Y body axis specific forces using explicit algebraic equations generated with SymPy.
     377             :  * See derivation/generate_2.py for derivation
     378             :  * Output for change reference: derivation/generated/acc_bf_generated.cpp
     379             : */
     380        5366 : void NavEKF3_core::FuseDragForces()
     381             : {
     382             :     // drag model parameters
     383        5366 :     const ftype bcoef_x = frontend->_ballisticCoef_x.get();
     384        5366 :     const ftype bcoef_y = frontend->_ballisticCoef_y.get();
     385        5366 :     const ftype mcoef = frontend->_momentumDragCoef.get();
     386        5366 :     const bool using_bcoef_x = bcoef_x > 1.0f;
     387        5366 :     const bool using_bcoef_y = bcoef_y > 1.0f;
     388        5366 :     const bool using_mcoef = mcoef > 0.001f;
     389             : 
     390        5366 :     ZERO_FARRAY(Kfusion);
     391             :     Vector24 Hfusion; // Observation Jacobians
     392        5366 :     const ftype R_ACC = sq(fmaxF(frontend->_dragObsNoise, 0.5f));
     393        5366 :     const ftype density_ratio = 1.0f/sq(dal.get_EAS2TAS());
     394        5366 :     const ftype rho = fmaxF(1.225f * density_ratio, 0.1f); // air density
     395             : 
     396             :     // get latest estimated orientation
     397        5366 :     const ftype &q0 = stateStruct.quat[0];
     398        5366 :     const ftype &q1 = stateStruct.quat[1];
     399        5366 :     const ftype &q2 = stateStruct.quat[2];
     400        5366 :     const ftype &q3 = stateStruct.quat[3];
     401             : 
     402             :     // get latest velocity in earth frame
     403             :     const ftype &vn = stateStruct.velocity.x;
     404             :     const ftype &ve = stateStruct.velocity.y;
     405             :     const ftype &vd = stateStruct.velocity.z;
     406             : 
     407             :     // get latest wind velocity in earth frame
     408             :     const ftype &vwn = stateStruct.wind_vel.x;
     409             :     const ftype &vwe = stateStruct.wind_vel.y;
     410             : 
     411             :     // predicted specific forces
     412             :     // calculate relative wind velocity in earth frame and rotate into body frame
     413        5366 :     const Vector3F rel_wind_earth(vn - vwn, ve - vwe, vd);
     414        5366 :     const Vector3F rel_wind_body = prevTnb * rel_wind_earth;
     415             : 
     416             :     // perform sequential fusion of XY specific forces
     417       16098 :     for (uint8_t axis_index = 0; axis_index < 2; axis_index++) {
     418             :         // correct accel data for bias
     419       10732 :         const ftype mea_acc = dragSampleDelayed.accelXY[axis_index]  - stateStruct.accel_bias[axis_index] / dtEkfAvg;
     420             : 
     421             :         // Acceleration in m/s/s predicted using vehicle and wind velocity estimates
     422             :         // Initialised to measured value and updated later using available drag model
     423             :         ftype predAccel = mea_acc;
     424             : 
     425             :         // predicted sign of drag force
     426       10732 :         const ftype dragForceSign = is_positive(rel_wind_body[axis_index]) ? -1.0f : 1.0f;
     427             : 
     428       10732 :         if (axis_index == 0) {
     429             :             // drag can be modelled as an arbitrary  combination of bluff body drag that proportional to
     430             :             // speed squared, and rotor momentum drag that is proportional to speed.
     431             :             ftype Kaccx; // Derivative of specific force wrt airspeed
     432        5366 :             if (using_mcoef && using_bcoef_x) {
     433             :                 // mixed bluff body and propeller momentum drag
     434        5366 :                 const ftype airSpd = (bcoef_x / rho) * (- mcoef + sqrtF(sq(mcoef) + 2.0f * (rho / bcoef_x) * fabsF(mea_acc)));
     435        5366 :                 Kaccx = fmaxF(1e-1f, (rho / bcoef_x) * airSpd + mcoef * density_ratio);
     436        5366 :                 predAccel = (0.5f / bcoef_x) * rho * sq(rel_wind_body[0]) * dragForceSign - rel_wind_body[0] * mcoef * density_ratio;
     437           0 :             } else if (using_mcoef) {
     438             :                 // propeller momentum drag only
     439           0 :                 Kaccx = fmaxF(1e-1f, mcoef * density_ratio);
     440           0 :                 predAccel = - rel_wind_body[0] * mcoef * density_ratio;
     441           0 :             } else if (using_bcoef_x) {
     442             :                 // bluff body drag only
     443           0 :                 const ftype airSpd = sqrtF((2.0f * bcoef_x * fabsF(mea_acc)) / rho);
     444           0 :                 Kaccx = fmaxF(1e-1f, (rho / bcoef_x) * airSpd);
     445           0 :                 predAccel = (0.5f / bcoef_x) * rho * sq(rel_wind_body[0]) * dragForceSign;
     446             :             } else {
     447             :                 // skip this axis
     448           0 :                 continue;
     449             :             }
     450             : 
     451             :             // intermediate variables
     452        5366 :             const ftype HK0 = vn - vwn;
     453        5366 :             const ftype HK1 = ve - vwe;
     454        5366 :             const ftype HK2 = HK0*q0 + HK1*q3 - q2*vd;
     455        5366 :             const ftype HK3 = 2*Kaccx;
     456        5366 :             const ftype HK4 = HK0*q1 + HK1*q2 + q3*vd;
     457        5366 :             const ftype HK5 = HK0*q2 - HK1*q1 + q0*vd;
     458        5366 :             const ftype HK6 = -HK0*q3 + HK1*q0 + q1*vd;
     459        5366 :             const ftype HK7 = sq(q0) + sq(q1) - sq(q2) - sq(q3);
     460        5366 :             const ftype HK8 = HK7*Kaccx;
     461        5366 :             const ftype HK9 = q0*q3 + q1*q2;
     462        5366 :             const ftype HK10 = HK3*HK9;
     463        5366 :             const ftype HK11 = q0*q2 - q1*q3;
     464        5366 :             const ftype HK12 = 2*HK9;
     465        5366 :             const ftype HK13 = 2*HK11;
     466        5366 :             const ftype HK14 = 2*HK4;
     467        5366 :             const ftype HK15 = 2*HK2;
     468        5366 :             const ftype HK16 = 2*HK5;
     469        5366 :             const ftype HK17 = 2*HK6;
     470        5366 :             const ftype HK18 = -HK12*P[0][23] + HK12*P[0][5] - HK13*P[0][6] + HK14*P[0][1] + HK15*P[0][0] - HK16*P[0][2] + HK17*P[0][3] - HK7*P[0][22] + HK7*P[0][4];
     471        5366 :             const ftype HK19 = HK12*P[5][23];
     472        5366 :             const ftype HK20 = -HK12*P[23][23] - HK13*P[6][23] + HK14*P[1][23] + HK15*P[0][23] - HK16*P[2][23] + HK17*P[3][23] + HK19 - HK7*P[22][23] + HK7*P[4][23];
     473        5366 :             const ftype HK21 = sq(Kaccx);
     474        5366 :             const ftype HK22 = HK12*HK21;
     475        5366 :             const ftype HK23 = HK12*P[5][5] - HK13*P[5][6] + HK14*P[1][5] + HK15*P[0][5] - HK16*P[2][5] + HK17*P[3][5] - HK19 + HK7*P[4][5] - HK7*P[5][22];
     476        5366 :             const ftype HK24 = HK12*P[5][6] - HK12*P[6][23] - HK13*P[6][6] + HK14*P[1][6] + HK15*P[0][6] - HK16*P[2][6] + HK17*P[3][6] + HK7*P[4][6] - HK7*P[6][22];
     477        5366 :             const ftype HK25 = HK7*P[4][22];
     478        5366 :             const ftype HK26 = -HK12*P[4][23] + HK12*P[4][5] - HK13*P[4][6] + HK14*P[1][4] + HK15*P[0][4] - HK16*P[2][4] + HK17*P[3][4] - HK25 + HK7*P[4][4];
     479        5366 :             const ftype HK27 = HK21*HK7;
     480        5366 :             const ftype HK28 = -HK12*P[22][23] + HK12*P[5][22] - HK13*P[6][22] + HK14*P[1][22] + HK15*P[0][22] - HK16*P[2][22] + HK17*P[3][22] + HK25 - HK7*P[22][22];
     481        5366 :             const ftype HK29 = -HK12*P[1][23] + HK12*P[1][5] - HK13*P[1][6] + HK14*P[1][1] + HK15*P[0][1] - HK16*P[1][2] + HK17*P[1][3] - HK7*P[1][22] + HK7*P[1][4];
     482        5366 :             const ftype HK30 = -HK12*P[2][23] + HK12*P[2][5] - HK13*P[2][6] + HK14*P[1][2] + HK15*P[0][2] - HK16*P[2][2] + HK17*P[2][3] - HK7*P[2][22] + HK7*P[2][4];
     483        5366 :             const ftype HK31 = -HK12*P[3][23] + HK12*P[3][5] - HK13*P[3][6] + HK14*P[1][3] + HK15*P[0][3] - HK16*P[2][3] + HK17*P[3][3] - HK7*P[3][22] + HK7*P[3][4];
     484             :             // const ftype HK32 = Kaccx/(-HK13*HK21*HK24 + HK14*HK21*HK29 + HK15*HK18*HK21 - HK16*HK21*HK30 + HK17*HK21*HK31 - HK20*HK22 + HK22*HK23 + HK26*HK27 - HK27*HK28 + R_ACC);
     485             : 
     486             :             // calculate innovation variance and exit if badly conditioned
     487        5366 :             innovDragVar.x = (-HK13*HK21*HK24 + HK14*HK21*HK29 + HK15*HK18*HK21 - HK16*HK21*HK30 + HK17*HK21*HK31 - HK20*HK22 + HK22*HK23 + HK26*HK27 - HK27*HK28 + R_ACC);
     488        5366 :             if (innovDragVar.x < R_ACC) {
     489           0 :                 return;
     490             :             }
     491        5366 :             const ftype HK32 = Kaccx / innovDragVar.x;
     492             : 
     493             :             // Observation Jacobians
     494        5366 :             Hfusion[0] = -HK2*HK3;
     495        5366 :             Hfusion[1] = -HK3*HK4;
     496        5366 :             Hfusion[2] = HK3*HK5;
     497        5366 :             Hfusion[3] = -HK3*HK6;
     498        5366 :             Hfusion[4] = -HK8;
     499        5366 :             Hfusion[5] = -HK10;
     500        5366 :             Hfusion[6] = HK11*HK3;
     501             :             Hfusion[22] = HK8;
     502             :             Hfusion[23] = HK10;
     503             : 
     504             :             // Kalman gains
     505             :             // Don't allow modification of any states other than wind velocity - we only need a wind estimate.
     506             :             // See derivation/generated/acc_bf_generated.cpp for un-implemented Kalman gain equations.
     507        5366 :             Kfusion[22] = -HK28*HK32;
     508        5366 :             Kfusion[23] = -HK20*HK32;
     509             : 
     510             : 
     511             :         } else if (axis_index == 1) {
     512             :             // drag can be modelled as an arbitrary combination of bluff body drag that proportional to
     513             :             // speed squared, and rotor momentum drag that is proportional to speed.
     514             :             ftype Kaccy; // Derivative of specific force wrt airspeed
     515        5366 :             if (using_mcoef && using_bcoef_y) {
     516             :                 // mixed bluff body and propeller momentum drag
     517        5366 :                 const ftype airSpd = (bcoef_y / rho) * (- mcoef + sqrtF(sq(mcoef) + 2.0f * (rho / bcoef_y) * fabsF(mea_acc)));
     518        5366 :                 Kaccy = fmaxF(1e-1f, (rho / bcoef_y) * airSpd + mcoef * density_ratio);
     519        5366 :                 predAccel = (0.5f / bcoef_y) * rho * sq(rel_wind_body[1]) * dragForceSign - rel_wind_body[1] * mcoef * density_ratio;
     520           0 :             } else if (using_mcoef) {
     521             :                 // propeller momentum drag only
     522           0 :                 Kaccy = fmaxF(1e-1f, mcoef * density_ratio);
     523           0 :                 predAccel = - rel_wind_body[1] * mcoef * density_ratio;
     524           0 :             } else if (using_bcoef_y) {
     525             :                 // bluff body drag only
     526           0 :                 const ftype airSpd = sqrtF((2.0f * bcoef_y * fabsF(mea_acc)) / rho);
     527           0 :                 Kaccy = fmaxF(1e-1f, (rho / bcoef_y) * airSpd);
     528           0 :                 predAccel = (0.5f / bcoef_y) * rho * sq(rel_wind_body[1]) * dragForceSign;
     529             :             } else {
     530             :                 // nothing more to do
     531             :                 return;
     532             :             }
     533             : 
     534             :             // intermediate variables
     535        5366 :             const ftype HK0 = ve - vwe;
     536        5366 :             const ftype HK1 = vn - vwn;
     537        5366 :             const ftype HK2 = HK0*q0 - HK1*q3 + q1*vd;
     538        5366 :             const ftype HK3 = 2*Kaccy;
     539        5366 :             const ftype HK4 = -HK0*q1 + HK1*q2 + q0*vd;
     540        5366 :             const ftype HK5 = HK0*q2 + HK1*q1 + q3*vd;
     541        5366 :             const ftype HK6 = HK0*q3 + HK1*q0 - q2*vd;
     542        5366 :             const ftype HK7 = q0*q3 - q1*q2;
     543        5366 :             const ftype HK8 = HK3*HK7;
     544        5366 :             const ftype HK9 = sq(q0) - sq(q1) + sq(q2) - sq(q3);
     545        5366 :             const ftype HK10 = HK9*Kaccy;
     546        5366 :             const ftype HK11 = q0*q1 + q2*q3;
     547        5366 :             const ftype HK12 = 2*HK11;
     548        5366 :             const ftype HK13 = 2*HK7;
     549        5366 :             const ftype HK14 = 2*HK5;
     550        5366 :             const ftype HK15 = 2*HK2;
     551        5366 :             const ftype HK16 = 2*HK4;
     552        5366 :             const ftype HK17 = 2*HK6;
     553        5366 :             const ftype HK18 = HK12*P[0][6] + HK13*P[0][22] - HK13*P[0][4] + HK14*P[0][2] + HK15*P[0][0] + HK16*P[0][1] - HK17*P[0][3] - HK9*P[0][23] + HK9*P[0][5];
     554        5366 :             const ftype HK19 = sq(Kaccy);
     555        5366 :             const ftype HK20 = HK12*P[6][6] - HK13*P[4][6] + HK13*P[6][22] + HK14*P[2][6] + HK15*P[0][6] + HK16*P[1][6] - HK17*P[3][6] + HK9*P[5][6] - HK9*P[6][23];
     556        5366 :             const ftype HK21 = HK13*P[4][22];
     557        5366 :             const ftype HK22 = HK12*P[6][22] + HK13*P[22][22] + HK14*P[2][22] + HK15*P[0][22] + HK16*P[1][22] - HK17*P[3][22] - HK21 - HK9*P[22][23] + HK9*P[5][22];
     558        5366 :             const ftype HK23 = HK13*HK19;
     559        5366 :             const ftype HK24 = HK12*P[4][6] - HK13*P[4][4] + HK14*P[2][4] + HK15*P[0][4] + HK16*P[1][4] - HK17*P[3][4] + HK21 - HK9*P[4][23] + HK9*P[4][5];
     560        5366 :             const ftype HK25 = HK9*P[5][23];
     561        5366 :             const ftype HK26 = HK12*P[5][6] - HK13*P[4][5] + HK13*P[5][22] + HK14*P[2][5] + HK15*P[0][5] + HK16*P[1][5] - HK17*P[3][5] - HK25 + HK9*P[5][5];
     562        5366 :             const ftype HK27 = HK19*HK9;
     563        5366 :             const ftype HK28 = HK12*P[6][23] + HK13*P[22][23] - HK13*P[4][23] + HK14*P[2][23] + HK15*P[0][23] + HK16*P[1][23] - HK17*P[3][23] + HK25 - HK9*P[23][23];
     564        5366 :             const ftype HK29 = HK12*P[2][6] + HK13*P[2][22] - HK13*P[2][4] + HK14*P[2][2] + HK15*P[0][2] + HK16*P[1][2] - HK17*P[2][3] - HK9*P[2][23] + HK9*P[2][5];
     565        5366 :             const ftype HK30 = HK12*P[1][6] + HK13*P[1][22] - HK13*P[1][4] + HK14*P[1][2] + HK15*P[0][1] + HK16*P[1][1] - HK17*P[1][3] - HK9*P[1][23] + HK9*P[1][5];
     566        5366 :             const ftype HK31 = HK12*P[3][6] + HK13*P[3][22] - HK13*P[3][4] + HK14*P[2][3] + HK15*P[0][3] + HK16*P[1][3] - HK17*P[3][3] - HK9*P[3][23] + HK9*P[3][5];
     567             :             // const ftype HK32 = Kaccy/(HK12*HK19*HK20 + HK14*HK19*HK29 + HK15*HK18*HK19 + HK16*HK19*HK30 - HK17*HK19*HK31 + HK22*HK23 - HK23*HK24 + HK26*HK27 - HK27*HK28 + R_ACC);
     568             : 
     569        5366 :             innovDragVar.y = (HK12*HK19*HK20 + HK14*HK19*HK29 + HK15*HK18*HK19 + HK16*HK19*HK30 - HK17*HK19*HK31 + HK22*HK23 - HK23*HK24 + HK26*HK27 - HK27*HK28 + R_ACC);
     570        5366 :             if (innovDragVar.y < R_ACC) {
     571             :                 // calculation is badly conditioned
     572             :                 return;
     573             :             }
     574        5366 :             const ftype HK32 = Kaccy / innovDragVar.y;
     575             : 
     576             :             // Observation Jacobians
     577        5366 :             Hfusion[0] = -HK2*HK3;
     578        5366 :             Hfusion[1] = -HK3*HK4;
     579        5366 :             Hfusion[2] = -HK3*HK5;
     580        5366 :             Hfusion[3] = HK3*HK6;
     581             :             Hfusion[4] = HK8;
     582        5366 :             Hfusion[5] = -HK10;
     583        5366 :             Hfusion[6] = -HK11*HK3;
     584        5366 :             Hfusion[22] = -HK8;
     585             :             Hfusion[23] = HK10;
     586             : 
     587             :             // Kalman gains
     588             :             // Don't allow modification of any states other than wind velocity at this stage of development - we only need a wind estimate.
     589             :             // See derivation/generated/acc_bf_generated.cpp for un-implemented Kalman gain equations.
     590        5366 :             Kfusion[22] = -HK22*HK32;
     591        5366 :             Kfusion[23] = -HK28*HK32;
     592             :         }
     593             : 
     594       10732 :         innovDrag[axis_index] = predAccel - mea_acc;
     595       10732 :         dragTestRatio[axis_index] = sq(innovDrag[axis_index]) / (25.0f * innovDragVar[axis_index]);
     596             : 
     597             :         // if the innovation consistency check fails then don't fuse the sample
     598       10732 :         if (dragTestRatio[axis_index] > 1.0f) {
     599             :             return;
     600             :         }
     601             : 
     602             :         // correct the covariance P = (I - K*H)*P = P - K*H*P. take advantage of
     603             :         // the zero elements of H to reduce the number of operations.
     604      268300 :         for (unsigned i = 0; i<=stateIndexLim; i++) {
     605             :             // j as the inner loop allows the compiler to hoist the KH product
     606             :             // to save computation, and do the inner indexing more efficiently.
     607     6439200 :             for (unsigned j = 0; j<=stateIndexLim; j++) {
     608             :                 ftype res = 0;
     609     6181632 :                 res += (Kfusion[i] * Hfusion[0]) * P[0][j];
     610     6181632 :                 res += (Kfusion[i] * Hfusion[1]) * P[1][j];
     611     6181632 :                 res += (Kfusion[i] * Hfusion[2]) * P[2][j];
     612     6181632 :                 res += (Kfusion[i] * Hfusion[3]) * P[3][j];
     613     6181632 :                 res += (Kfusion[i] * Hfusion[4]) * P[4][j];
     614     6181632 :                 res += (Kfusion[i] * Hfusion[5]) * P[5][j];
     615     6181632 :                 res += (Kfusion[i] * Hfusion[6]) * P[6][j];
     616     6181632 :                 res += (Kfusion[i] * Hfusion[22]) * P[22][j];
     617     6181632 :                 res += (Kfusion[i] * Hfusion[23]) * P[23][j];
     618     6181632 :                 KHP[i][j] = res;
     619             :             }
     620             :         }
     621             : 
     622             :         // finish fusion from KHP and Kfusion
     623       10732 :         FinishFusion(innovDrag[axis_index], true); // forcing fusion is probably a bug
     624             :     }
     625             : 
     626             :     // record time of successful fusion
     627        5366 :     lastDragPassTime_ms = imuSampleTime_ms;
     628             : }
     629             : #endif // EK3_FEATURE_DRAG_FUSION
     630             : 
     631             : /********************************************************
     632             : *                   MISC FUNCTIONS                      *
     633             : ********************************************************/
     634             : 

Generated by: LCOV version 1.14