Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/source/concepts/actuators.rst
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,9 @@ estimates effort telemetry from the current state when the backend does not expo
torque limit at :math:`\pm\,\tau_{max}`.

**DCMotor.** Adds a linear four-quadrant torque-speed curve. ``saturation_effort`` is the stall
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.


**DelayedPDActuator.** An ideal PD controller with delayed position, velocity, and effort commands.
The delay is sampled uniformly from ``[min_delay, max_delay]`` at reset.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Changed
^^^^^^^

* Changed :attr:`~isaaclab.actuators.DCMotorCfg.saturation_effort` to accept a joint-name-pattern
dictionary in addition to a scalar. Joints in one actuator group that sit behind different gear
reductions can now be given their own stall torque, which previously required splitting them into
separate actuator groups. Existing scalar configurations are unaffected.
4 changes: 3 additions & 1 deletion source/isaaclab/isaaclab/actuators/actuator_pd.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,9 @@ def __init__(self, cfg: DCMotorCfg, *args, **kwargs):
# parse configuration
if self.cfg.saturation_effort is None:
raise ValueError("The saturation_effort must be provided for the DC motor actuator model.")
self._saturation_effort = self.cfg.saturation_effort
self._saturation_effort = resolve_joint_parameter(
self.cfg.saturation_effort, None, self._joint_names, self._num_envs, self._device
)
Comment on lines +408 to +410

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.

# check that quantities are provided
if self.cfg.actuator_velocity_limit is None:
raise ValueError("The velocity limit must be provided for the DC motor actuator model.")
Expand Down
9 changes: 7 additions & 2 deletions source/isaaclab/isaaclab/actuators/actuator_pd_cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,13 @@ class DCMotorCfg(IdealPDActuatorCfg):

class_type: type["DCMotor"] | str = "{DIR}.actuator_pd:DCMotor"

saturation_effort: float = MISSING
"""Peak motor force/torque of the electric DC motor (in N-m)."""
saturation_effort: dict[str, float] | float = MISSING
"""Peak motor force/torque of the electric DC motor [N or N·m, depending on joint type].

The motor's stall torque reflected at the joint, i.e. the torque produced at zero speed.
Joints in the same group that sit behind different gear reductions need different values,
so this accepts a joint-name-pattern dictionary as well as a scalar.
"""


@configclass
Expand Down
33 changes: 33 additions & 0 deletions source/isaaclab/test/actuators/test_dc_motor.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,36 @@ def test_dc_motor_clip(num_envs, num_joints, device, test_point):
expected_clipped_effort[test_point] * torch.ones(num_envs, num_joints, device=device),
clipped_effort,
)


@pytest.mark.parametrize("device", ["cuda:0", "cpu"])
def test_dc_motor_clip_with_per_joint_saturation_effort(device):
"""Test that a per-joint ``saturation_effort`` gives each joint its own torque-speed curve.

Joints behind different gear reductions belong to one actuator group but do not share a stall
torque, e.g. the Unitree Go2 calf, which sits behind an extra knee reduction.
"""
joint_names = ["hip", "calf"]
actuator_cfg = DCMotorCfg(
joint_names_expr=joint_names,
stiffness=200.0,
damping=10.0,
actuator_effort_limit=100.0,
actuator_velocity_limit=50.0,
saturation_effort={"hip": 100.0, "calf": 190.0},
)
actuator = actuator_cfg.class_type(
actuator_cfg,
joint_names=joint_names,
joint_ids=[0, 1],
num_envs=1,
device=device,
stiffness=actuator_cfg.stiffness,
damping=actuator_cfg.damping,
)

# at half the no-load speed each joint delivers half of its own stall torque, and the shared
# effort limit is high enough to clip neither
actuator._joint_vel[:] = 25.0
clipped_effort = actuator._clip_effort(torch.full((1, 2), 500.0, device=device))
torch.testing.assert_close(clipped_effort, torch.tensor([[50.0, 95.0]], device=device))
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Fixed
^^^^^

* Fixed the Unitree Go1 and Go2 leg actuator limits ignoring the knee reduction. Both robots applied
the hip and thigh limits to the calf joints, which capped calf torque well below its rated value and
let the torque-speed curve keep motoring past its rated speed. The calf joints now use the limits
authored in ``go1.usd`` and ``go2.usd`` (Go1: 35.55 N·m, 20.06 rad/s; Go2: 45.43 N·m, 15.70 rad/s),
and the hip and thigh limits were aligned with the same assets (23.7 N·m, 30.1 rad/s).

These robots now produce more calf torque at lower calf speeds, so policies trained on the previous
configuration should be retrained rather than reused directly.
16 changes: 10 additions & 6 deletions source/isaaclab_assets/isaaclab_assets/robots/unitree.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@
torque_scale=1.0,
input_order="pos_vel",
input_idx=[0, 1, 2],
actuator_effort_limit=23.7, # taken from spec sheet
actuator_velocity_limit=30.0, # taken from spec sheet
saturation_effort=23.7, # same as effort limit
# the calf sits behind an extra 1.5:1 knee reduction, so its joint-side torque is higher
# and its joint-side speed lower than the hip and thigh. Values match ``go1.usd``.
actuator_effort_limit={".*_hip_joint": 23.7, ".*_thigh_joint": 23.7, ".*_calf_joint": 35.55},
actuator_velocity_limit={".*_hip_joint": 30.1, ".*_thigh_joint": 30.1, ".*_calf_joint": 20.06},
saturation_effort={".*_hip_joint": 23.7, ".*_thigh_joint": 23.7, ".*_calf_joint": 35.55},
)
"""Configuration of Go1 actuators using MLP model.

Expand Down Expand Up @@ -171,9 +173,11 @@
actuators={
"base_legs": DCMotorCfg(
joint_names_expr=[".*_hip_joint", ".*_thigh_joint", ".*_calf_joint"],
actuator_effort_limit=23.5,
saturation_effort=23.5,
actuator_velocity_limit=30.0,
# the calf sits behind an extra 1.92:1 knee reduction, so its joint-side torque is
# higher and its joint-side speed lower than the hip and thigh. Values match ``go2.usd``.
actuator_effort_limit={".*_hip_joint": 23.7, ".*_thigh_joint": 23.7, ".*_calf_joint": 45.43},
saturation_effort={".*_hip_joint": 23.7, ".*_thigh_joint": 23.7, ".*_calf_joint": 45.43},
actuator_velocity_limit={".*_hip_joint": 30.1, ".*_thigh_joint": 30.1, ".*_calf_joint": 15.70},
stiffness=25.0,
damping=0.5,
friction=0.0,
Expand Down
Loading