Skip to content

Support impedance control on MuJoCo POSITION actuators - #9

Merged
lgulich merged 5 commits into
impedence-control-supportfrom
nvidia/position-actuator-impedance
Oct 5, 2026
Merged

lgulich merged 5 commits into
impedence-control-supportfrom
nvidia/position-actuator-impedance

Conversation

@lgulich

@lgulich lgulich commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator

Ported from the GitLab mirror MR (Isaac/github/mujoco_ros2_control!2, by @somudrog), rebased onto current impedence-control-support.

Summary

  • When kp/kd command interfaces are claimed on a joint driven by a MuJoCo POSITION actuator, program the actuator's gainprm/biasprm so MuJoCo applies effort_ff + kp*(q_target - q) + kd*(v_target - v) natively, instead of writing the computed PD torque into ctrl (which a position actuator interprets as a position setpoint).
  • Restore the MJCF-authored gains when the joint is released (impedance lifecycle fix).

Needed for Sharpa hand fingers on Unitree H2 (position-servo actuators) driven by the impedance-style ForwardJointCommandController.

🤖 Generated with Claude Code

somudro and others added 4 commits October 5, 2026 22:49
Impedance mode assumed the target actuator interprets ctrl as a torque, which
holds for MOTOR actuators but not for POSITION ones. A MuJoCo position actuator
applies

    force = gainprm[0]*ctrl + biasprm[0] + biasprm[1]*q + biasprm[2]*v

so ctrl is a position setpoint. Summing kp*position_error + kd*velocity_error
into it is a unit mismatch: the resulting error is non-monotonic in kp, so no
choice of gains recovers correct behaviour, and any model whose actuators use
biastype="affine" silently misbehaves under a controller that claims kp/kd.

Drive those actuators through their own gains instead. Programming

    gainprm[0] =  kp
    biasprm[0] =  effort_ff + kd*v_target
    biasprm[1] = -kp
    biasprm[2] = -kd

and passing the target through as ctrl makes the actuator apply

    effort_ff + kp*(q_target - q) + kd*(v_target - v)

which is the same expression the MOTOR path computes by hand -- verified equal
to machine precision against MuJoCo's actuator_force across random states, with
forcerange clamping still applied as authored.

The MJCF gains become defaults rather than fixed values, and are captured at
registration and restored when the joint is released. mjModel is shared with
the physics loop, so unlike ctrl -- which is staged -- gain writes take the sim
mutex and happen only when a commanded gain actually changes, keeping the lock
off the steady-state control path.

Also add the <limits> include data.hpp already relied on transitively.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The package builds as C++17 in some environments (e.g. pixi), where a
defaulted operator== and std::erase_if are unavailable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lgulich
lgulich force-pushed the nvidia/position-actuator-impedance branch from 0b6d70d to db41de2 Compare October 5, 2026 21:29
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lgulich
lgulich merged commit c8b0363 into impedence-control-support Oct 5, 2026
3 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants