pub enum Fusion {
Accepted {
test_ratio: f32,
},
Reset,
Rejected {
test_ratio: f32,
},
NotInitialized,
NoReference,
Unobservable,
NotFinite,
InvalidNoise,
StateInvalid,
OutOfHorizon {
age: Seconds,
},
}Expand description
The outcome of one measurement update.
Carries the dimensionless test ratio r = ε / γ of equation (38) in both the accepted
and rejected cases, so a rejection is diagnosable rather than a bare failure. r > 1
means rejected, whatever the dimension of the observation.
Not #[must_use], unlike Propagation and the reset_* outcomes, because acceptance
and rejection reach the caller by a second route: Diagnostics keeps the latest test
ratio, the running counts and the timer for every source, so a loop that reads
Eskf::state each cycle loses nothing by discarding this value.
The refusals keep their own counters there rather than a lint here:
SourceHealth::refused and SourceHealth::last_refusal say how often a source was
turned away and why, and SourceHealth::adopted separates a Reset from
the ordinary acceptance it otherwise looks like. A refusal still moves no timer — it is not
aiding, and pretending otherwise would let a stream of NaN hold off
Status::DeadReckoning.
Variants§
Accepted
The measurement passed the gate and was fused.
Reset
The measurement was adopted outright rather than fused, because the filter had no estimate of that quantity to fuse it against, or none the gate would let it correct.
The second is recovery from gate lockout: a source whose measurements have been
rejected for longer than Recovery allows has the next one adopted,
counted in SourceHealth::recovered. For the barometer the adoption is of its
reference α₀ rather than of any state, so nothing the estimate reports steps.
The first happens once per quantity, and for three of them. Position and velocity are
adopted on the first GNSS fix after a coarse start: a vehicle that initialized
while moving knows neither where it is nor how fast it is going, and no gate can
judge a measurement against nothing. A static start and a seed both have an
estimate of those two, so their first fix is fused normally, or, given as latitude
and longitude, places the origin under that estimate; see
Eskf::fuse_gnss_geodetic.
Heading is the third, and it is not a coarse start’s alone: stillness observes
tilt and never yaw, so a static window carrying no magnetometer leaves yaw a
prior nothing measured, and the first heading from
fuse_mag_heading,
fuse_gnss_heading or
fuse_course is adopted there too. Only a seed escapes,
having vouched for every quantity.
The state becomes the measurement and its covariance block becomes the measurement’s, which is what fusing against an infinitely uncertain prior converges to — the limit, taken exactly rather than approached with an invented variance.
Worth reacting to: position, velocity or attitude stepped, which a controller consuming the estimate may care about. Heading is the largest of the three — an adopted heading can turn the estimate by half a circle, where an adopted position moves a quantity no controller was flying on.
Rejected
The measurement failed the gate and was discarded. The state is unchanged.
NotInitialized
No initialization has succeeded — Eskf::initialize,
initialize_coarse or initialize_from — so there is no state to fuse against. The measurement was discarded.
NoReference
The measurement has nothing to be relative to. The measurement was discarded.
Barometer: no reference altitude α₀ is held, and position is not yet established to
read one against — a start in motion before its first fix — or
Config::baro_reference_from_estimate
is off. Fusing it anyway would invent the origin from whichever sample happened to
arrive first. Eskf::set_baro_reference names one.
Geodetic GNSS: no navigation origin is held and this fix cannot place one, because
its latitude is beyond ±90°. The next usable fix will. See
Eskf::fuse_gnss_geodetic.
Course: no GNSS velocity has been accepted within its own
timeout, so the velocity the
course would be taken along is dead-reckoned or was never established. Usually a call
made before the first Eskf::fuse_gnss_velocity, or
after the receiver stopped; see Eskf::fuse_course.
Unobservable
The geometry says nothing about the quantity measured. The measurement was discarded and no health timer moved.
A heading from Eskf::fuse_gnss_heading or
Eskf::fuse_course, in two ways. The vehicle’s forward axis
is within 30° of vertical, where the heading of that axis is undefined, and a tailsitter
hovering is the case: PX4 refuses a GNSS yaw reset on the same bar
(EKF/aid_sources/gnss/gnss_yaw_control.cpp:221 at c4e4ef98). Or, for the course, the
vehicle is not moving fast enough for the direction of its estimated velocity to mean
anything: a cross-track uncertainty over 15° of course, which is a hover or a slow taxi.
Fusing either would hand the gate an angle drawn from noise.
Not the magnetometer’s: (34) levels the field rather than reading the heading of body
x, and (36′) prices the leveling at any tilt, so a magnetic heading is fused through
125° of tilt on 285ee2e7.
NotFinite
A number in the measurement or its noise is NaN or infinite. The measurement was discarded and no health timer moved.
Refused before anything else, including the adoption of a quantity initialization left unestablished: one NaN in the state or covariance spreads to every quantity at the next update and never leaves.
InvalidNoise
A variance in the measurement noise is zero or negative. The measurement was discarded and no health timer moved.
R has to be a variance some sensor could have. Zero makes the innovation
covariance S = H P Hᵀ + R of equation (24) singular as soon as the state it
observes is itself certain, and a negative one is worse: it claims a measurement
better than perfect, and where the measurement is adopted outright it writes that
negative variance straight into P, which
Validity then reads as an excellent estimate.
Usually a floor or a unit mistake at the boundary — a receiver reporting eph = 0
while it has no fix, or a variance arrived at by subtracting one σ² from another.
Refused alongside NotFinite, before any adoption.
One bad component refuses the measurement it belongs to. A GNSS fix is two, so a
receiver in 2D-fix mode reporting a good eph with epv = 0 keeps its horizontal
aiding and has its height refused; see GnssFusion. The filter reports rather than
repairs: PX4 and ArduPilot clamp such a value into range, and a caller that wants that
behavior floors noise before the call, where the policy is visible. See
Eskf::fuse_gnss_position.
StateInvalid
The filter’s own numbers could not support an update, so nothing was committed. The state and covariance are unchanged, no health timer moved, and the measurement is not at fault.
Two ways to get here, one meaning. The innovation covariance S = H P Hᵀ + R of
equation (24) was not positive-definite, which with a positive R means P itself had
stopped being a covariance; or the correction of (26) and (39)–(41) overflowed f32 on
finite inputs, as (11)–(14) can. Either way a caller learns the same thing: the
estimate, not the sensor, is what needs attention.
State-qualified by the rule Propagation::StateNotFinite follows, and not merged
into it: NotFinite is always the input handed in, and this is what
the filter produced. Kept out of Rejected because the gate never gave
a verdict — a rejection says the measurement disagreed with a sound estimate, and
counting this as one would put a filter fault in the column that times out a sensor.
OutOfHorizon
The measurement’s time is one the filter cannot place it at: older than
LATENCY_HORIZON, later than the state by more than
Config::max_predict_dt, or before a start that did not
show the vehicle at rest, whose motion before it nothing describes. The measurement was
discarded and no health timer moved.
Inside those bounds the measurement is fused at its own time, equation (23′): against the state as it was, or, for one timed between the last IMU sample and the next, as it will be, carried forward on the estimated velocity and the last sample’s rate.
Fused anyway, an old measurement is a statement about where the vehicle was, applied to
where it is: at 20 m/s a fix a second old is 20 m of error handed to the gate as truth.
One later than the state is a clock the IMU and the sensor do not share, or a sample
the caller has not yet handed to predict. Either way it is the
timestamp that is wrong, which is a refusal rather than a verdict on the value.
Implementations§
Source§impl Fusion
impl Fusion
Sourcepub const fn is_accepted(self) -> bool
pub const fn is_accepted(self) -> bool
Whether the filter took the measurement, whether by fusing it or by adopting it
outright. See is_reset to tell those apart.
Sourcepub const fn is_reset(self) -> bool
pub const fn is_reset(self) -> bool
Whether the measurement replaced the estimate rather than correcting it, stepping the state.
Sourcepub const fn refusal(self) -> Option<Refusal>
pub const fn refusal(self) -> Option<Refusal>
Why the measurement was turned away, or None if it reached the gate.
Reset is not a refusal: the measurement was taken, just adopted rather
than fused.
Sourcepub const fn test_ratio(self) -> Option<f32>
pub const fn test_ratio(self) -> Option<f32>
The test ratio, where the gate ran at all.
Trait Implementations§
impl Copy for Fusion
impl StructuralPartialEq for Fusion
Auto Trait Implementations§
impl Freeze for Fusion
impl RefUnwindSafe for Fusion
impl Send for Fusion
impl Sync for Fusion
impl Unpin for Fusion
impl UnsafeUnpin for Fusion
impl UnwindSafe for Fusion
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> Scalar for T
Source§impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
Source§fn to_subset(&self) -> Option<SS>
fn to_subset(&self) -> Option<SS>
self from the equivalent element of its
superset. Read moreSource§fn is_in_subset(&self) -> bool
fn is_in_subset(&self) -> bool
self is actually part of its subset T (and can be converted to it).Source§fn to_subset_unchecked(&self) -> SS
fn to_subset_unchecked(&self) -> SS
self.to_subset but without any property checks. Always succeeds.Source§fn from_subset(element: &SS) -> SP
fn from_subset(element: &SS) -> SP
self to the equivalent element of its superset.