MeasurementModel has difference and combine so that an angular measurement
wraps correctly. There is no equivalent for the state, and the state side has
exactly the same problem.
Where it breaks today:
sigma::weighted_mean over state points is a plain weighted sum, so the mean of
sigma points either side of a wrap is wrong.
Gaussian::mean + correction in Ukf::update assumes a vector space, so the
corrected state can leave the manifold.
ScaledUnscented::generate offsets the mean by Cholesky columns additively,
which is the same assumption again.
A filter with a heading, an attitude quaternion or an MRP in its state is
therefore silently wrong across the wrap, and nothing warns about it. The library
does not currently say this anywhere, which is the immediate fix.
The standard treatment is a manifold (boxplus/boxminus) UKF: the state carries oplus(x, delta) and ominus(a, b) operations between the manifold and a local
tangent vector, the sigma points are generated in the tangent space, the mean is
found by an iterative Karcher-mean loop rather than a weighted sum, and the
covariance lives in the tangent space throughout.
Shape of the work:
Document the limitation now, in ProcessModel and in the crate docs. Cheap and
it stops someone trusting it.
A StateSpace<N> trait (or two provided methods on ProcessModel, mirroring
the measurement side) with oplus, ominus and a combine. A Euclidean
default impl keeps every existing caller unchanged.
sigma, Ukf::update and Imm's mixing all route their state arithmetic
through it. ConsiderUkf too, which is more awkward because its joint state
may be part manifold and part not.
A test on a 2D pose (position plus heading) crossing the wrap, built the same
way as angle_wrapping_is_what_makes_a_bearing_filter_work in tests/nonlinear.rs — which is worth reading first, because the first version
of that test crossed nothing and passed anyway.
Step 2 is where the design risk is: an iterative mean inside the sigma-point
reconstruction changes the cost of every step, so it wants a measurement before
it is made unconditional.
`MeasurementModel` has `difference` and `combine` so that an angular *measurement*
wraps correctly. There is no equivalent for the *state*, and the state side has
exactly the same problem.
Where it breaks today:
- `sigma::weighted_mean` over state points is a plain weighted sum, so the mean of
sigma points either side of a wrap is wrong.
- `Gaussian::mean + correction` in `Ukf::update` assumes a vector space, so the
corrected state can leave the manifold.
- `ScaledUnscented::generate` offsets the mean by Cholesky columns additively,
which is the same assumption again.
A filter with a heading, an attitude quaternion or an MRP in its state is
therefore silently wrong across the wrap, and nothing warns about it. The library
does not currently say this anywhere, which is the immediate fix.
The standard treatment is a manifold (boxplus/boxminus) UKF: the state carries
`oplus(x, delta)` and `ominus(a, b)` operations between the manifold and a local
tangent vector, the sigma points are generated in the tangent space, the mean is
found by an iterative Karcher-mean loop rather than a weighted sum, and the
covariance lives in the tangent space throughout.
Shape of the work:
1. Document the limitation now, in `ProcessModel` and in the crate docs. Cheap and
it stops someone trusting it.
2. A `StateSpace<N>` trait (or two provided methods on `ProcessModel`, mirroring
the measurement side) with `oplus`, `ominus` and a `combine`. A Euclidean
default impl keeps every existing caller unchanged.
3. `sigma`, `Ukf::update` and `Imm`'s mixing all route their state arithmetic
through it. `ConsiderUkf` too, which is more awkward because its joint state
may be part manifold and part not.
4. A test on a 2D pose (position plus heading) crossing the wrap, built the same
way as `angle_wrapping_is_what_makes_a_bearing_filter_work` in
`tests/nonlinear.rs` — which is worth reading first, because the first version
of that test crossed nothing and passed anyway.
Step 2 is where the design risk is: an iterative mean inside the sigma-point
reconstruction changes the cost of every step, so it wants a measurement before
it is made unconditional.
connor
added the size/L label 2026-10-03 18:59:55 -04:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
MeasurementModelhasdifferenceandcombineso that an angular measurementwraps correctly. There is no equivalent for the state, and the state side has
exactly the same problem.
Where it breaks today:
sigma::weighted_meanover state points is a plain weighted sum, so the mean ofsigma points either side of a wrap is wrong.
Gaussian::mean + correctioninUkf::updateassumes a vector space, so thecorrected state can leave the manifold.
ScaledUnscented::generateoffsets the mean by Cholesky columns additively,which is the same assumption again.
A filter with a heading, an attitude quaternion or an MRP in its state is
therefore silently wrong across the wrap, and nothing warns about it. The library
does not currently say this anywhere, which is the immediate fix.
The standard treatment is a manifold (boxplus/boxminus) UKF: the state carries
oplus(x, delta)andominus(a, b)operations between the manifold and a localtangent vector, the sigma points are generated in the tangent space, the mean is
found by an iterative Karcher-mean loop rather than a weighted sum, and the
covariance lives in the tangent space throughout.
Shape of the work:
ProcessModeland in the crate docs. Cheap andit stops someone trusting it.
StateSpace<N>trait (or two provided methods onProcessModel, mirroringthe measurement side) with
oplus,ominusand acombine. A Euclideandefault impl keeps every existing caller unchanged.
sigma,Ukf::updateandImm's mixing all route their state arithmeticthrough it.
ConsiderUkftoo, which is more awkward because its joint statemay be part manifold and part not.
way as
angle_wrapping_is_what_makes_a_bearing_filter_workintests/nonlinear.rs— which is worth reading first, because the first versionof that test crossed nothing and passed anyway.
Step 2 is where the design risk is: an iterative mean inside the sigma-point
reconstruction changes the cost of every step, so it wants a measurement before
it is made unconditional.