Skip to content

Accelerate and strengthen asset tests [15/24] - #8148

Closed
AntoineRichard wants to merge 6 commits into
isaac-sim:developfrom
AntoineRichard:antoiner/asset-tests-p15-physx-rigid-object-collection
Closed

AntoineRichard wants to merge 6 commits into
isaac-sim:developfrom
AntoineRichard:antoiner/asset-tests-p15-physx-rigid-object-collection

Conversation

@AntoineRichard

Copy link
Copy Markdown
Collaborator

Description

Part 15/24 of the asset-test series (tracker: #7282). Depends on #8131; the branch contains its commit until it merges.

This PR moves the PhysX rigid-object-collection tests onto one module-scoped scene of locally authored cubes, instead of a Nucleus DexCube scene per test. The scene has two environments of three gravity-free cubes and one single-cube kinematic collection, run on cpu and cuda:0. PhysX stores collection bodies body-major, so writes select non-sorted environment and body subsets and read the view back. Five nodes:

  • Initialization: includes the 1×1 collection; kinematic cubes hold their pose under gravity.
  • State writes: non-sorted partial pose and velocity writes. Every body writer is checked with a COM offset, including the cross-frame consistency, plus the spin kinematics about the COM.
  • Masses: non-sorted partial mass writes, read back from the body-major view.
  • Wrenches: local and global forces, and forces at a position, on every other body; unselected bodies stay at rest.
  • Reset: partial and full composer reset assertions, verbatim.

Both regressions added on develop (test_subset_write_reaches_selected_view_entry and #8131's test_inertial_property_subset_writes_reach_selected_view_entries) are kept verbatim in their own scenes. The inertial one runs on a buffer that has never been read. Reverting any of the three #8131 changes turns it red: the COM float view, the COM cache freshness, and the inertia 3-D reshape.

Disposition

Removed develop test Keeper Proof
test_initialization (1×1, 2×3) initialization assertions carried
test_initialization_with_kinematic_enabled initialization assertions carried
test_external_force_on_single_body, ..._at_position wrenches mutation: is_global negated; torques zeroed
test_set_object_state state writes mutation: partial body pose kernel writes cell (i, j)
test_object_state_properties state writes (spin) mutation: COM→link velocity lever sign flipped
test_write_object_state state writes mutation: COM→link pose offset sign flipped
test_reset_object_collection wrenches verbatim assertions
test_initialization_with_no_rigid_body, test_gravity_vec_w same tests kept, local cubes; the gravity rows are now CUDA only

Timing

Warm Warp cache, repository orchestrator (pytest tools), one process per file, second of two runs, RTX 5090. "Before" is develop at 09b1d54. "Before" includes #8131's test.

File Before After
test_rigid_object_collection.py 13.2 s (40 tests) 5.1 s (15 tests)

Product bug found (not fixed, not pinned)

A full RigidObjectCollection.reset() still leaves the wrench composers active. reset() passes _ALL_ENV_INDICES to WrenchComposer.reset, which takes the partial-reset path. #8009 reported this. The reset node keeps develop's assertions, which do not check active.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Test-only change; no package code changes.

Release backport

  • Backport this pull request to the active release branch after it merges into develop

Checklist

Docker and GPU tests run on demand. Push the commits you want tested, then
comment run-ci on the pull request.

  • I have read and understood the contribution guidelines
  • I have run the pre-commit checks with uv run isaaclab --format
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have validated the changed behavior with focused coverage and applied the test-audit criteria to any test changes
  • I have added a changelog fragment under source/<pkg>/changelog.d/ for every touched package (do not edit CHANGELOG.rst or bump package versions — CI handles that)
  • I have added my name to the CONTRIBUTORS.md or my name already exists there

RigidObjectCollection.set_coms_index passed wp.transformf data to the
PhysX tensor API, which raises a KeyError. It also marked the lazily
filled body_com_pose_b cache as fresh after a partial write, so the
unselected entries read back as zeros when the cache had not been read
before. set_inertias_index flattened the (num_instances, num_bodies, 9)
inertias with the 2D reshape, which the backend rejects.

Pass the COM poses as float32, let the next read refresh the COM cache
from the view as RigidObject does, and reshape the inertias with the 3D
helper. A new test writes both properties to a non-sorted subset of
(env, body) cells and checks the PhysX view and the data buffers.
Share one module-scoped scene of locally authored cubes instead of a
Nucleus scene per test. Partial state, mass, and wrench writes select
non-sorted body-major subsets and read the PhysX view back; a
center-of-mass offset checks every body-frame writer and the spin
kinematics. Failure modes, the gravity vector, and the subset-write
regressions keep their own scenes.
@AntoineRichard
AntoineRichard requested a review from a team September 29, 2026 12:48
@github-actions github-actions Bot added the isaac-lab Related to Isaac Lab team label Sep 29, 2026
@greptile-apps

greptile-apps Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

[Medium risk] Fixes physics simulation bugs in rigid object collection.

The PR should not merge until collection tests remain valid when selected alongside own-context tests in a different order.

Findings

  1. P1 Shared scene can lose its stage ▶
  2. P2 Zero offset coverage was removed ▶
  3. P2 Written state is not stepped ▶

Summary

The PR replaces per-test remote-asset scenes with local cubes and a shared PhysX collection scene, while fixing COM and inertia writes and adding subset read-back tests.

  • The model-property changes align with the view layouts exercised by the new regression test.
  • The shared scene depends on test execution order, and consolidation removes two distinct state-coverage cases.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Own-context tests] --> B[Create and close stage]
  C[Module-scoped collection scene] --> D[Retained PhysX views]
  B -. if run while C is active .-> D
  D --> E[Collection tests]
Loading

Reviews (1) · Last reviewed commit: "Run PhysX rigid-object-collection tests ..."

Comment on lines +264 to +268
@pytest.fixture(scope="module", params=test_devices())
def collection_scene(request) -> _CollectionScene:
"""Initialize the composite collection scene once per device."""
device = request.param
with build_simulation_context(device=device, gravity_enabled=True) as sim:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Shared scene can lose its stage

If a collection test runs before one of the tests that creates its own simulation context, that second test replaces the stage while the module-scoped collection scene is still active. A later collection test then uses PhysX views from the old stage and can fail for a reason unrelated to the behavior it checks. The scene lifetime needs an explicit boundary rather than relying on test order.

Knowledge Base Used: PhysX backend

Comment on lines +356 to +362
# With a center-of-mass offset, every writer round-trips through its own frame.
scene.place_cubes_at_rest(_rest_poses(scene))
offset = torch.tensor([0.1, 0.0, 0.0], device=device).repeat(_NUM_ENVS, _NUM_CUBES, 1)
coms = collection.data.body_com_pose_b.torch.clone()
coms[..., :3] = offset
coms[..., 3:] = torch.tensor([0.0, 0.0, 0.0, 1.0], device=device)
collection.set_coms_index(coms=coms)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Zero offset coverage was removed

The consolidated state test only sets a nonzero COM offset. The removed zero-offset case checked that link and COM poses and velocities agree when their frames coincide. Losing that case means a regression in zero-offset frame handling would no longer be caught by this suite.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +368 to +384
for state_location in ("com", "link", "root"):
for ids in ({}, all_ids):
if state_location == "com":
collection.write_body_com_pose_to_sim_index(body_poses=rand_state[..., :7], **ids)
collection.write_body_com_velocity_to_sim_index(body_velocities=rand_state[..., 7:], **ids)
torch.testing.assert_close(rand_state[..., :7], collection.data.body_com_pose_w.torch)
torch.testing.assert_close(rand_state[..., 7:], collection.data.body_com_vel_w.torch)
elif state_location == "link":
collection.write_body_link_pose_to_sim_index(body_poses=rand_state[..., :7], **ids)
collection.write_body_link_velocity_to_sim_index(body_velocities=rand_state[..., 7:], **ids)
torch.testing.assert_close(rand_state[..., :7], collection.data.body_link_pose_w.torch)
torch.testing.assert_close(rand_state[..., 7:], collection.data.body_link_vel_w.torch)
else:
state_dict[state_type_to_randomize] = torch.randn(num_envs, num_cubes, 3, device=sim.device)
# make sure objects do not overlap
if state_type_to_randomize == "body_link_pos_w":
state_dict[state_type_to_randomize][..., :2] += origins.unsqueeze(1)[..., :2]

# perform simulation
for _ in range(5):
body_pose = torch.cat(
[state_dict["body_link_pos_w"], state_dict["body_link_quat_w"]],
dim=-1,
)
body_vel = torch.cat(
[state_dict["body_com_lin_vel_w"], state_dict["body_com_ang_vel_w"]],
dim=-1,
)
# reset object state
object_collection.write_body_link_pose_to_sim_index(body_poses=body_pose)
object_collection.write_body_com_velocity_to_sim_index(body_velocities=body_vel)
sim.step()

# assert that set object quantities are equal to the ones set in the state_dict
for key, expected_value in state_dict.items():
value = getattr(object_collection.data, key).torch
torch.testing.assert_close(value, expected_value, rtol=1e-5, atol=1e-5)

object_collection.update(sim.cfg.dt)


@pytest.mark.parametrize("num_envs", [4])
@pytest.mark.parametrize("num_cubes", [2])
@pytest.mark.parametrize("device", test_devices())
@pytest.mark.parametrize("with_offset", [True, False])
@pytest.mark.parametrize("gravity_enabled", [False])
def test_object_state_properties(sim, num_envs, num_cubes, device, with_offset, gravity_enabled):
"""Test the object_com_state_w and object_link_state_w properties."""
cube_object, env_pos = generate_cubes_scene(num_envs=num_envs, num_cubes=num_cubes, height=0.0, device=device)
view_ids = torch.tensor([x for x in range(num_cubes * num_envs)], dtype=torch.int32)

sim.reset()

# check if cube_object is initialized
assert cube_object.is_initialized

# change center of mass offset from link frame
offset = (
torch.tensor([0.1, 0.0, 0.0], device=device).repeat(num_envs, num_cubes, 1)
if with_offset
else torch.tensor([0.0, 0.0, 0.0], device=device).repeat(num_envs, num_cubes, 1)
)

com = wp.to_torch(cube_object.reshape_view_to_data_2d(cube_object.root_view.get_coms().view(wp.transformf)))
com[..., :3] = offset.to("cpu")
cube_object.root_view.set_coms(
cube_object.reshape_data_to_view_2d(wp.from_torch(com.clone(), dtype=wp.transformf)).view(wp.float32),
wp.from_torch(view_ids, dtype=wp.int32),
)

# check center of mass has been set
torch.testing.assert_close(
wp.to_torch(cube_object.reshape_view_to_data_2d(cube_object.root_view.get_coms().view(wp.transformf))), com
)

# random z spin velocity
spin_twist = torch.zeros(6, device=device)
spin_twist[5] = torch.randn(1, device=device)

# initial spawn point
init_com = cube_object.data.body_com_pose_w.torch[..., :3]

for i in range(10):
# spin the object around Z axis (com)
cube_object.write_body_com_velocity_to_sim_index(body_velocities=spin_twist.repeat(num_envs, num_cubes, 1))
sim.step()
cube_object.update(sim.cfg.dt)

# get state properties
object_link_pose_w = cube_object.data.body_link_pose_w.torch
object_link_vel_w = cube_object.data.body_link_vel_w.torch
object_com_pose_w = cube_object.data.body_com_pose_w.torch
object_com_vel_w = cube_object.data.body_com_vel_w.torch

# if offset is [0,0,0] all object_state_%_w will match and all body_%_w will match
if not with_offset:
torch.testing.assert_close(object_link_pose_w, object_com_pose_w)
torch.testing.assert_close(object_com_vel_w, object_link_vel_w)
else:
# cubes are spinning around center of mass
# position will not match
# center of mass position will be constant (i.e. spinning around com)
torch.testing.assert_close(init_com, object_com_pose_w[..., :3])

# link position will be moving but should stay constant away from center of mass
object_link_state_pos_rel_com = quat_apply_inverse(
object_link_pose_w[..., 3:],
object_link_pose_w[..., :3] - object_com_pose_w[..., :3],
collection.write_body_link_pose_to_sim_index(body_poses=rand_state[..., :7], **ids)
collection.write_body_com_velocity_to_sim_index(body_velocities=rand_state[..., 7:], **ids)
torch.testing.assert_close(rand_state[..., :7], collection.data.body_link_pose_w.torch)
torch.testing.assert_close(rand_state[..., 7:], collection.data.body_com_vel_w.torch)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Written state is not stepped

These checks read poses and velocities immediately after each write. The removed state test also advanced physics and checked that the written values persisted. A writer that updates its buffers correctly but loses the state during simulation would now pass this test, so the replacement needs a post-step check.

@AntoineRichard
AntoineRichard marked this pull request as draft September 29, 2026 12:54
Drop step-narrating comments from the gravity test and annotate the tests and fixtures.
A second step must double the velocity from a permanent force, which
the deleted single-body force test covered. The writer passes step in
between, the link velocity write is read back from PhysX, and the
invalid-body and gravity tests run on every device again.
@AntoineRichard

Copy link
Copy Markdown
Collaborator Author

Superseded by #8147: the asset-test series was consolidated from 24 to 13 PRs, and this part's changes are now included in #8147 ([8/13]). Series overview: #7282.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

isaac-lab Related to Isaac Lab team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant