Skip to content

Fix Unitree Go1 and Go2 calf actuator limits ignoring the knee reduction - #7564

Merged
kellyguo11 merged 2 commits into
isaac-sim:developfrom
AntoineRichard:antoiner/fix-unitree-knee-reduction
Sep 4, 2026
Merged

Fix Unitree Go1 and Go2 calf actuator limits ignoring the knee reduction#7564
kellyguo11 merged 2 commits into
isaac-sim:developfrom
AntoineRichard:antoiner/fix-unitree-knee-reduction

Conversation

@AntoineRichard

@AntoineRichard AntoineRichard commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Description

Fixes #7479.

UNITREE_GO1_CFG and UNITREE_GO2_CFG apply one set of DC-motor limits to all twelve leg joints, but on both robots the calf sits behind an extra knee reduction. The calf was capped at roughly half its rated torque, while the torque-speed curve kept producing motoring torque up to a no-load speed the hardware cannot reach.

The values are not a guess — they are already authored in the USD assets we ship, and the Python configs were discarding them. Dumping the joint drives from the configured asset root:

joint go2.usd maxForce go2.usd maxJointVelocity old config
hip / thigh 23.7 N·m 1724.6 °/s = 30.1 rad/s 23.5 / 30.0
calf 45.43 N·m 899.54 °/s = 15.70 rad/s 23.5 / 30.0
joint go1.usd maxForce go1.usd maxJointVelocity old config
hip / thigh 23.7 N·m 1724.6 °/s = 30.1 rad/s 23.7 / 30.0
calf 35.55 N·m 1149.35 °/s = 20.06 rad/s 23.7 / 30.0

Both match the official Unitree URDFs exactly (go2, go1), giving reductions of 1.9169 and 1.5000.

Go1 is affected for the same reason even though it uses an actuator net: ActuatorNetMLP subclasses DCMotor, so the network output goes through the same envelope.

UNITREE_A1_CFG was checked and needs no change — a1.urdf and a1.usd are both flat at 33.5 N·m / 21.0 rad/s, matching the config.

Why saturation_effort had to change

The calf needs its own stall torque, not just its own effort and velocity limits. saturation_effort was the one actuator limit typed as a bare float, so expressing this previously required splitting the legs into two actuator groups — which changes the actuators dict keys that go1/rough_env_cfg.py, go2/rough_env_cfg.py and test_hydra.py index by name, and adds a second group to iterate every step.

The first commit resolves it through resolve_joint_parameter like every other limit, so it accepts a joint-name-pattern dict. This is not a new concept in the codebase: sim/schemas/schemas_actuators.py already expands dict-shaped saturation_effort when authoring NewtonActuator prims, so the config type was simply narrower than the machinery behind it. Scalar configurations are unaffected.

Note on #7562

@sylvesterkaczmarek filed #7479 and opened #7562 for the Go2 half of this. That PR takes the second-actuator-group route and derives the calf values from the reduction ratio (45.05 / 15.65) rather than reading them off the asset. This PR covers Go1 as well, keeps a single actuator group, and uses the values already in the USD. Happy to defer if maintainers prefer the other shape.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

Simulated Go1 and Go2 behavior changes: the calf now produces up to 1.92x more torque and stops motoring at roughly half the previous speed. Policies trained against the old configuration should be retrained rather than reused. This is called out in the isaaclab_assets changelog fragment.

Tests

  • source/isaaclab/test/actuators/test_dc_motor.py gains test_dc_motor_clip_with_per_joint_saturation_effort, which checks that a dict-configured two-joint group reproduces, column by column, what a scalar-configured actuator produces for the same joint. Verified failing before the fix (TypeError: unsupported operand type(s) for /: 'Tensor' and 'dict') and passing after.
  • source/isaaclab/test/actuators/test_dc_motor.py — 170 passed.
  • source/isaaclab/test/actuators/test_actuator_collection.py — 30 passed.
  • source/isaaclab_assets/test/test_valid_configs.py -k cpu — 1 passed (spawns every registered asset, including both Go configs).
  • Confirmed the patterns resolve against the real joint names: all four calf joints get 45.43 / 15.70 (Go2) and 35.55 / 20.06 (Go1); hips and thighs get 23.7 / 30.1.

No config-literal assertion test was added for unitree.py — it would restate the constants without checking behavior. The behavioral contract is covered by the DC-motor test above.

Release backport

  • Backport this pull request to the active release branch after it merges into develop

Checklist

  • I have read and understood the contribution guidelines
  • I have run the pre-commit checks with ./isaaclab.sh --format
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • I have added a changelog fragment under source/<pkg>/changelog.d/ for every touched package
  • I have added my name to the CONTRIBUTORS.md or my name already exists there

@AntoineRichard
AntoineRichard requested a review from a team September 4, 2026 07:54
@github-actions github-actions Bot added bug Something isn't working documentation Improvements or additions to documentation asset New asset feature or request labels Sep 4, 2026
@greptile-apps

greptile-apps Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds per-joint DC-motor stall-torque resolution and uses it to represent the knee reductions of the Unitree Go1 and Go2 without splitting their actuator groups.

  • Broadens DCMotorCfg.saturation_effort from a scalar to scalar-or-pattern-dictionary configuration.
  • Resolves stall torque into a batched per-joint tensor used by DC-motor and actuator-network clipping.
  • Updates Go1 and Go2 calf torque and velocity limits to match their shipped USD assets.
  • Adds focused clipping tests, documentation, and changelog entries.

Confidence Score: 4/5

The PR appears safe to merge, with a non-blocking configuration-hardening issue for incomplete saturation-effort dictionaries.

The intended Unitree configurations fully cover their joints and the resolved tensor follows existing actuator shapes, but the newly public dictionary form can silently disable an uncovered joint instead of rejecting invalid configuration.

Files Needing Attention: source/isaaclab/isaaclab/actuators/actuator_pd.py and source/isaaclab/test/actuators/test_dc_motor.py

Important Files Changed

Filename Overview
source/isaaclab/isaaclab/actuators/actuator_pd.py Resolves saturation effort per joint; correct tensor arithmetic, but uncovered dictionary entries silently become zero-torque joints.
source/isaaclab/isaaclab/actuators/actuator_pd_cfg.py Expands the public configuration type and documentation to accept joint-pattern dictionaries.
source/isaaclab_assets/isaaclab_assets/robots/unitree.py Applies asset-derived, calf-specific torque and velocity limits to the Go1 and Go2 actuator groups.
source/isaaclab/test/actuators/test_dc_motor.py Verifies grouped per-joint clipping against scalar actuators, but does not exercise incomplete pattern coverage.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Cfg[Scalar or joint-pattern limits] --> Resolve[Resolve per-joint tensors]
  Resolve --> Motor[DCMotor / ActuatorNet]
  State[Joint velocity] --> Motor
  Motor --> Curve[Torque-speed envelope]
  Curve --> Clip[Clipped joint effort]
Loading

Reviews (1): Last reviewed commit: "Fix Unitree Go1 and Go2 calf actuator li..." | Re-trigger Greptile

Comment on lines +408 to +410
self._saturation_effort = resolve_joint_parameter(
self.cfg.saturation_effort, None, self._joint_names, self._num_envs, self._device
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Uncovered joints get zero torque

A dictionary that does not cover every joint resolves uncovered entries to 0.0. The motor then divides by that value when calculating the corner velocity and constructs a zero torque envelope, silently disabling those joints instead of rejecting the invalid configuration.

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isaac Lab Review Bot

Reviewed the per-joint saturation_effort design and the Unitree Go1/Go2 actuator-limit corrections. The change uses the existing joint-parameter resolution path, preserves the single base_legs actuator group, and includes focused behavioral coverage and release notes.

  • Design and architecture: Resolving saturation_effort through resolve_joint_parameter is consistent with the existing per-joint effort and velocity limit abstractions. Keeping one actuator group avoids changing group-name consumers while allowing calf-specific torque-speed envelopes.
  • API: The public configuration type is compatibly widened from a scalar to dict[str, float] | float; existing scalar configurations remain supported. The new dictionary behavior and physical units are documented in the API docstring, concepts documentation, and package changelog.
  • Implementation: The resolved saturation values feed the existing element-wise corner-velocity and torque-clipping calculations. The regression test compares each column of a grouped per-joint configuration against equivalent scalar actuators on CPU and CUDA. The Go1/Go2 behavior intentionally changes substantially, and the asset changelog appropriately advises retraining existing policies.

No blocking issues. No inline issue met the actionable-evidence threshold; the assessment above records the review feedback.

Automated review; human maintainers own approval decisions.

The DC motor torque-speed curve is anchored on the stall torque, but
saturation_effort was the only actuator limit that could not vary across
the joints of a group. Joints behind different gear reductions therefore
had to be split into separate actuator groups to get correct curves.

Resolve it through resolve_joint_parameter like the other limits, so it
accepts a joint-name-pattern dictionary. The USD authoring path in
schemas_actuators already expanded dict-shaped saturation_effort, so this
brings the config type in line with the rest of the stack.
Both configurations applied one set of DC motor limits to all twelve leg
joints, but the Go1 and Go2 calf sits behind an extra knee reduction
(1.50:1 and 1.92:1). The calf was therefore capped at roughly half its
rated torque while its torque-speed curve kept motoring up to a no-load
speed it cannot reach on hardware.

Give the calf its own limits, taken from the joint drives authored in
go1.usd and go2.usd, which already carry the reduction. The hip and thigh
limits are aligned with the same assets so all three come from one source.
@AntoineRichard
AntoineRichard force-pushed the antoiner/fix-unitree-knee-reduction branch from c99f7e1 to 5cfe96d Compare September 4, 2026 08:26

@mmichelis mmichelis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!

torque, and ``actuator_velocity_limit`` is the no-load speed.
torque, and ``actuator_velocity_limit`` is the no-load speed. Both accept a joint-name-pattern
dictionary, so joints behind different gear reductions can share one group and still get their own
curve — for example a quadruped whose knee sits behind an extra reduction relative to its hip.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

personally I dislike the emdashes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well Klaude love's them.

@AntoineRichard

Copy link
Copy Markdown
Collaborator Author

run-ci

@isaaclab-bot isaaclab-bot Bot added ci:run-docker Trigger the on-demand Docker and GPU CI workflow and removed ci:run-docker Trigger the on-demand Docker and GPU CI workflow labels Sep 4, 2026
@maxkra15

maxkra15 commented Sep 4, 2026

Copy link
Copy Markdown

tests could be extended to safeguard the known asset issues that arise sometimes, but otherwise lgtm

@AntoineRichard

Copy link
Copy Markdown
Collaborator Author

tests could be extended to safeguard the known asset issues that arise sometimes, but otherwise lgtm

Yeah, but i'm not sure we should be testing for that, if they get a legitimate update it would break. Ideally we'd have version controlled assets! I know I know crazy!

@kellyguo11
kellyguo11 merged commit 5c78379 into isaac-sim:develop Sep 4, 2026
53 checks passed
@isaaclab-bot

isaaclab-bot Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Backported to release/3.0.0 as a4d4297.

isaaclab-bot Bot pushed a commit that referenced this pull request Sep 4, 2026
…ion (#7564)

# Description

Fixes #7479.

`UNITREE_GO1_CFG` and `UNITREE_GO2_CFG` apply one set of DC-motor limits
to all twelve leg joints, but on both robots the calf sits behind an
extra knee reduction. The calf was capped at roughly half its rated
torque, while the torque-speed curve kept producing motoring torque up
to a no-load speed the hardware cannot reach.

The values are not a guess — they are already authored in the USD assets
we ship, and the Python configs were discarding them. Dumping the joint
drives from the configured asset root:

| joint | `go2.usd` `maxForce` | `go2.usd` `maxJointVelocity` | old
config |
| --- | --- | --- | --- |
| hip / thigh | 23.7 N·m | 1724.6 °/s = 30.1 rad/s | 23.5 / 30.0 |
| calf | **45.43 N·m** | 899.54 °/s = **15.70 rad/s** | 23.5 / 30.0 |

| joint | `go1.usd` `maxForce` | `go1.usd` `maxJointVelocity` | old
config |
| --- | --- | --- | --- |
| hip / thigh | 23.7 N·m | 1724.6 °/s = 30.1 rad/s | 23.7 / 30.0 |
| calf | **35.55 N·m** | 1149.35 °/s = **20.06 rad/s** | 23.7 / 30.0 |

Both match the official Unitree URDFs exactly
([go2](https://github.com/unitreerobotics/unitree_ros/blob/master/robots/go2_description/urdf/go2_description.urdf),
[go1](https://github.com/unitreerobotics/unitree_ros/blob/master/robots/go1_description/urdf/go1.urdf)),
giving reductions of 1.9169 and 1.5000.

Go1 is affected for the same reason even though it uses an actuator net:
`ActuatorNetMLP` subclasses `DCMotor`, so the network output goes
through the same envelope.

`UNITREE_A1_CFG` was checked and needs no change — `a1.urdf` and
`a1.usd` are both flat at 33.5 N·m / 21.0 rad/s, matching the config.

## Why `saturation_effort` had to change

The calf needs its own *stall* torque, not just its own effort and
velocity limits. `saturation_effort` was the one actuator limit typed as
a bare `float`, so expressing this previously required splitting the
legs into two actuator groups — which changes the `actuators` dict keys
that `go1/rough_env_cfg.py`, `go2/rough_env_cfg.py` and `test_hydra.py`
index by name, and adds a second group to iterate every step.

The first commit resolves it through `resolve_joint_parameter` like
every other limit, so it accepts a joint-name-pattern dict. This is not
a new concept in the codebase: `sim/schemas/schemas_actuators.py`
already expands dict-shaped `saturation_effort` when authoring
`NewtonActuator` prims, so the config type was simply narrower than the
machinery behind it. Scalar configurations are unaffected.

## Note on #7562

@sylvesterkaczmarek filed #7479 and opened #7562 for the Go2 half of
this. That PR takes the second-actuator-group route and derives the calf
values from the reduction ratio (45.05 / 15.65) rather than reading them
off the asset. This PR covers Go1 as well, keeps a single actuator
group, and uses the values already in the USD. Happy to defer if
maintainers prefer the other shape.

## Type of change

- Bug fix (non-breaking change which fixes an issue)
- New feature (non-breaking change which adds functionality)

Simulated Go1 and Go2 behavior changes: the calf now produces up to
1.92x more torque and stops motoring at roughly half the previous speed.
Policies trained against the old configuration should be retrained
rather than reused. This is called out in the `isaaclab_assets`
changelog fragment.

## Tests

- `source/isaaclab/test/actuators/test_dc_motor.py` gains
`test_dc_motor_clip_with_per_joint_saturation_effort`, which checks that
a dict-configured two-joint group reproduces, column by column, what a
scalar-configured actuator produces for the same joint. Verified failing
before the fix (`TypeError: unsupported operand type(s) for /: 'Tensor'
and 'dict'`) and passing after.
- `source/isaaclab/test/actuators/test_dc_motor.py` — 170 passed.
- `source/isaaclab/test/actuators/test_actuator_collection.py` — 30
passed.
- `source/isaaclab_assets/test/test_valid_configs.py -k cpu` — 1 passed
(spawns every registered asset, including both Go configs).
- Confirmed the patterns resolve against the real joint names: all four
calf joints get 45.43 / 15.70 (Go2) and 35.55 / 20.06 (Go1); hips and
thighs get 23.7 / 30.1.

No config-literal assertion test was added for `unitree.py` — it would
restate the constants without checking behavior. The behavioral contract
is covered by the DC-motor test above.

## Release backport

- [x] <!-- backport-active-release --> Backport this pull request to the
active release branch after it merges into `develop`

## Checklist

- [x] I have read and understood the [contribution
guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html)
- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] I have added a changelog fragment under
`source/<pkg>/changelog.d/` for every touched package
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there

(cherry picked from commit 5c78379)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

asset New asset feature or request bug Something isn't working documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants