Fix predict_to_current_time forward extrapolation and Omnidirectional3D jacobian#35
Fix predict_to_current_time forward extrapolation and Omnidirectional3D jacobian#35bkanator wants to merge 2 commits into
Conversation
34e3ace to
66fd9bb
Compare
|
Updated to address review:
Cross-checked: the 2D publisher and unicycle path don't need either fix (2D |
66fd9bb to
98c7ec1
Compare
griswaldbrooks
left a comment
There was a problem hiding this comment.
Looks good. How hard is it to stand up a test for fuse_models::predict? Looks like it's just a function?
The forward prediction delta was computed as std::min(to_predict_to - stamp, 0.0), which is <= 0 in normal forward operation, forcing dt == 0 and silently disabling predict_to_current_time: the publisher emitted the stale latest-optimized pose stamped as "now". This manifested as a turn-rate-proportional yaw lag (~150 ms; up to ~4.4 deg at 30 deg/s on a mecanum base) that no configuration could fix. Regressed in #24. Clamp the delta to be non-negative (std::max, matching the 2D publisher) so forward prediction is preserved while backward prediction is still prevented. Extract the delta into detail::forwardPredictionDt() and add a regression test that would have caught this. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The assembled state jacobian placed J[1] (d(state)/d(quaternion), 15x4) directly into the orientation columns and truncated the acceleration block (J[4].block<15,2>), leaving Ceres an incorrect orientation gradient during 3D rotation. Convert J[1] to RPY space (15x3) via the pseudo-inverse of the quat->rpy jacobian (chain rule). A rank-revealing CompleteOrthogonalDecomposition is used so it degrades gracefully at gimbal lock, where quaternion2rpy zeros rows of the quat->rpy jacobian and an explicit (A*A^T)^-1 would NaN. Add a predictJacobians test that compares the analytic 15x15 jacobian against ceres::Jet autodiff; it fails against the prior truncated assembly and passes with this fix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
98c7ec1 to
ac553fc
Compare
|
[written by AI] Good call — added it. It computes the analytic 15×15 state jacobian via the Verified it's a real guard, not just green: it fails against the prior truncated |
Summary
Two fixes to the 3D omnidirectional estimation path, found while debugging a turn-rate-proportional yaw lag on a mecanum base (MoveIt Pro hangar_sim).
1.
fix(odometry_3d): restorepredict_to_current_timeforward extrapolationOdometry3DPublisher::predict()computed the forward delta asstd::min(to_predict_to - stamp, 0.0). Sinceto_predict_to(now) is always ≥ the latest optimizedstamp, this forcesdt == 0and silently disablespredict_to_current_time: the publisher emits the stale latest-optimized pose stamped as "now". Regressed in #24 (a 2‑line change riding along in an unrelated multi-camera PR); the 2D publisher does it correctly.Fix:
std::min→std::max(clamp to non-negative — preserve forward prediction, still prevent backward). Extracted intodetail::forwardPredictionDt()with a regression test.2.
fix(omnidirectional_3d): compute prediction jacobian in RPY spaceThe assembled state jacobian used
jacobian << J[0], J[1], J[2], J[3], J[4].block<15,2>(0,0)— placing the quaternion-spaceJ[1](15×4) into the orientation columns and truncating the acceleration block, giving Ceres an incorrect orientation gradient during 3D rotation. (This was theTODO(henrygerardmoore): figure out how to fix thisreferencing locusrobotics#354.) ConvertsJ[1]to RPY space (15×3) via the pseudo-inverse of the quat→RPY jacobian (chain rule). Covered by the existing analytic-vs-autodiff comparison intest_omnidirectional_3d_state_cost_function.Measured impact (fuse
/odom_filteredyaw error vs. ground truth, mecanum spins)The lag was linear in turn rate (a ~150 ms constant delay); the fix flattens it to <1° at all turn rates.
predict_to_current_timeon/off was verified to make no difference before the fix (extrapolation was inert) and to work after.Tests
test_prediction_time(regression for thepredict_to_current_timedelta).test_omnidirectional_3d_state_cost_functionanalytic-vs-autodiff jacobian comparison passes with the jacobian fix.fuse_modelsbuilds and all touched tests pass on Humble.Notes
ros-humble-fuseandros-jazzy-fuseapt packages build from thishumblebranch, so this fixes both distros.