Skip to content

Commit 3748a72

Browse files
authored
[Fix] Make --deterministic reproduce training runs by configuring the physics backend (#7334)
# Description `--deterministic` configured PyTorch and the Isaac RTX renderer but never reached the physics solver, so training on Newton backends was not reproducible even with the flag passed. Two defaults moved out from under the flag between 3.0beta2 and GA: | Default | v3.0.0-beta2 | GA | |---|---|---| | `CartpolePhysicsCfg.default` | `PhysxCfg()` | `NewtonCfg` MJWarp, `deterministic_mode="not_guaranteed"` | | `MultiBackendRendererCfg.default` | `IsaacRtxRendererCfg()` | `NewtonWarpRendererCfg()` | The physics switch landed in `0caae64dc7c` (#7066), absent from all three `v3.0.0-beta*` tags. At beta2 the flag worked because PhysX is run-to-run deterministic for rigid bodies **and** Isaac RTX was the default renderer; both premises were removed without re-wiring the flag. `--deterministic` now sets `PhysicsCfg.deterministic` on the resolved physics config, in `apply_env_overrides()` — the existing CLI-to-cfg seam, after `scan()` resolves the backend and before the solver is built. That field is the backend-agnostic request; each physics manager translates it when the simulation starts: - **Newton** derives `deterministic_mode="run_to_run"`, applies the MJWarp `disable_sensors` prerequisite on the GPU path, and leaves MuJoCo-CPU alone. An explicitly set `deterministic_mode` wins. - **PhysX / OvPhysX** enable `enable_enhanced_determinism`. OvPhysX is best-effort and not verified end to end. Validation stays with the backend: `NewtonManager._validate_deterministic_solver_cfg()` rejects an unsupported solver at solver initialization, so there is one policy and one set of error messages rather than a copy in the RL layer. Adding a backend no longer means editing `isaaclab_rl`. **A determinism request that would starve a sensor is now refused.** Disabling MuJoCo Warp's sensors also skips its `rne_postconstraint` stage, which fills Newton's `body_qdd` / `body_parent_f`. The IMU, PVA and joint-wrench sensors read that state, so `Isaac-Ant`, `Isaac-Humanoid` and `Isaac-Repose-Cube-Shadow` — all defaulting to `newton_mjwarp` and feeding `joint_wrench` into their policy observations — would have trained on values that are never refreshed, with no error. `NewtonManager` raises at solver initialization, the only point where both the solver config and the registered sensors are visible. The guard restates `{"body_qdd", "body_parent_f"}`, which Newton also states internally (`solver_mujoco.py:4360` as a set, `:5179` as an equivalent `or`-chain). That duplication is tracked upstream in newton-physics/newton#4109, which asks for two things: Newton refusing the combination at the source, and exporting the field set as a public constant. Either lets this guard shrink — the constant and its sensor-tracking delete entirely once Isaac Lab pins a Newton that raises. Until then the guard is what prevents the silent case, so it stays. Fixes NVBug 6658578 (P0, Isaac Lab 3.0 GA). ## Type of change - Bug fix (non-breaking change which fixes an issue) ## Validation Three `--deterministic` runs of `Isaac-Cartpole-Camera` (50 epochs, task defaults) on one L40, each in its own container: ``` det1 vs det2 55/55 checkpoint tensors bitwise identical det1 vs det3 55/55 checkpoint tensors bitwise identical ctrl1 vs ctrl2 41/55 differ <- same task, no flag ``` The unflagged controls diverge, so the agreement above is the flag's doing rather than a task that is trivially reproducible. `Isaac-Ant --deterministic` fails at startup with the sensor message. MuJoCo-CPU could not be exercised: `SolverMuJoCo.get_max_contact_count()` raises `NotImplementedError` on that path, so it is unreachable in Isaac Lab today. 8 unit tests added; 200 pass across `test_entrypoints_common.py` and `test_newton_manager_abstraction.py`. ## 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
1 parent d592423 commit 3748a72

15 files changed

Lines changed: 345 additions & 13 deletions

File tree

docs/source/features/reproducibility.rst

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ simulation results are reproducible across different runs. The seed is set into
2020
parameters :attr:`isaaclab.envs.ManagerBasedEnvCfg.seed` or :attr:`isaaclab.envs.DirectRLEnvCfg.seed`
2121
depending on the manager-based or direct environment implementation respectively.
2222

23-
App-level deterministic rendering via ``AppLauncher``
24-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
23+
The ``--deterministic`` flag
24+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2525

2626
The ``--deterministic`` flag is provided by :meth:`isaaclab.app.AppLauncher.add_app_launcher_args`.
2727
:class:`~isaaclab.app.app_launcher.AppLauncher` publishes ``/isaaclab/render/deterministic``.
@@ -33,11 +33,19 @@ The Isaac RTX backend reads it on init and applies
3333
for **RL-Games**, **skrl**, **RSL-RL**, and **Stable-Baselines3**: each calls
3434
:meth:`~isaaclab.utils.seed.configure_seed` after constructing its framework runner or agent object
3535
so library initialization is not disturbed, then training proceeds with the requested global RNG and
36-
optional PyTorch deterministic algorithms. Whether you need ``--deterministic`` at the app level
37-
depends on the workload: **physics-only** simulation does not require it; **RTX** rendering
38-
(non-minimal mode) does require it for reproducible imagery; **Newton** rendering does not require it.
39-
40-
Pass ``--deterministic`` to enable reproducible rendering from the app launcher. (Isaac RTX only)
36+
optional PyTorch deterministic algorithms. Whether the **rendering** half of the flag matters depends
37+
on the workload: **physics-only** simulation does not render at all; **RTX** rendering (non-minimal
38+
mode) needs it for reproducible imagery; **Newton** rendering is already deterministic.
39+
40+
**Physics determinism** comes from the same flag in the Isaac Lab RL training entrypoints, which
41+
set :attr:`~isaaclab.physics.PhysicsCfg.deterministic` on the backend resolved by presets. That
42+
field is the backend-agnostic request: each physics manager translates it into its own settings
43+
when the simulation starts, and raises when its configuration cannot provide the guarantee. Newton
44+
selects ``deterministic_mode="run_to_run"`` and applies the MJWarp prerequisite; PhysX and OvPhysX
45+
enable enhanced determinism, which is best-effort on OvPhysX and not verified end to end. Set
46+
:attr:`~isaaclab.physics.PhysicsCfg.deterministic` directly to get the same behavior from a script
47+
that does not use the RL entrypoints; ``--deterministic`` alone configures Torch and rendering
48+
only.
4149

4250
.. tab-set::
4351

@@ -66,7 +74,26 @@ ordering in its collision pipeline. Deterministic execution can increase
6674
memory use and reduce simulation performance. MJWarp on the GPU with
6775
:attr:`isaaclab_newton.physics.MJWarpSolverCfg.disable_sensors` set to ``True``,
6876
XPBD, and Featherstone are supported; selecting an unsupported solver raises
69-
an error.
77+
an error. MuJoCo on the CPU
78+
(:attr:`isaaclab_newton.physics.MJWarpSolverCfg.use_mujoco_cpu`) is already
79+
reproducible and is left unchanged. Set this attribute directly to request the
80+
stronger ``"gpu_to_gpu"`` guarantee, which takes precedence over
81+
:attr:`isaaclab.physics.PhysicsCfg.deterministic`. Setting it to
82+
``"not_guaranteed"`` does not opt out, because that is indistinguishable from
83+
the default; clear :attr:`isaaclab.physics.PhysicsCfg.deterministic` instead.
84+
85+
.. note::
86+
87+
``disable_sensors`` is required rather than optional: MuJoCo Warp's tactile
88+
sensor kernel applies two atomic reduction families to one output array, which
89+
Warp's deterministic code generation cannot lower, so the sensor module fails
90+
to compile under a determinism guarantee. Disabling it also skips the
91+
``rne_postconstraint`` stage, which fills the Newton ``body_qdd`` and
92+
``body_parent_f`` state. The IMU, PVA, and joint-wrench sensors read that
93+
state, so Newton raises rather than let them report stale values: remove those
94+
sensors, or drop the determinism request. Integrations
95+
that consume native MJWarp sensor outputs directly are affected too, and are
96+
not detected.
7097

7198
.. warning::
7299

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Added
2+
^^^^^
3+
4+
* Added :attr:`~isaaclab.physics.PhysicsCfg.deterministic`, a backend-agnostic request for
5+
reproducible physics. Each physics manager translates it into its own settings when the
6+
simulation starts and raises when its configuration cannot provide the guarantee. A
7+
backend-specific determinism attribute set explicitly takes precedence.

source/isaaclab/isaaclab/physics/physics_manager_cfg.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,19 @@ class PhysicsCfg:
3434
class_type: type[PhysicsManager] | Any = MISSING
3535
"""The physics manager class to use. Must be set by subclasses."""
3636

37+
deterministic: bool = False
38+
"""Whether to request reproducible physics from the backend. Defaults to False.
39+
40+
This is the backend-agnostic form of the request, set by the ``--deterministic`` command-line
41+
flag. Each physics manager translates it into its own settings when the simulation starts, and
42+
raises when its configuration cannot provide the guarantee. A backend-specific determinism
43+
attribute set explicitly, such as
44+
:attr:`~isaaclab_newton.physics.NewtonCfg.deterministic_mode`, is the more specific instruction
45+
and takes precedence.
46+
47+
Deterministic execution can increase memory use and reduce simulation performance.
48+
"""
49+
3750

3851
@configclass
3952
class PhysxAutoCfg(PhysicsCfg):
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Added
2+
^^^^^
3+
4+
* Added translation of :attr:`~isaaclab.physics.PhysicsCfg.deterministic` in ``NewtonManager``.
5+
The request selects ``deterministic_mode="run_to_run"`` and sets ``MJWarpSolverCfg.disable_sensors``
6+
on the MJWarp GPU path. An explicitly set ``deterministic_mode`` takes precedence. MuJoCo on the
7+
CPU is left unchanged: Warp's deterministic mode does not reach that path, and the request is
8+
logged instead of applied.
9+
10+
Fixed
11+
^^^^^
12+
13+
* Fixed a determinism request silently starving the IMU, PVA, and joint-wrench sensors. Disabling
14+
MuJoCo Warp's sensors also skips the ``rne_postconstraint`` stage that fills ``body_qdd`` and
15+
``body_parent_f``, so those sensors reported stale values. ``NewtonManager`` now raises at solver
16+
initialization when a scene requests both.

source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,16 @@ def _paused_gc():
121121
from isaaclab_newton.physics.newton_collision_cfg import NewtonCollisionPipelineCfg
122122

123123

124+
_SENSORS_BY_STATE_ATTRIBUTE = {
125+
"body_qdd": "the IMU or PVA sensor",
126+
"body_parent_f": "the joint-wrench sensor",
127+
}
128+
"""Which Isaac Lab sensors read each state attribute that MuJoCo's sensor stage fills."""
129+
130+
_SENSOR_STAGE_STATE_ATTRIBUTES = frozenset(_SENSORS_BY_STATE_ATTRIBUTE)
131+
"""Extended state attributes that MuJoCo Warp's sensor stage fills via ``rne_postconstraint``."""
132+
133+
124134
def _compile_label_pattern(expr: str | list[str] | None) -> re.Pattern[str] | None:
125135
"""Compile selector expressions for Newton's full label matching."""
126136
if not expr:
@@ -433,6 +443,7 @@ class NewtonManager(PhysicsManager):
433443
_newton_frame_transform_sensors: list = [] # List of SensorFrameTransform
434444
_newton_imu_sensors: list = [] # List of NewtonSensorIMU
435445
_pending_extended_state_attributes: set[str] = set()
446+
_active_extended_state_attributes: set[str] = set()
436447
_pending_extended_contact_attributes: set[str] = set()
437448
_report_contacts: bool = False
438449
_supports_contact_sensors: bool = True
@@ -1188,6 +1199,7 @@ def clear(cls):
11881199
NewtonManager._world_xforms = None
11891200
NewtonManager._cl_protos = {}
11901201
NewtonManager._pending_extended_state_attributes = set()
1202+
NewtonManager._active_extended_state_attributes = set()
11911203
NewtonManager._pending_extended_contact_attributes = set()
11921204
for key in [key for key in NewtonManager.views if key[0] is NewtonManager]:
11931205
del NewtonManager.views[key]
@@ -1589,9 +1601,12 @@ def start_simulation(cls) -> None:
15891601
device = PhysicsManager._device
15901602
logger.info(f"Finalizing model on device: {device}")
15911603
cls._builder.up_axis = Axis.from_string(cls._up_axis)
1592-
# Forward pending extended attribute requests to builder and clear them
1604+
# Forward pending extended attribute requests to builder and clear them. The requests are
1605+
# retained because initialize_solver() runs afterwards and must know which sensors depend
1606+
# on state that MuJoCo's sensor stage fills.
15931607
if cls._pending_extended_state_attributes:
15941608
cls._builder.request_state_attributes(*cls._pending_extended_state_attributes)
1609+
NewtonManager._active_extended_state_attributes |= cls._pending_extended_state_attributes
15951610
NewtonManager._pending_extended_state_attributes = set()
15961611
cls._prepare_builder_for_finalize(cls._builder)
15971612
with Timer(name="newton_finalize_builder", msg="Finalize builder took:", activity="Finalizing physics model"):
@@ -2111,9 +2126,9 @@ def _filter_solver_kwargs(solver_cls: type, solver_cfg) -> dict:
21112126
kwargs["deterministic"] = NewtonManager._deterministic_mode
21122127
return kwargs
21132128

2114-
@staticmethod
2129+
@classmethod
21152130
def _validate_deterministic_solver_cfg(
2116-
solver_cfg: NewtonSolverCfg, deterministic_mode: wp.DeterministicMode
2131+
cls, solver_cfg: NewtonSolverCfg, deterministic_mode: wp.DeterministicMode
21172132
) -> None:
21182133
"""Validate that a solver can provide the requested determinism guarantee."""
21192134
if deterministic_mode == wp.DeterministicMode.NOT_GUARANTEED:
@@ -2135,6 +2150,51 @@ def _validate_deterministic_solver_cfg(
21352150
"internal sensor computation is enabled. Set MJWarpSolverCfg.disable_sensors=True or disable "
21362151
"deterministic mode."
21372152
)
2153+
blocked = cls._active_extended_state_attributes & _SENSOR_STAGE_STATE_ATTRIBUTES
2154+
if isinstance(solver_cfg, MJWarpSolverCfg) and blocked:
2155+
sensors = sorted({_SENSORS_BY_STATE_ATTRIBUTE[attr] for attr in blocked})
2156+
raise ValueError(
2157+
f"This task does not support deterministic physics: it uses {' and '.join(sensors)},"
2158+
f" reading {sorted(blocked)}. Those attributes come from MuJoCo's post-constraint pass,"
2159+
" which runs inside the sensor stage that a determinism guarantee must disable, so the"
2160+
" values would never be refreshed. Remove the sensors, or drop the determinism request"
2161+
f" (deterministic_mode={deterministic_mode.name})."
2162+
)
2163+
2164+
@classmethod
2165+
def _apply_deterministic_request(cls, cfg: NewtonCfg) -> wp.DeterministicMode:
2166+
"""Translate the backend-agnostic determinism request into Newton settings.
2167+
2168+
:attr:`~isaaclab.physics.PhysicsCfg.deterministic` is the generic request. An explicitly
2169+
set :attr:`~isaaclab_newton.physics.NewtonCfg.deterministic_mode` is the more specific
2170+
instruction and wins. MuJoCo on the CPU is already reproducible and Warp's deterministic
2171+
mode does not reach it, so no mode is applied there and the request is logged instead.
2172+
2173+
Args:
2174+
cfg: Resolved Newton configuration.
2175+
2176+
Returns:
2177+
The deterministic mode to apply to the solver.
2178+
"""
2179+
solver_cfg = cfg.solver_cfg
2180+
# MuJoCo-C is reproducible on its own and Warp's deterministic mode never reaches it, so
2181+
# no Newton setting applies -- report the request rather than dropping it silently.
2182+
if getattr(solver_cfg, "use_mujoco_cpu", False):
2183+
if cfg.deterministic or cfg.deterministic_mode != "not_guaranteed":
2184+
logger.info("MuJoCo CPU backend is already reproducible; Newton's deterministic mode is not applied.")
2185+
return wp.DeterministicMode.NOT_GUARANTEED
2186+
2187+
# Precedence: an explicit mode, then the generic request, then no guarantee. An explicit
2188+
# mode's prerequisites stay the caller's responsibility, so it is returned untouched.
2189+
if cfg.deterministic_mode != "not_guaranteed":
2190+
return cls._resolve_deterministic_mode(cfg.deterministic_mode)
2191+
if not cfg.deterministic:
2192+
return wp.DeterministicMode.NOT_GUARANTEED
2193+
# MuJoCo Warp cannot honour a guarantee while its internal sensor kernels run, so the
2194+
# generic request implies the prerequisite rather than failing on it.
2195+
if isinstance(solver_cfg, MJWarpSolverCfg):
2196+
solver_cfg.disable_sensors = True
2197+
return wp.DeterministicMode.RUN_TO_RUN
21382198

21392199
@staticmethod
21402200
def _resolve_deterministic_mode(deterministic_mode: str) -> wp.DeterministicMode:
@@ -2224,7 +2284,7 @@ def initialize_solver(cls) -> None:
22242284
with Timer(name="newton_initialize_solver", msg="Initialize solver took:", activity="Initializing solver"):
22252285
NewtonManager._num_substeps = cfg.num_substeps # type: ignore[union-attr]
22262286
NewtonManager._collision_decimation = cfg.collision_decimation # type: ignore[union-attr]
2227-
deterministic_mode = cls._resolve_deterministic_mode(cfg.deterministic_mode) # type: ignore[union-attr]
2287+
deterministic_mode = cls._apply_deterministic_request(cfg) # type: ignore[arg-type]
22282288
cls._validate_deterministic_solver_cfg(cfg.solver_cfg, deterministic_mode) # type: ignore[union-attr]
22292289
NewtonManager._deterministic_mode = deterministic_mode
22302290
NewtonManager._solver_dt = cls.get_physics_dt() / cls._num_substeps

source/isaaclab_newton/test/physics/test_newton_manager_abstraction.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
from __future__ import annotations
2727

28+
import logging
2829
from inspect import signature
2930
from types import SimpleNamespace
3031

@@ -1592,3 +1593,86 @@ def test_hard_reset_then_step_runs(use_cuda_graph):
15921593
# A hard device sync surfaces any deferred illegal access as an exception.
15931594
sim.step(render=False)
15941595
wp.synchronize_device("cuda:0")
1596+
1597+
1598+
@pytest.fixture
1599+
def clean_extended_state_attributes():
1600+
"""Isolate the class-level record of sensor-requested state attributes."""
1601+
previous = NewtonManager._active_extended_state_attributes
1602+
NewtonManager._active_extended_state_attributes = set()
1603+
yield
1604+
NewtonManager._active_extended_state_attributes = previous
1605+
1606+
1607+
@pytest.mark.parametrize(
1608+
"solver_cfg, deterministic, expected",
1609+
[
1610+
pytest.param(XPBDSolverCfg(), False, wp.DeterministicMode.NOT_GUARANTEED, id="no_request"),
1611+
pytest.param(XPBDSolverCfg(), True, wp.DeterministicMode.RUN_TO_RUN, id="request_xpbd"),
1612+
pytest.param(MJWarpSolverCfg(), True, wp.DeterministicMode.RUN_TO_RUN, id="request_mjwarp_gpu"),
1613+
# MuJoCo on the CPU is already reproducible; Warp's mode does not reach it.
1614+
pytest.param(
1615+
MJWarpSolverCfg(use_mujoco_cpu=True), True, wp.DeterministicMode.NOT_GUARANTEED, id="request_mujoco_cpu"
1616+
),
1617+
],
1618+
)
1619+
def test_apply_deterministic_request_translates_the_generic_flag(
1620+
solver_cfg, deterministic, expected, clean_extended_state_attributes
1621+
) -> None:
1622+
"""The backend owns translation of :attr:`PhysicsCfg.deterministic` into Newton settings."""
1623+
cfg = NewtonCfg(solver_cfg=solver_cfg, deterministic=deterministic)
1624+
1625+
assert NewtonManager._apply_deterministic_request(cfg) == expected
1626+
1627+
1628+
@pytest.mark.parametrize("mode", ["run_to_run", "gpu_to_gpu"])
1629+
def test_apply_deterministic_request_skips_an_explicit_mode_on_mujoco_cpu(
1630+
mode, clean_extended_state_attributes, caplog
1631+
) -> None:
1632+
"""MuJoCo-C is reproducible on its own, so an explicit mode is reported rather than enforced."""
1633+
cfg = NewtonCfg(solver_cfg=MJWarpSolverCfg(use_mujoco_cpu=True), deterministic_mode=mode)
1634+
1635+
with caplog.at_level(logging.INFO, logger="isaaclab_newton.physics.newton_manager"):
1636+
assert NewtonManager._apply_deterministic_request(cfg) == wp.DeterministicMode.NOT_GUARANTEED
1637+
1638+
assert any("already reproducible" in r.getMessage() for r in caplog.records)
1639+
1640+
1641+
def test_apply_deterministic_request_sets_the_mjwarp_sensor_prerequisite(clean_extended_state_attributes) -> None:
1642+
"""MJWarp on the GPU needs its internal sensors off, so the request implies it."""
1643+
# NewtonCfg copies the nested solver config, so assert on the instance it actually holds.
1644+
cfg = NewtonCfg(solver_cfg=MJWarpSolverCfg(), deterministic=True)
1645+
assert cfg.solver_cfg.disable_sensors is False
1646+
1647+
NewtonManager._apply_deterministic_request(cfg)
1648+
1649+
assert cfg.solver_cfg.disable_sensors is True
1650+
1651+
1652+
def test_apply_deterministic_request_keeps_an_explicit_mode(clean_extended_state_attributes) -> None:
1653+
"""An explicitly requested mode is the more specific instruction and wins."""
1654+
cfg = NewtonCfg(solver_cfg=MJWarpSolverCfg(), deterministic=True, deterministic_mode="gpu_to_gpu")
1655+
1656+
assert NewtonManager._apply_deterministic_request(cfg) == wp.DeterministicMode.GPU_TO_GPU
1657+
1658+
1659+
@pytest.mark.parametrize("attr", ["body_qdd", "body_parent_f"])
1660+
def test_deterministic_mode_rejects_sensors_that_need_the_sensor_stage(attr, clean_extended_state_attributes) -> None:
1661+
"""Disabling MJWarp sensors starves IMU/PVA/joint-wrench, so the request is refused."""
1662+
NewtonManager._active_extended_state_attributes = {attr}
1663+
1664+
with pytest.raises(ValueError, match="does not support deterministic physics"):
1665+
NewtonManager._validate_deterministic_solver_cfg(
1666+
MJWarpSolverCfg(disable_sensors=True), wp.DeterministicMode.RUN_TO_RUN
1667+
)
1668+
1669+
1670+
def test_deterministic_mode_allows_those_sensors_without_a_guarantee(
1671+
clean_extended_state_attributes,
1672+
) -> None:
1673+
"""The sensors are only incompatible with the guarantee, not with MJWarp itself."""
1674+
NewtonManager._active_extended_state_attributes = {"body_qdd"}
1675+
1676+
NewtonManager._validate_deterministic_solver_cfg(
1677+
MJWarpSolverCfg(disable_sensors=True), wp.DeterministicMode.NOT_GUARANTEED
1678+
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Added
2+
^^^^^
3+
4+
* Added translation of :attr:`~isaaclab.physics.PhysicsCfg.deterministic` in ``OvPhysxManager``, which
5+
enables ``physxScene:enableEnhancedDeterminism``. Reproducibility on OvPhysX is best-effort and is
6+
not verified end to end.

source/isaaclab_ov/isaaclab_ov/physics/ovphysx_manager.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1120,8 +1120,10 @@ def _configure_physx_scene_prim(scene_prim, cfg, device: str) -> None:
11201120
scene_prim.CreateAttribute("physxScene:enableSceneQuerySupport", Sdf.ValueTypeNames.Bool).Set(enable_sq)
11211121

11221122
if cfg is not None:
1123+
# OvPhysX answers the backend-agnostic determinism request with enhanced determinism.
1124+
# This is best-effort: reproducibility is not verified end to end.
11231125
scene_prim.CreateAttribute("physxScene:enableEnhancedDeterminism", Sdf.ValueTypeNames.Bool).Set(
1124-
cfg.enable_enhanced_determinism
1126+
cfg.enable_enhanced_determinism or cfg.deterministic
11251127
)
11261128
scene_prim.CreateAttribute("physxScene:enableExternalForcesEveryIteration", Sdf.ValueTypeNames.Bool).Set(
11271129
cfg.enable_external_forces_every_iteration
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Added
2+
^^^^^
3+
4+
* Added translation of :attr:`~isaaclab.physics.PhysicsCfg.deterministic` in ``PhysxManager``, which
5+
enables :attr:`~isaaclab_physx.physics.PhysxCfg.enable_enhanced_determinism`.

source/isaaclab_physx/isaaclab_physx/physics/physx_manager.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -797,8 +797,15 @@ def _configure_physics(cls) -> None:
797797
if bool(sim.get_setting("/isaaclab/has_gui")):
798798
cfg.enable_scene_query_support = True
799799

800+
# PhysX answers the backend-agnostic determinism request with enhanced determinism. An
801+
# explicitly enabled flag stays enabled.
802+
if cfg.deterministic:
803+
cfg.enable_enhanced_determinism = True
804+
800805
# apply remaining cfg attributes to scene (physxScene:*)
801806
skip = {
807+
# generic request, translated above; PhysX has no physxScene:deterministic attribute
808+
"deterministic",
802809
"solver_type",
803810
"enable_ccd",
804811
"solve_articulation_contact_last",

0 commit comments

Comments
 (0)