Use manipulability to diversify trajectory augmentation - #643
Open
Yuan-Xinyi wants to merge 6 commits into
Open
Yuan-Xinyi wants to merge 6 commits into
Yuan-Xinyi wants to merge 6 commits into
Conversation
Add manipulability-aware candidate generation and selection to embodichain/lab/sim/motion/expansion/, so collected expert trajectories cover a wider range of arm postures instead of clustering around the few comfortable configurations a uniform residual sampler tends to produce. New expansion/manipulability.py reuses the shared Yoshikawa helpers in embodichain.compute.kinematics: - describe_manipulability() scores caller-supplied Jacobians and reports per-sample values plus the minimum inside each trajectory phase. Scoring runs in float64 because a float32 determinant underflows to zero for small-scale Jacobians and reports a healthy posture as singular. - ManipulabilityBands groups scores into ordered ranges expressed as ratios against a per-case reference value, so one set of boundaries works across robots and tasks. - manipulability_guided_residual() draws several joint_residual proposals from one local generator and keeps the one closest to a requested range. CoverageIndex gains an optional per-range quota: once a range is full, new rollouts in it are rejected even when their geometry is new, so a single well-conditioned posture cannot consume the whole collection budget. GenerationSession classifies a rollout from the measured "manipulability" observation recorded during execution, never from a planned estimate, and reports the per-range commit counts in snapshot(). The feature is opt-in through augmentation.factors.manipulability and is disabled by default, so existing jobs behave exactly as before. Jacobians are supplied by the caller, keeping the package free of robot, solver, and simulation imports. Manipulability only ranks postures; path, dynamic, and task validation remain separate and unchanged. Fixes #634 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
matafela
reviewed
Sep 17, 2026
A reference bottleneck describes one reference trajectory, not the scene, so two initial states of the same case may carry different values. The session stored it as a shared case condition and rejected the second registration, and both the band map and `_measured_band` were keyed by scene case alone, so a second reference would have been ignored anyway. Resolve the normalizer by `(scene_case_id, initial_state_id)` and drop the reference from the cross-initial-state condition check. Re-registering one initial state with a changed reference still fails, and coverage quotas stay shared by scene case. Also make the float32 underflow regression test reject the broken implementation: a 3x3 Jacobian scaled by 1e-4 yields a determinant near 1e-24 that float32 represents fine, and `pytest.approx(1e-12)` accepted an incorrect zero through its default absolute tolerance. Score a 6x6 Jacobian instead, whose 1e-48 determinant does underflow, and compare with a purely relative tolerance. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…sue-634-3654e6 Both context pages were rewritten on main by the agent-context streamlining change, so this branch's edits to their old structure no longer applied. Take main's rewritten pages, then re-apply this branch's contribution onto the new structure: the `expansion/manipulability.py` contract moves into main's "Trajectory augmentation boundary" section, keeping every constraint it stated (caller-supplied Jacobians, per-initial-state reference bottleneck, opt-in factor disabled by default, the `register_case` requirement, the `CoverageIndex` per-band quota, and band classification from the measured observation). Both ownership-table rows this branch edited are gone from main's rewritten pages, which describe responsibilities instead of listing components. The routing they provided is already served by main's tables and by this branch's `MAP.yaml` keywords, which merged cleanly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`manipulability_guided_residual` lives in `manipulability.py` rather than `operators.py` because it composes `joint_residual` with scoring, and moving it next to the primitive operators would make the two modules import each other. It still has to reject a template that does not authorize `joint_residual` before entering the proposal loop, otherwise a permission error is caught by the retry handler and reported as a rejected sample. It was reaching into `operators._allowed_phases` for that check, the only cross-module use of a private name in the package. Promote the helper to `operators.allowed_phases` with a docstring stating the contract, so an operator implemented outside that module has a supported way to validate template permissions. No behavior change; the check, its error messages and every call site are the same. Keeps the package ready for a stacked change that dispatches this operator alongside the other joint-path methods. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Overlay the joint paths that band targeting actually produces, so the effect is visible without running a collection job: phase endpoints stay exact while the interior of every joint moves, and variants steered toward different bands separate instead of scattering uniformly. The reference passes through an interior waypoint near the wrist singularity. That is not cosmetic. `joint_residual` preserves phase endpoints exactly, so a reference whose bottleneck sits on an endpoint cannot have that bottleneck moved by any residual, and every variant would report the reference score. The robot is an asset-backed kinematic chain and solver, as in the workspace benchmark, so the figure is produced with no simulation manager, renderer, physics backend or display. Output is byte-identical across runs: each draw owns a seeded local generator and nothing consumes the global RNG. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…iants The joint paths show that band targeting moves something; they do not show what it moved it to. Add the companion view: manipulability measured along every variant, with the band edges drawn as ratios of the reference bottleneck and a marker on each curve at the bottleneck its band is actually read from. Both figures come from one run over one set of variants, so they cannot drift apart. Scores are taken from the profile each variant was selected with rather than re-evaluated, which also drops a redundant Jacobian pass; the reported bottleneck spread is unchanged, confirming the two paths agree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
yuecideng
approved these changes
Sep 21, 2026
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.
Description
Expert trajectory collection currently samples joint residuals uniformly around one reference path, so the accepted rollouts tend to cluster around the same few comfortable arm postures. This change lets manipulability guide both how candidates are proposed and which ones are kept, so a run covers a wider range of postures, including tighter ones that still complete the task.
The work stays inside
embodichain/lab/sim/motion/expansion/and reuses the existing Yoshikawa helpers inembodichain.compute.kinematics; no new numerical algorithm is introduced.New module
expansion/manipulability.pydescribe_manipulability()scores caller-supplied Jacobians and reports the per-sample values together with the lowest value inside each trajectory phase. Scoring runs in float64: the determinant of a float32 Jacobian underflows to exactly zero at small scales and would report a healthy posture as singular.ManipulabilityBandsgroups scores into ordered ranges expressed as ratios against a per-case reference value, usually the reference trajectory's lowest score. One set of boundaries therefore transfers across robots and tasks.manipulability_guided_residual()draws severaljoint_residualproposals from one local generator, scores each, and keeps the first one that lands in the requested range, falling back to the nearest reachable range. Proposals that leave the joint limits are skipped; if every proposal fails, the last error is raised rather than swallowed.Operator permission check
manipulability_guided_residual()stays inmanipulability.pyrather than moving next to the primitive operators, because it composesjoint_residual()with scoring and the two modules would otherwise import each other. It still rejects a template that does not authorizejoint_residualbefore entering the proposal loop, so a permission error is never caught by the retry handler and reported as a rejected sample. That check previously reached intooperators._allowed_phases, the only cross-module use of a private name in the package; it is now the publicoperators.allowed_phases(), so an operator implemented outside that module has a supported way to validate template permissions. No behaviour changes.Selection side
CoverageIndexgains an optional per-range quota. Once a range is full, further rollouts in it are rejected even when their measured geometry is new, so one well-conditioned posture cannot consume the whole collection budget while tighter postures still have room.GenerationSessionclassifies each rollout from themanipulabilityobservation measured during execution, never from a planned estimate, and reports per-range commit counts insnapshot().Compatibility
Everything is opt-in through
augmentation.factors.manipulability, which is disabled by default. Existing jobs decode, validate, and run exactly as before;register_case()accepts a reference value only when the factor is enabled, andCoverageIndexkeeps its current behaviour when no quota is configured. Jacobians are supplied by the caller, so the package still imports no robot, solver, or simulation code. Manipulability only ranks postures: path, dynamic, and task validation are unchanged and remain the sole source of feasibility evidence.Figures
scripts/tutorials/sim/planner/manipulability_augmentation_plot.pyoverlays twelve guided variants on one UR5 reference, four steered at each band, and writes two views of that same set in one run so they cannot drift apart. The robot is an asset-backed kinematic chain and solver, so the figure needs no simulation manager, renderer, physics backend or display, and it is byte-identical across runs.Three things are readable from it. Every variant meets the reference exactly at both phase endpoints, which is the contract
joint_residualenforces and the reason contacts and grasps stay untouched. The interiors separate by target band rather than scattering uniformly: nine of twelve reached the band they asked for, and the variant bottlenecks span[0.0301, 0.0534]around a reference bottleneck of0.0392. The three that missed are drawn dashed and keep the color of the band they actually reached, since reporting the nearest reachable band is the defined fallback rather than a failure.The reference deliberately passes through an interior waypoint near the wrist singularity. A reference whose bottleneck sits on an endpoint cannot have that bottleneck moved by any residual, so every variant would report the reference score and the figure would show nothing.
The joint paths show that targeting moved something. The companion figure shows what it moved it to: manipulability measured along those same paths, with the band edges drawn as ratios of the reference bottleneck.
Each curve carries a marker at its own bottleneck, which is the single value its band membership is read from. All thirteen curves meet at both ends because the endpoints are shared, and the dip is the interior waypoint. The markers separate across the
0.8xand1.2xedges rather than clustering at the reference, which is band targeting working. Scores are the profile each variant was selected with, not a fresh evaluation, so the curves are exactly the evidence the operator ranked.Relationship to #651
This is the lower layer of a stack. It contributes the scoring, the reference-normalized bands and the guided sampler, but it deliberately adds no orchestration: in this package the host drives
propose()and calls the operator itself, somanipulability_guided_residual()has no in-package caller here by design.#651 introduces that orchestration (
expand_trajectory_variants()), already takes per-sample Jacobians, and enumerates joint-path methods from a closedSPATIAL_METHODSregistry. Wiring this operator into that registry belongs there, on top of this change, so each layer stays one logical change. The two touch different fields offactorsand different members ofCoverageIndex, so they compose without redesign.Not included: a coverage or downstream-training comparison. Measuring the benefit needs real scene collection plus policy training, which is outside this algorithm package; it is a reasonable follow-up once a host integration adopts the option.
No new dependencies.
Merged with
mainThe agent-context streamlining change rewrote both context pages this branch had edited, so their old structure was gone.
simulation-system.mdis taken frommainunchanged: its per-subsystem inventory table no longer exists, and augmentation now routes to the motion topic throughmain's own tables plus this branch'sMAP.yamlkeywords. Theexpansion/manipulability.pycontract was re-applied ontomain's "Trajectory augmentation boundary" section, keeping every constraint it stated. No source file conflicted.Fixes #634
Type of change
Validation
python -m pytest tests/sim/motion/expansion tests/compute/test_manipulability.py— 215 passed. 29 of them are new cases intests/sim/motion/expansion/test_manipulability.py, plus added cases for the range quota intest_coverage.py, the new configuration fields intest_cfg.py, and the session's use of measured evidence intest_session.py.black .with Black 26.3.1: 1118 files unchanged.python docs/scripts/check_api_docs.py: 2187/2187 exports documented.python scripts/tutorials/sim/planner/manipulability_augmentation_plot.py: both figures written, byte-identical on re-run.context.py check: agent context map ok.Checklist
black .command to format the code base.python docs/scripts/check_api_docs.py).🤖 Generated with Claude Code