Repository navigation
Accelerate and strengthen asset tests [15/24] - #8148
AntoineRichard wants to merge 6 commits into
Conversation
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.
|
| @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: |
There was a problem hiding this comment.
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
| # 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) |
There was a problem hiding this comment.
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!
| 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) |
There was a problem hiding this comment.
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.
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.
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:
Both regressions added on
develop(test_subset_write_reaches_selected_view_entryand #8131'stest_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
test_initialization(1×1, 2×3)test_initialization_with_kinematic_enabledtest_external_force_on_single_body,..._at_positionis_globalnegated; torques zeroedtest_set_object_state(i, j)test_object_state_propertiestest_write_object_statetest_reset_object_collectiontest_initialization_with_no_rigid_body,test_gravity_vec_wTiming
Warm Warp cache, repository orchestrator (
pytest tools), one process per file, second of two runs, RTX 5090. "Before" isdevelopat 09b1d54. "Before" includes #8131's test.test_rigid_object_collection.pyProduct bug found (not fixed, not pinned)
A full
RigidObjectCollection.reset()still leaves the wrench composers active.reset()passes_ALL_ENV_INDICEStoWrenchComposer.reset, which takes the partial-reset path. #8009 reported this. The reset node keepsdevelop's assertions, which do not checkactive.Type of change
Test-only change; no package code changes.
Release backport
developChecklist
Docker and GPU tests run on demand. Push the commits you want tested, then
comment
run-cion the pull request.pre-commitchecks withuv run isaaclab --formatsource/<pkg>/changelog.d/for every touched package (do not editCHANGELOG.rstor bump package versions — CI handles that)CONTRIBUTORS.mdor my name already exists there