Skip to content

AP_NavEKF3: keep the covariance consistent for states flow leaves out - #34933

Open
andyp1per wants to merge 1 commit into
ArduPilot:masterfrom
andyp1per:pr-ekf3-flow-mask-covariance
Open

andyp1per wants to merge 1 commit into
ArduPilot:masterfrom
andyp1per:pr-ekf3-flow-mask-covariance

Conversation

@andyp1per

Copy link
Copy Markdown
Contributor

Summary

Fixes #34932. Optical flow fusion zeroes the gain of the states it leaves out, the Z gyro bias among them while no yaw is fused (#33498), but applied only half of their covariance correction. That can leave P no longer positive definite, after which EKF3 refuses later fusions without raising a fault: a hand-launched flow copter stopped fusing baro and climbed in altitude hold with the stick fully down. This applies the full, Joseph form, correction for those states.

Classification & Testing (check all that apply and add your own)

  • Checked by a human programmer
  • Tested manually, description below (e.g. SITL)
  • Logs available on request

Replay of the hand-launch log from #34932 through master's EKF: before, P stops being positive definite at the first flow fusion and 669 fusions are refused before the vehicle comes down, 2460 by the end of the log. After, none are refused and P stays positive definite throughout. On the build that flew, the same change keeps every baro height fusion (222 of 225 were refused) and keeps the attitude level after the crash instead of drifting 2.5 deg/s on frozen gyro biases.

SITL, FlowGyroZBiasNoYawReference with an LDL^T probe of P after every flow fusion, the measurement AP-Review made on #33498. Flow updates that left P indefinite, core 0:

case master this PR
no yaw source 32 / 1839 0 / 1819
compass lost 373 / 1818 8 / 1836
GPS yaw lost 173 / 1860 2 / 1838

The 8 match the 9 AP-Review measured before #33498; the 2 are rounding at 1e-19 of the largest variance. The test passes on both with the same max |GZ| (0 to 0.01 deg/s), so flow still teaches the Z gyro bias nothing. FlowAidingRestartsWithoutYawFusion, OpticalFlow, OpticalFlowLimits, OpticalFlowLocation, OpticalFlowCalibration, EK3_AccelBiasZeroVelOptFlow, ModeFlowHold and LoiterFlowBrakeOvershoot pass. No autotest is added: none reproduces the flight's failure, for the reasons in #34932, and a passing one would not discriminate.

Description

FuseOptFlow() builds KHP for every state. A state that kalman_mask leaves out has a zero row there, while its column holds the correction to its covariance with the other states, and FinishFusion() averages KHP with its transpose, so those cross-covariances got half. Copying each masked state's column into its row gives the Joseph form for the gain actually used, which is the standard Schmidt (consider state) update. The masked state's own estimate and variance are unchanged, as before. This fixes a regression from #33498, so it is not behind an option.

test.Copter.Replay passes.

Detail for reviewers, not needed to review the change in outline:

  • Until AP_NavEKF3: inhibit Z gyro bias from optical flow with no yaw reference #33498 nothing the flow mask left out kept live cross-covariances on a flow-only vehicle. Magnetic field, wind, fully inhibited gyro biases and inhibited accel bias axes are decorrelated every step by ConstrainVariances() or CovariancePrediction(), so for them this changes nothing. The accel biases during bad IMU data keep theirs, but badIMUdata is only set while GPS vertical velocity is fused; they now get the same consistent update.
  • Why flow is the case that bites. With one observed state, one masked state at correlation rho, and x the share of the observed variance a fusion removes, the half-corrected update stays positive definite only while rho^2 < (1-x)/(1-x/2)^2: rho < 0.28 at x = 0.98, rho < 0.999 at x = 0.1. The Joseph form leaves rho^2(1-x). Flow close to the ground on an unaided filter is the high-x case.
  • The same short-form update follows other gain masks: FuseVelPosNED()'s AID_NONE poorObservability and horizInhibit masks, the range beacon height masks and wind-only airspeed fusion. Those observations are weak or the states decorrelated, so they are left for a separate change rather than moving this fix into FinishFusion(), where it would also change Plane airspeed and beacon behaviour.
  • Decorrelating state 12 before each flow fusion would also keep P valid, but it throws away the yaw to bias correlation that the static yaw fusion on the ground uses to learn the bias. Zeroing both the row and the column, which AP-Review tried on AP_NavEKF3: inhibit Z gyro bias from optical flow with no yaw reference #33498, is far worse.

Optical flow fusion leaves some states out of its Kalman gain. Their
covariance with the other states still has to be corrected, and K*H*P
holds that correction in their column only, so symmetrising the update
applies half of it and can leave P no longer positive definite. After
that the check against negative variances refuses fusions with no fault
raised: sensors stop being used while the filter reports itself healthy.

Give each masked state's row of K*H*P its column, so the update is the
Joseph form for the gain applied. The masked states' own estimates and
variances are unchanged.

This fixes a regression from ArduPilot#33498, which made the Z gyro bias one of
those states while no yaw is fused. Until then the only masked state
with live cross-covariances was the accel bias during bad IMU data,
which needs GPS vertical velocity, so a flow-only vehicle never had
one. The other masked states are inhibited outright and decorrelated
every step, which this leaves as it was.

Replay of a flow-only Copter with no compass, launched by hand: the
first flow fusion, 3 cm above the hand with the filter unaided, broke
positive definiteness, and 669 fusions were refused before the vehicle
came down, 2460 by the end of the log. With this change none were, and
P stayed positive definite throughout. In SITL, an LDL^T probe after
each flow fusion in FlowGyroZBiasNoYawReference counted 32, 373 and 173
updates leaving P indefinite in its three cases before, 0, 8 and 2
after.
@github-actions

Copy link
Copy Markdown

Global Size Summary (Total Flash delta in bytes)

Built from 47d399e rebased onto 5b36047

Board Periph tracker blimp bootloader copter heli iofw plane rover sub
DrotekP3Pro _ _ _ * _ _ _ _ _ _
Durandal _ +80 +80 _ +80 +80 _ +80 +80 +80
Hitec-Airspeed * _ _ _ _ _ _ _ _ _
KakuteF7 _ * * _ * * _ * * *
MambaH743v4 _ +100 +100 _ +100 +100 _ +100 +92 +100
MatekF405-CAN _ _ _ * _ _ _ _ _ _
MatekF405 _ * * _ * * _ * * *
MatekH743-bdshot _ +80 +80 _ +80 +80 _ +80 +80 +80
Pixhawk1-1M _ +88 +88 _ +88 +88 _ +88 +84 +88
disco _ +80 +80 _ +88 +88 _ +88 +98 +96
f103-GPS * _ _ _ _ _ _ _ _ _

@AP-Review

Copy link
Copy Markdown

Automated review note — AI-generated (Claude+Codex), validated against the live diff. Please sanity-check before acting.
Verdict: ACCEPT

Reviewed at head 47d399e569.
Full report: https://firmware.ardupilot.org/Tools/APReview/DevCallReviews/PRReviews/ardupilot/ardupilot/34933/1.html#pr34933

No problems found: the row copy makes the flow covariance update match the Joseph form for states with zero gain, and both the algebra and SITL probes support it.

The change (OptFlowFusion.cpp:360, OptFlowFusion.cpp:722-730) was checked against the full covariance equation over random covariances, in two extracted-code harnesses (one with a control that fails without the copy), and with a SITL positive-definiteness probe. Indefinite flow updates on core 0 dropped from 28/348/303 to 0/8/2. The flow autotests pass at head.

Not independently confirmed: the replay of the flight log from #34932, and the cause of the red sitltest-quadplane-tests1c CI job. That bucket runs no optical flow tests and looks like a known flaky job.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EKF3: masking the Z gyro bias out of optical flow fusion can stop baro fusion with no fault

2 participants