Let NeuralPlanner retime with the differentiable TOPP - #725
Yuan-Xinyi wants to merge 1 commit into
Conversation
`NeuralPlannerCfg.constraints` retimes the rollout with the `toppra` library, one environment at a time on the CPU. `compute.trajectory.retime_time_optimal` solves the same problem batched on the planner's device and differentiably. - Add `NeuralPlannerCfg.retime_backend`, `toppra` by default so existing behavior is unchanged, or `differentiable`. An unknown value is rejected at construction rather than at the first constrained plan. - Scalar limits apply to every joint the policy drives, and a per-joint limit fixes how many joints are retimed; control-part joints beyond those are held. - Forward `retime_backend` from the benchmark's NMG adapter. Measured with a real NMG checkpoint on Franka FR3, the two backends produce the same trajectories -- same validity, zero limit violations, durations within a millisecond -- except in one case: in a batch where an environment converges early and holds its pose, `toppra` lengthened that environment's motion by 5-6% relative to planning it alone, because its deduplication keeps a trailing duplicate knot. The differentiable backend leaves it unchanged. The retiming step itself goes from 506 ms to 9 ms for a batch of eight. An earlier version inferred the joint count from the limits' length, so scalar limits retimed only joint 0 while the rest jumped to their final values at t = 0. A unit test passed anyway because it checked only limits and shapes; it now checks each joint's start and end against the rollout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
| joints = ( | ||
| max( | ||
| velocity.numel() if velocity.dim() else 0, | ||
| acceleration.numel() if acceleration.dim() else 0, | ||
| ) | ||
| or self._action_dim |
There was a problem hiding this comment.
Short limits cause joint jumps When a per-joint limit list has fewer than seven entries, the policy can still move all seven joints during the rollout, but this code retimes only the joints covered by the list. It then holds each remaining joint at its final rollout position from the first output sample and reports zero velocity for it. With six limits, joint 7 therefore jumps to its endpoint at time zero instead of following the planned path. Reject limit lists that do not cover every policy-driven joint.
Knowledge Base Used:
Prompt To Fix With AI
This is a comment left during a code review.
Path: embodichain/lab/sim/motion/planners/neural_planner.py
Line: 765-770
Comment:
**Short limits cause joint jumps** When a per-joint limit list has fewer than seven entries, the policy can still move all seven joints during the rollout, but this code retimes only the joints covered by the list. It then holds each remaining joint at its final rollout position from the first output sample and reports zero velocity for it. With six limits, joint 7 therefore jumps to its endpoint at time zero instead of following the planned path. Reject limit lists that do not cover every policy-driven joint.
**Knowledge Base Used:**
- [Motion planning and kinematics](https://app.greptile.com/dexforce/-/custom-context/knowledge-base/dexforce/embodichain/-/docs/motion-planning-and-kinematics.md)
- [Trajectory timing and velocity tracking](https://app.greptile.com/dexforce/-/custom-context/knowledge-base/dexforce/embodichain/-/docs/trajectory-timing-and-tracking.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| @pytest.mark.parametrize("backend", ["toppra", "differentiable"]) | ||
| def test_neural_planner_retime_backends_bound_dynamics(tmp_path, monkeypatch, backend): | ||
| retimed = _rollout( | ||
| tmp_path, | ||
| monkeypatch, | ||
| policy=SaturatingOnnxPolicy, | ||
| constraints={"velocity": 1.0, "acceleration": 2.0}, | ||
| retime_backend=backend, |
There was a problem hiding this comment.
Key backend cases untested These backend tests use only scalar limits and a single-environment rollout. Add a per-joint-limit case and a constrained batch where one environment finishes early. Without those cases, tests will not catch regressions in the new joint-counting logic or the held-tail behavior that distinguishes this backend.
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/sim/motion/planners/test_neural_planner.py
Line: 510-517
Comment:
**Key backend cases untested** These backend tests use only scalar limits and a single-environment rollout. Add a per-joint-limit case and a constrained batch where one environment finishes early. Without those cases, tests will not catch regressions in the new joint-counting logic or the held-tail behavior that distinguishes this backend.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
|
Closing: this switch selected between |
Stack
xinyi/differentiable-toppDescription
NeuralPlannerCfg.constraints(#714) retimes the NMG rollout with thetoppralibrary, one environment at a time on the CPU. #723 addscompute.trajectory.retime_time_optimal, which solves the same time-optimal problem batched on the planner's device and differentiably. This PR lets the planner choose between them.NeuralPlannerCfg.retime_backend:toppra(default, so existing behavior is unchanged) ordifferentiable. An unknown value is rejected at construction rather than at the first constrained plan.retime_backend.Nothing is trained through the solver yet. This only replaces the forward computation; using its gradients in NMG training is a separate change.
Dependencies: none.
Refs #684
Type of change
Measured evidence
Real NMG checkpoint (
swa3.onnx, K=5) on Franka FR3.Same trajectories, less time
smokesuite, 9 free-space cases at W=1/3/5, 3 measured trials, both batch sizes:retime_backend: toppraretime_backend: differentiableValidity, violations and limit utilization are identical and durations agree within a millisecond. The differentiable backend adds almost nothing over the unretimed rollout;
toppragrows with the batch because it solves each environment separately.The benchmark's B=8 cases are one problem replicated eight times — a throughput measurement — so every environment converges on the same step. That hides the one place the backends differ, which needs a batch of different problems.
The backends differ when an environment finishes early
Eight environments holding three distinct problems, each planned alone and then inside the batch:
toppraalone → in batchdifferentiablealone → in batchSolid: planned alone. Dashed: the same problem inside the batch. With
topprathe batched trajectory departs after about 0.75 s, joint 7 gains an extra reversal before the goal, and the motion ends 60 ms later. With the differentiable backend the two coincide.An environment that converges first holds its pose while the others finish, so its rollout ends in repeated samples.
toppra's deduplication in_toppra_solve_one_envkeeps one trailing duplicate knot, which changes the spline. Withtoppra, an environment's trajectory depends on which other environments share its batch. The differentiable solver drops the held tail. Thetoppradeduplication itself is left for a separate fix.Time for the batch of eight:
toppra837 ms, of which retiming 506 ms;differentiable349 ms, of which retiming 9 ms.Validation
pytest tests/sim/motion/planners/test_neural_planner.py tests/sim/motion/planners/test_neural_batched.py tests/sim/motion/test_motion_generator.py tests/benchmark/motion_generation/ tests/compute/test_trajectory_topp.py— 208 passed, 3 skipped.black .— clean.python docs/scripts/check_api_docs.py— aligned; no new exports, the new config field is documented by autodoc.context.py affected—motion-planningandsimulation-system.planner-details.md's neural-adapter section now describesretime_backendand where the backends differ.simulation-systemmatches only through thetests/sim/watch path.context.py check— ok.A bug the unit tests missed. An earlier version inferred the joint count from the length of the limits, so scalar limits retimed only joint 0 and the other joints jumped to their final values at t = 0. The first unit test passed because it checked only limits and shapes. Running the real benchmark exposed it. The test now checks each joint's start and end against the rollout, and fails on that version.
Checklist
black .command to format the code base.python docs/scripts/check_api_docs.py), if applicable🤖 Generated with Claude Code