LCOV - code coverage report
Current view: top level - AP_NavEKF2 - AP_NavEKF2_core.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 983 999 98.4 %
Date: 2026-09-09 11:02:08 Functions: 24 25 96.0 %

          Line data    Source code
       1             : #include "AP_NavEKF2_core.h"
       2             : 
       3             : #include <AP_HAL/AP_HAL.h>
       4             : #include <AP_DAL/AP_DAL.h>
       5             : #include <GCS_MAVLink/GCS.h>
       6             : 
       7             : #include "AP_NavEKF2.h"
       8             : 
       9             : extern const AP_HAL::HAL& hal;
      10             : 
      11             : #define earthRate 0.000072921f // earth rotation rate (rad/sec)
      12             : 
      13             : // initial imu bias uncertainty (deg/sec)
      14             : #define INIT_ACCEL_BIAS_UNCERTAINTY 0.5f
      15             : 
      16             : // maximum allowed gyro bias (rad/sec)
      17             : #define GYRO_BIAS_LIMIT 0.5f
      18             : 
      19             : /*
      20             :   to run EK2 timing tests you need to set ENABLE_EKF_TIMING to 1, plus setup as follows:
      21             :     - copter at 400Hz
      22             :     - INS_FAST_SAMPLE=0
      23             :     - EKF2_MAG_CAL=4
      24             :     - GPS_TYPE=14
      25             :     - load fakegps in mavproxy
      26             :     - ensure a compass is enabled
      27             :     - wait till EK2 reports "using GPS" (this is important, ignore earlier results)
      28             : 
      29             :     DO NOT FLY WITH THIS ENABLED
      30             :  */
      31             : #define ENABLE_EKF_TIMING 0
      32             : 
      33             : NavEKF2_core::Matrix24 NavEKF2_core::KH;
      34             : NavEKF2_core::Matrix24 NavEKF2_core::nextP;
      35             : 
      36             : // constructor
      37        1022 : NavEKF2_core::NavEKF2_core(NavEKF2 *_frontend) :
      38        1022 :     dal(AP::dal()),
      39       14308 :     frontend(_frontend)
      40             : {
      41        1022 : }
      42             : 
      43             : // setup this core backend
      44        1022 : bool NavEKF2_core::setup_core(uint8_t _imu_index, uint8_t _core_index)
      45             : {
      46        1022 :     imu_index = _imu_index;
      47        1022 :     gyro_index_active = _imu_index;
      48        1022 :     accel_index_active = _imu_index;
      49        1022 :     core_index = _core_index;
      50             : 
      51             :     /*
      52             :       the imu_buffer_length needs to cope with a 260ms delay at a
      53             :       maximum fusion rate of 100Hz. Non-imu data coming in at faster
      54             :       than 100Hz is downsampled. For 50Hz main loop rate we need a
      55             :       shorter buffer.
      56             :      */
      57        1022 :     if (dal.ins().get_loop_rate_hz() < 100) {
      58         968 :         imu_buffer_length = 13;
      59             :     } else {
      60             :         // maximum 260 msec delay at 100 Hz fusion rate
      61          54 :         imu_buffer_length = 26;
      62             :     }
      63        1022 :     if(!storedGPS.init(OBS_BUFFER_LENGTH)) {
      64             :         return false;
      65             :     }
      66        1022 :     if(!storedMag.init(OBS_BUFFER_LENGTH)) {
      67             :         return false;
      68             :     }
      69        1022 :     if(!storedBaro.init(OBS_BUFFER_LENGTH)) {
      70             :         return false;
      71             :     } 
      72        1022 :     if(!storedTAS.init(OBS_BUFFER_LENGTH)) {
      73             :         return false;
      74             :     }
      75        1022 :     if(!storedOF.init(FLOW_BUFFER_LENGTH)) {
      76             :         return false;
      77             :     }
      78             :     // Note: the use of dual range finders potentially doubles the amount of to be stored
      79        1022 :     if(!storedRange.init(2*OBS_BUFFER_LENGTH)) {
      80             :         return false;
      81             :     }
      82             :     // Note: range beacon data is read one beacon at a time and can arrive at a high rate
      83        1022 :     if(!storedRangeBeacon.init(imu_buffer_length)) {
      84             :         return false;
      85             :     }
      86        1022 :     if(!storedExtNav.init(EXTNAV_BUFFER_LENGTH)) {
      87             :         return false;
      88             :     }
      89        1022 :     if(!storedIMU.init(imu_buffer_length)) {
      90             :         return false;
      91             :     }
      92        1022 :     if(!storedOutput.init(imu_buffer_length)) {
      93             :         return false;
      94             :     }
      95        1022 :     if(!storedExtNavVel.init(EXTNAV_BUFFER_LENGTH)) {
      96             :        return false;
      97             :     }
      98             : 
      99        1022 :     if ((yawEstimator == nullptr) && (frontend->_gsfRunMask & (1U<<core_index))) {
     100             :         // check if there is enough memory to create the EKF-GSF object
     101        1022 :         if (dal.available_memory() < sizeof(EKFGSF_yaw) + 1024) {
     102           0 :             GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "EKF2 IMU%u GSF: not enough memory",(unsigned)imu_index);
     103           0 :             return false;
     104             :         }
     105             : 
     106             :         // try to instantiate
     107        1022 :         yawEstimator = NEW_NOTHROW EKFGSF_yaw();
     108        1022 :         if (yawEstimator == nullptr) {
     109           0 :             GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "EKF2 IMU%uGSF: allocation failed",(unsigned)imu_index);
     110           0 :             return false;
     111             :         }
     112             :     }
     113             :     
     114             :     return true;
     115             : }
     116             :     
     117             : 
     118             : /********************************************************
     119             : *                   INIT FUNCTIONS                      *
     120             : ********************************************************/
     121             : 
     122             : // Use a function call rather than a constructor to initialise variables because it enables the filter to be re-started in flight if necessary.
     123         558 : void NavEKF2_core::InitialiseVariables()
     124             : {
     125             :     // calculate the nominal filter update rate
     126         558 :     const auto &ins = dal.ins();
     127         558 :     localFilterTimeStep_ms = (uint8_t)(1000*ins.get_loop_delta_t());
     128         558 :     localFilterTimeStep_ms = MAX(localFilterTimeStep_ms,10);
     129             : 
     130             :     // initialise time stamps
     131         558 :     imuSampleTime_ms = frontend->imuSampleTime_us / 1000;
     132         558 :     prevTasStep_ms = imuSampleTime_ms;
     133         558 :     prevBetaStep_ms = imuSampleTime_ms;
     134         558 :     lastBaroReceived_ms = imuSampleTime_ms;
     135         558 :     lastVelPassTime_ms = 0;
     136         558 :     lastPosPassTime_ms = 0;
     137         558 :     lastHgtPassTime_ms = 0;
     138         558 :     lastTasPassTime_ms = 0;
     139         558 :     lastYawTime_ms = imuSampleTime_ms;
     140         558 :     lastTimeGpsReceived_ms = 0;
     141         558 :     timeAtLastAuxEKF_ms = imuSampleTime_ms;
     142         558 :     flowValidMeaTime_ms = imuSampleTime_ms;
     143         558 :     rngValidMeaTime_ms = imuSampleTime_ms;
     144         558 :     flowMeaTime_ms = 0;
     145         558 :     prevFlowFuseTime_ms = 0;
     146         558 :     gndHgtValidTime_ms = 0;
     147         558 :     ekfStartTime_ms = imuSampleTime_ms;
     148         558 :     lastGpsVelFail_ms = 0;
     149         558 :     lastGpsVelPass_ms = 0;
     150         558 :     lastGpsAidBadTime_ms = 0;
     151         558 :     timeTasReceived_ms = 0;
     152         558 :     lastPreAlignGpsCheckTime_ms = imuSampleTime_ms;
     153         558 :     posNEResetCount = 0;
     154         558 :     posDResetCount = 0;
     155         558 :     lastRngMeasTime_ms = 0;
     156             : 
     157             :     // initialise other variables
     158         558 :     gpsNoiseScaler = 1.0f;
     159         558 :     hgtTimeout = true;
     160         558 :     tasTimeout = true;
     161         558 :     badIMUdata = false;
     162         558 :     dtIMUavg = 0.0025f;
     163         558 :     dtEkfAvg = EKF_TARGET_DT;
     164         558 :     dt = 0;
     165         558 :     velDotNEDfilt.zero();
     166         558 :     lastKnownPositionNE.zero();
     167         558 :     prevTnb.zero();
     168         558 :     memset(&P[0][0], 0, sizeof(P));
     169         558 :     memset(&KH[0][0], 0, sizeof(KH));
     170         558 :     memset(&KHP[0][0], 0, sizeof(KHP));
     171         558 :     memset(&nextP[0][0], 0, sizeof(nextP));
     172         558 :     flowDataValid = false;
     173         558 :     rangeDataToFuse  = false;
     174         558 :     Popt = 0.0f;
     175         558 :     terrainState = 0.0f;
     176         558 :     prevPosN = stateStruct.position.x;
     177         558 :     prevPosE = stateStruct.position.y;
     178         558 :     inhibitGndState = false;
     179         558 :     flowGyroBias.x = 0;
     180         558 :     flowGyroBias.y = 0;
     181         558 :     PV_AidingMode = AID_NONE;
     182         558 :     PV_AidingModePrev = AID_NONE;
     183         558 :     posTimeout = true;
     184         558 :     velTimeout = true;
     185         558 :     memset(&faultStatus, 0, sizeof(faultStatus));
     186         558 :     hgtRate = 0.0f;
     187         558 :     mag_state.q0 = 1;
     188         558 :     mag_state.DCM.identity();
     189         558 :     onGround = true;
     190         558 :     prevOnGround = true;
     191         558 :     inFlight = false;
     192         558 :     prevInFlight = false;
     193         558 :     manoeuvring = false;
     194         558 :     inhibitWindStates = true;
     195         558 :     gndOffsetValid =  false;
     196         558 :     validOrigin = false;
     197         558 :     gpsSpdAccuracy = 0.0f;
     198         558 :     gpsPosAccuracy = 0.0f;
     199         558 :     gpsHgtAccuracy = 0.0f;
     200         558 :     baroHgtOffset = 0.0f;
     201         558 :     rngOnGnd = 0.05f;
     202         558 :     yawResetCount = 0;
     203         558 :     tiltErrFilt = 1.0f;
     204         558 :     tiltAlignComplete = false;
     205         558 :     stateIndexLim = 23;
     206         558 :     delAngCorrection.zero();
     207         558 :     velErrintegral.zero();
     208         558 :     posErrintegral.zero();
     209         558 :     gpsGoodToAlign = false;
     210         558 :     gpsNotAvailable = true;
     211         558 :     motorsArmed = false;
     212         558 :     prevMotorsArmed = false;
     213         558 :     memset(&gpsCheckStatus, 0, sizeof(gpsCheckStatus));
     214         558 :     gpsSpdAccPass = false;
     215         558 :     ekfInnovationsPass = false;
     216         558 :     sAccFilterState1 = 0.0f;
     217         558 :     sAccFilterState2 = 0.0f;
     218         558 :     lastGpsCheckTime_ms = 0;
     219         558 :     lastInnovPassTime_ms = 0;
     220         558 :     lastInnovFailTime_ms = 0;
     221         558 :     gpsAccuracyGood = false;
     222         558 :     gpsloc_prev = {};
     223         558 :     gpsDriftNE = 0.0f;
     224         558 :     gpsVertVelFilt = 0.0f;
     225         558 :     gpsHorizVelFilt = 0.0f;
     226         558 :     ZERO_FARRAY(statesArray);
     227         558 :     memset(&vertCompFiltState, 0, sizeof(vertCompFiltState));
     228         558 :     posVelFusionDelayed = false;
     229         558 :     optFlowFusionDelayed = false;
     230         558 :     airSpdFusionDelayed = false;
     231         558 :     sideSlipFusionDelayed = false;
     232         558 :     posResetNE.zero();
     233         558 :     posResetD = 0.0f;
     234         558 :     hgtInnovFiltState = 0.0f;
     235             : 
     236         558 :     imuDataDownSampledNew.delAng.zero();
     237         558 :     imuDataDownSampledNew.delVel.zero();
     238         558 :     imuDataDownSampledNew.delAngDT = 0.0f;
     239         558 :     imuDataDownSampledNew.delVelDT = 0.0f;
     240         558 :     runUpdates = false;
     241         558 :     framesSincePredict = 0;
     242         558 :     gpsYawResetRequest = false;
     243         558 :     stateStruct.quat.initialise();
     244         558 :     quatAtLastMagReset = stateStruct.quat;
     245         558 :     delAngBiasLearned = false;
     246         558 :     memset(&filterStatus, 0, sizeof(filterStatus));
     247         558 :     activeHgtSource = 0;
     248             : #if AP_RANGEFINDER_ENABLED
     249         558 :     memset(&rngMeasIndex, 0, sizeof(rngMeasIndex));
     250         558 :     memset(&storedRngMeasTime_ms, 0, sizeof(storedRngMeasTime_ms));
     251         558 :     memset(&storedRngMeas, 0, sizeof(storedRngMeas));
     252             : #endif
     253         558 :     terrainHgtStable = true;
     254         558 :     ekfOriginHgtVar = 0.0f;
     255         558 :     ekfGpsRefHgt = 0.0;
     256         558 :     velOffsetNED.zero();
     257         558 :     posOffsetNED.zero();
     258         558 :     ZERO_FARRAY(velPosObs);
     259             : 
     260             :     // range beacon fusion variables
     261         558 :     memset((void *)&rngBcnDataNew, 0, sizeof(rngBcnDataNew));
     262         558 :     memset((void *)&rngBcnDataDelayed, 0, sizeof(rngBcnDataDelayed));
     263         558 :     lastRngBcnPassTime_ms = 0;
     264         558 :     rngBcnTestRatio = 0.0f;
     265         558 :     rngBcnHealth = false;
     266         558 :     rngBcnTimeout = true;
     267         558 :     varInnovRngBcn = 0.0f;
     268         558 :     innovRngBcn = 0.0f;
     269         558 :     memset(&lastTimeRngBcn_ms, 0, sizeof(lastTimeRngBcn_ms));
     270             : #if AP_BEACON_ENABLED
     271         558 :     rngBcnDataToFuse = false;
     272             : #endif
     273         558 :     beaconVehiclePosNED.zero();
     274         558 :     beaconVehiclePosErr = 1.0f;
     275         558 :     rngBcnLast3DmeasTime_ms = 0;
     276         558 :     rngBcnGoodToAlign = false;
     277         558 :     lastRngBcnChecked = 0;
     278         558 :     receiverPos.zero();
     279         558 :     memset(&receiverPosCov, 0, sizeof(receiverPosCov));
     280         558 :     rngBcnAlignmentStarted =  false;
     281         558 :     rngBcnAlignmentCompleted = false;
     282         558 :     lastBeaconIndex = 0;
     283         558 :     rngBcnPosSum.zero();
     284         558 :     numBcnMeas = 0;
     285         558 :     rngSum = 0.0f;
     286         558 :     N_beacons = 0;
     287         558 :     maxBcnPosD = 0.0f;
     288         558 :     minBcnPosD = 0.0f;
     289         558 :     bcnPosOffset = 0.0f;
     290         558 :     bcnPosOffsetMax = 0.0f;
     291         558 :     bcnPosOffsetMaxVar = 0.0f;
     292         558 :     OffsetMaxInnovFilt = 0.0f;
     293         558 :     bcnPosOffsetMin = 0.0f;
     294         558 :     bcnPosOffsetMinVar = 0.0f;
     295         558 :     OffsetMinInnovFilt = 0.0f;
     296         558 :     rngBcnFuseDataReportIndex = 0;
     297         558 :     memset(&rngBcnFusionReport, 0, sizeof(rngBcnFusionReport));
     298         558 :     last_gps_idx = 0;
     299             : 
     300             :     // external nav data fusion
     301         558 :     memset((void *)&extNavDataNew, 0, sizeof(extNavDataNew));
     302         558 :     memset((void *)&extNavDataDelayed, 0, sizeof(extNavDataDelayed));
     303         558 :     extNavDataToFuse = false;
     304         558 :     extNavMeasTime_ms = 0;
     305         558 :     extNavLastPosResetTime_ms = 0;
     306         558 :     extNavUsedForYaw = false;
     307         558 :     extNavUsedForPos = false;
     308         558 :     extNavYawResetRequest = false;
     309             : 
     310         558 :     extNavVelNew = {};
     311         558 :     extNavVelDelayed = {};
     312         558 :     extNavVelToFuse = false;
     313         558 :     extNavVelMeasTime_ms = 0;
     314         558 :     useExtNavVel = false;
     315             : 
     316             :     // zero data buffers
     317         558 :     storedIMU.reset();
     318         558 :     storedGPS.reset();
     319         558 :     storedBaro.reset();
     320         558 :     storedTAS.reset();
     321         558 :     storedRange.reset();
     322         558 :     storedOutput.reset();
     323         558 :     storedRangeBeacon.reset();
     324         558 :     storedExtNav.reset();
     325         558 :     storedExtNavVel.reset();
     326             : 
     327             :     // now init mag variables
     328         558 :     yawAlignComplete = false;
     329         558 :     have_table_earth_field = false;
     330             : 
     331             :     // initialise pre-arm message
     332         558 :     dal.snprintf(prearm_fail_string, sizeof(prearm_fail_string), "EKF2 still initialising");
     333             : 
     334         558 :     InitialiseVariablesMag();
     335             : 
     336             :     // emergency reset of yaw to EKFGSF estimate
     337         558 :     EKFGSF_yaw_reset_ms = 0;
     338         558 :     EKFGSF_yaw_reset_request_ms = 0;
     339         558 :     EKFGSF_yaw_reset_count = 0;
     340         558 :     EKFGSF_run_filterbank = false;
     341         558 : }
     342             : 
     343             : 
     344             : /*
     345             :   separate out the mag reset so it can be used when compass learning completes
     346             :  */
     347         558 : void NavEKF2_core::InitialiseVariablesMag()
     348             : {
     349         558 :     lastHealthyMagTime_ms = imuSampleTime_ms;
     350         558 :     lastMagUpdate_us = 0;
     351         558 :     magYawResetTimer_ms = imuSampleTime_ms;
     352         558 :     magTimeout = false;
     353         558 :     allMagSensorsFailed = false;
     354         558 :     finalInflightYawInit = false;
     355         558 :     finalInflightMagInit = false;
     356             : 
     357         558 :     inhibitMagStates = true;
     358             : 
     359         558 :     magSelectIndex = dal.compass().get_first_usable();
     360         558 :     lastMagOffsetsValid = false;
     361         558 :     magStateResetRequest = false;
     362         558 :     magStateInitComplete = false;
     363         558 :     magYawResetRequest = false;
     364             : 
     365         558 :     posDownAtLastMagReset = stateStruct.position.z;
     366         558 :     yawInnovAtLastMagReset = 0.0f;
     367         558 :     magFieldLearned = false;
     368             : 
     369         558 :     storedMag.reset();
     370         558 : }
     371             : 
     372             : // Initialise the states from accelerometer and magnetometer data (if present)
     373             : // This method can only be used when the vehicle is static
     374      207598 : bool NavEKF2_core::InitialiseFilterBootstrap(void)
     375             : {
     376             :     // If we are a plane and don't have GPS lock then don't initialise
     377      207598 :     if (assume_zero_sideslip() && dal.gps().status(dal.gps().primary_sensor()) < AP_GPS_FixType::FIX_3D) {
     378      190082 :         dal.snprintf(prearm_fail_string,
     379             :                            sizeof(prearm_fail_string),
     380             :                            "EKF2 init failure: No GPS lock");
     381      190082 :         statesInitialised = false;
     382      190082 :         return false;
     383             :     }
     384             : 
     385       17516 :     if (statesInitialised) {
     386             :         // we are initialised, but we don't return true until the IMU
     387             :         // buffer has been filled. This prevents a timing
     388             :         // vulnerability with a pause in IMU data during filter startup
     389       16958 :         readIMUData();
     390       16958 :         readMagData();
     391       16958 :         readGpsData();
     392       16958 :         readBaroData();
     393       16958 :         return storedIMU.is_filled();
     394             :     }
     395             : 
     396             :     // set re-used variables to zero
     397         558 :     InitialiseVariables();
     398             : 
     399         558 :     const auto &ins = dal.ins();
     400             : 
     401             :     // Initialise IMU data
     402         558 :     dtIMUavg = ins.get_loop_delta_t();
     403         558 :     readIMUData();
     404         558 :     storedIMU.reset_history(imuDataNew);
     405         558 :     imuDataDelayed = imuDataNew;
     406             : 
     407             :     // acceleration vector in XYZ body axes measured by the IMU (m/s^2)
     408         558 :     Vector3F initAccVec;
     409             : 
     410             :     // TODO we should average accel readings over several cycles
     411         558 :     initAccVec = ins.get_accel(accel_index_active).toftype();
     412             : 
     413             :     // read the magnetometer data
     414         558 :     readMagData();
     415             : 
     416             :     // normalise the acceleration vector
     417             :     ftype pitch=0, roll=0;
     418         558 :     if (initAccVec.length() > 0.001f) {
     419         558 :         initAccVec.normalize();
     420             : 
     421             :         // calculate initial pitch angle
     422         558 :         pitch = asinF(initAccVec.x);
     423             : 
     424             :         // calculate initial roll angle
     425         558 :         roll = atan2F(-initAccVec.y , -initAccVec.z);
     426             :     }
     427             : 
     428             :     // calculate initial roll and pitch orientation
     429         558 :     stateStruct.quat.from_euler(roll, pitch, 0.0f);
     430             : 
     431             :     // initialise dynamic states
     432         558 :     stateStruct.velocity.zero();
     433         558 :     stateStruct.position.zero();
     434         558 :     stateStruct.angErr.zero();
     435             : 
     436             :     // initialise static process model states
     437         558 :     stateStruct.gyro_bias.zero();
     438         558 :     stateStruct.gyro_scale.x = 1.0f;
     439         558 :     stateStruct.gyro_scale.y = 1.0f;
     440         558 :     stateStruct.gyro_scale.z = 1.0f;
     441         558 :     stateStruct.accel_zbias = 0.0f;
     442         558 :     stateStruct.wind_vel.zero();
     443         558 :     stateStruct.earth_magfield.zero();
     444         558 :     stateStruct.body_magfield.zero();
     445             : 
     446             :     // read the GPS and set the position and velocity states
     447         558 :     readGpsData();
     448         558 :     ResetVelocity();
     449         558 :     ResetPosition();
     450             : 
     451             :     // read the barometer and set the height state
     452         558 :     readBaroData();
     453         558 :     ResetHeight();
     454             : 
     455             :     // define Earth rotation vector in the NED navigation frame
     456         558 :     calcEarthRateNED(earthRateNED, dal.get_home().lat);
     457             : 
     458             :     // initialise the covariance matrix
     459         558 :     CovarianceInit();
     460             : 
     461             :     // reset output states
     462         558 :     StoreOutputReset();
     463             : 
     464             :     // set to true now that states have be initialised
     465         558 :     statesInitialised = true;
     466             : 
     467             :     // reset inactive biases
     468        2232 :     for (uint8_t i=0; i<INS_MAX_INSTANCES; i++) {
     469        1674 :         inactiveBias[i].gyro_bias.zero();
     470        1674 :         inactiveBias[i].accel_zbias = 0;
     471        1674 :         inactiveBias[i].gyro_scale.x = 1;
     472        1674 :         inactiveBias[i].gyro_scale.y = 1;
     473        1674 :         inactiveBias[i].gyro_scale.z = 1;
     474             :     }
     475             : 
     476             :     // we initially return false to wait for the IMU buffer to fill
     477             :     return false;
     478             : }
     479             : 
     480             : // initialise the covariance matrix
     481         558 : void NavEKF2_core::CovarianceInit()
     482             : {
     483             :     // zero the matrix
     484         558 :     memset(&P[0][0], 0, sizeof(P));
     485             : 
     486             :     // attitude error
     487         558 :     P[0][0]   = 0.1f;
     488         558 :     P[1][1]   = 0.1f;
     489         558 :     P[2][2]   = 0.1f;
     490             :     // velocities
     491         558 :     P[3][3]   = sq(frontend->_gpsHorizVelNoise);
     492         558 :     P[4][4]   = P[3][3];
     493         558 :     P[5][5]   = sq(frontend->_gpsVertVelNoise);
     494             :     // positions
     495         558 :     P[6][6]   = sq(frontend->_gpsHorizPosNoise);
     496         558 :     P[7][7]   = P[6][6];
     497         558 :     P[8][8]   = sq(frontend->_baroAltNoise);
     498             :     // gyro delta angle biases
     499         558 :     P[9][9] = sq(radians(InitialGyroBiasUncertainty() * dtEkfAvg));
     500         558 :     P[10][10] = P[9][9];
     501         558 :     P[11][11] = P[9][9];
     502             :     // gyro scale factor biases
     503         558 :     P[12][12] = sq(1e-3);
     504         558 :     P[13][13] = P[12][12];
     505         558 :     P[14][14] = P[12][12];
     506             :     // Z delta velocity bias
     507         558 :     P[15][15] = sq(INIT_ACCEL_BIAS_UNCERTAINTY * dtEkfAvg);
     508             :     // earth magnetic field
     509         558 :     P[16][16] = 0.0f;
     510         558 :     P[17][17] = P[16][16];
     511         558 :     P[18][18] = P[16][16];
     512             :     // body magnetic field
     513         558 :     P[19][19] = 0.0f;
     514         558 :     P[20][20] = P[19][19];
     515         558 :     P[21][21] = P[19][19];
     516             :     // wind velocities
     517         558 :     P[22][22] = 0.0f;
     518         558 :     P[23][23]  = P[22][22];
     519             : 
     520             :     // optical flow ground height covariance
     521         558 :     Popt = 0.25f;
     522         558 : }
     523             : 
     524             : /********************************************************
     525             : *                 UPDATE FUNCTIONS                      *
     526             : ********************************************************/
     527             : // Update Filter States - this should be called whenever new IMU data is available
     528     4213784 : void NavEKF2_core::UpdateFilter(bool predict)
     529             : {
     530             :     // Set the flag to indicate to the filter that the front-end has given permission for a new state prediction cycle to be started
     531     4213784 :     startPredictEnabled = predict;
     532             : 
     533             :     // don't run filter updates if states have not been initialised
     534     4213784 :     if (!statesInitialised) {
     535             :         return;
     536             :     }
     537             : 
     538             :     // start the timer used for load measurement
     539             : #if ENABLE_EKF_TIMING
     540             :     void *istate = hal.scheduler->disable_interrupts_save();
     541             :     static uint32_t timing_start_us;
     542             :     timing_start_us = dal.micros();
     543             : #endif
     544             : 
     545     4213784 :     fill_scratch_variables();
     546             : #if CONFIG_HAL_BOARD == HAL_BOARD_SITL
     547     4213784 :     fill_nanf(&KH[0][0], sizeof(KH)/sizeof(ftype)); // see fill_scratch_variables()
     548     4213784 :     fill_nanf(&nextP[0][0], sizeof(nextP)/sizeof(ftype));
     549             : #endif
     550             : 
     551             :     // TODO - in-flight restart method
     552             : 
     553             :     //get starting time for update step
     554     4213784 :     imuSampleTime_ms = frontend->imuSampleTime_us / 1000;
     555             : 
     556             :     // Check arm status and perform required checks and mode changes
     557     4213784 :     controlFilterModes();
     558             : 
     559             :     // read IMU data as delta angles and velocities
     560     4213784 :     readIMUData();
     561             : 
     562             :     // Run the EKF equations to estimate at the fusion time horizon if new IMU data is available in the buffer
     563     4213784 :     if (runUpdates) {
     564             :         // Predict states using IMU data from the delayed time horizon
     565     3248540 :         UpdateStrapdownEquationsNED();
     566             : 
     567             :         // Predict the covariance growth
     568     3248540 :         CovariancePrediction();
     569             : 
     570             :         // Run the IMU prediction step for the GSF yaw estimator algorithm
     571             :         // using IMU and optionally true airspeed data.
     572             :         // Must be run before SelectMagFusion() to provide an up to date yaw estimate
     573     3248540 :         runYawEstimatorPrediction();
     574             : 
     575             :         // Update states using  magnetometer data
     576     3248540 :         SelectMagFusion();
     577             : 
     578             :         // Update states using GPS and altimeter data
     579     3248540 :         SelectVelPosFusion();
     580             : 
     581             :         // Run the GPS velocity correction step for the GSF yaw estimator algorithm
     582             :         // and use the yaw estimate to reset the main EKF yaw if requested
     583             :         // Muat be run after SelectVelPosFusion() so that fresh GPS data is available
     584     3248540 :         runYawEstimatorCorrection();
     585             : 
     586             : #if AP_BEACON_ENABLED
     587             :         // Update states using range beacon data
     588     3248540 :         SelectRngBcnFusion();
     589             : #endif
     590             : 
     591             :         // Update states using optical flow data
     592     3248540 :         SelectFlowFusion();
     593             : 
     594             :         // Update states using airspeed data
     595     3248540 :         SelectTasFusion();
     596             : 
     597             :         // Update states using sideslip constraint assumption for fly-forward vehicles
     598     3248540 :         SelectBetaFusion();
     599             : 
     600             :         // Update the filter status
     601     3248540 :         updateFilterStatus();
     602             :     }
     603             : 
     604             :     // Wind output forward from the fusion to output time horizon
     605     4213784 :     calcOutputStates();
     606             : 
     607             :     // stop the timer used for load measurement
     608             : #if ENABLE_EKF_TIMING
     609             :     static uint32_t total_us;
     610             :     static uint32_t timing_counter;
     611             :     total_us += dal.micros() - timing_start_us;
     612             :     if (timing_counter++ == 4000) {
     613             :         DEV_PRINTF("ekf2 avg %.2f us\n", total_us / float(timing_counter));
     614             :         total_us = 0;
     615             :         timing_counter = 0;
     616             :     }
     617             :     hal.scheduler->restore_interrupts(istate);
     618             : #endif
     619             : 
     620             :     /*
     621             :       this is a check to cope with a vehicle sitting idle on the
     622             :       ground and getting over-confident of the state. The symptoms
     623             :       would be "gyros still settling" when the user tries to arm. In
     624             :       that state the EKF can't recover, so we do a hard reset and let
     625             :       it try again.
     626             :      */
     627     4213784 :     if (filterStatus.value != 0) {
     628     4184630 :         last_filter_ok_ms = dal.millis();
     629             :     }
     630       29154 :     if (filterStatus.value == 0 &&
     631       29154 :         last_filter_ok_ms != 0 &&
     632     4213784 :         dal.millis() - last_filter_ok_ms > 5000 &&
     633           0 :         !dal.get_armed()) {
     634             :         // we've been unhealthy for 5 seconds after being healthy, reset the filter
     635           0 :         GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "EKF2 IMU%u forced reset",(unsigned)imu_index);
     636           0 :         last_filter_ok_ms = 0;
     637           0 :         statesInitialised = false;
     638           0 :         InitialiseFilterBootstrap();
     639             :     }
     640             :     
     641             : }
     642             : 
     643     7470324 : void NavEKF2_core::correctDeltaAngle(Vector3F &delAng, ftype delAngDT, uint8_t gyro_index)
     644             : {
     645     7470324 :     delAng.x = delAng.x * stateStruct.gyro_scale.x;
     646     7470324 :     delAng.y = delAng.y * stateStruct.gyro_scale.y;
     647     7470324 :     delAng.z = delAng.z * stateStruct.gyro_scale.z;
     648     7470324 :     delAng -= inactiveBias[gyro_index].gyro_bias * (delAngDT / dtEkfAvg);
     649     7470324 : }
     650             : 
     651     7470324 : void NavEKF2_core::correctDeltaVelocity(Vector3F &delVel, ftype delVelDT, uint8_t accel_index)
     652             : {
     653     7470324 :     delVel.z -= inactiveBias[accel_index].accel_zbias * (delVelDT / dtEkfAvg);
     654     7470324 : }
     655             : 
     656             : /*
     657             :  * Update the quaternion, velocity and position states using delayed IMU measurements
     658             :  * because the EKF is running on a delayed time horizon. Note that the quaternion is
     659             :  * not used by the EKF equations, which instead estimate the error in the attitude of
     660             :  * the vehicle when each observation is fused. This attitude error is then used to correct
     661             :  * the quaternion.
     662             : */
     663     3248540 : void NavEKF2_core::UpdateStrapdownEquationsNED()
     664             : {
     665             :     // update the quaternion states by rotating from the previous attitude through
     666             :     // the delta angle rotation quaternion and normalise
     667             :     // apply correction for earth's rotation rate
     668             :     // % * - and + operators have been overloaded
     669     3248540 :     stateStruct.quat.rotate(delAngCorrected - prevTnb * earthRateNED*imuDataDelayed.delAngDT);
     670     3248540 :     stateStruct.quat.normalize();
     671             : 
     672             :     // transform body delta velocities to delta velocities in the nav frame
     673             :     // use the nav frame from previous time step as the delta velocities
     674             :     // have been rotated into that frame
     675             :     // * and + operators have been overloaded
     676     3248540 :     Vector3F delVelNav;  // delta velocity vector in earth axes
     677     3248540 :     delVelNav  = prevTnb.mul_transpose(delVelCorrected);
     678     3248540 :     delVelNav.z += GRAVITY_MSS*imuDataDelayed.delVelDT;
     679             : 
     680             :     // calculate the nav to body cosine matrix
     681     3248540 :     stateStruct.quat.inverse().rotation_matrix(prevTnb);
     682             : 
     683             :     // calculate the rate of change of velocity (used for launch detect and other functions)
     684     3248540 :     velDotNED = delVelNav / imuDataDelayed.delVelDT;
     685             : 
     686             :     // apply a first order lowpass filter
     687     3248540 :     velDotNEDfilt = velDotNED * 0.05f + velDotNEDfilt * 0.95f;
     688             : 
     689             :     // calculate a magnitude of the filtered nav acceleration (required for GPS
     690             :     // variance estimation)
     691     3248540 :     accNavMag = velDotNEDfilt.length();
     692     3248540 :     accNavMagHoriz = velDotNEDfilt.xy().length();
     693             : 
     694             :     // if we are not aiding, then limit the horizontal magnitude of acceleration
     695             :     // to prevent large manoeuvre transients disturbing the attitude
     696     3248540 :     if ((PV_AidingMode == AID_NONE) && (accNavMagHoriz > 5.0f)) {
     697       62662 :         ftype gain = 5.0f/accNavMagHoriz;
     698       62662 :         delVelNav.x *= gain;
     699       62662 :         delVelNav.y *= gain;
     700             :     }
     701             : 
     702             :     // save velocity for use in trapezoidal integration for position calcuation
     703     3248540 :     Vector3F lastVelocity = stateStruct.velocity;
     704             : 
     705             :     // sum delta velocities to get velocity
     706     3248540 :     stateStruct.velocity += delVelNav;
     707             : 
     708             :     // apply a trapezoidal integration to velocities to calculate position
     709     3248540 :     stateStruct.position += (stateStruct.velocity + lastVelocity) * (imuDataDelayed.delVelDT*0.5f);
     710             : 
     711             :     // accumulate the bias delta angle and time since last reset by an OF measurement arrival
     712     3248540 :     delAngBodyOF += delAngCorrected;
     713     3248540 :     delTimeOF += imuDataDelayed.delAngDT;
     714             : 
     715             :     // limit states to protect against divergence
     716     3248540 :     ConstrainStates();
     717     3248540 : }
     718             : 
     719             : /*
     720             :  * Propagate PVA solution forward from the fusion time horizon to the current time horizon
     721             :  * using simple observer which performs two functions:
     722             :  * 1) Corrects for the delayed time horizon used by the EKF.
     723             :  * 2) Applies a LPF to state corrections to prevent 'stepping' in states due to measurement
     724             :  * fusion introducing unwanted noise into the control loops.
     725             :  * The inspiration for using a complementary filter to correct for time delays in the EKF
     726             :  * is based on the work by A Khosravian.
     727             :  *
     728             :  * "Recursive Attitude Estimation in the Presence of Multi-rate and Multi-delay Vector Measurements"
     729             :  * A Khosravian, J Trumpf, R Mahony, T Hamel, Australian National University
     730             : */
     731     4213784 : void NavEKF2_core::calcOutputStates()
     732             : {
     733             :     // apply corrections to the IMU data
     734     4213784 :     Vector3F delAngNewCorrected = imuDataNew.delAng;
     735     4213784 :     Vector3F delVelNewCorrected = imuDataNew.delVel;
     736     4213784 :     correctDeltaAngle(delAngNewCorrected, imuDataNew.delAngDT, imuDataNew.gyro_index);
     737     4213784 :     correctDeltaVelocity(delVelNewCorrected, imuDataNew.delVelDT, imuDataNew.accel_index);
     738             : 
     739             :     // apply corections to track EKF solution
     740     4213784 :     Vector3F delAng = delAngNewCorrected + delAngCorrection;
     741             : 
     742             :     // convert the rotation vector to its equivalent quaternion
     743     4213784 :     QuaternionF deltaQuat;
     744     4213784 :     deltaQuat.from_axis_angle(delAng);
     745             : 
     746             :     // update the quaternion states by rotating from the previous attitude through
     747             :     // the delta angle rotation quaternion and normalise
     748     4213784 :     outputDataNew.quat *= deltaQuat;
     749     4213784 :     outputDataNew.quat.normalize();
     750             : 
     751             :     // calculate the body to nav cosine matrix
     752     4213784 :     Matrix3F Tbn_temp;
     753     4213784 :     outputDataNew.quat.rotation_matrix(Tbn_temp);
     754             : 
     755             :     // transform body delta velocities to delta velocities in the nav frame
     756     4213784 :     Vector3F delVelNav  = Tbn_temp*delVelNewCorrected;
     757     4213784 :     delVelNav.z += GRAVITY_MSS*imuDataNew.delVelDT;
     758             : 
     759             :     // save velocity for use in trapezoidal integration for position calcuation
     760     4213784 :     Vector3F lastVelocity = outputDataNew.velocity;
     761             : 
     762             :     // sum delta velocities to get velocity
     763     4213784 :     outputDataNew.velocity += delVelNav;
     764             : 
     765             :     // Implement third order complementary filter for height and height rate
     766             :     // Reference Paper :
     767             :     // Optimizing the Gains of the Baro-Inertial Vertical Channel
     768             :     // Widnall W.S, Sinha P.K,
     769             :     // AIAA Journal of Guidance and Control, 78-1307R
     770             : 
     771             :     // Perform filter calculation using backwards Euler integration
     772             :     // Coefficients selected to place all three filter poles at omega
     773     4213784 :     const ftype CompFiltOmega = M_2PI * constrain_ftype(frontend->_hrt_filt_freq, 0.1f, 30.0f);
     774     4213784 :     ftype omega2 = CompFiltOmega * CompFiltOmega;
     775     4213784 :     ftype pos_err = constrain_ftype(outputDataNew.position.z - vertCompFiltState.pos, -1e5, 1e5);
     776     4213784 :     ftype integ1_input = pos_err * omega2 * CompFiltOmega * imuDataNew.delVelDT;
     777     4213784 :     vertCompFiltState.acc += integ1_input;
     778     4213784 :     ftype integ2_input = delVelNav.z + (vertCompFiltState.acc + pos_err * omega2 * 3.0f) * imuDataNew.delVelDT;
     779     4213784 :     vertCompFiltState.vel += integ2_input;
     780     4213784 :     ftype integ3_input = (vertCompFiltState.vel + pos_err * CompFiltOmega * 3.0f) * imuDataNew.delVelDT;
     781     4213784 :     vertCompFiltState.pos += integ3_input; 
     782             : 
     783             :     // apply a trapezoidal integration to velocities to calculate position
     784     4213784 :     outputDataNew.position += (outputDataNew.velocity + lastVelocity) * (imuDataNew.delVelDT*0.5f);
     785             : 
     786             :     // If the IMU accelerometer is offset from the body frame origin, then calculate corrections
     787             :     // that can be added to the EKF velocity and position outputs so that they represent the velocity
     788             :     // and position of the body frame origin.
     789             :     // Note the * operator has been overloaded to operate as a dot product
     790     4213784 :     if (!accelPosOffset.is_zero()) {
     791             :         // calculate the average angular rate across the last IMU update
     792             :         // note delAngDT is prevented from being zero in readIMUData()
     793      128217 :         Vector3F angRate = imuDataNew.delAng * (1.0f/imuDataNew.delAngDT);
     794             : 
     795             :         // Calculate the velocity of the body frame origin relative to the IMU in body frame
     796             :         // and rotate into earth frame. Note % operator has been overloaded to perform a cross product
     797      128217 :         Vector3F velBodyRelIMU = angRate % (- accelPosOffset);
     798      128217 :         velOffsetNED = Tbn_temp * velBodyRelIMU;
     799             : 
     800             :         // calculate the earth frame position of the body frame origin relative to the IMU
     801      128217 :         posOffsetNED = Tbn_temp * (- accelPosOffset);
     802             :     } else {
     803     4085567 :         velOffsetNED.zero();
     804     4085567 :         posOffsetNED.zero();
     805             :     }
     806             : 
     807             :     // store INS states in a ring buffer that with the same length and time coordinates as the IMU data buffer
     808     4213784 :     if (runUpdates) {
     809             :         // store the states at the output time horizon
     810     3248540 :         storedOutput[storedIMU.get_youngest_index()] = outputDataNew;
     811             : 
     812             :         // recall the states from the fusion time horizon
     813     3248540 :         outputDataDelayed = storedOutput[storedIMU.get_oldest_index()];
     814             : 
     815             :         // compare quaternion data with EKF quaternion at the fusion time horizon and calculate correction
     816             : 
     817             :         // divide the demanded quaternion by the estimated to get the error
     818     3248540 :         QuaternionF quatErr = stateStruct.quat / outputDataDelayed.quat;
     819             : 
     820             :         // Convert to a delta rotation using a small angle approximation
     821     3248540 :         quatErr.normalize();
     822     3248540 :         Vector3F deltaAngErr;
     823             :         ftype scaler;
     824     3248540 :         if (quatErr[0] >= 0.0f) {
     825             :             scaler = 2.0f;
     826             :         } else {
     827             :             scaler = -2.0f;
     828             :         }
     829     3248540 :         deltaAngErr.x = scaler * quatErr[1];
     830     3248540 :         deltaAngErr.y = scaler * quatErr[2];
     831     3248540 :         deltaAngErr.z = scaler * quatErr[3];
     832             : 
     833             :         // calculate a gain that provides tight tracking of the estimator states and
     834             :         // adjust for changes in time delay to maintain consistent damping ratio of ~0.7
     835     3248540 :         ftype timeDelay = 1e-3f * (float)(imuDataNew.time_ms - imuDataDelayed.time_ms);
     836     3248540 :         timeDelay = fmaxF(timeDelay, dtIMUavg);
     837     3248540 :         ftype errorGain = 0.5f / timeDelay;
     838             : 
     839             :         // calculate a corrrection to the delta angle
     840             :         // that will cause the INS to track the EKF quaternions
     841     3248540 :         delAngCorrection = deltaAngErr * errorGain * dtIMUavg;
     842             : 
     843             :         // calculate velocity and position tracking errors
     844     3248540 :         Vector3F velErr = (stateStruct.velocity - outputDataDelayed.velocity);
     845     3248540 :         Vector3F posErr = (stateStruct.position - outputDataDelayed.position);
     846             : 
     847             :         // collect magnitude tracking error for diagnostics
     848     3248540 :         outputTrackError.x = deltaAngErr.length();
     849     3248540 :         outputTrackError.y = velErr.length();
     850     3248540 :         outputTrackError.z = posErr.length();
     851             : 
     852             :         // convert user specified time constant from centi-seconds to seconds
     853     3248540 :         ftype tauPosVel = constrain_ftype(0.01f*(ftype)frontend->_tauVelPosOutput, 0.1f, 0.5f);
     854             : 
     855             :         // calculate a gain to track the EKF position states with the specified time constant
     856     3248540 :         ftype velPosGain = dtEkfAvg / constrain_ftype(tauPosVel, dtEkfAvg, 10.0f);
     857             : 
     858             :         // use a PI feedback to calculate a correction that will be applied to the output state history
     859     3248540 :         posErrintegral += posErr;
     860     3248540 :         velErrintegral += velErr;
     861     3248540 :         Vector3F velCorrection = velErr * velPosGain + velErrintegral * sq(velPosGain) * 0.1f;
     862     3248540 :         Vector3F posCorrection = posErr * velPosGain + posErrintegral * sq(velPosGain) * 0.1f;
     863             : 
     864             :         // loop through the output filter state history and apply the corrections to the velocity and position states
     865             :         // this method is too expensive to use for the attitude states due to the quaternion operations required
     866             :         // but does not introduce a time delay in the 'correction loop' and allows smaller tracking time constants
     867             :         // to be used
     868     3248540 :         output_elements outputStates;
     869    50038452 :         for (unsigned index=0; index < imu_buffer_length; index++) {
     870    46789912 :             outputStates = storedOutput[index];
     871             : 
     872             :             // a constant  velocity correction is applied
     873    46789912 :             outputStates.velocity += velCorrection;
     874             : 
     875             :             // a constant position correction is applied
     876    46789912 :             outputStates.position += posCorrection;
     877             : 
     878             :             // push the updated data to the buffer
     879    46789912 :             storedOutput[index] = outputStates;
     880             :         }
     881             : 
     882             :         // update output state to corrected values
     883     3248540 :         outputDataNew = storedOutput[storedIMU.get_youngest_index()];
     884             : 
     885             :     }
     886     4213784 : }
     887             : 
     888             : /*
     889             :  * Calculate the predicted state covariance matrix using algebraic equations generated with Matlab symbolic toolbox.
     890             :  * The script file used to generate these and other equations in this filter can be found here:
     891             :  * https://github.com/priseborough/InertialNav/blob/master/derivations/RotationVectorAttitudeParameterisation/GenerateNavFilterEquations.m
     892             : */
     893     3248540 : void NavEKF2_core::CovariancePrediction()
     894             : {
     895             :     ftype windVelSigma; // wind velocity 1-sigma process noise - m/s
     896             :     ftype dAngBiasSigma;// delta angle bias 1-sigma process noise - rad/s
     897             :     ftype dVelBiasSigma;// delta velocity bias 1-sigma process noise - m/s
     898             :     ftype dAngScaleSigma;// delta angle scale factor 1-Sigma process noise
     899             :     ftype magEarthSigma;// earth magnetic field 1-sigma process noise
     900             :     ftype magBodySigma; // body magnetic field 1-sigma process noise
     901             :     ftype daxNoise;     // X axis delta angle noise variance rad^2
     902             :     ftype dayNoise;     // Y axis delta angle noise variance rad^2
     903             :     ftype dazNoise;     // Z axis delta angle noise variance rad^2
     904             :     ftype dvxNoise;     // X axis delta velocity variance noise (m/s)^2
     905             :     ftype dvyNoise;     // Y axis delta velocity variance noise (m/s)^2
     906             :     ftype dvzNoise;     // Z axis delta velocity variance noise (m/s)^2
     907             :     ftype dvx;          // X axis delta velocity (m/s)
     908             :     ftype dvy;          // Y axis delta velocity (m/s)
     909             :     ftype dvz;          // Z axis delta velocity (m/s)
     910             :     ftype dax;          // X axis delta angle (rad)
     911             :     ftype day;          // Y axis delta angle (rad)
     912             :     ftype daz;          // Z axis delta angle (rad)
     913             :     ftype q0;           // attitude quaternion
     914             :     ftype q1;           // attitude quaternion
     915             :     ftype q2;           // attitude quaternion
     916             :     ftype q3;           // attitude quaternion
     917             :     ftype dax_b;        // X axis delta angle measurement bias (rad)
     918             :     ftype day_b;        // Y axis delta angle measurement bias (rad)
     919             :     ftype daz_b;        // Z axis delta angle measurement bias (rad)
     920             :     ftype dax_s;        // X axis delta angle measurement scale factor
     921             :     ftype day_s;        // Y axis delta angle measurement scale factor
     922             :     ftype daz_s;        // Z axis delta angle measurement scale factor
     923             :     ftype dvz_b;        // Z axis delta velocity measurement bias (rad)
     924             :     Vector25 SF;
     925             :     Vector5 SG;
     926             :     Vector8 SQ;
     927             :     Vector24 processNoise;
     928             : 
     929             :     // calculate covariance prediction process noise
     930             :     // use filtered height rate to increase wind process noise when climbing or descending
     931             :     // this allows for wind gradient effects.
     932             :     // filter height rate using a 10 second time constant filter
     933     3248540 :     dt = imuDataDelayed.delAngDT;
     934     3248540 :     ftype alpha = 0.1f * dt;
     935     3248540 :     hgtRate = hgtRate * (1.0f - alpha) - stateStruct.velocity.z * alpha;
     936             : 
     937             :     // use filtered height rate to increase wind process noise when climbing or descending
     938             :     // this allows for wind gradient effects.
     939     3248540 :     windVelSigma  = dt * constrain_ftype(frontend->_windVelProcessNoise, 0.0f, 1.0f) * (1.0f + constrain_ftype(frontend->_wndVarHgtRateScale, 0.0f, 1.0f) * fabsF(hgtRate));
     940     3248540 :     dAngBiasSigma = sq(dt) * constrain_ftype(frontend->_gyroBiasProcessNoise, 0.0f, 1.0f);
     941     3248540 :     dVelBiasSigma = sq(dt) * constrain_ftype(frontend->_accelBiasProcessNoise, 0.0f, 1.0f);
     942     3248540 :     dAngScaleSigma = dt * constrain_ftype(frontend->_gyroScaleProcessNoise, 0.0f, 1.0f);
     943     3248540 :     magEarthSigma = dt * constrain_ftype(frontend->_magEarthProcessNoise, 0.0f, 1.0f);
     944     3248540 :     magBodySigma  = dt * constrain_ftype(frontend->_magBodyProcessNoise, 0.0f, 1.0f);
     945    32485400 :     for (uint8_t i= 0; i<=8;  i++) processNoise[i] = 0.0f;
     946    12994160 :     for (uint8_t i=9; i<=11; i++) processNoise[i] = dAngBiasSigma;
     947    12994160 :     for (uint8_t i=12; i<=14; i++) processNoise[i] = dAngScaleSigma;
     948     3248540 :     if (dal.get_takeoff_expected()) {
     949       29980 :         processNoise[15] = 0.0f;
     950             :     } else {
     951     3218560 :         processNoise[15] = dVelBiasSigma;
     952             :     }
     953    12994160 :     for (uint8_t i=16; i<=18; i++) processNoise[i] = magEarthSigma;
     954    12994160 :     for (uint8_t i=19; i<=21; i++) processNoise[i] = magBodySigma;
     955     9745620 :     for (uint8_t i=22; i<=23; i++) processNoise[i] = windVelSigma;
     956             : 
     957    73250190 :     for (uint8_t i= 0; i<=stateIndexLim; i++) processNoise[i] = sq(processNoise[i]);
     958             : 
     959             :     // set variables used to calculate covariance growth
     960     3248540 :     dvx = imuDataDelayed.delVel.x;
     961     3248540 :     dvy = imuDataDelayed.delVel.y;
     962     3248540 :     dvz = imuDataDelayed.delVel.z;
     963     3248540 :     dax = imuDataDelayed.delAng.x;
     964     3248540 :     day = imuDataDelayed.delAng.y;
     965     3248540 :     daz = imuDataDelayed.delAng.z;
     966     3248540 :     q0 = stateStruct.quat[0];
     967     3248540 :     q1 = stateStruct.quat[1];
     968     3248540 :     q2 = stateStruct.quat[2];
     969     3248540 :     q3 = stateStruct.quat[3];
     970     3248540 :     dax_b = stateStruct.gyro_bias.x;
     971     3248540 :     day_b = stateStruct.gyro_bias.y;
     972     3248540 :     daz_b = stateStruct.gyro_bias.z;
     973     3248540 :     dax_s = stateStruct.gyro_scale.x;
     974     3248540 :     day_s = stateStruct.gyro_scale.y;
     975     3248540 :     daz_s = stateStruct.gyro_scale.z;
     976     3248540 :     dvz_b = stateStruct.accel_zbias;
     977     3248540 :     ftype _gyrNoise = constrain_ftype(frontend->_gyrNoise, 0.0f, 1.0f);
     978     3248540 :     daxNoise = dayNoise = dazNoise = sq(dt*_gyrNoise);
     979     3248540 :     ftype _accNoise = constrain_ftype(frontend->_accNoise, 0.0f, 10.0f);
     980     3248540 :     dvxNoise = dvyNoise = dvzNoise = sq(dt*_accNoise);
     981             : 
     982             :     // calculate the predicted covariance due to inertial sensor error propagation
     983             :     // we calculate the upper diagonal and copy to take advantage of symmetry
     984     3248540 :     SF[0] = daz_b/2 - (daz*daz_s)/2;
     985     3248540 :     SF[1] = day_b/2 - (day*day_s)/2;
     986     3248540 :     SF[2] = dax_b/2 - (dax*dax_s)/2;
     987     3248540 :     SF[3] = q3/2 - (q0*SF[0])/2 + (q1*SF[1])/2 - (q2*SF[2])/2;
     988     3248540 :     SF[4] = q0/2 - (q1*SF[2])/2 - (q2*SF[1])/2 + (q3*SF[0])/2;
     989     3248540 :     SF[5] = q1/2 + (q0*SF[2])/2 - (q2*SF[0])/2 - (q3*SF[1])/2;
     990     3248540 :     SF[6] = q3/2 + (q0*SF[0])/2 - (q1*SF[1])/2 - (q2*SF[2])/2;
     991     3248540 :     SF[7] = q0/2 - (q1*SF[2])/2 + (q2*SF[1])/2 - (q3*SF[0])/2;
     992     3248540 :     SF[8] = q0/2 + (q1*SF[2])/2 - (q2*SF[1])/2 - (q3*SF[0])/2;
     993     3248540 :     SF[9] = q2/2 + (q0*SF[1])/2 + (q1*SF[0])/2 + (q3*SF[2])/2;
     994     3248540 :     SF[10] = q2/2 - (q0*SF[1])/2 - (q1*SF[0])/2 + (q3*SF[2])/2;
     995     3248540 :     SF[11] = q2/2 + (q0*SF[1])/2 - (q1*SF[0])/2 - (q3*SF[2])/2;
     996     3248540 :     SF[12] = q1/2 + (q0*SF[2])/2 + (q2*SF[0])/2 + (q3*SF[1])/2;
     997     3248540 :     SF[13] = q1/2 - (q0*SF[2])/2 + (q2*SF[0])/2 - (q3*SF[1])/2;
     998     3248540 :     SF[14] = q3/2 + (q0*SF[0])/2 + (q1*SF[1])/2 + (q2*SF[2])/2;
     999     3248540 :     SF[15] = - sq(q0) - sq(q1) - sq(q2) - sq(q3);
    1000     3248540 :     SF[16] = dvz_b - dvz;
    1001             :     SF[17] = dvx;
    1002             :     SF[18] = dvy;
    1003     3248540 :     SF[19] = sq(q2);
    1004     3248540 :     SF[20] = SF[19] - sq(q0) + sq(q1) - sq(q3);
    1005     3248540 :     SF[21] = SF[19] + sq(q0) - sq(q1) - sq(q3);
    1006     3248540 :     SF[22] = 2*q0*q1 - 2*q2*q3;
    1007     3248540 :     SF[23] = SF[19] - sq(q0) - sq(q1) + sq(q3);
    1008     3248540 :     SF[24] = 2*q1*q2;
    1009             : 
    1010     3248540 :     SG[0] = - sq(q0) - sq(q1) - sq(q2) - sq(q3);
    1011     3248540 :     SG[1] = sq(q3);
    1012     3248540 :     SG[2] = sq(q2);
    1013     3248540 :     SG[3] = sq(q1);
    1014     3248540 :     SG[4] = sq(q0);
    1015             : 
    1016     3248540 :     SQ[0] = - dvyNoise*(2*q0*q1 + 2*q2*q3)*(SG[1] - SG[2] + SG[3] - SG[4]) - dvzNoise*(2*q0*q1 - 2*q2*q3)*(SG[1] - SG[2] - SG[3] + SG[4]) - dvxNoise*(2*q0*q2 - 2*q1*q3)*(2*q0*q3 + 2*q1*q2);
    1017     3248540 :     SQ[1] = dvxNoise*(2*q0*q2 - 2*q1*q3)*(SG[1] + SG[2] - SG[3] - SG[4]) + dvzNoise*(2*q0*q2 + 2*q1*q3)*(SG[1] - SG[2] - SG[3] + SG[4]) - dvyNoise*(2*q0*q1 + 2*q2*q3)*(2*q0*q3 - 2*q1*q2);
    1018     3248540 :     SQ[2] = dvyNoise*(2*q0*q3 - 2*q1*q2)*(SG[1] - SG[2] + SG[3] - SG[4]) - dvxNoise*(2*q0*q3 + 2*q1*q2)*(SG[1] + SG[2] - SG[3] - SG[4]) - dvzNoise*(2*q0*q1 - 2*q2*q3)*(2*q0*q2 + 2*q1*q3);
    1019     3248540 :     SQ[3] = sq(SG[0]);
    1020             :     SQ[4] = 2*q2*q3;
    1021             :     SQ[5] = 2*q1*q3;
    1022             :     SQ[6] = 2*q1*q2;
    1023             :     SQ[7] = SG[4];
    1024             : 
    1025             :     Vector23 SPP;
    1026     3248540 :     SPP[0] = SF[17]*(2*q0*q1 + 2*q2*q3) + SF[18]*(2*q0*q2 - 2*q1*q3);
    1027     3248540 :     SPP[1] = SF[18]*(2*q0*q2 + 2*q1*q3) + SF[16]*(SF[24] - 2*q0*q3);
    1028     3248540 :     SPP[2] = 2*q3*SF[8] + 2*q1*SF[11] - 2*q0*SF[14] - 2*q2*SF[13];
    1029     3248540 :     SPP[3] = 2*q1*SF[7] + 2*q2*SF[6] - 2*q0*SF[12] - 2*q3*SF[10];
    1030     3248540 :     SPP[4] = 2*q0*SF[6] - 2*q3*SF[7] - 2*q1*SF[10] + 2*q2*SF[12];
    1031     3248540 :     SPP[5] = 2*q0*SF[8] + 2*q2*SF[11] + 2*q1*SF[13] + 2*q3*SF[14];
    1032     3248540 :     SPP[6] = 2*q0*SF[7] + 2*q3*SF[6] + 2*q2*SF[10] + 2*q1*SF[12];
    1033     3248540 :     SPP[7] = SF[18]*SF[20] - SF[16]*(2*q0*q1 + 2*q2*q3);
    1034     3248540 :     SPP[8] = 2*q1*SF[3] - 2*q2*SF[4] - 2*q3*SF[5] + 2*q0*SF[9];
    1035     3248540 :     SPP[9] = 2*q0*SF[5] - 2*q1*SF[4] - 2*q2*SF[3] + 2*q3*SF[9];
    1036     3248540 :     SPP[10] = SF[17]*SF[20] + SF[16]*(2*q0*q2 - 2*q1*q3);
    1037     3248540 :     SPP[11] = SF[17]*SF[21] - SF[18]*(SF[24] + 2*q0*q3);
    1038     3248540 :     SPP[12] = SF[17]*SF[22] - SF[16]*(SF[24] + 2*q0*q3);
    1039     3248540 :     SPP[13] = 2*q0*SF[4] + 2*q1*SF[5] + 2*q3*SF[3] + 2*q2*SF[9];
    1040     3248540 :     SPP[14] = 2*q2*SF[8] - 2*q0*SF[11] - 2*q1*SF[14] + 2*q3*SF[13];
    1041     3248540 :     SPP[15] = SF[18]*SF[23] + SF[17]*(SF[24] - 2*q0*q3);
    1042     3248540 :     SPP[16] = daz*SF[19] + daz*sq(q0) + daz*sq(q1) + daz*sq(q3);
    1043     3248540 :     SPP[17] = day*SF[19] + day*sq(q0) + day*sq(q1) + day*sq(q3);
    1044     3248540 :     SPP[18] = dax*SF[19] + dax*sq(q0) + dax*sq(q1) + dax*sq(q3);
    1045     3248540 :     SPP[19] = SF[16]*SF[23] - SF[17]*(2*q0*q2 + 2*q1*q3);
    1046     3248540 :     SPP[20] = SF[16]*SF[21] - SF[18]*SF[22];
    1047             :     SPP[21] = 2*q0*q2 + 2*q1*q3;
    1048             :     SPP[22] = SF[15];
    1049             : 
    1050     3248540 :     if (inhibitMagStates) {
    1051      952942 :         zeroRows(P,16,21);
    1052      952942 :         zeroCols(P,16,21);
    1053     2295598 :     } else if (inhibitWindStates) {
    1054      261255 :         zeroRows(P,22,23);
    1055      261255 :         zeroCols(P,22,23);
    1056             :     }
    1057             : 
    1058     3248540 :     if (!inhibitMagStates && lastInhibitMagStates) {
    1059             :         // when starting 3D fusion we want to reset body mag variances
    1060         308 :         needMagBodyVarReset = true;
    1061             :     }
    1062             : 
    1063     3248540 :     if (needMagBodyVarReset) {
    1064             :         // reset body mag variances
    1065         326 :         needMagBodyVarReset = false;
    1066         326 :         zeroCols(P,19,21);
    1067         326 :         zeroRows(P,19,21);
    1068         326 :         P[19][19] = sq(frontend->_magNoise);
    1069         326 :         P[20][20] = P[19][19];
    1070         326 :         P[21][21] = P[19][19];
    1071             :     }
    1072             : 
    1073     3248540 :     lastInhibitMagStates = inhibitMagStates;
    1074             : 
    1075     3248540 :     nextP[0][0] = daxNoise*SQ[3] + SPP[5]*(P[0][0]*SPP[5] - P[1][0]*SPP[4] + P[9][0]*SPP[22] + P[12][0]*SPP[18] + P[2][0]*(2*q1*SF[3] - 2*q2*SF[4] - 2*q3*SF[5] + 2*q0*SF[9])) - SPP[4]*(P[0][1]*SPP[5] - P[1][1]*SPP[4] + P[9][1]*SPP[22] + P[12][1]*SPP[18] + P[2][1]*(2*q1*SF[3] - 2*q2*SF[4] - 2*q3*SF[5] + 2*q0*SF[9])) + SPP[8]*(P[0][2]*SPP[5] + P[2][2]*SPP[8] + P[9][2]*SPP[22] + P[12][2]*SPP[18] - P[1][2]*(2*q0*SF[6] - 2*q3*SF[7] - 2*q1*SF[10] + 2*q2*SF[12])) + SPP[22]*(P[0][9]*SPP[5] - P[1][9]*SPP[4] + P[9][9]*SPP[22] + P[12][9]*SPP[18] + P[2][9]*(2*q1*SF[3] - 2*q2*SF[4] - 2*q3*SF[5] + 2*q0*SF[9])) + SPP[18]*(P[0][12]*SPP[5] - P[1][12]*SPP[4] + P[9][12]*SPP[22] + P[12][12]*SPP[18] + P[2][12]*(2*q1*SF[3] - 2*q2*SF[4] - 2*q3*SF[5] + 2*q0*SF[9]));
    1076     3248540 :     nextP[0][1] = SPP[6]*(P[0][1]*SPP[5] - P[1][1]*SPP[4] + P[2][1]*SPP[8] + P[9][1]*SPP[22] + P[12][1]*SPP[18]) - SPP[2]*(P[0][0]*SPP[5] - P[1][0]*SPP[4] + P[2][0]*SPP[8] + P[9][0]*SPP[22] + P[12][0]*SPP[18]) + SPP[22]*(P[0][10]*SPP[5] - P[1][10]*SPP[4] + P[2][10]*SPP[8] + P[9][10]*SPP[22] + P[12][10]*SPP[18]) + SPP[17]*(P[0][13]*SPP[5] - P[1][13]*SPP[4] + P[2][13]*SPP[8] + P[9][13]*SPP[22] + P[12][13]*SPP[18]) - (2*q0*SF[5] - 2*q1*SF[4] - 2*q2*SF[3] + 2*q3*SF[9])*(P[0][2]*SPP[5] - P[1][2]*SPP[4] + P[2][2]*SPP[8] + P[9][2]*SPP[22] + P[12][2]*SPP[18]);
    1077     3248540 :     nextP[1][1] = dayNoise*SQ[3] - SPP[2]*(P[1][0]*SPP[6] - P[0][0]*SPP[2] - P[2][0]*SPP[9] + P[10][0]*SPP[22] + P[13][0]*SPP[17]) + SPP[6]*(P[1][1]*SPP[6] - P[0][1]*SPP[2] - P[2][1]*SPP[9] + P[10][1]*SPP[22] + P[13][1]*SPP[17]) - SPP[9]*(P[1][2]*SPP[6] - P[0][2]*SPP[2] - P[2][2]*SPP[9] + P[10][2]*SPP[22] + P[13][2]*SPP[17]) + SPP[22]*(P[1][10]*SPP[6] - P[0][10]*SPP[2] - P[2][10]*SPP[9] + P[10][10]*SPP[22] + P[13][10]*SPP[17]) + SPP[17]*(P[1][13]*SPP[6] - P[0][13]*SPP[2] - P[2][13]*SPP[9] + P[10][13]*SPP[22] + P[13][13]*SPP[17]);
    1078     3248540 :     nextP[0][2] = SPP[13]*(P[0][2]*SPP[5] - P[1][2]*SPP[4] + P[2][2]*SPP[8] + P[9][2]*SPP[22] + P[12][2]*SPP[18]) - SPP[3]*(P[0][1]*SPP[5] - P[1][1]*SPP[4] + P[2][1]*SPP[8] + P[9][1]*SPP[22] + P[12][1]*SPP[18]) + SPP[22]*(P[0][11]*SPP[5] - P[1][11]*SPP[4] + P[2][11]*SPP[8] + P[9][11]*SPP[22] + P[12][11]*SPP[18]) + SPP[16]*(P[0][14]*SPP[5] - P[1][14]*SPP[4] + P[2][14]*SPP[8] + P[9][14]*SPP[22] + P[12][14]*SPP[18]) + (2*q2*SF[8] - 2*q0*SF[11] - 2*q1*SF[14] + 2*q3*SF[13])*(P[0][0]*SPP[5] - P[1][0]*SPP[4] + P[2][0]*SPP[8] + P[9][0]*SPP[22] + P[12][0]*SPP[18]);
    1079     3248540 :     nextP[1][2] = SPP[13]*(P[1][2]*SPP[6] - P[0][2]*SPP[2] - P[2][2]*SPP[9] + P[10][2]*SPP[22] + P[13][2]*SPP[17]) - SPP[3]*(P[1][1]*SPP[6] - P[0][1]*SPP[2] - P[2][1]*SPP[9] + P[10][1]*SPP[22] + P[13][1]*SPP[17]) + SPP[22]*(P[1][11]*SPP[6] - P[0][11]*SPP[2] - P[2][11]*SPP[9] + P[10][11]*SPP[22] + P[13][11]*SPP[17]) + SPP[16]*(P[1][14]*SPP[6] - P[0][14]*SPP[2] - P[2][14]*SPP[9] + P[10][14]*SPP[22] + P[13][14]*SPP[17]) + (2*q2*SF[8] - 2*q0*SF[11] - 2*q1*SF[14] + 2*q3*SF[13])*(P[1][0]*SPP[6] - P[0][0]*SPP[2] - P[2][0]*SPP[9] + P[10][0]*SPP[22] + P[13][0]*SPP[17]);
    1080     3248540 :     nextP[2][2] = dazNoise*SQ[3] - SPP[3]*(P[0][1]*SPP[14] - P[1][1]*SPP[3] + P[2][1]*SPP[13] + P[11][1]*SPP[22] + P[14][1]*SPP[16]) + SPP[14]*(P[0][0]*SPP[14] - P[1][0]*SPP[3] + P[2][0]*SPP[13] + P[11][0]*SPP[22] + P[14][0]*SPP[16]) + SPP[13]*(P[0][2]*SPP[14] - P[1][2]*SPP[3] + P[2][2]*SPP[13] + P[11][2]*SPP[22] + P[14][2]*SPP[16]) + SPP[22]*(P[0][11]*SPP[14] - P[1][11]*SPP[3] + P[2][11]*SPP[13] + P[11][11]*SPP[22] + P[14][11]*SPP[16]) + SPP[16]*(P[0][14]*SPP[14] - P[1][14]*SPP[3] + P[2][14]*SPP[13] + P[11][14]*SPP[22] + P[14][14]*SPP[16]);
    1081     3248540 :     nextP[0][3] = P[0][3]*SPP[5] - P[1][3]*SPP[4] + P[2][3]*SPP[8] + P[9][3]*SPP[22] + P[12][3]*SPP[18] + SPP[1]*(P[0][0]*SPP[5] - P[1][0]*SPP[4] + P[2][0]*SPP[8] + P[9][0]*SPP[22] + P[12][0]*SPP[18]) + SPP[15]*(P[0][2]*SPP[5] - P[1][2]*SPP[4] + P[2][2]*SPP[8] + P[9][2]*SPP[22] + P[12][2]*SPP[18]) - SPP[21]*(P[0][15]*SPP[5] - P[1][15]*SPP[4] + P[2][15]*SPP[8] + P[9][15]*SPP[22] + P[12][15]*SPP[18]) + (SF[16]*SF[23] - SF[17]*SPP[21])*(P[0][1]*SPP[5] - P[1][1]*SPP[4] + P[2][1]*SPP[8] + P[9][1]*SPP[22] + P[12][1]*SPP[18]);
    1082     3248540 :     nextP[1][3] = P[1][3]*SPP[6] - P[0][3]*SPP[2] - P[2][3]*SPP[9] + P[10][3]*SPP[22] + P[13][3]*SPP[17] + SPP[1]*(P[1][0]*SPP[6] - P[0][0]*SPP[2] - P[2][0]*SPP[9] + P[10][0]*SPP[22] + P[13][0]*SPP[17]) + SPP[15]*(P[1][2]*SPP[6] - P[0][2]*SPP[2] - P[2][2]*SPP[9] + P[10][2]*SPP[22] + P[13][2]*SPP[17]) - SPP[21]*(P[1][15]*SPP[6] - P[0][15]*SPP[2] - P[2][15]*SPP[9] + P[10][15]*SPP[22] + P[13][15]*SPP[17]) + (SF[16]*SF[23] - SF[17]*SPP[21])*(P[1][1]*SPP[6] - P[0][1]*SPP[2] - P[2][1]*SPP[9] + P[10][1]*SPP[22] + P[13][1]*SPP[17]);
    1083     3248540 :     nextP[2][3] = P[0][3]*SPP[14] - P[1][3]*SPP[3] + P[2][3]*SPP[13] + P[11][3]*SPP[22] + P[14][3]*SPP[16] + SPP[1]*(P[0][0]*SPP[14] - P[1][0]*SPP[3] + P[2][0]*SPP[13] + P[11][0]*SPP[22] + P[14][0]*SPP[16]) + SPP[15]*(P[0][2]*SPP[14] - P[1][2]*SPP[3] + P[2][2]*SPP[13] + P[11][2]*SPP[22] + P[14][2]*SPP[16]) - SPP[21]*(P[0][15]*SPP[14] - P[1][15]*SPP[3] + P[2][15]*SPP[13] + P[11][15]*SPP[22] + P[14][15]*SPP[16]) + (SF[16]*SF[23] - SF[17]*SPP[21])*(P[0][1]*SPP[14] - P[1][1]*SPP[3] + P[2][1]*SPP[13] + P[11][1]*SPP[22] + P[14][1]*SPP[16]);
    1084     3248540 :     nextP[3][3] = P[3][3] + P[0][3]*SPP[1] + P[1][3]*SPP[19] + P[2][3]*SPP[15] - P[15][3]*SPP[21] + dvyNoise*sq(SQ[6] - 2*q0*q3) + dvzNoise*sq(SQ[5] + 2*q0*q2) + SPP[1]*(P[3][0] + P[0][0]*SPP[1] + P[1][0]*SPP[19] + P[2][0]*SPP[15] - P[15][0]*SPP[21]) + SPP[19]*(P[3][1] + P[0][1]*SPP[1] + P[1][1]*SPP[19] + P[2][1]*SPP[15] - P[15][1]*SPP[21]) + SPP[15]*(P[3][2] + P[0][2]*SPP[1] + P[1][2]*SPP[19] + P[2][2]*SPP[15] - P[15][2]*SPP[21]) - SPP[21]*(P[3][15] + P[0][15]*SPP[1] + P[2][15]*SPP[15] - P[15][15]*SPP[21] + P[1][15]*(SF[16]*SF[23] - SF[17]*SPP[21])) + dvxNoise*sq(SG[1] + SG[2] - SG[3] - SQ[7]);
    1085     3248540 :     nextP[0][4] = P[0][4]*SPP[5] - P[1][4]*SPP[4] + P[2][4]*SPP[8] + P[9][4]*SPP[22] + P[12][4]*SPP[18] + SF[22]*(P[0][15]*SPP[5] - P[1][15]*SPP[4] + P[2][15]*SPP[8] + P[9][15]*SPP[22] + P[12][15]*SPP[18]) + SPP[12]*(P[0][1]*SPP[5] - P[1][1]*SPP[4] + P[2][1]*SPP[8] + P[9][1]*SPP[22] + P[12][1]*SPP[18]) + SPP[20]*(P[0][0]*SPP[5] - P[1][0]*SPP[4] + P[2][0]*SPP[8] + P[9][0]*SPP[22] + P[12][0]*SPP[18]) + SPP[11]*(P[0][2]*SPP[5] - P[1][2]*SPP[4] + P[2][2]*SPP[8] + P[9][2]*SPP[22] + P[12][2]*SPP[18]);
    1086     3248540 :     nextP[1][4] = P[1][4]*SPP[6] - P[0][4]*SPP[2] - P[2][4]*SPP[9] + P[10][4]*SPP[22] + P[13][4]*SPP[17] + SF[22]*(P[1][15]*SPP[6] - P[0][15]*SPP[2] - P[2][15]*SPP[9] + P[10][15]*SPP[22] + P[13][15]*SPP[17]) + SPP[12]*(P[1][1]*SPP[6] - P[0][1]*SPP[2] - P[2][1]*SPP[9] + P[10][1]*SPP[22] + P[13][1]*SPP[17]) + SPP[20]*(P[1][0]*SPP[6] - P[0][0]*SPP[2] - P[2][0]*SPP[9] + P[10][0]*SPP[22] + P[13][0]*SPP[17]) + SPP[11]*(P[1][2]*SPP[6] - P[0][2]*SPP[2] - P[2][2]*SPP[9] + P[10][2]*SPP[22] + P[13][2]*SPP[17]);
    1087     3248540 :     nextP[2][4] = P[0][4]*SPP[14] - P[1][4]*SPP[3] + P[2][4]*SPP[13] + P[11][4]*SPP[22] + P[14][4]*SPP[16] + SF[22]*(P[0][15]*SPP[14] - P[1][15]*SPP[3] + P[2][15]*SPP[13] + P[11][15]*SPP[22] + P[14][15]*SPP[16]) + SPP[12]*(P[0][1]*SPP[14] - P[1][1]*SPP[3] + P[2][1]*SPP[13] + P[11][1]*SPP[22] + P[14][1]*SPP[16]) + SPP[20]*(P[0][0]*SPP[14] - P[1][0]*SPP[3] + P[2][0]*SPP[13] + P[11][0]*SPP[22] + P[14][0]*SPP[16]) + SPP[11]*(P[0][2]*SPP[14] - P[1][2]*SPP[3] + P[2][2]*SPP[13] + P[11][2]*SPP[22] + P[14][2]*SPP[16]);
    1088     3248540 :     nextP[3][4] = P[3][4] + SQ[2] + P[0][4]*SPP[1] + P[1][4]*SPP[19] + P[2][4]*SPP[15] - P[15][4]*SPP[21] + SF[22]*(P[3][15] + P[0][15]*SPP[1] + P[1][15]*SPP[19] + P[2][15]*SPP[15] - P[15][15]*SPP[21]) + SPP[12]*(P[3][1] + P[0][1]*SPP[1] + P[1][1]*SPP[19] + P[2][1]*SPP[15] - P[15][1]*SPP[21]) + SPP[20]*(P[3][0] + P[0][0]*SPP[1] + P[1][0]*SPP[19] + P[2][0]*SPP[15] - P[15][0]*SPP[21]) + SPP[11]*(P[3][2] + P[0][2]*SPP[1] + P[1][2]*SPP[19] + P[2][2]*SPP[15] - P[15][2]*SPP[21]);
    1089     3248540 :     nextP[4][4] = P[4][4] + P[15][4]*SF[22] + P[0][4]*SPP[20] + P[1][4]*SPP[12] + P[2][4]*SPP[11] + dvxNoise*sq(SQ[6] + 2*q0*q3) + dvzNoise*sq(SQ[4] - 2*q0*q1) + SF[22]*(P[4][15] + P[15][15]*SF[22] + P[0][15]*SPP[20] + P[1][15]*SPP[12] + P[2][15]*SPP[11]) + SPP[12]*(P[4][1] + P[15][1]*SF[22] + P[0][1]*SPP[20] + P[1][1]*SPP[12] + P[2][1]*SPP[11]) + SPP[20]*(P[4][0] + P[15][0]*SF[22] + P[0][0]*SPP[20] + P[1][0]*SPP[12] + P[2][0]*SPP[11]) + SPP[11]*(P[4][2] + P[15][2]*SF[22] + P[0][2]*SPP[20] + P[1][2]*SPP[12] + P[2][2]*SPP[11]) + dvyNoise*sq(SG[1] - SG[2] + SG[3] - SQ[7]);
    1090     3248540 :     nextP[0][5] = P[0][5]*SPP[5] - P[1][5]*SPP[4] + P[2][5]*SPP[8] + P[9][5]*SPP[22] + P[12][5]*SPP[18] + SF[20]*(P[0][15]*SPP[5] - P[1][15]*SPP[4] + P[2][15]*SPP[8] + P[9][15]*SPP[22] + P[12][15]*SPP[18]) - SPP[7]*(P[0][0]*SPP[5] - P[1][0]*SPP[4] + P[2][0]*SPP[8] + P[9][0]*SPP[22] + P[12][0]*SPP[18]) + SPP[0]*(P[0][2]*SPP[5] - P[1][2]*SPP[4] + P[2][2]*SPP[8] + P[9][2]*SPP[22] + P[12][2]*SPP[18]) + SPP[10]*(P[0][1]*SPP[5] - P[1][1]*SPP[4] + P[2][1]*SPP[8] + P[9][1]*SPP[22] + P[12][1]*SPP[18]);
    1091     3248540 :     nextP[1][5] = P[1][5]*SPP[6] - P[0][5]*SPP[2] - P[2][5]*SPP[9] + P[10][5]*SPP[22] + P[13][5]*SPP[17] + SF[20]*(P[1][15]*SPP[6] - P[0][15]*SPP[2] - P[2][15]*SPP[9] + P[10][15]*SPP[22] + P[13][15]*SPP[17]) - SPP[7]*(P[1][0]*SPP[6] - P[0][0]*SPP[2] - P[2][0]*SPP[9] + P[10][0]*SPP[22] + P[13][0]*SPP[17]) + SPP[0]*(P[1][2]*SPP[6] - P[0][2]*SPP[2] - P[2][2]*SPP[9] + P[10][2]*SPP[22] + P[13][2]*SPP[17]) + SPP[10]*(P[1][1]*SPP[6] - P[0][1]*SPP[2] - P[2][1]*SPP[9] + P[10][1]*SPP[22] + P[13][1]*SPP[17]);
    1092     3248540 :     nextP[2][5] = P[0][5]*SPP[14] - P[1][5]*SPP[3] + P[2][5]*SPP[13] + P[11][5]*SPP[22] + P[14][5]*SPP[16] + SF[20]*(P[0][15]*SPP[14] - P[1][15]*SPP[3] + P[2][15]*SPP[13] + P[11][15]*SPP[22] + P[14][15]*SPP[16]) - SPP[7]*(P[0][0]*SPP[14] - P[1][0]*SPP[3] + P[2][0]*SPP[13] + P[11][0]*SPP[22] + P[14][0]*SPP[16]) + SPP[0]*(P[0][2]*SPP[14] - P[1][2]*SPP[3] + P[2][2]*SPP[13] + P[11][2]*SPP[22] + P[14][2]*SPP[16]) + SPP[10]*(P[0][1]*SPP[14] - P[1][1]*SPP[3] + P[2][1]*SPP[13] + P[11][1]*SPP[22] + P[14][1]*SPP[16]);
    1093     3248540 :     nextP[3][5] = P[3][5] + SQ[1] + P[0][5]*SPP[1] + P[1][5]*SPP[19] + P[2][5]*SPP[15] - P[15][5]*SPP[21] + SF[20]*(P[3][15] + P[0][15]*SPP[1] + P[1][15]*SPP[19] + P[2][15]*SPP[15] - P[15][15]*SPP[21]) - SPP[7]*(P[3][0] + P[0][0]*SPP[1] + P[1][0]*SPP[19] + P[2][0]*SPP[15] - P[15][0]*SPP[21]) + SPP[0]*(P[3][2] + P[0][2]*SPP[1] + P[1][2]*SPP[19] + P[2][2]*SPP[15] - P[15][2]*SPP[21]) + SPP[10]*(P[3][1] + P[0][1]*SPP[1] + P[1][1]*SPP[19] + P[2][1]*SPP[15] - P[15][1]*SPP[21]);
    1094     3248540 :     nextP[4][5] = P[4][5] + SQ[0] + P[15][5]*SF[22] + P[0][5]*SPP[20] + P[1][5]*SPP[12] + P[2][5]*SPP[11] + SF[20]*(P[4][15] + P[15][15]*SF[22] + P[0][15]*SPP[20] + P[1][15]*SPP[12] + P[2][15]*SPP[11]) - SPP[7]*(P[4][0] + P[15][0]*SF[22] + P[0][0]*SPP[20] + P[1][0]*SPP[12] + P[2][0]*SPP[11]) + SPP[0]*(P[4][2] + P[15][2]*SF[22] + P[0][2]*SPP[20] + P[1][2]*SPP[12] + P[2][2]*SPP[11]) + SPP[10]*(P[4][1] + P[15][1]*SF[22] + P[0][1]*SPP[20] + P[1][1]*SPP[12] + P[2][1]*SPP[11]);
    1095     3248540 :     nextP[5][5] = P[5][5] + P[15][5]*SF[20] - P[0][5]*SPP[7] + P[1][5]*SPP[10] + P[2][5]*SPP[0] + dvxNoise*sq(SQ[5] - 2*q0*q2) + dvyNoise*sq(SQ[4] + 2*q0*q1) + SF[20]*(P[5][15] + P[15][15]*SF[20] - P[0][15]*SPP[7] + P[1][15]*SPP[10] + P[2][15]*SPP[0]) - SPP[7]*(P[5][0] + P[15][0]*SF[20] - P[0][0]*SPP[7] + P[1][0]*SPP[10] + P[2][0]*SPP[0]) + SPP[0]*(P[5][2] + P[15][2]*SF[20] - P[0][2]*SPP[7] + P[1][2]*SPP[10] + P[2][2]*SPP[0]) + SPP[10]*(P[5][1] + P[15][1]*SF[20] - P[0][1]*SPP[7] + P[1][1]*SPP[10] + P[2][1]*SPP[0]) + dvzNoise*sq(SG[1] - SG[2] - SG[3] + SQ[7]);
    1096     3248540 :     nextP[0][6] = P[0][6]*SPP[5] - P[1][6]*SPP[4] + P[2][6]*SPP[8] + P[9][6]*SPP[22] + P[12][6]*SPP[18] + dt*(P[0][3]*SPP[5] - P[1][3]*SPP[4] + P[2][3]*SPP[8] + P[9][3]*SPP[22] + P[12][3]*SPP[18]);
    1097     3248540 :     nextP[1][6] = P[1][6]*SPP[6] - P[0][6]*SPP[2] - P[2][6]*SPP[9] + P[10][6]*SPP[22] + P[13][6]*SPP[17] + dt*(P[1][3]*SPP[6] - P[0][3]*SPP[2] - P[2][3]*SPP[9] + P[10][3]*SPP[22] + P[13][3]*SPP[17]);
    1098     3248540 :     nextP[2][6] = P[0][6]*SPP[14] - P[1][6]*SPP[3] + P[2][6]*SPP[13] + P[11][6]*SPP[22] + P[14][6]*SPP[16] + dt*(P[0][3]*SPP[14] - P[1][3]*SPP[3] + P[2][3]*SPP[13] + P[11][3]*SPP[22] + P[14][3]*SPP[16]);
    1099     3248540 :     nextP[3][6] = P[3][6] + P[0][6]*SPP[1] + P[1][6]*SPP[19] + P[2][6]*SPP[15] - P[15][6]*SPP[21] + dt*(P[3][3] + P[0][3]*SPP[1] + P[1][3]*SPP[19] + P[2][3]*SPP[15] - P[15][3]*SPP[21]);
    1100     3248540 :     nextP[4][6] = P[4][6] + P[15][6]*SF[22] + P[0][6]*SPP[20] + P[1][6]*SPP[12] + P[2][6]*SPP[11] + dt*(P[4][3] + P[15][3]*SF[22] + P[0][3]*SPP[20] + P[1][3]*SPP[12] + P[2][3]*SPP[11]);
    1101     3248540 :     nextP[5][6] = P[5][6] + P[15][6]*SF[20] - P[0][6]*SPP[7] + P[1][6]*SPP[10] + P[2][6]*SPP[0] + dt*(P[5][3] + P[15][3]*SF[20] - P[0][3]*SPP[7] + P[1][3]*SPP[10] + P[2][3]*SPP[0]);
    1102     3248540 :     nextP[6][6] = P[6][6] + P[3][6]*dt + dt*(P[6][3] + P[3][3]*dt);
    1103     3248540 :     nextP[0][7] = P[0][7]*SPP[5] - P[1][7]*SPP[4] + P[2][7]*SPP[8] + P[9][7]*SPP[22] + P[12][7]*SPP[18] + dt*(P[0][4]*SPP[5] - P[1][4]*SPP[4] + P[2][4]*SPP[8] + P[9][4]*SPP[22] + P[12][4]*SPP[18]);
    1104     3248540 :     nextP[1][7] = P[1][7]*SPP[6] - P[0][7]*SPP[2] - P[2][7]*SPP[9] + P[10][7]*SPP[22] + P[13][7]*SPP[17] + dt*(P[1][4]*SPP[6] - P[0][4]*SPP[2] - P[2][4]*SPP[9] + P[10][4]*SPP[22] + P[13][4]*SPP[17]);
    1105     3248540 :     nextP[2][7] = P[0][7]*SPP[14] - P[1][7]*SPP[3] + P[2][7]*SPP[13] + P[11][7]*SPP[22] + P[14][7]*SPP[16] + dt*(P[0][4]*SPP[14] - P[1][4]*SPP[3] + P[2][4]*SPP[13] + P[11][4]*SPP[22] + P[14][4]*SPP[16]);
    1106     3248540 :     nextP[3][7] = P[3][7] + P[0][7]*SPP[1] + P[1][7]*SPP[19] + P[2][7]*SPP[15] - P[15][7]*SPP[21] + dt*(P[3][4] + P[0][4]*SPP[1] + P[1][4]*SPP[19] + P[2][4]*SPP[15] - P[15][4]*SPP[21]);
    1107     3248540 :     nextP[4][7] = P[4][7] + P[15][7]*SF[22] + P[0][7]*SPP[20] + P[1][7]*SPP[12] + P[2][7]*SPP[11] + dt*(P[4][4] + P[15][4]*SF[22] + P[0][4]*SPP[20] + P[1][4]*SPP[12] + P[2][4]*SPP[11]);
    1108     3248540 :     nextP[5][7] = P[5][7] + P[15][7]*SF[20] - P[0][7]*SPP[7] + P[1][7]*SPP[10] + P[2][7]*SPP[0] + dt*(P[5][4] + P[15][4]*SF[20] - P[0][4]*SPP[7] + P[1][4]*SPP[10] + P[2][4]*SPP[0]);
    1109     3248540 :     nextP[6][7] = P[6][7] + P[3][7]*dt + dt*(P[6][4] + P[3][4]*dt);
    1110     3248540 :     nextP[7][7] = P[7][7] + P[4][7]*dt + dt*(P[7][4] + P[4][4]*dt);
    1111     3248540 :     nextP[0][8] = P[0][8]*SPP[5] - P[1][8]*SPP[4] + P[2][8]*SPP[8] + P[9][8]*SPP[22] + P[12][8]*SPP[18] + dt*(P[0][5]*SPP[5] - P[1][5]*SPP[4] + P[2][5]*SPP[8] + P[9][5]*SPP[22] + P[12][5]*SPP[18]);
    1112     3248540 :     nextP[1][8] = P[1][8]*SPP[6] - P[0][8]*SPP[2] - P[2][8]*SPP[9] + P[10][8]*SPP[22] + P[13][8]*SPP[17] + dt*(P[1][5]*SPP[6] - P[0][5]*SPP[2] - P[2][5]*SPP[9] + P[10][5]*SPP[22] + P[13][5]*SPP[17]);
    1113     3248540 :     nextP[2][8] = P[0][8]*SPP[14] - P[1][8]*SPP[3] + P[2][8]*SPP[13] + P[11][8]*SPP[22] + P[14][8]*SPP[16] + dt*(P[0][5]*SPP[14] - P[1][5]*SPP[3] + P[2][5]*SPP[13] + P[11][5]*SPP[22] + P[14][5]*SPP[16]);
    1114     3248540 :     nextP[3][8] = P[3][8] + P[0][8]*SPP[1] + P[1][8]*SPP[19] + P[2][8]*SPP[15] - P[15][8]*SPP[21] + dt*(P[3][5] + P[0][5]*SPP[1] + P[1][5]*SPP[19] + P[2][5]*SPP[15] - P[15][5]*SPP[21]);
    1115     3248540 :     nextP[4][8] = P[4][8] + P[15][8]*SF[22] + P[0][8]*SPP[20] + P[1][8]*SPP[12] + P[2][8]*SPP[11] + dt*(P[4][5] + P[15][5]*SF[22] + P[0][5]*SPP[20] + P[1][5]*SPP[12] + P[2][5]*SPP[11]);
    1116     3248540 :     nextP[5][8] = P[5][8] + P[15][8]*SF[20] - P[0][8]*SPP[7] + P[1][8]*SPP[10] + P[2][8]*SPP[0] + dt*(P[5][5] + P[15][5]*SF[20] - P[0][5]*SPP[7] + P[1][5]*SPP[10] + P[2][5]*SPP[0]);
    1117     3248540 :     nextP[6][8] = P[6][8] + P[3][8]*dt + dt*(P[6][5] + P[3][5]*dt);
    1118     3248540 :     nextP[7][8] = P[7][8] + P[4][8]*dt + dt*(P[7][5] + P[4][5]*dt);
    1119     3248540 :     nextP[8][8] = P[8][8] + P[5][8]*dt + dt*(P[8][5] + P[5][5]*dt);
    1120     3248540 :     nextP[0][9] = P[0][9]*SPP[5] - P[1][9]*SPP[4] + P[2][9]*SPP[8] + P[9][9]*SPP[22] + P[12][9]*SPP[18];
    1121     3248540 :     nextP[1][9] = P[1][9]*SPP[6] - P[0][9]*SPP[2] - P[2][9]*SPP[9] + P[10][9]*SPP[22] + P[13][9]*SPP[17];
    1122     3248540 :     nextP[2][9] = P[0][9]*SPP[14] - P[1][9]*SPP[3] + P[2][9]*SPP[13] + P[11][9]*SPP[22] + P[14][9]*SPP[16];
    1123     3248540 :     nextP[3][9] = P[3][9] + P[0][9]*SPP[1] + P[1][9]*SPP[19] + P[2][9]*SPP[15] - P[15][9]*SPP[21];
    1124     3248540 :     nextP[4][9] = P[4][9] + P[15][9]*SF[22] + P[0][9]*SPP[20] + P[1][9]*SPP[12] + P[2][9]*SPP[11];
    1125     3248540 :     nextP[5][9] = P[5][9] + P[15][9]*SF[20] - P[0][9]*SPP[7] + P[1][9]*SPP[10] + P[2][9]*SPP[0];
    1126     3248540 :     nextP[6][9] = P[6][9] + P[3][9]*dt;
    1127     3248540 :     nextP[7][9] = P[7][9] + P[4][9]*dt;
    1128     3248540 :     nextP[8][9] = P[8][9] + P[5][9]*dt;
    1129     3248540 :     nextP[9][9] = P[9][9];
    1130     3248540 :     nextP[0][10] = P[0][10]*SPP[5] - P[1][10]*SPP[4] + P[2][10]*SPP[8] + P[9][10]*SPP[22] + P[12][10]*SPP[18];
    1131     3248540 :     nextP[1][10] = P[1][10]*SPP[6] - P[0][10]*SPP[2] - P[2][10]*SPP[9] + P[10][10]*SPP[22] + P[13][10]*SPP[17];
    1132     3248540 :     nextP[2][10] = P[0][10]*SPP[14] - P[1][10]*SPP[3] + P[2][10]*SPP[13] + P[11][10]*SPP[22] + P[14][10]*SPP[16];
    1133     3248540 :     nextP[3][10] = P[3][10] + P[0][10]*SPP[1] + P[1][10]*SPP[19] + P[2][10]*SPP[15] - P[15][10]*SPP[21];
    1134     3248540 :     nextP[4][10] = P[4][10] + P[15][10]*SF[22] + P[0][10]*SPP[20] + P[1][10]*SPP[12] + P[2][10]*SPP[11];
    1135     3248540 :     nextP[5][10] = P[5][10] + P[15][10]*SF[20] - P[0][10]*SPP[7] + P[1][10]*SPP[10] + P[2][10]*SPP[0];
    1136     3248540 :     nextP[6][10] = P[6][10] + P[3][10]*dt;
    1137     3248540 :     nextP[7][10] = P[7][10] + P[4][10]*dt;
    1138     3248540 :     nextP[8][10] = P[8][10] + P[5][10]*dt;
    1139     3248540 :     nextP[9][10] = P[9][10];
    1140     3248540 :     nextP[10][10] = P[10][10];
    1141     3248540 :     nextP[0][11] = P[0][11]*SPP[5] - P[1][11]*SPP[4] + P[2][11]*SPP[8] + P[9][11]*SPP[22] + P[12][11]*SPP[18];
    1142     3248540 :     nextP[1][11] = P[1][11]*SPP[6] - P[0][11]*SPP[2] - P[2][11]*SPP[9] + P[10][11]*SPP[22] + P[13][11]*SPP[17];
    1143     3248540 :     nextP[2][11] = P[0][11]*SPP[14] - P[1][11]*SPP[3] + P[2][11]*SPP[13] + P[11][11]*SPP[22] + P[14][11]*SPP[16];
    1144     3248540 :     nextP[3][11] = P[3][11] + P[0][11]*SPP[1] + P[1][11]*SPP[19] + P[2][11]*SPP[15] - P[15][11]*SPP[21];
    1145     3248540 :     nextP[4][11] = P[4][11] + P[15][11]*SF[22] + P[0][11]*SPP[20] + P[1][11]*SPP[12] + P[2][11]*SPP[11];
    1146     3248540 :     nextP[5][11] = P[5][11] + P[15][11]*SF[20] - P[0][11]*SPP[7] + P[1][11]*SPP[10] + P[2][11]*SPP[0];
    1147     3248540 :     nextP[6][11] = P[6][11] + P[3][11]*dt;
    1148     3248540 :     nextP[7][11] = P[7][11] + P[4][11]*dt;
    1149     3248540 :     nextP[8][11] = P[8][11] + P[5][11]*dt;
    1150     3248540 :     nextP[9][11] = P[9][11];
    1151     3248540 :     nextP[10][11] = P[10][11];
    1152     3248540 :     nextP[11][11] = P[11][11];
    1153     3248540 :     nextP[0][12] = P[0][12]*SPP[5] - P[1][12]*SPP[4] + P[2][12]*SPP[8] + P[9][12]*SPP[22] + P[12][12]*SPP[18];
    1154     3248540 :     nextP[1][12] = P[1][12]*SPP[6] - P[0][12]*SPP[2] - P[2][12]*SPP[9] + P[10][12]*SPP[22] + P[13][12]*SPP[17];
    1155     3248540 :     nextP[2][12] = P[0][12]*SPP[14] - P[1][12]*SPP[3] + P[2][12]*SPP[13] + P[11][12]*SPP[22] + P[14][12]*SPP[16];
    1156     3248540 :     nextP[3][12] = P[3][12] + P[0][12]*SPP[1] + P[1][12]*SPP[19] + P[2][12]*SPP[15] - P[15][12]*SPP[21];
    1157     3248540 :     nextP[4][12] = P[4][12] + P[15][12]*SF[22] + P[0][12]*SPP[20] + P[1][12]*SPP[12] + P[2][12]*SPP[11];
    1158     3248540 :     nextP[5][12] = P[5][12] + P[15][12]*SF[20] - P[0][12]*SPP[7] + P[1][12]*SPP[10] + P[2][12]*SPP[0];
    1159     3248540 :     nextP[6][12] = P[6][12] + P[3][12]*dt;
    1160     3248540 :     nextP[7][12] = P[7][12] + P[4][12]*dt;
    1161     3248540 :     nextP[8][12] = P[8][12] + P[5][12]*dt;
    1162     3248540 :     nextP[9][12] = P[9][12];
    1163     3248540 :     nextP[10][12] = P[10][12];
    1164     3248540 :     nextP[11][12] = P[11][12];
    1165     3248540 :     nextP[12][12] = P[12][12];
    1166     3248540 :     nextP[0][13] = P[0][13]*SPP[5] - P[1][13]*SPP[4] + P[2][13]*SPP[8] + P[9][13]*SPP[22] + P[12][13]*SPP[18];
    1167     3248540 :     nextP[1][13] = P[1][13]*SPP[6] - P[0][13]*SPP[2] - P[2][13]*SPP[9] + P[10][13]*SPP[22] + P[13][13]*SPP[17];
    1168     3248540 :     nextP[2][13] = P[0][13]*SPP[14] - P[1][13]*SPP[3] + P[2][13]*SPP[13] + P[11][13]*SPP[22] + P[14][13]*SPP[16];
    1169     3248540 :     nextP[3][13] = P[3][13] + P[0][13]*SPP[1] + P[1][13]*SPP[19] + P[2][13]*SPP[15] - P[15][13]*SPP[21];
    1170     3248540 :     nextP[4][13] = P[4][13] + P[15][13]*SF[22] + P[0][13]*SPP[20] + P[1][13]*SPP[12] + P[2][13]*SPP[11];
    1171     3248540 :     nextP[5][13] = P[5][13] + P[15][13]*SF[20] - P[0][13]*SPP[7] + P[1][13]*SPP[10] + P[2][13]*SPP[0];
    1172     3248540 :     nextP[6][13] = P[6][13] + P[3][13]*dt;
    1173     3248540 :     nextP[7][13] = P[7][13] + P[4][13]*dt;
    1174     3248540 :     nextP[8][13] = P[8][13] + P[5][13]*dt;
    1175     3248540 :     nextP[9][13] = P[9][13];
    1176     3248540 :     nextP[10][13] = P[10][13];
    1177     3248540 :     nextP[11][13] = P[11][13];
    1178     3248540 :     nextP[12][13] = P[12][13];
    1179     3248540 :     nextP[13][13] = P[13][13];
    1180     3248540 :     nextP[0][14] = P[0][14]*SPP[5] - P[1][14]*SPP[4] + P[2][14]*SPP[8] + P[9][14]*SPP[22] + P[12][14]*SPP[18];
    1181     3248540 :     nextP[1][14] = P[1][14]*SPP[6] - P[0][14]*SPP[2] - P[2][14]*SPP[9] + P[10][14]*SPP[22] + P[13][14]*SPP[17];
    1182     3248540 :     nextP[2][14] = P[0][14]*SPP[14] - P[1][14]*SPP[3] + P[2][14]*SPP[13] + P[11][14]*SPP[22] + P[14][14]*SPP[16];
    1183     3248540 :     nextP[3][14] = P[3][14] + P[0][14]*SPP[1] + P[1][14]*SPP[19] + P[2][14]*SPP[15] - P[15][14]*SPP[21];
    1184     3248540 :     nextP[4][14] = P[4][14] + P[15][14]*SF[22] + P[0][14]*SPP[20] + P[1][14]*SPP[12] + P[2][14]*SPP[11];
    1185     3248540 :     nextP[5][14] = P[5][14] + P[15][14]*SF[20] - P[0][14]*SPP[7] + P[1][14]*SPP[10] + P[2][14]*SPP[0];
    1186     3248540 :     nextP[6][14] = P[6][14] + P[3][14]*dt;
    1187     3248540 :     nextP[7][14] = P[7][14] + P[4][14]*dt;
    1188     3248540 :     nextP[8][14] = P[8][14] + P[5][14]*dt;
    1189     3248540 :     nextP[9][14] = P[9][14];
    1190     3248540 :     nextP[10][14] = P[10][14];
    1191     3248540 :     nextP[11][14] = P[11][14];
    1192     3248540 :     nextP[12][14] = P[12][14];
    1193     3248540 :     nextP[13][14] = P[13][14];
    1194     3248540 :     nextP[14][14] = P[14][14];
    1195     3248540 :     nextP[0][15] = P[0][15]*SPP[5] - P[1][15]*SPP[4] + P[2][15]*SPP[8] + P[9][15]*SPP[22] + P[12][15]*SPP[18];
    1196     3248540 :     nextP[1][15] = P[1][15]*SPP[6] - P[0][15]*SPP[2] - P[2][15]*SPP[9] + P[10][15]*SPP[22] + P[13][15]*SPP[17];
    1197     3248540 :     nextP[2][15] = P[0][15]*SPP[14] - P[1][15]*SPP[3] + P[2][15]*SPP[13] + P[11][15]*SPP[22] + P[14][15]*SPP[16];
    1198     3248540 :     nextP[3][15] = P[3][15] + P[0][15]*SPP[1] + P[1][15]*SPP[19] + P[2][15]*SPP[15] - P[15][15]*SPP[21];
    1199     3248540 :     nextP[4][15] = P[4][15] + P[15][15]*SF[22] + P[0][15]*SPP[20] + P[1][15]*SPP[12] + P[2][15]*SPP[11];
    1200     3248540 :     nextP[5][15] = P[5][15] + P[15][15]*SF[20] - P[0][15]*SPP[7] + P[1][15]*SPP[10] + P[2][15]*SPP[0];
    1201     3248540 :     nextP[6][15] = P[6][15] + P[3][15]*dt;
    1202     3248540 :     nextP[7][15] = P[7][15] + P[4][15]*dt;
    1203     3248540 :     nextP[8][15] = P[8][15] + P[5][15]*dt;
    1204     3248540 :     nextP[9][15] = P[9][15];
    1205     3248540 :     nextP[10][15] = P[10][15];
    1206     3248540 :     nextP[11][15] = P[11][15];
    1207     3248540 :     nextP[12][15] = P[12][15];
    1208     3248540 :     nextP[13][15] = P[13][15];
    1209     3248540 :     nextP[14][15] = P[14][15];
    1210     3248540 :     nextP[15][15] = P[15][15];
    1211             : 
    1212     3248540 :     if (stateIndexLim > 15) {
    1213     2318440 :         nextP[0][16] = P[0][16]*SPP[5] - P[1][16]*SPP[4] + P[2][16]*SPP[8] + P[9][16]*SPP[22] + P[12][16]*SPP[18];
    1214     2318440 :         nextP[1][16] = P[1][16]*SPP[6] - P[0][16]*SPP[2] - P[2][16]*SPP[9] + P[10][16]*SPP[22] + P[13][16]*SPP[17];
    1215     2318440 :         nextP[2][16] = P[0][16]*SPP[14] - P[1][16]*SPP[3] + P[2][16]*SPP[13] + P[11][16]*SPP[22] + P[14][16]*SPP[16];
    1216     2318440 :         nextP[3][16] = P[3][16] + P[0][16]*SPP[1] + P[1][16]*SPP[19] + P[2][16]*SPP[15] - P[15][16]*SPP[21];
    1217     2318440 :         nextP[4][16] = P[4][16] + P[15][16]*SF[22] + P[0][16]*SPP[20] + P[1][16]*SPP[12] + P[2][16]*SPP[11];
    1218     2318440 :         nextP[5][16] = P[5][16] + P[15][16]*SF[20] - P[0][16]*SPP[7] + P[1][16]*SPP[10] + P[2][16]*SPP[0];
    1219     2318440 :         nextP[6][16] = P[6][16] + P[3][16]*dt;
    1220     2318440 :         nextP[7][16] = P[7][16] + P[4][16]*dt;
    1221     2318440 :         nextP[8][16] = P[8][16] + P[5][16]*dt;
    1222     2318440 :         nextP[9][16] = P[9][16];
    1223     2318440 :         nextP[10][16] = P[10][16];
    1224     2318440 :         nextP[11][16] = P[11][16];
    1225     2318440 :         nextP[12][16] = P[12][16];
    1226     2318440 :         nextP[13][16] = P[13][16];
    1227     2318440 :         nextP[14][16] = P[14][16];
    1228     2318440 :         nextP[15][16] = P[15][16];
    1229     2318440 :         nextP[16][16] = P[16][16];
    1230     2318440 :         nextP[0][17] = P[0][17]*SPP[5] - P[1][17]*SPP[4] + P[2][17]*SPP[8] + P[9][17]*SPP[22] + P[12][17]*SPP[18];
    1231     2318440 :         nextP[1][17] = P[1][17]*SPP[6] - P[0][17]*SPP[2] - P[2][17]*SPP[9] + P[10][17]*SPP[22] + P[13][17]*SPP[17];
    1232     2318440 :         nextP[2][17] = P[0][17]*SPP[14] - P[1][17]*SPP[3] + P[2][17]*SPP[13] + P[11][17]*SPP[22] + P[14][17]*SPP[16];
    1233     2318440 :         nextP[3][17] = P[3][17] + P[0][17]*SPP[1] + P[1][17]*SPP[19] + P[2][17]*SPP[15] - P[15][17]*SPP[21];
    1234     2318440 :         nextP[4][17] = P[4][17] + P[15][17]*SF[22] + P[0][17]*SPP[20] + P[1][17]*SPP[12] + P[2][17]*SPP[11];
    1235     2318440 :         nextP[5][17] = P[5][17] + P[15][17]*SF[20] - P[0][17]*SPP[7] + P[1][17]*SPP[10] + P[2][17]*SPP[0];
    1236     2318440 :         nextP[6][17] = P[6][17] + P[3][17]*dt;
    1237     2318440 :         nextP[7][17] = P[7][17] + P[4][17]*dt;
    1238     2318440 :         nextP[8][17] = P[8][17] + P[5][17]*dt;
    1239     2318440 :         nextP[9][17] = P[9][17];
    1240     2318440 :         nextP[10][17] = P[10][17];
    1241     2318440 :         nextP[11][17] = P[11][17];
    1242     2318440 :         nextP[12][17] = P[12][17];
    1243     2318440 :         nextP[13][17] = P[13][17];
    1244     2318440 :         nextP[14][17] = P[14][17];
    1245     2318440 :         nextP[15][17] = P[15][17];
    1246     2318440 :         nextP[16][17] = P[16][17];
    1247     2318440 :         nextP[17][17] = P[17][17];
    1248     2318440 :         nextP[0][18] = P[0][18]*SPP[5] - P[1][18]*SPP[4] + P[2][18]*SPP[8] + P[9][18]*SPP[22] + P[12][18]*SPP[18];
    1249     2318440 :         nextP[1][18] = P[1][18]*SPP[6] - P[0][18]*SPP[2] - P[2][18]*SPP[9] + P[10][18]*SPP[22] + P[13][18]*SPP[17];
    1250     2318440 :         nextP[2][18] = P[0][18]*SPP[14] - P[1][18]*SPP[3] + P[2][18]*SPP[13] + P[11][18]*SPP[22] + P[14][18]*SPP[16];
    1251     2318440 :         nextP[3][18] = P[3][18] + P[0][18]*SPP[1] + P[1][18]*SPP[19] + P[2][18]*SPP[15] - P[15][18]*SPP[21];
    1252     2318440 :         nextP[4][18] = P[4][18] + P[15][18]*SF[22] + P[0][18]*SPP[20] + P[1][18]*SPP[12] + P[2][18]*SPP[11];
    1253     2318440 :         nextP[5][18] = P[5][18] + P[15][18]*SF[20] - P[0][18]*SPP[7] + P[1][18]*SPP[10] + P[2][18]*SPP[0];
    1254     2318440 :         nextP[6][18] = P[6][18] + P[3][18]*dt;
    1255     2318440 :         nextP[7][18] = P[7][18] + P[4][18]*dt;
    1256     2318440 :         nextP[8][18] = P[8][18] + P[5][18]*dt;
    1257     2318440 :         nextP[9][18] = P[9][18];
    1258     2318440 :         nextP[10][18] = P[10][18];
    1259     2318440 :         nextP[11][18] = P[11][18];
    1260     2318440 :         nextP[12][18] = P[12][18];
    1261     2318440 :         nextP[13][18] = P[13][18];
    1262     2318440 :         nextP[14][18] = P[14][18];
    1263     2318440 :         nextP[15][18] = P[15][18];
    1264     2318440 :         nextP[16][18] = P[16][18];
    1265     2318440 :         nextP[17][18] = P[17][18];
    1266     2318440 :         nextP[18][18] = P[18][18];
    1267     2318440 :         nextP[0][19] = P[0][19]*SPP[5] - P[1][19]*SPP[4] + P[2][19]*SPP[8] + P[9][19]*SPP[22] + P[12][19]*SPP[18];
    1268     2318440 :         nextP[1][19] = P[1][19]*SPP[6] - P[0][19]*SPP[2] - P[2][19]*SPP[9] + P[10][19]*SPP[22] + P[13][19]*SPP[17];
    1269     2318440 :         nextP[2][19] = P[0][19]*SPP[14] - P[1][19]*SPP[3] + P[2][19]*SPP[13] + P[11][19]*SPP[22] + P[14][19]*SPP[16];
    1270     2318440 :         nextP[3][19] = P[3][19] + P[0][19]*SPP[1] + P[1][19]*SPP[19] + P[2][19]*SPP[15] - P[15][19]*SPP[21];
    1271     2318440 :         nextP[4][19] = P[4][19] + P[15][19]*SF[22] + P[0][19]*SPP[20] + P[1][19]*SPP[12] + P[2][19]*SPP[11];
    1272     2318440 :         nextP[5][19] = P[5][19] + P[15][19]*SF[20] - P[0][19]*SPP[7] + P[1][19]*SPP[10] + P[2][19]*SPP[0];
    1273     2318440 :         nextP[6][19] = P[6][19] + P[3][19]*dt;
    1274     2318440 :         nextP[7][19] = P[7][19] + P[4][19]*dt;
    1275     2318440 :         nextP[8][19] = P[8][19] + P[5][19]*dt;
    1276     2318440 :         nextP[9][19] = P[9][19];
    1277     2318440 :         nextP[10][19] = P[10][19];
    1278     2318440 :         nextP[11][19] = P[11][19];
    1279     2318440 :         nextP[12][19] = P[12][19];
    1280     2318440 :         nextP[13][19] = P[13][19];
    1281     2318440 :         nextP[14][19] = P[14][19];
    1282     2318440 :         nextP[15][19] = P[15][19];
    1283     2318440 :         nextP[16][19] = P[16][19];
    1284     2318440 :         nextP[17][19] = P[17][19];
    1285     2318440 :         nextP[18][19] = P[18][19];
    1286     2318440 :         nextP[19][19] = P[19][19];
    1287     2318440 :         nextP[0][20] = P[0][20]*SPP[5] - P[1][20]*SPP[4] + P[2][20]*SPP[8] + P[9][20]*SPP[22] + P[12][20]*SPP[18];
    1288     2318440 :         nextP[1][20] = P[1][20]*SPP[6] - P[0][20]*SPP[2] - P[2][20]*SPP[9] + P[10][20]*SPP[22] + P[13][20]*SPP[17];
    1289     2318440 :         nextP[2][20] = P[0][20]*SPP[14] - P[1][20]*SPP[3] + P[2][20]*SPP[13] + P[11][20]*SPP[22] + P[14][20]*SPP[16];
    1290     2318440 :         nextP[3][20] = P[3][20] + P[0][20]*SPP[1] + P[1][20]*SPP[19] + P[2][20]*SPP[15] - P[15][20]*SPP[21];
    1291     2318440 :         nextP[4][20] = P[4][20] + P[15][20]*SF[22] + P[0][20]*SPP[20] + P[1][20]*SPP[12] + P[2][20]*SPP[11];
    1292     2318440 :         nextP[5][20] = P[5][20] + P[15][20]*SF[20] - P[0][20]*SPP[7] + P[1][20]*SPP[10] + P[2][20]*SPP[0];
    1293     2318440 :         nextP[6][20] = P[6][20] + P[3][20]*dt;
    1294     2318440 :         nextP[7][20] = P[7][20] + P[4][20]*dt;
    1295     2318440 :         nextP[8][20] = P[8][20] + P[5][20]*dt;
    1296     2318440 :         nextP[9][20] = P[9][20];
    1297     2318440 :         nextP[10][20] = P[10][20];
    1298     2318440 :         nextP[11][20] = P[11][20];
    1299     2318440 :         nextP[12][20] = P[12][20];
    1300     2318440 :         nextP[13][20] = P[13][20];
    1301     2318440 :         nextP[14][20] = P[14][20];
    1302     2318440 :         nextP[15][20] = P[15][20];
    1303     2318440 :         nextP[16][20] = P[16][20];
    1304     2318440 :         nextP[17][20] = P[17][20];
    1305     2318440 :         nextP[18][20] = P[18][20];
    1306     2318440 :         nextP[19][20] = P[19][20];
    1307     2318440 :         nextP[20][20] = P[20][20];
    1308     2318440 :         nextP[0][21] = P[0][21]*SPP[5] - P[1][21]*SPP[4] + P[2][21]*SPP[8] + P[9][21]*SPP[22] + P[12][21]*SPP[18];
    1309     2318440 :         nextP[1][21] = P[1][21]*SPP[6] - P[0][21]*SPP[2] - P[2][21]*SPP[9] + P[10][21]*SPP[22] + P[13][21]*SPP[17];
    1310     2318440 :         nextP[2][21] = P[0][21]*SPP[14] - P[1][21]*SPP[3] + P[2][21]*SPP[13] + P[11][21]*SPP[22] + P[14][21]*SPP[16];
    1311     2318440 :         nextP[3][21] = P[3][21] + P[0][21]*SPP[1] + P[1][21]*SPP[19] + P[2][21]*SPP[15] - P[15][21]*SPP[21];
    1312     2318440 :         nextP[4][21] = P[4][21] + P[15][21]*SF[22] + P[0][21]*SPP[20] + P[1][21]*SPP[12] + P[2][21]*SPP[11];
    1313     2318440 :         nextP[5][21] = P[5][21] + P[15][21]*SF[20] - P[0][21]*SPP[7] + P[1][21]*SPP[10] + P[2][21]*SPP[0];
    1314     2318440 :         nextP[6][21] = P[6][21] + P[3][21]*dt;
    1315     2318440 :         nextP[7][21] = P[7][21] + P[4][21]*dt;
    1316     2318440 :         nextP[8][21] = P[8][21] + P[5][21]*dt;
    1317     2318440 :         nextP[9][21] = P[9][21];
    1318     2318440 :         nextP[10][21] = P[10][21];
    1319     2318440 :         nextP[11][21] = P[11][21];
    1320     2318440 :         nextP[12][21] = P[12][21];
    1321     2318440 :         nextP[13][21] = P[13][21];
    1322     2318440 :         nextP[14][21] = P[14][21];
    1323     2318440 :         nextP[15][21] = P[15][21];
    1324     2318440 :         nextP[16][21] = P[16][21];
    1325     2318440 :         nextP[17][21] = P[17][21];
    1326     2318440 :         nextP[18][21] = P[18][21];
    1327     2318440 :         nextP[19][21] = P[19][21];
    1328     2318440 :         nextP[20][21] = P[20][21];
    1329     2318440 :         nextP[21][21] = P[21][21];
    1330             : 
    1331     2318440 :         if (stateIndexLim > 21) {
    1332     2057185 :             nextP[0][22] = P[0][22]*SPP[5] - P[1][22]*SPP[4] + P[2][22]*SPP[8] + P[9][22]*SPP[22] + P[12][22]*SPP[18];
    1333     2057185 :             nextP[1][22] = P[1][22]*SPP[6] - P[0][22]*SPP[2] - P[2][22]*SPP[9] + P[10][22]*SPP[22] + P[13][22]*SPP[17];
    1334     2057185 :             nextP[2][22] = P[0][22]*SPP[14] - P[1][22]*SPP[3] + P[2][22]*SPP[13] + P[11][22]*SPP[22] + P[14][22]*SPP[16];
    1335     2057185 :             nextP[3][22] = P[3][22] + P[0][22]*SPP[1] + P[1][22]*SPP[19] + P[2][22]*SPP[15] - P[15][22]*SPP[21];
    1336     2057185 :             nextP[4][22] = P[4][22] + P[15][22]*SF[22] + P[0][22]*SPP[20] + P[1][22]*SPP[12] + P[2][22]*SPP[11];
    1337     2057185 :             nextP[5][22] = P[5][22] + P[15][22]*SF[20] - P[0][22]*SPP[7] + P[1][22]*SPP[10] + P[2][22]*SPP[0];
    1338     2057185 :             nextP[6][22] = P[6][22] + P[3][22]*dt;
    1339     2057185 :             nextP[7][22] = P[7][22] + P[4][22]*dt;
    1340     2057185 :             nextP[8][22] = P[8][22] + P[5][22]*dt;
    1341     2057185 :             nextP[9][22] = P[9][22];
    1342     2057185 :             nextP[10][22] = P[10][22];
    1343     2057185 :             nextP[11][22] = P[11][22];
    1344     2057185 :             nextP[12][22] = P[12][22];
    1345     2057185 :             nextP[13][22] = P[13][22];
    1346     2057185 :             nextP[14][22] = P[14][22];
    1347     2057185 :             nextP[15][22] = P[15][22];
    1348     2057185 :             nextP[16][22] = P[16][22];
    1349     2057185 :             nextP[17][22] = P[17][22];
    1350     2057185 :             nextP[18][22] = P[18][22];
    1351     2057185 :             nextP[19][22] = P[19][22];
    1352     2057185 :             nextP[20][22] = P[20][22];
    1353     2057185 :             nextP[21][22] = P[21][22];
    1354     2057185 :             nextP[22][22] = P[22][22];
    1355     2057185 :             nextP[0][23] = P[0][23]*SPP[5] - P[1][23]*SPP[4] + P[2][23]*SPP[8] + P[9][23]*SPP[22] + P[12][23]*SPP[18];
    1356     2057185 :             nextP[1][23] = P[1][23]*SPP[6] - P[0][23]*SPP[2] - P[2][23]*SPP[9] + P[10][23]*SPP[22] + P[13][23]*SPP[17];
    1357     2057185 :             nextP[2][23] = P[0][23]*SPP[14] - P[1][23]*SPP[3] + P[2][23]*SPP[13] + P[11][23]*SPP[22] + P[14][23]*SPP[16];
    1358     2057185 :             nextP[3][23] = P[3][23] + P[0][23]*SPP[1] + P[1][23]*SPP[19] + P[2][23]*SPP[15] - P[15][23]*SPP[21];
    1359     2057185 :             nextP[4][23] = P[4][23] + P[15][23]*SF[22] + P[0][23]*SPP[20] + P[1][23]*SPP[12] + P[2][23]*SPP[11];
    1360     2057185 :             nextP[5][23] = P[5][23] + P[15][23]*SF[20] - P[0][23]*SPP[7] + P[1][23]*SPP[10] + P[2][23]*SPP[0];
    1361     2057185 :             nextP[6][23] = P[6][23] + P[3][23]*dt;
    1362     2057185 :             nextP[7][23] = P[7][23] + P[4][23]*dt;
    1363     2057185 :             nextP[8][23] = P[8][23] + P[5][23]*dt;
    1364     2057185 :             nextP[9][23] = P[9][23];
    1365     2057185 :             nextP[10][23] = P[10][23];
    1366     2057185 :             nextP[11][23] = P[11][23];
    1367     2057185 :             nextP[12][23] = P[12][23];
    1368     2057185 :             nextP[13][23] = P[13][23];
    1369     2057185 :             nextP[14][23] = P[14][23];
    1370     2057185 :             nextP[15][23] = P[15][23];
    1371     2057185 :             nextP[16][23] = P[16][23];
    1372     2057185 :             nextP[17][23] = P[17][23];
    1373     2057185 :             nextP[18][23] = P[18][23];
    1374     2057185 :             nextP[19][23] = P[19][23];
    1375     2057185 :             nextP[20][23] = P[20][23];
    1376     2057185 :             nextP[21][23] = P[21][23];
    1377     2057185 :             nextP[22][23] = P[22][23];
    1378     2057185 :             nextP[23][23] = P[23][23];
    1379             :         }
    1380             :     }
    1381             : 
    1382             :     // Copy upper diagonal to lower diagonal taking advantage of symmetry
    1383    73250190 :     for (uint8_t colIndex=0; colIndex<=stateIndexLim; colIndex++)
    1384             :     {
    1385   809746615 :         for (uint8_t rowIndex=0; rowIndex<colIndex; rowIndex++)
    1386             :         {
    1387   739744965 :             nextP[colIndex][rowIndex] = nextP[rowIndex][colIndex];
    1388             :         }
    1389             :     }
    1390             : 
    1391             :     // add the general state process noise variances
    1392    73250190 :     for (uint8_t i=0; i<=stateIndexLim; i++)
    1393             :     {
    1394    70001650 :         nextP[i][i] = nextP[i][i] + processNoise[i];
    1395             :     }
    1396             : 
    1397             :     // if the total position variance exceeds 1e4 (100m), then stop covariance
    1398             :     // growth by setting the predicted to the previous values
    1399             :     // This prevent an ill conditioned matrix from occurring for long periods
    1400             :     // without GPS
    1401     3248540 :     if ((P[6][6] + P[7][7]) > 1e4f)
    1402             :     {
    1403          54 :         for (uint8_t i=6; i<=7; i++)
    1404             :         {
    1405         900 :             for (uint8_t j=0; j<=stateIndexLim; j++)
    1406             :             {
    1407         864 :                 nextP[i][j] = P[i][j];
    1408         864 :                 nextP[j][i] = P[j][i];
    1409             :             }
    1410             :         }
    1411             :     }
    1412             : 
    1413             :     // copy covariances to output
    1414     3248540 :     CopyCovariances();
    1415             : 
    1416             :     // constrain diagonals to prevent ill-conditioning
    1417     3248540 :     ConstrainVariances();
    1418     3248540 : }
    1419             : 
    1420             : // zero specified range of rows in the state covariance matrix
    1421     2313418 : void NavEKF2_core::zeroRows(Matrix24 &covMat, uint8_t first, uint8_t last)
    1422             : {
    1423             :     uint8_t row;
    1424    11847739 :     for (row=first; row<=last; row++)
    1425             :     {
    1426     9534321 :         zero_range(&covMat[row][0], 0, 23);
    1427             :     }
    1428     2313418 : }
    1429             : 
    1430             : // zero specified range of columns in the state covariance matrix
    1431     2313418 : void NavEKF2_core::zeroCols(Matrix24 &covMat, uint8_t first, uint8_t last)
    1432             : {
    1433             :     uint8_t row;
    1434    57835450 :     for (row=0; row<=23; row++)
    1435             :     {
    1436    55522032 :         zero_range(&covMat[row][0], first, last);
    1437             :     }
    1438     2313418 : }
    1439             : 
    1440             : // reset the output data to the current EKF state
    1441         558 : void NavEKF2_core::StoreOutputReset()
    1442             : {
    1443         558 :     outputDataNew.quat = stateStruct.quat;
    1444         558 :     outputDataNew.velocity = stateStruct.velocity;
    1445         558 :     outputDataNew.position = stateStruct.position;
    1446             :     // write current measurement to entire table
    1447        8514 :     for (uint8_t i=0; i<imu_buffer_length; i++) {
    1448        7956 :         storedOutput[i] = outputDataNew;
    1449             :     }
    1450         558 :     outputDataDelayed = outputDataNew;
    1451             :     // reset the states for the complementary filter used to provide a vertical position dervative output
    1452         558 :     vertCompFiltState.pos = stateStruct.position.z;
    1453         558 :     vertCompFiltState.vel = stateStruct.velocity.z;
    1454         558 : }
    1455             : 
    1456             : // Reset the stored output quaternion history to current EKF state
    1457           0 : void NavEKF2_core::StoreQuatReset()
    1458             : {
    1459           0 :     outputDataNew.quat = stateStruct.quat;
    1460             :     // write current measurement to entire table
    1461           0 :     for (uint8_t i=0; i<imu_buffer_length; i++) {
    1462           0 :         storedOutput[i].quat = outputDataNew.quat;
    1463             :     }
    1464           0 :     outputDataDelayed.quat = outputDataNew.quat;
    1465           0 : }
    1466             : 
    1467             : // Rotate the stored output quaternion history through a quaternion rotation
    1468         836 : void NavEKF2_core::StoreQuatRotate(const QuaternionF &deltaQuat)
    1469             : {
    1470         836 :     outputDataNew.quat = outputDataNew.quat*deltaQuat;
    1471             :     // write current measurement to entire table
    1472       12770 :     for (uint8_t i=0; i<imu_buffer_length; i++) {
    1473       11934 :         storedOutput[i].quat = storedOutput[i].quat*deltaQuat;
    1474             :     }
    1475         836 :     outputDataDelayed.quat = outputDataDelayed.quat*deltaQuat;
    1476         836 : }
    1477             : 
    1478             : // force symmetry on the covariance matrix to prevent ill-conditioning
    1479     4426288 : void NavEKF2_core::ForceSymmetry()
    1480             : {
    1481    99920494 :     for (uint8_t i=1; i<=stateIndexLim; i++)
    1482             :     {
    1483  1192443705 :         for (uint8_t j=0; j<=i-1; j++)
    1484             :         {
    1485  1096949499 :             ftype temp = 0.5f*(P[i][j] + P[j][i]);
    1486  1096949499 :             P[i][j] = temp;
    1487  1096949499 :             P[j][i] = temp;
    1488             :         }
    1489             :     }
    1490     4426288 : }
    1491             : 
    1492             : // copy covariances across from covariance prediction calculation
    1493     3248540 : void NavEKF2_core::CopyCovariances()
    1494             : {
    1495             :     // copy predicted covariances
    1496    73250190 :     for (uint8_t i=0; i<=stateIndexLim; i++) {
    1497  1619493230 :         for (uint8_t j=0; j<=stateIndexLim; j++)
    1498             :         {
    1499  1549491580 :             P[i][j] = nextP[i][j];
    1500             :         }
    1501             :     }
    1502     3248540 : }
    1503             : 
    1504             : // constrain variances (diagonal terms) in the state covariance matrix to  prevent ill-conditioning
    1505     7674828 : void NavEKF2_core::ConstrainVariances()
    1506             : {
    1507    30699312 :     for (uint8_t i=0; i<=2; i++) P[i][i] = constrain_ftype(P[i][i],0.0f,1.0f); // attitude error
    1508    30699312 :     for (uint8_t i=3; i<=5; i++) P[i][i] = constrain_ftype(P[i][i],0.0f,1.0e3f); // velocities
    1509    23024484 :     for (uint8_t i=6; i<=7; i++) P[i][i] = constrain_ftype(P[i][i],0.0f,EK2_POSXY_STATE_LIMIT);
    1510     7674828 :     P[8][8] = constrain_ftype(P[8][8],0.0f,1.0e6f); // vertical position
    1511    30699312 :     for (uint8_t i=9; i<=11; i++) P[i][i] = constrain_ftype(P[i][i],0.0f,sq(0.175f * dtEkfAvg)); // delta angle biases
    1512     7674828 :     if (PV_AidingMode != AID_NONE) {
    1513    26323508 :         for (uint8_t i=12; i<=14; i++) P[i][i] = constrain_ftype(P[i][i],0.0f,0.01f); // delta angle scale factors
    1514             :     } else {
    1515             :         // we can't reliably estimate scale factors when there is no aiding data due to transient manoeuvre induced innovations
    1516             :         // so inhibit estimation by keeping covariance elements at zero
    1517     1093951 :         zeroRows(P,12,14);
    1518     1093951 :         zeroCols(P,12,14);
    1519             :     }
    1520     7674828 :     P[15][15] = constrain_ftype(P[15][15],0.0f,sq(10.0f * dtEkfAvg)); // delta velocity bias
    1521    30699312 :     for (uint8_t i=16; i<=18; i++) P[i][i] = constrain_ftype(P[i][i],0.0f,0.01f); // earth magnetic field
    1522    30699312 :     for (uint8_t i=19; i<=21; i++) P[i][i] = constrain_ftype(P[i][i],0.0f,0.01f); // body magnetic field
    1523    23024484 :     for (uint8_t i=22; i<=23; i++) P[i][i] = constrain_ftype(P[i][i],0.0f,1.0e3f); // wind velocity
    1524     7674828 : }
    1525             : 
    1526             : // constrain states using WMM tables and specified limit
    1527     4304607 : void NavEKF2_core::MagTableConstrain(void)
    1528             : {
    1529             :     // constrain to error from table earth field
    1530     4304607 :     ftype limit_ga = frontend->_mag_ef_limit * 0.001f;
    1531     4304607 :     stateStruct.earth_magfield.x = constrain_ftype(stateStruct.earth_magfield.x,
    1532             :                                                    table_earth_field_ga.x-limit_ga,
    1533             :                                                    table_earth_field_ga.x+limit_ga);
    1534     4304607 :     stateStruct.earth_magfield.y = constrain_ftype(stateStruct.earth_magfield.y,
    1535             :                                                    table_earth_field_ga.y-limit_ga,
    1536             :                                                    table_earth_field_ga.y+limit_ga);
    1537     4304607 :     stateStruct.earth_magfield.z = constrain_ftype(stateStruct.earth_magfield.z,
    1538             :                                                    table_earth_field_ga.z-limit_ga,
    1539             :                                                    table_earth_field_ga.z+limit_ga);
    1540     4304607 : }
    1541             : 
    1542             : // constrain states to prevent ill-conditioning
    1543     3248540 : void NavEKF2_core::ConstrainStates()
    1544             : {
    1545             :     // attitude errors are limited between +-1
    1546    12994160 :     for (uint8_t i=0; i<=2; i++) statesArray[i] = constrain_ftype(statesArray[i],-1.0f,1.0f);
    1547             :     // velocity limit 500 m/sec (could set this based on some multiple of max airspeed * EAS2TAS)
    1548    12994160 :     for (uint8_t i=3; i<=5; i++) statesArray[i] = constrain_ftype(statesArray[i],-5.0e2f,5.0e2f);
    1549             :     // position limit - TODO apply circular limit
    1550     9745620 :     for (uint8_t i=6; i<=7; i++) statesArray[i] = constrain_ftype(statesArray[i],-EK2_POSXY_STATE_LIMIT,EK2_POSXY_STATE_LIMIT);
    1551             :     // height limit covers home alt on everest through to home alt at SL and ballon drop
    1552     3248540 :     stateStruct.position.z = constrain_ftype(stateStruct.position.z,-4.0e4f,1.0e4f);
    1553             :     // gyro bias limit (this needs to be set based on manufacturers specs)
    1554    12994160 :     for (uint8_t i=9; i<=11; i++) statesArray[i] = constrain_ftype(statesArray[i],-GYRO_BIAS_LIMIT*dtEkfAvg,GYRO_BIAS_LIMIT*dtEkfAvg);
    1555             :     // gyro scale factor limit of +-5% (this needs to be set based on manufacturers specs)
    1556    12994160 :     for (uint8_t i=12; i<=14; i++) statesArray[i] = constrain_ftype(statesArray[i],0.95f,1.05f);
    1557             :     // Z accel bias limit 1.0 m/s^2     (this needs to be finalised from test data)
    1558     3248540 :     stateStruct.accel_zbias = constrain_ftype(stateStruct.accel_zbias,-1.0f*dtEkfAvg,1.0f*dtEkfAvg);
    1559             : 
    1560             :     // earth magnetic field limit
    1561     3248540 :     if (frontend->_mag_ef_limit <= 0 || !have_table_earth_field) {
    1562             :         // constrain to +/-1Ga
    1563     1023560 :         for (uint8_t i=16; i<=18; i++) statesArray[i] = constrain_ftype(statesArray[i],-1.0f,1.0f);
    1564             :     } else {
    1565             :         // use table constrain
    1566     2992650 :         MagTableConstrain();
    1567             :     }
    1568             : 
    1569             :     // body magnetic field limit
    1570    12994160 :     for (uint8_t i=19; i<=21; i++) statesArray[i] = constrain_ftype(statesArray[i],-0.5f,0.5f);
    1571             :     // wind velocity limit 100 m/s (could be based on some multiple of max airspeed * EAS2TAS) - TODO apply circular limit
    1572     9745620 :     for (uint8_t i=22; i<=23; i++) statesArray[i] = constrain_ftype(statesArray[i],-100.0f,100.0f);
    1573             :     // constrain the terrain state to be below the vehicle height unless we are using terrain as the height datum
    1574     3248540 :     if (!inhibitGndState) {
    1575     3248540 :         terrainState = MAX(terrainState, stateStruct.position.z + rngOnGnd);
    1576             :     }
    1577     3248540 : }
    1578             : 
    1579             : // calculate the NED earth spin vector in rad/sec
    1580         952 : void NavEKF2_core::calcEarthRateNED(Vector3F &omega, int32_t latitude) const
    1581             : {
    1582         952 :     ftype lat_rad = radians(latitude*1.0e-7f);
    1583         952 :     omega.x  = earthRate*cosF(lat_rad);
    1584         952 :     omega.y  = 0;
    1585         952 :     omega.z  = -earthRate*sinF(lat_rad);
    1586         952 : }
    1587             : 
    1588             : // initialise the earth magnetic field states using declination, suppled roll/pitch
    1589             : // and magnetometer measurements and return initial attitude quaternion
    1590         850 : QuaternionF NavEKF2_core::calcQuatAndFieldStates(ftype roll, ftype pitch)
    1591             : {
    1592             :     // declare local variables required to calculate initial orientation and magnetic field
    1593             :     ftype yaw;
    1594         850 :     Matrix3F Tbn;
    1595             :     Vector3F initMagNED;
    1596         850 :     QuaternionF initQuat;
    1597             : 
    1598         850 :     if (use_compass()) {
    1599             :         // calculate rotation matrix from body to NED frame
    1600         850 :         Tbn.from_euler(roll, pitch, 0.0f);
    1601             : 
    1602             :         // read the magnetometer data
    1603         850 :         readMagData();
    1604             : 
    1605             :         // rotate the magnetic field into NED axes
    1606         850 :         initMagNED = Tbn * magDataDelayed.mag;
    1607             : 
    1608             :         // calculate heading of mag field rel to body heading
    1609         850 :         ftype magHeading = atan2F(initMagNED.y, initMagNED.x);
    1610             : 
    1611             :         // get the magnetic declination
    1612         850 :         ftype magDecAng = MagDeclination();
    1613             : 
    1614             :         // calculate yaw angle rel to true north
    1615         850 :         yaw = magDecAng - magHeading;
    1616             : 
    1617             :         // calculate initial filter quaternion states using yaw from magnetometer
    1618             :         // record the reset so that it can be detected externally for use by the control loops to prevent yaw disturbances following a reset
    1619         850 :         yawResetCount++;
    1620             :         // calculate an initial quaternion using the new yaw value
    1621         850 :         initQuat.from_euler(roll, pitch, yaw);
    1622             :         // zero the attitude covariances because the corelations will now be invalid
    1623         850 :         zeroAttCovOnly();
    1624             : 
    1625             :         // calculate initial Tbn matrix and rotate Mag measurements into NED
    1626             :         // to set initial NED magnetic field states
    1627             :         // don't do this if the earth field has already been learned
    1628         850 :         if (!magFieldLearned) {
    1629         848 :             initQuat.rotation_matrix(Tbn);
    1630         848 :             if (have_table_earth_field && frontend->_mag_ef_limit > 0) {
    1631         292 :                 stateStruct.earth_magfield = table_earth_field_ga;
    1632             :             } else {
    1633         556 :                 stateStruct.earth_magfield = Tbn * magDataDelayed.mag;
    1634             :             }
    1635             : 
    1636             :             // set the NE earth magnetic field states using the published declination
    1637             :             // and set the corresponding variances and covariances
    1638         848 :             alignMagStateDeclination();
    1639             : 
    1640             :             // set the remaining variances and covariances
    1641         848 :             zeroRows(P,18,21);
    1642         848 :             zeroCols(P,18,21);
    1643         848 :             P[18][18] = sq(frontend->_magNoise);
    1644         848 :             P[19][19] = P[18][18];
    1645         848 :             P[20][20] = P[18][18];
    1646         848 :             P[21][21] = P[18][18];
    1647             : 
    1648             :         }
    1649             : 
    1650             :         // record the fact we have initialised the magnetic field states
    1651         850 :         recordMagReset();
    1652             : 
    1653             :         // clear mag state reset request
    1654         850 :         magStateResetRequest = false;
    1655             : 
    1656             :     } else {
    1657             :         // this function should not be called if there is no compass data but if is is, return the
    1658             :         // current attitude
    1659           0 :         initQuat = stateStruct.quat;
    1660             :     }
    1661             : 
    1662             :     // return attitude quaternion
    1663         850 :     return initQuat;
    1664             : }
    1665             : 
    1666             : // zero the attitude covariances, but preserve the variances
    1667         860 : void NavEKF2_core::zeroAttCovOnly()
    1668             : {
    1669             :     ftype varTemp[3];
    1670        3440 :     for (uint8_t index=0; index<=2; index++) {
    1671        2580 :         varTemp[index] = P[index][index];
    1672             :     }
    1673         860 :     zeroCols(P,0,2);
    1674         860 :     zeroRows(P,0,2);
    1675        3440 :     for (uint8_t index=0; index<=2; index++) {
    1676        2580 :         P[index][index] = varTemp[index];
    1677             :     }
    1678         860 : }
    1679             : 

Generated by: LCOV version 1.14