Drop a held tail instead of appending a duplicate end knot - #726
Open
Yuan-Xinyi wants to merge 1 commit into
Open
Yuan-Xinyi wants to merge 1 commit into
Yuan-Xinyi wants to merge 1 commit into
Conversation
Waypoint cleanup merged samples within 1e-6, then appended the final sample
whenever it had been merged. A path ending in repeated samples therefore kept
a zero-length last segment: the not-a-knot fit bends back through it, and
because the knot count changes, every knot's uniform spacing changes too.
That tail is what a batched NMG rollout produces for an environment that
converges early and holds its pose while the others finish. Retiming one such
rollout alone and with four held samples appended:
before 0.9933 s -> 1.0553 s (+6.2%), with a reversal before the goal
after identical duration and samples
so an environment's trajectory depended on which environments shared its
batch. The simplest case shows the reversal directly: [0.3, 0.3, 0.5, 0.5]
kept three knots and the fitted parabola overshot 0.5 to 0.525 before
returning; it now keeps two and moves monotonically.
When the tail is merged, move the last kept knot onto the final sample instead
of appending it, in both the NumPy reference and the Warp preparation kernel.
A lone first point still gains the final sample when they differ at all, so a
tiny move keeps a positive duration, and an all-identical row stays stationary.
The test that fixed the old knot list is updated for the one case it covered,
and new tests check that a held tail leaves the duration, samples and path
unchanged at the reference, Warp and planner levels, and that the tail no
longer reverses. They fail before this change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
This was referenced Sep 29, 2026
This branch has not been deployed
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.
Stack
feat/differentiable-toppra(Replace external TOPPRA with differentiable CPU/CUDA retiming #724)Description
#724 keeps the previous waypoint cleanup exactly, including its final step: when the last sample was merged as a duplicate, it is appended again. A path that ends in repeated samples therefore keeps a zero-length last segment. The not-a-knot spline bends back through it, and because the knot count changes, every knot's uniform spacing changes too — the whole curve moves, not just the tail.
That tail is exactly what a batched NMG rollout produces: an environment that converges early holds its pose while the others finish. With the current cleanup, an environment's retimed trajectory depends on which other environments share its batch.
This PR moves the last kept knot onto the final sample instead of appending it, in both the NumPy reference (
_toppra.py) and the Warp preparation kernel (_warp/toppra.py), so the two stay identical. Two edge cases are kept deliberately:Dependencies: none.
Type of change
Measured evidence
A real NMG rollout (7 samples, Franka FR3 limits), retimed alone and with four held samples appended, on #724 as-is and with this change:
NumPy and Warp agree to 1e-15 in both cases.
The one-joint case
[0.3, 0.3, 0.5, 0.5]shows the mechanism directly. #724 keeps three knots; the not-a-knot fit through them is a parabola that overshoots the goal to 0.525 before returning. With this change it keeps two and moves monotonically to 0.5.Measured before #724 with the external
topprapackage, which had the same cleanup: dashed is the same problem inside a batch, solid is the problem alone. After about 0.75 s the batched trajectory departs, gains an extra reversal before the goal, and ends 60 ms later. #724 reproduces the same knot list, so it reproduces this.Validation
pytest tests/compute/test_toppra.py tests/sim/motion/planners/test_toppra_batched.py --run-gpu— 97 passed.pytest tests/compute/ tests/sim/motion/— 1151 passed, 111 skipped.[0.3, 0.3, 0.5, 0.5]case no longer reverses. Four of them fail on Replace external TOPPRA with differentiable CPU/CUDA retiming #724 as-is.test_legacy_waypoint_cleanup_preserves_spline_geometryfixed the old knot list; its held-tail case now expects[0, 3]instead of[0, 2, 3], it gains a move-then-hold case, and it is renamed for what it now checks. The other cases are unchanged.black .— clean.check_api_docs.py— aligned, no public API change.context.py check— ok;planner-details.mdno longer says the final knot is preserved and states the held-tail rule instead.Checklist
black .command to format the code base.python docs/scripts/check_api_docs.py), if applicable🤖 Generated with Claude Code