Skip to content

Adds environment tests for Newton#4862

Merged
kellyguo11 merged 37 commits intoisaac-sim:developfrom
kellyguo11:add-newton-env-tests
Mar 10, 2026
Merged

Adds environment tests for Newton#4862
kellyguo11 merged 37 commits intoisaac-sim:developfrom
kellyguo11:add-newton-env-tests

Conversation

@kellyguo11
Copy link
Contributor

@kellyguo11 kellyguo11 commented Mar 7, 2026

Description

Adds environment tests for supported Newton environments running with Newton backend.
Removes explicit definition of ls_iterations and ls_parallel in Newton cfgs as they are no longer needed.
Fixes goal orientation matching for in hand manipulation environment.
Fixes franka cabinet initialization orientation.
Fixes reach environment training - uses a box for table as AssetBase doesn't work with Newton.
Fixes mass randomization indexing.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

Screenshots

Please attach before and after screenshots of the change if applicable.

Checklist

  • I have read and understood the contribution guidelines
  • I have run the pre-commit checks with ./isaaclab.sh --format
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • I have updated the changelog and the corresponding version in the extension's config/extension.toml file
  • I have added my name to the CONTRIBUTORS.md or my name already exists there

@kellyguo11 kellyguo11 requested a review from Mayankm96 as a code owner March 7, 2026 00:01
@kellyguo11 kellyguo11 marked this pull request as draft March 7, 2026 00:01
@github-actions github-actions bot added the isaac-lab Related to Isaac Lab team label Mar 7, 2026
Signed-off-by: Kelly Guo <kellyg@nvidia.com>
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 7, 2026

Greptile Summary

This PR adds Newton physics backend support for environment testing by introducing test_environments_newton.py, refactoring preset application logic in parse_cfg.py, and fixing several environment-specific bugs to make them Newton-compatible. It also expands gravity randomization to Newton via a new backend-agnostic SimulationContext.set_gravity() API, and removes deprecated ls_iterations/ls_parallel solver parameters across many Newton configs.

Key changes:

  • New Newton test harness: setup_environment(newton_envs=True) filters to only environments that declare a Newton preset; _check_random_actions applies the named preset at test time using the new apply_named_preset() utility.
  • Bug fixes: corrects the rotation_distance quaternion slice in inhand_manipulation_env.py (IsaacLab uses (x,y,z,w) so [:, 0:3] is the vector part, not [:, 1:4]); fixes the non-unit Franka cabinet init quaternion (0.1,0,0,0) → identity (0,0,0,1); and fixes mass/inertia randomization to pass correctly-indexed partial tensors.
  • Gravity refactor: replaces direct carb/PhysX-only gravity calls in events.py with env.sim.set_gravity(), which delegates through the new PhysicsManager.set_gravity() override contract to both PhysxManager and NewtonManager.
  • Reach env table: replaces the static USD table asset with a TableCfg preset that spawns a CuboidCfg articulation for Newton (since AssetBase is unsupported) and the original USD table for PhysX.
  • Locomotion joint gears: adds per-backend gear resolution when joint_gears is a dict, but leaves an accidental debug print statement and uses fragile __class__.__name__ string dispatch.

Confidence Score: 4/5

  • Safe to merge after removing the accidental debug print statement in locomotion_env.py; all other changes are well-scoped bug fixes and Newton support additions.
  • The core bug fixes (quaternion slice, non-unit quaternion, mass indexing, gravity abstraction) are correct and well-targeted. The new Newton test infrastructure is sound. The only blocking issue is a leftover debug print statement in locomotion_env.py that will produce unwanted output in every training run that uses a dict-valued joint_gears config. The fragile class.name dispatch is a style concern but not a runtime blocker for current class names.
  • source/isaaclab_tasks/isaaclab_tasks/direct/locomotion/locomotion_env.py — contains debug print statement that should be removed before merge.

Important Files Changed

Filename Overview
source/isaaclab_tasks/isaaclab_tasks/direct/locomotion/locomotion_env.py Adds physics-aware joint gear resolution for Newton/PhysX. Contains an accidental debug print statement and uses fragile string-based class dispatch instead of isinstance().
source/isaaclab/isaaclab/envs/mdp/events.py Fixes mass/inertia randomization to pass correctly-indexed partial tensors to backend APIs, and replaces direct carb/PhysX gravity calls with backend-agnostic env.sim.set_gravity().
source/isaaclab_tasks/isaaclab_tasks/direct/inhand_manipulation/inhand_manipulation_env.py Fixes rotation_distance by slicing quat_diff[:, 0:3] (correct x,y,z vector part in xyzw convention) instead of the erroneous [:, 1:4] slice that included w and excluded x.
source/isaaclab_tasks/isaaclab_tasks/utils/parse_cfg.py Adds apply_named_preset() to let tests apply non-default physics presets after parse_env_cfg resolves wrappers; recursive call passes resolved as both env_cfg and raw_cfg which may cause in-place mutation across calls (noted in prior threads).
source/isaaclab/isaaclab/sim/simulation_context.py Adds set_gravity() method delegating to the active physics backend and normalizes "cuda" → "cuda:" device string at init time; the int() cast on the cudaDevice setting was flagged in prior threads.
source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/reach/reach_env_cfg.py Replaces static AssetBaseCfg table with a TableCfg preset: USD table for PhysX and a CuboidCfg articulation for Newton (since AssetBase doesn't work with Newton). Also bumps njmax from 30 to 50 and removes deprecated ls_iterations/ls_parallel.
source/isaaclab_tasks/test/test_environments_newton.py New test file that exercises all environments with a Newton physics preset, parametrized over 1 and 2 env counts, filtered to only environments that declare a newton preset.
source/isaaclab_tasks/test/env_test_utils.py Adds newton_envs filter to setup_environment() and physics_preset_name support to _check_random_actions(); loads raw config a second time per Newton test run to retrieve unresolved preset (noted as a double-load in prior threads).

Sequence Diagram

sequenceDiagram
    participant Test as test_environments_newton.py
    participant Utils as env_test_utils.py
    participant ParseCfg as parse_cfg.py
    participant Env as ManagerBased/DirectRLEnv
    participant SimCtx as SimulationContext
    participant PhysMgr as PhysicsManager (Newton/PhysX)

    Test->>Utils: _run_environments(task, device, num_envs, physics_preset_name="newton")
    Utils->>Utils: _check_random_actions(...)
    Utils->>ParseCfg: parse_env_cfg(task_name) → resolved env_cfg (default preset)
    Utils->>ParseCfg: load_cfg_from_registry(task_name) → raw_cfg (PresetCfg wrappers intact)
    Utils->>ParseCfg: apply_named_preset(env_cfg, raw_cfg, "newton")
    Note over ParseCfg: Walks raw_cfg fields, replaces<br/>preset wrappers in env_cfg with<br/>the named preset value
    Utils->>Env: gym.make(task_name, env_cfg=env_cfg)
    Env->>SimCtx: SimulationContext(cfg.sim)
    SimCtx->>PhysMgr: initialize Newton backend
    Env->>Env: step(random_actions) × N
    Note over Env: Gravity randomization calls<br/>env.sim.set_gravity(g)
    Env->>SimCtx: set_gravity(gravity)
    SimCtx->>PhysMgr: set_gravity(gravity)
    PhysMgr-->>SimCtx: gravity applied
    Env-->>Utils: obs, reward, done, info
    Utils->>Env: env.close()
Loading

Last reviewed commit: ed34c01


# Note: Semantic Versioning is used: https://semver.org/
version = "0.5.1"
version = "0.5.2"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Version skips 0.4.1 in extension.toml

The extension.toml jumps from 0.4.0 directly to 0.4.2. The CHANGELOG already contains a 0.4.1 (2026-03-03) entry (added in a prior PR), but its corresponding extension.toml bump was never committed. As a result the package version is permanently out of sync with its own changelog history. Consider whether 0.4.1 should have been released separately (and the toml fixed retroactively) or if the two changelog entries should be collapsed under 0.4.2.

Suggested change
version = "0.5.2"
version = "0.4.2"

(The line content is already correct for this PR's intent; the concern is the missing intermediate 0.4.1 bump that should have been applied in the previous PR.)

# device index. PhysxManager does the same via /physics/cudaDevice setting.
device = PhysicsManager._device
if "cuda" in device and ":" not in device:
cuda_device = sim_context.get_setting("/physics/cudaDevice")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

int() cast may raise ValueError on float-valued setting

sim_context.get_setting("/physics/cudaDevice") can return a float (e.g., 0.0) in some Isaac Sim versions/configurations. Calling int(cuda_device) directly on a float string like "0.0" raises ValueError. A round-trip through float makes this robust without any behaviour change for integer values:

Suggested change
cuda_device = sim_context.get_setting("/physics/cudaDevice")
device_id = max(0, int(float(cuda_device)) if cuda_device is not None else 0)

Comment on lines 325 to +342
env_cfg = parse_env_cfg(task_name, device=device, num_envs=num_envs)
# apply physics preset override before creating the environment
if physics_preset_name is not None:
# parse_env_cfg already resolved PresetCfg wrappers to their default,
# so we load the raw config to retrieve the named preset.
raw_cfg = load_cfg_from_registry(task_name, "env_cfg_entry_point")
# Unwrap if the top-level cfg is itself a PresetCfg wrapper.
raw_env_cfg = raw_cfg
if (
not isinstance(raw_cfg, dict)
and hasattr(raw_cfg, "__dataclass_fields__")
and hasattr(raw_cfg, "default")
and not hasattr(type(raw_cfg), "class_type")
):
raw_env_cfg = raw_cfg.default
# Apply the named preset to all preset wrappers in the config tree
# (e.g. sim.physics, scene.contact_forces, ...), not just sim.physics.
apply_named_preset(env_cfg, raw_env_cfg, physics_preset_name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Config loaded twice per Newton test run

parse_env_cfg (line 325) internally calls load_cfg_from_registry, and then a second call to load_cfg_from_registry is made here (line 330) to obtain the unresolved config for preset application. For large, complex environments this doubles the module-import and object-construction cost every time a Newton test runs.

A cleaner approach would be to either:

  1. Extend parse_env_cfg to accept an optional physics_preset_name argument and apply the preset before returning, or
  2. Load the raw config once and derive the resolved config from it (by calling _resolve_presets_to_default on a copy).

This does not affect correctness, but is worth addressing before this pattern is repeated for other preset types.

if hasattr(raw_value, preset_name):
resolved = getattr(raw_value, preset_name)
setattr(env_cfg, field_name, resolved)
apply_named_preset(resolved, resolved, preset_name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recursive call mutates the original preset object in-place

When a preset is found and applied, the recursion passes resolved as both env_cfg and raw_cfg:

apply_named_preset(resolved, resolved, preset_name)

Because resolved is the same object reference that lives inside raw_value.<preset_name>, any in-place mutations made by the recursive call (e.g., resolving nested PresetCfg fields on resolved) are written back to the original config class attribute. In tests this is harmless because each test call reloads the config via load_cfg_from_registry, but if the same resolved config object were reused across multiple calls the mutations would accumulate unexpectedly.

The same pattern appears on line 99 (old-style preset branch). Consider passing a shallow copy of resolved to isolate mutations:

import copy
apply_named_preset(resolved, copy.copy(resolved), preset_name)

@github-actions github-actions bot added the asset New asset feature or request label Mar 8, 2026
Copy link
Collaborator

@AntoineRichard AntoineRichard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't get Cassie to train yet, but I think with the attached settings Ant and Humanoid do train well. The Ant mods also work well with PhysX.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not longer be needed with #4890.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should no longer be needed with: #4890

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should no longer be needed with: #4890

Comment on lines +108 to +115
# Normalize "cuda" -> "cuda:<id>" so torch.cuda.set_device() gets an explicit
# device index. PhysxManager does the same via /physics/cudaDevice setting.
device = PhysicsManager._device
if "cuda" in device and ":" not in device:
cuda_device = sim_context.get_setting("/physics/cudaDevice")
device_id = max(0, int(cuda_device) if cuda_device is not None else 0)
PhysicsManager._device = f"cuda:{device_id}"

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we handle that at the config level?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@configclass
class AntPhysicsCfg(PresetCfg):
    default: PhysxCfg = PhysxCfg()
    physx: PhysxCfg = PhysxCfg()
    newton: NewtonCfg = NewtonCfg(
        solver_cfg=MJWarpSolverCfg(
            njmax=45,
            nconmax=25,
            cone="pyramidal",
            integrator="implicitfast",
            impratio=1,
        ),
        num_substeps=1,
        debug_mode=False,
    )

Comment on lines 48 to 54
"body": ImplicitActuatorCfg(
joint_names_expr=[".*"],
stiffness=0.0,
damping=0.0,
damping=0.5,
effort_limit_sim=30.0,
),
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    actuators={
        "body": ImplicitActuatorCfg(
            joint_names_expr=[".*"],
            stiffness=0.0,
            damping=0.1,
            armature=0.02,
            effort_limit_sim=15.0,
        ),
    },

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Joint ordering matters for humanoid:

    physx_joint_gears: list = [
        67.5000,  # lower_waist
        67.5000,  # lower_waist
        67.5000,  # right_upper_arm
        67.5000,  # right_upper_arm
        67.5000,  # left_upper_arm
        67.5000,  # left_upper_arm
        67.5000,  # pelvis
        45.0000,  # right_lower_arm
        45.0000,  # left_lower_arm
        45.0000,  # right_thigh: x
        135.0000,  # right_thigh: y
        45.0000,  # right_thigh: z
        45.0000,  # left_thigh: x
        135.0000,  # left_thigh: y
        45.0000,  # left_thigh: z
        90.0000,  # right_knee
        90.0000,  # left_knee
        22.5,  # right_foot
        22.5,  # right_foot
        22.5,  # left_foot
        22.5,  # left_foot
    ]
    newton_joint_gears: list = [
        67.5000,  # left_upper_arm
        67.5000,  # left_upper_arm
        45.0000,  # left_lower_arm
        67.5000,  # lower_waist
        67.5000,  # lower_waist
        67.5000,  # pelvis
        45.0000,  # left_thigh: x
        135.0000,  # left_thigh: y
        45.0000,  # left_thigh: z
        90.0000,  # left_knee
        22.5,  # left_foot
        22.5,  # left_foot
        45.0000,  # right_thigh: x
        135.0000,  # right_thigh: y
        45.0000,  # right_thigh: z
        90.0000,  # right_knee
        22.5,  # right_foot
        22.5,  # right_foot
        67.5000,  # right_upper_arm
        67.5000,  # right_upper_arm
        45.0000,  # right_lower_arm
    ]
    joint_gears = {
        "physx": physx_joint_gears,
        "newton": newton_joint_gears,
    }

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also needed in the locomotion env:

        # Resolve the joint gears based on the physics type, since they do not have the same joint ordering.
        if isinstance(self.cfg.joint_gears, dict):
            print(self.cfg.sim.physics)
            if self.cfg.sim.physics.__class__.__name__ == "PhysxCfg":
                joint_gears = self.cfg.joint_gears["physx"]
            elif self.cfg.sim.physics.__class__.__name__ == "NewtonCfg":
                joint_gears = self.cfg.joint_gears["newton"]
            else:
                raise ValueError(f"Invalid physics type: {self.cfg.sim.physics}")
        else:
            joint_gears = self.cfg.joint_gears
        self.joint_gears = torch.tensor(joint_gears, dtype=torch.float32, device=self.sim.device)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could make the material API for newton, we've had that working in dev/newton.

njmax=200,
nconmax=70,
impratio=10.0,
cone="elliptic",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need elliptic for it to work. It usually makes the solver less stable.

@kellyguo11 kellyguo11 marked this pull request as ready for review March 9, 2026 22:25
self.joint_gears = torch.tensor(self.cfg.joint_gears, dtype=torch.float32, device=self.sim.device)
# Resolve the joint gears based on the physics type, since they do not have the same joint ordering.
if isinstance(self.cfg.joint_gears, dict):
print(self.cfg.sim.physics)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statement left in production code

print(self.cfg.sim.physics) on this line is a debug statement that was accidentally left in. It will produce noise in every run where joint_gears is a dict, polluting logs with the full physics config object repr.

Suggested change
print(self.cfg.sim.physics)
# Resolve the joint gears based on the physics type, since they do not have the same joint ordering.
if isinstance(self.cfg.joint_gears, dict):

Comment on lines +81 to +86
if self.cfg.sim.physics.__class__.__name__ == "PhysxCfg":
joint_gears = self.cfg.joint_gears["physx"]
elif self.cfg.sim.physics.__class__.__name__ == "NewtonCfg":
joint_gears = self.cfg.joint_gears["newton"]
else:
raise ValueError(f"Invalid physics type: {self.cfg.sim.physics}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fragile physics-type dispatch via __class__.__name__ string comparison

Comparing class names as strings breaks silently if either config class is ever renamed, subclassed, or imported under a different alias. Using isinstance() is the idiomatic, refactor-safe approach.

For example:

from isaaclab_physx.physics import PhysxCfg
from isaaclab_newton.physics import NewtonCfg

if isinstance(self.cfg.sim.physics, PhysxCfg):
    joint_gears = self.cfg.joint_gears["physx"]
elif isinstance(self.cfg.sim.physics, NewtonCfg):
    joint_gears = self.cfg.joint_gears["newton"]
else:
    raise ValueError(f"Invalid physics type: {self.cfg.sim.physics}")

This also properly handles subclasses of PhysxCfg or NewtonCfg without additional changes.

@kellyguo11 kellyguo11 requested a review from jtigue-bdai as a code owner March 9, 2026 22:49
@github-actions github-actions bot added the documentation Improvements or additions to documentation label Mar 9, 2026
@kellyguo11 kellyguo11 merged commit 1507d97 into isaac-sim:develop Mar 10, 2026
10 checks passed
kellyguo11 added a commit to kellyguo11/IsaacLab-public that referenced this pull request Mar 10, 2026
Adds environment tests for supported Newton environments running with
Newton backend.
Removes explicit definition of ls_iterations and ls_parallel in Newton
cfgs as they are no longer needed.
Fixes goal orientation matching for in hand manipulation environment.
Fixes franka cabinet initialization orientation.
Fixes reach environment training - uses a box for table as AssetBase
doesn't work with Newton.
Fixes mass randomization indexing.

<!-- As you go through the list, delete the ones that are not
applicable. -->

- Bug fix (non-breaking change which fixes an issue)
- New feature (non-breaking change which adds functionality)

Please attach before and after screenshots of the change if applicable.

<!--
Example:

| Before | After |
| ------ | ----- |
| _gif/png before_ | _gif/png after_ |

To upload images to a PR -- simply drag and drop an image while in edit
mode and it should upload the image directly. You can then paste that
source into the above before/after sections.
-->

- [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
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file
- [ ] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there

<!--
As you go through the checklist above, you can mark something as done by
putting an x character in it

For example,
- [x] I have done this task
- [ ] I have not done this task
-->

---------

Signed-off-by: Kelly Guo <kellyg@nvidia.com>
Co-authored-by: Antoine Richard <antoiner@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

asset New asset feature or request documentation Improvements or additions to documentation infrastructure isaac-lab Related to Isaac Lab team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants