fix(data_augmentation): quintic kinematic consistency - #348
Draft
rej55 wants to merge 6 commits into
Draft
Conversation
… tangent interpolation_future_trajectory() derived theta0 from the chord between the perturbed position and the GT point 1 s ahead. That chord lags the true tangent by roughly half the heading change over that second, and it ignores the heading perturbation altogether, so the bridged target started off-tangent from the state the model is conditioned on. Use atan2(sin, cos) of the (perturbed) current state instead. Measured on synthetic GT with ZERO perturbation, where the interpolation should be the identity — max position error over the bridged 0-2 s window: 8 m/s, kappa=0.03 turn 0.533 m -> 0.243 m 8 m/s, kappa=0.06 turn 1.102 m -> 0.412 m 3 m/s, kappa=0.08 turn 0.200 m -> 0.091 m Straight-line cases are unaffected; the remaining error there comes from the acceleration boundary conditions and is addressed separately. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…undary conditions v0/a0/vT/aT were taken as vector magnitudes via torch.norm(). That was wrong in two ways: 1. The sign was dropped, so a braking sample was handed a positive longitudinal acceleration and the bridged target overshot forward. 2. The lateral component was folded in. ay is the centripetal term v*omega, and the quintic already expresses that through the -v0*sin(theta0)*omega0 term, so the norm counted it twice. On a constant-speed curve the true tangential acceleration is exactly 0 while ||(ax, ay)|| = v^2*kappa — 3.84 m/s^2 at 8 m/s on a 0.06 1/m curve. ego_current_state is already in the body frame, so the tangential components are a projection onto theta0. The terminal end gets the same treatment: project the first/second finite differences onto the GT heading rather than taking their magnitude. Zero-perturbation regression (the interpolation must be the identity), max position error over the bridged 0-2 s window: case before after 8 m/s straight a=-2 (decel) 0.553 m 0.031 m 8 m/s kappa=0.03 turn 0.243 m 0.030 m 8 m/s kappa=0.06 turn 0.412 m 0.065 m 3 m/s kappa=0.08 turn 0.091 m 0.011 m 2.5 m/s kappa=0.05 a=-1.5 0.232 m 0.042 m (before = with the theta0 fix already applied; against the original code the worst case was 1.102 m) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ay is the lateral (centripetal) acceleration and kinematically equals vx * yaw_rate. augment() perturbs vx by up to +-1 m/s and leaves yaw_rate untouched, but carried ay over unchanged, so the relation broke by dvx * yaw_rate. At yaw_rate 0.48 rad/s that is +-0.48 m/s^2 — nearly five times the +-0.1 m/s^2 the ay perturbation itself is allowed, i.e. the side effect of the vx perturbation dominated the noise the augmentation meant to inject. The resulting state described a vehicle whose curvature kappa = omega/v had changed while its centripetal acceleration had not. Rebuild ay from the perturbed speed and keep the sampled ay noise on top. steering_angle is already recomputed from (yaw_rate, v_new) a few lines above, so this brings ay in line with the treatment its siblings already get. v [m/s] omega dvx required ay ay before ay after 3.0 0.240 -1.0 0.480 0.720 0.480 8.0 0.240 +1.0 2.160 1.920 2.160 8.0 0.480 -1.0 3.360 3.840 3.360 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…sideslip The perturbed velocity and acceleration are body-frame quantities, but centric_transform() rotates every vector by R(-delta_heading) into the new perturbed-heading frame. Nothing compensated for that, so the velocity vector kept pointing along the OLD heading while the body turned — a sideslip of exactly delta_heading: v [m/s] delta_heading vy after transform sideslip 2.2 0.20 rad -0.437 m/s 11.5 deg 8.0 0.20 rad -1.589 m/s 11.5 deg 12.0 0.20 rad -2.384 m/s 11.5 deg A passenger car reaches a few degrees of sideslip at the limit, and Autoware reports twist.linear.y ~ 0, so this state does not occur in deployment. The same rotation also leaked longitudinal into lateral acceleration: ax = -2 m/s^2 at delta_heading = 0.2 showed up as ay = +0.40 m/s^2. Pre-rotate by R(+delta_heading) so the body-frame components survive the round trip. vy is now 0.000 m/s for every speed and heading offset tested. Note this makes the heading perturbation mean "the vehicle is yawed and moving along its own heading", which is what a lateral tracking error looks like. The +-0.5 m/s vy perturbation range is left as is — that is a tuning decision, not a consistency bug, but it is worth revisiting since it is also unreachable in deployment. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ed future The ego_past_noise_std scaling was applied to inputs["ego_current_state"] after interpolation_future_trajectory() had already consumed the unscaled state as its start boundary condition. The state therefore reported one speed while the target trajectory it is paired with started at another: scale state vx target initial speed gap 0.8 6.40 m/s 8.00 m/s -1.60 m/s 1.2 9.60 m/s 8.00 m/s +1.60 m/s That is up to +-20% of the speed, injected on half of the samples above the 2 m/s gate, and it decouples the reported speed from the speed the plan starts with. Move the scaling ahead of the interpolation and apply it to aug_ego_current_state so the state that gets written back is the same one the target was built from. The residual gap is now +-0.02 to 0.04 m/s, which is just the finite-difference discretisation of the first trajectory step. Note the intended semantics change slightly: the scaled speed now shapes the first 2 s of the target, which is the point — the sample means "the vehicle really was travelling 10% faster", so its plan should start there too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Fumiya Watanabe <fumiya.watanabe.44@gmail.com>
| scale_1d = scale.squeeze(-1) # (B_aug, 1) | ||
| inputs["ego_current_state"][aug_flag, 4:6] *= scale_1d # vx, vy | ||
| inputs["ego_current_state"][aug_flag, 6:8] *= scale_1d # ax, ay | ||
| aug_ego_current_state[aug_flag, 4:6] *= scale_1d # vx, vy |
There was a problem hiding this comment.
@rej55
I noticed that there are several problem.
- in order for directly scaling XY to match the velocity difference, there are several assumption:
- Ego past's t=0 is already in (0, 0) -- this is correct in default PR
- This is a bug that will also affect tau-augmentation from my PR
- but
self.augment(inputs)also modify the velocity with a diff, should we handle that, or just keep that discontinuity?
This was referenced Aug 18, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Fixes five kinematic inconsistencies in the quintic augmentation path
(
StatePerturbationinutils/data_augmentation.py), one per commit:theta0came from the chord to the GT point 1 s ahead, which lags the tangent and ignores the heading perturbationv0/a0/vT/aTwere vector magnitudes: sign dropped, and the centripetal term double-countedayconsistent with the perturbed speeday = vx * yaw_ratebroke bydvx * yaw_ratewhenvxwas perturbedego_past_noise_stdscaling was applied after the target had already been built from the unscaled stateaugment_typedefaults to"quintic", so this is the path actually used in training.Why this matters
interpolation_future_trajectory()was not the identity even with zeroperturbation. Feeding it a synthetic GT plus a current state that exactly matches
that GT should return the GT unchanged; instead it rewrote the first 2 s by up to
1.1 m. So the augmentation was corrupting the training target for accelerating,
braking and turning samples independently of the perturbation itself — every sample
with
augment_probhit and|vx| >= 2 m/s.The two root causes were both in how the boundary conditions were built:
torch.norm()on(ax, ay)dropped the sign, so a braking sample was handed apositive longitudinal acceleration.
ayis the centripetal termv * omega, and the quintic already expresses that through its-v0*sin(theta0)*omega0term, so it was counted twice. On a constant-speed curvethe true tangential acceleration is exactly 0 while
||(ax, ay)|| = v^2 * kappa— 3.84 m/s² at 8 m/s on a 0.06 1/m curve.
Verification
Zero-perturbation identity
Synthetic GT with a current state consistent with it, so the interpolation must be
the identity. Max position error over the bridged 0–2 s window:
Worst case 1.102 m → 0.065 m. The residual is the quintic polynomial approximating
a circular arc, which is inherent to the method.
Component consistency
ay − vx*yaw_rateat yaw_rate 0.48 rad/s, dvx = ±1End-to-end
Batch of 64 with real random sampling (53 above the 2 m/s gate), smoothing on:
ego_current_state,ego_agent_future,ego_agent_pastcentric_transformvymax 0.5324 m/s — exactly the±0.5explicit vy perturbation range, i.e. theheading-induced component is gone
|ay − vx*yaw_rate|max 0.1032 m/s² — exactly the±0.1explicit ay noise rangeTests
tests/test_data_augmentation.py: 32 failed / 26 passed before and after — nonew failures. Those 32 are pre-existing on
tier4-main: the testscall
StatePerturbation()with no arguments while the constructor requires five.Behaviour changes to be aware of
to one trained after without retraining.
ego_past_noise_stdsemantics shift slightly. The scaled speed now shapesthe first 2 s of the target, which is the point — the sample means "the vehicle
really was travelling 10% faster", so its plan should start there too. The
effective strength of that knob therefore changes.
Not in scope
Deliberately left alone, each worth a separate discussion:
±0.5 m/svyperturbation range. It is a tuning decision, not a consistencybug, but Autoware reports
twist.linear.y ~ 0so it is unreachable in deployment(12.8° of sideslip at 2.2 m/s). Worth revisiting.
|vx| >= 2.0 m/sgate that excludes the whole low-speed / standstill regime.Removing it needs a different formulation, not a different threshold: the method
spreads the lateral offset over arc length, and a stopped vehicle has none.
ego_agent_pastcoordinate transform being commented out incentric_transform.reference implementation addresses with M/N randomisation and a past-history bump.
Relation to the
devPRThe identical five commits are also proposed against
dev; this branch is the sameset cherry-picked onto
tier4-main(no conflicts, identical resulting diff).Note the two branches already differ in this file for unrelated reasons, and this PR
touches neither:
neighbors_futurebranch incentric_transform— present ontier4-main, absent ondev🤖 Generated with Claude Code