[Bug Report] Newton backend re-enables joint target stiffness/damping for explicit actuators, causing different rest pose from PhysX
Bug Description
When using the Newton backend with an articulation configured with an explicit actuator, such as DelayedPDActuatorCfg / IdealPDActuator, the robot reaches a different joint rest pose compared with the PhysX backend under the same action command.
The issue appears to come from the Newton articulation initialization path. For explicit actuators, IsaacLab correctly calls:
self.write_joint_stiffness_to_sim_index(stiffness=0.0, joint_ids=actuator.joint_indices)
self.write_joint_damping_to_sim_index(damping=0.0, joint_ids=actuator.joint_indices)
This matches the expected behavior: explicit actuators should compute joint torques in IsaacLab, while the simulator-side joint target drive should be disabled.
However, a few lines later, the Newton backend stores the configured actuator stiffness/damping back into:
self.data._sim_bind_joint_stiffness_sim
self.data._sim_bind_joint_damping_sim
These buffers are directly bound to Newton model attributes:
"joint_target_ke"
"joint_target_kd"
Therefore, for explicit actuators, Newton's internal joint target drive is unintentionally re-enabled.
This causes the Newton backend to apply two controllers at the same time:
- IsaacLab explicit actuator torque, pulling the joint toward the processed action target.
- Newton internal joint target drive, pulling the joint toward the simulator-side
joint_target_pos.
For explicit actuators, Newton only writes effort targets every step. It does not write position/velocity targets unless _has_implicit_actuators is true:
self.data._sim_bind_joint_effort.assign(self._joint_effort_target_sim)
if self._has_implicit_actuators:
self.data._sim_bind_joint_position_target.assign(self._joint_pos_target_sim)
self.data._sim_bind_joint_velocity_target.assign(self._joint_vel_target_sim)
As a result, the internal Newton target position can remain at its default value, commonly zero, while the explicit actuator tries to track the actual command target. This makes Newton behave differently from PhysX.
Expected Behavior
For explicit actuators:
- simulator-side joint stiffness/damping should remain zero
- only explicit actuator effort should be sent to the simulator
- Newton and PhysX should produce consistent behavior under the same articulation config and action command
For implicit actuators:
- simulator-side joint stiffness/damping should still be written into the simulation
- simulator-side position/velocity targets should be written every step
- Newton's internal joint target drive should remain active
Actual Behavior
For explicit actuators in the Newton backend:
- simulator-side joint stiffness/damping are first set to zero
- then later overwritten with actuator stiffness/damping again
- Newton internal
joint_target_ke/kd remains active
- the articulation is pulled toward the default simulator-side target position, producing a different rest pose from PhysX
Relevant Code
Newton articulation initialization
File:
source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py
Current behavior:
if isinstance(actuator, ImplicitActuator):
self._has_implicit_actuators = True
self.write_joint_stiffness_to_sim_index(stiffness=actuator.stiffness, joint_ids=actuator.joint_indices)
self.write_joint_damping_to_sim_index(damping=actuator.damping, joint_ids=actuator.joint_indices)
else:
self.write_joint_stiffness_to_sim_index(stiffness=0.0, joint_ids=actuator.joint_indices)
self.write_joint_damping_to_sim_index(damping=0.0, joint_ids=actuator.joint_indices)
# Later:
wp.launch(
shared_kernels.write_2d_data_to_buffer_with_indices,
...
outputs=[
self.data._sim_bind_joint_stiffness_sim,
],
)
wp.launch(
shared_kernels.write_2d_data_to_buffer_with_indices,
...
outputs=[
self.data._sim_bind_joint_damping_sim,
],
)
Newton data binding
File:
source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation_data.py
The buffers above are bound to Newton model drive gains:
self._sim_bind_joint_stiffness_sim = self._root_view.get_attribute(
"joint_target_ke", SimulationManager.get_model()
)[:, 0]
self._sim_bind_joint_damping_sim = self._root_view.get_attribute(
"joint_target_kd", SimulationManager.get_model()
)[:, 0]
The property docstring says explicit actuators should have zero simulator-side stiffness/damping:
@property
def joint_stiffness(self) -> wp.array:
"""Joint stiffness provided to the simulation.
In the case of explicit actuators, the value for the corresponding joints is zero.
"""
return self._sim_bind_joint_stiffness_sim
Minimal Reproduction Pattern
- Configure an articulation with an explicit actuator, for example:
DelayedPDActuatorCfg(
joint_names_expr=[...],
stiffness=100.0,
damping=1.0,
)
- Run the same scene once with PhysX and once with Newton.
- Send the same joint position action.
- Observe that the final/rest joint pose differs between the two backends.
- Inspect Newton-side simulator joint stiffness/damping after actuator initialization. They remain non-zero for explicit actuators, even though they should be zero.
This becomes especially visible when using JointPositionToLimitsActionCfg or EMAJointPositionToLimitsActionCfg with rescale_to_limits=True. In that case, action 0 is correctly mapped to the midpoint of the joint limits, but Newton additionally pulls toward the default simulator-side target position because the internal drive was unintentionally left active.
Proposed Fix
Store the actuator model's configured stiffness/damping into the actuator-data buffers, not the simulator-bound Newton drive buffers.
Patch:
diff --git a/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py
--- a/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py
+++ b/source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py
@@ -3398,7 +3398,7 @@ class Articulation(BaseArticulation):
outputs=[
- self.data._sim_bind_joint_stiffness_sim,
+ self.data._actuator_stiffness,
],
device=self.device,
)
@@ -3411,7 +3411,7 @@ class Articulation(BaseArticulation):
outputs=[
- self.data._sim_bind_joint_damping_sim,
+ self.data._actuator_damping,
],
device=self.device,
)
This keeps the existing implicit actuator behavior intact, because the implicit branch still explicitly calls:
self.write_joint_stiffness_to_sim_index(stiffness=actuator.stiffness, ...)
self.write_joint_damping_to_sim_index(damping=actuator.damping, ...)
But for explicit actuators, the simulator-side Newton drive remains disabled as intended.
Why this fix seems correct
PhysX stores configured actuator stiffness/damping separately from the actual simulator-side drive state. For explicit actuators, PhysX sets simulator stiffness/damping to zero after storing the configured values.
Newton already has dedicated buffers:
self._actuator_stiffness
self._actuator_damping
So the configured actuator gains can be preserved without writing them back into joint_target_ke/kd.
Checklist
System Info
- OS: Ubuntu 24.04.4 LTS, noble
- Kernel: Linux 6.17.0-29-generic x86_64
- Python: 3.12.13
- Isaac Sim: 6.0.0.0
- IsaacLab package: 4.5.22
- IsaacLab repo tag: v3.0.0-beta
- IsaacLab commit: a4a7602
- IsaacLab commit date: 2026-03-16 23:38:12 -0700
- isaaclab_newton: 0.5.9
- isaaclab_physx: 0.5.11
- isaaclab_tasks: 1.5.11
- Newton package: 1.0.0
- newton-actuators: 0.1.0
- MuJoCo: 3.5.0
- mujoco-warp: 3.5.0.2
- warp-lang: 1.12.0
- PyTorch: 2.10.0+cu128
- PyTorch CUDA build: 12.8
- IsaacLab install mode: editable local checkout
- Local patch status:
source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py modified with the proposed 2-line fix
[Bug Report] Newton backend re-enables joint target stiffness/damping for explicit actuators, causing different rest pose from PhysX
Bug Description
When using the Newton backend with an articulation configured with an explicit actuator, such as
DelayedPDActuatorCfg/IdealPDActuator, the robot reaches a different joint rest pose compared with the PhysX backend under the same action command.The issue appears to come from the Newton articulation initialization path. For explicit actuators, IsaacLab correctly calls:
This matches the expected behavior: explicit actuators should compute joint torques in IsaacLab, while the simulator-side joint target drive should be disabled.
However, a few lines later, the Newton backend stores the configured actuator stiffness/damping back into:
These buffers are directly bound to Newton model attributes:
Therefore, for explicit actuators, Newton's internal joint target drive is unintentionally re-enabled.
This causes the Newton backend to apply two controllers at the same time:
joint_target_pos.For explicit actuators, Newton only writes effort targets every step. It does not write position/velocity targets unless
_has_implicit_actuatorsis true:As a result, the internal Newton target position can remain at its default value, commonly zero, while the explicit actuator tries to track the actual command target. This makes Newton behave differently from PhysX.
Expected Behavior
For explicit actuators:
For implicit actuators:
Actual Behavior
For explicit actuators in the Newton backend:
joint_target_ke/kdremains activeRelevant Code
Newton articulation initialization
File:
Current behavior:
Newton data binding
File:
The buffers above are bound to Newton model drive gains:
The property docstring says explicit actuators should have zero simulator-side stiffness/damping:
Minimal Reproduction Pattern
This becomes especially visible when using
JointPositionToLimitsActionCfgorEMAJointPositionToLimitsActionCfgwithrescale_to_limits=True. In that case, action0is correctly mapped to the midpoint of the joint limits, but Newton additionally pulls toward the default simulator-side target position because the internal drive was unintentionally left active.Proposed Fix
Store the actuator model's configured stiffness/damping into the actuator-data buffers, not the simulator-bound Newton drive buffers.
Patch:
This keeps the existing implicit actuator behavior intact, because the implicit branch still explicitly calls:
But for explicit actuators, the simulator-side Newton drive remains disabled as intended.
Why this fix seems correct
PhysX stores configured actuator stiffness/damping separately from the actual simulator-side drive state. For explicit actuators, PhysX sets simulator stiffness/damping to zero after storing the configured values.
Newton already has dedicated buffers:
So the configured actuator gains can be preserved without writing them back into
joint_target_ke/kd.Checklist
System Info
source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.pymodified with the proposed 2-line fix