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
1 change: 1 addition & 0 deletions changelog/4109.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Raise a `ValueError` from `SolverMuJoCo.step()` on the MuJoCo Warp backend when `disable_sensors=True` and the output state requests `body_qdd` or `body_parent_f`, instead of publishing stale values.
12 changes: 11 additions & 1 deletion newton/_src/solvers/mujoco/solver_mujoco.py
Original file line number Diff line number Diff line change
Expand Up @@ -3763,7 +3763,7 @@ def __init__(
nvmax: Maximum number of active degrees of freedom per world when sleeping is enabled. Must accommodate every initially awake degree of freedom. If None, allocates space for every degree of freedom, which is safe but provides no compact-solver memory savings.
sleep_tolerance: Sleep velocity tolerance. If None, uses model custom attribute or MuJoCo default (0.001).
disable_contacts: If True, disable contact computation in MuJoCo.
disable_sensors: If True, disable sensor computation in MuJoCo.
disable_sensors: If True, disable sensor computation in MuJoCo. On the MuJoCo Warp backend, :meth:`step` raises ``ValueError`` if the output state requests ``body_qdd`` or ``body_parent_f``, which MuJoCo computes inside the sensor stage.
update_data_interval: Frequency (in simulation steps) at which to update the MuJoCo Data object from the Newton state. If 0, Data is never updated after initialization.
save_to_mjcf: Optional path to save the generated MJCF model file.
use_mujoco_contacts: If True, use the MuJoCo contact solver. If False, use the Newton contact solver (newton contacts must be passed in through the step function in that case).
Expand Down Expand Up @@ -4158,6 +4158,7 @@ def step(self, state_in: State, state_out: State, control: Control, contacts: Co
self._mujoco.mj_step(self.mj_model, self.mj_data)
self._update_newton_state(self.model, state_out, self.mj_data, state_prev=state_in)
else:
self._validate_rne_postconstraint(state_out)
with wp.ScopedDevice(self.model.device), self._scoped_mujoco_warp_execution():
self._enable_rne_postconstraint(state_out)
self._apply_mjc_control(self.model, state_in, control, self.mjw_data)
Expand Down Expand Up @@ -4431,6 +4432,15 @@ def _wake_sleeping_worlds(
device=self.model.device,
)

def _validate_rne_postconstraint(self, state_out: State):
"""Reject state fields whose post-constraint RNE stage is disabled."""
if self.mj_model.opt.disableflags & self._mujoco.mjtDisableBit.mjDSBL_SENSOR and (
state_out.body_qdd is not None or state_out.body_parent_f is not None
):
raise ValueError(
"disable_sensors=True is incompatible with requested body_qdd or body_parent_f state attributes."
)

def _enable_rne_postconstraint(self, state_out: State):
"""Request computation of RNE forces if required for state fields."""
rne_postconstraint_fields = {"body_qdd", "body_parent_f"}
Expand Down
18 changes: 18 additions & 0 deletions newton/tests/test_mujoco_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -8029,6 +8029,24 @@ def test_iterations_constructor_override(self):
self.assertEqual(solver.mj_model.opt.iterations, 5, "Constructor value should override custom attribute")
self.assertEqual(solver.mj_model.opt.ls_iterations, 3, "Constructor value should override custom attribute")

def test_disable_sensors_rejects_rne_state_attributes(self):
"""Reject disabled sensors when RNE-derived state attributes are requested."""
for attribute in ("body_qdd", "body_parent_f"):
with self.subTest(attribute=attribute):
model = self._create_multiworld_model(world_count=1)
solver = SolverMuJoCo(model, disable_sensors=True)
model.request_state_attributes(attribute)
state = model.state()
with self.assertRaisesRegex(ValueError, "disable_sensors"):
solver.step(state, state, None, None, 0.01)

def test_disable_sensors_allows_unrelated_state_attributes(self):
"""Step with disabled sensors when no RNE-derived state attribute is requested."""
model = self._create_multiworld_model(world_count=1)
solver = SolverMuJoCo(model, disable_sensors=True)
state_in, state_out = model.state(), model.state()
solver.step(state_in, state_out, model.control(), None, 0.01)

def test_enable_multiccd_default_off(self):
"""Verify that multi-CCD is disabled by default (Newton default differs from MuJoCo 3.8+)."""
model = self._create_multiworld_model(world_count=1)
Expand Down
Loading