-
Notifications
You must be signed in to change notification settings - Fork 192
Support multi-articulation selections #1100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Support multi-articulation selections #1100
Conversation
📝 WalkthroughWalkthroughArticulationView was refactored to be world-aware using a new FrequencyLayout abstraction for per-attribute layout; mask/kernel APIs now use 2D world×articulation indexing. A multi-world selection example and a test registering it were added. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant ArtView as ArticulationView
participant Freq as FrequencyLayout
participant Kernel as Masking Kernel
participant Model
User->>ArtView: construct(selection, squeeze_axes)
ArtView->>ArtView: group articulations by world
ArtView->>Freq: create per-attribute FrequencyLayout(s)
note right of Freq `#D3E4CD`: offset, outer/inner stride,\nvalue_count, indices/slice, contiguity
User->>ArtView: get_model_articulation_mask(optional_mask)
ArtView->>ArtView: build 2D view_mask & view_to_model_map
ArtView->>Kernel: invoke(view_mask, view_to_model_map)
Kernel->>Model: apply per-world, per-articulation masking
Kernel-->>ArtView: masked result
User->>ArtView: get_attribute(name)
ArtView->>Freq: lookup layout for attribute
Freq-->>ArtView: provide 2D indices/slice
ArtView->>ArtView: gather & apply squeeze_axes
ArtView-->>User: attribute array
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Points to review closely:
Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used🧠 Learnings (3)📚 Learning: 2025-08-25T20:20:35.899ZApplied to files:
📚 Learning: 2025-09-22T21:08:31.901ZApplied to files:
📚 Learning: 2025-09-22T21:08:31.901ZApplied to files:
🧬 Code graph analysis (1)newton/_src/utils/selection.py (2)
🪛 Ruff (0.14.5)newton/_src/utils/selection.py173-173: Avoid specifying long messages outside the exception class (TRY003) 250-250: Avoid specifying long messages outside the exception class (TRY003) 282-282: Avoid specifying long messages outside the exception class (TRY003) 285-285: Avoid specifying long messages outside the exception class (TRY003) 386-386: Avoid specifying long messages outside the exception class (TRY003) 426-426: Avoid specifying long messages outside the exception class (TRY003) 467-467: Avoid specifying long messages outside the exception class (TRY003) 726-728: Avoid specifying long messages outside the exception class (TRY003) 778-778: Avoid specifying long messages outside the exception class (TRY003) ⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
🔇 Additional comments (9)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
newton/_src/utils/selection.py (1)
984-1003: Fix kernel launch dimensions in get_model_articulation_mask.Line 1000 uses
dim=mask.size, butmaskis 1D with shape(world_count,)while the kernelset_model_articulation_mask_kernelexpects 2D indexing(world, arti). The launch should usedim=(self.world_count, self.count_per_world)to match the kernel signature.Apply this diff:
assert mask.shape == (self.world_count,) articulation_mask = wp.zeros(self.model.articulation_count, dtype=bool, device=self.device) wp.launch( set_model_articulation_mask_kernel, - dim=mask.size, + dim=(self.world_count, self.count_per_world), inputs=[mask, self.articulation_ids, articulation_mask], ) return articulation_mask
🧹 Nitpick comments (2)
newton/examples/selection/example_selection_multiple.py (2)
149-187: Remove unused noqa directives.Lines 150, 227, and 254 have
# noqa: PLC0415comments that are flagged as unused by Ruff. These can be removed for cleaner code.Apply this diff:
if USE_TORCH: - import torch # noqa: PLC0415 + import torch # default ant root states self.default_root_transforms = wp.to_torch(self.ants.get_root_transforms(self.model)).clone()Similar changes for lines 227 and 254.
280-286: Mark unused lambda parameter with underscore.The
qdparameter in the test lambda is unused. Consider using_to indicate it's intentionally ignored.Apply this diff:
newton.examples.test_body_state( self.model, self.state_0, "all bodies are above the ground", - lambda q, qd: q[2] > 0.01, + lambda q, _: q[2] > 0.01, )
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
newton/_src/utils/selection.py(12 hunks)newton/examples/selection/example_selection_multiple.py(1 hunks)newton/tests/test_examples.py(1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-08-27T19:05:44.697Z
Learnt from: Milad-Rakhsha-NV
Repo: newton-physics/newton PR: 535
File: newton/tests/test_examples.py:320-414
Timestamp: 2025-08-27T19:05:44.697Z
Learning: In newton/examples/__init__.py, the robot policy example is registered with the key "robot_policy" (not "robot.example_robot_policy"), so tests should reference it as name="robot_policy".
Applied to files:
newton/tests/test_examples.py
📚 Learning: 2025-09-22T21:08:31.901Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/examples/ik/example_ik_franka.py:121-123
Timestamp: 2025-09-22T21:08:31.901Z
Learning: In the newton physics framework, when creating warp arrays for IK solver joint variables using wp.array(self.model.joint_q, shape=(1, coord_count)), the resulting array acts as a reference/pointer to the original model's joint coordinates, so updates from the IK solver automatically reflect in the model's joint_q buffer used for rendering.
Applied to files:
newton/_src/utils/selection.py
📚 Learning: 2025-09-22T21:08:31.901Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/examples/ik/example_ik_franka.py:121-123
Timestamp: 2025-09-22T21:08:31.901Z
Learning: In the newton physics framework, when creating warp arrays for IK solver joint variables using wp.array(self.model.joint_q, shape=(1, coord_count)), the resulting array acts as a reference/pointer to the original model's joint coordinates, so updates from the IK solver automatically reflect in the model's joint_q buffer used for rendering, despite the general warp documentation suggesting copies are made by default.
Applied to files:
newton/_src/utils/selection.py
🧬 Code graph analysis (2)
newton/examples/selection/example_selection_multiple.py (3)
newton/_src/utils/selection.py (11)
ArticulationView(196-1015)get_root_transforms(818-836)get_root_velocities(853-872)get_attribute(785-796)get_dof_forces(958-968)set_dof_forces(970-979)set_root_transforms(838-851)set_root_velocities(874-886)set_dof_positions(924-933)set_dof_velocities(947-956)eval_fk(1005-1015)newton/_src/sim/builder.py (5)
ModelBuilder(70-5272)add_mjcf(1185-1275)collapse_fixed_joints(2535-2829)add_ground_plane(3036-3056)replicate(939-968)newton/examples/__init__.py (4)
get_asset(34-35)test_body_state(38-113)create_parser(271-328)init(331-374)
newton/_src/utils/selection.py (3)
newton/_src/sim/joints.py (1)
JointType(20-44)newton/_src/sim/state.py (3)
joint_dof_count(108-112)joint_coord_count(101-105)State(21-112)newton/_src/sim/model.py (3)
ModelAttributeFrequency(49-70)Model(96-798)get_attribute_frequency(782-798)
🪛 GitHub Actions: Pull Request
newton/_src/utils/selection.py
[error] 727-727: SyntaxError: invalid syntax. The expression 'attrib = attrib[*leading_slices, _slice, *trailing_slices]' uses Python syntax not supported in this environment.
🪛 Ruff (0.14.5)
newton/examples/selection/example_selection_multiple.py
150-150: Unused noqa directive (non-enabled: PLC0415)
Remove unused noqa directive
(RUF100)
227-227: Unused noqa directive (non-enabled: PLC0415)
Remove unused noqa directive
(RUF100)
254-254: Unused noqa directive (non-enabled: PLC0415)
Remove unused noqa directive
(RUF100)
285-285: Unused lambda argument: qd
(ARG005)
newton/_src/utils/selection.py
168-168: Avoid specifying long messages outside the exception class
(TRY003)
245-245: Avoid specifying long messages outside the exception class
(TRY003)
269-269: Avoid specifying long messages outside the exception class
(TRY003)
272-272: Avoid specifying long messages outside the exception class
(TRY003)
367-367: Avoid specifying long messages outside the exception class
(TRY003)
403-403: Avoid specifying long messages outside the exception class
(TRY003)
434-434: Avoid specifying long messages outside the exception class
(TRY003)
674-674: Avoid specifying long messages outside the exception class
(TRY003)
720-720: Avoid specifying long messages outside the exception class
(TRY003)
727-727: Cannot use star expression in index on Python 3.10 (syntax was added in Python 3.11)
(invalid-syntax)
727-727: Cannot use star expression in index on Python 3.10 (syntax was added in Python 3.11)
(invalid-syntax)
734-734: Cannot use star expression in index on Python 3.10 (syntax was added in Python 3.11)
(invalid-syntax)
734-734: Cannot use star expression in index on Python 3.10 (syntax was added in Python 3.11)
(invalid-syntax)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Run GPU Benchmarks / Run GPU Benchmarks on AWS EC2
- GitHub Check: Run GPU Tests / Run GPU Unit Tests on AWS EC2
🔇 Additional comments (5)
newton/tests/test_examples.py (1)
489-496: LGTM!Test registration is consistent with other selection examples and properly configured.
newton/_src/utils/selection.py (4)
28-38: LGTM!Kernel correctly updated to handle 2D (world, articulation) indexing for multi-articulation support.
126-150: LGTM!
FrequencyLayoutprovides a clean abstraction for articulation attribute layout metadata with clear separation of contiguous vs. indexed cases.
153-194: LGTM!Helper functions provide clear, well-scoped utilities for world-aware articulation grouping and layout computations.
216-659: Excellent refactor for multi-articulation support.The initialization logic properly validates uniform articulation topology across worlds, computes per-world and per-articulation offsets/strides, and constructs
FrequencyLayoutentries for efficient attribute access. Thesqueeze_axesparameter provides flexible dimensionality control.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (1)
newton/examples/selection/example_selection_multiple.py (1)
150-150: Remove unused noqa directives.The
# noqa: PLC0415comments on lines 150, 227, and 254 are unnecessary because the PLC0415 rule (import-outside-toplevel) is not enabled in your linter configuration.Apply this diff to clean up the noqa directives:
if USE_TORCH: - import torch # noqa: PLC0415 + import torchApply the same change to lines 227 and 254.
Also applies to: 227-227, 254-254
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
newton/_src/utils/selection.py(12 hunks)newton/examples/selection/example_selection_multiple.py(1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-08-25T20:20:35.899Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 635
File: newton/_src/sim/builder.py:3191-3195
Timestamp: 2025-08-25T20:20:35.899Z
Learning: When reviewing PRs that update Python version requirements (e.g., dropping Python 3.9 support), focus on syntax modernization rather than suggesting behavioral changes. Adding explicit `strict=False` to zip() calls is appropriate for Python 3.10+ compatibility and maintains existing behavior while satisfying linters.
Applied to files:
newton/_src/utils/selection.py
📚 Learning: 2025-09-22T21:08:31.901Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/examples/ik/example_ik_franka.py:121-123
Timestamp: 2025-09-22T21:08:31.901Z
Learning: In the newton physics framework, when creating warp arrays for IK solver joint variables using wp.array(self.model.joint_q, shape=(1, coord_count)), the resulting array acts as a reference/pointer to the original model's joint coordinates, so updates from the IK solver automatically reflect in the model's joint_q buffer used for rendering.
Applied to files:
newton/_src/utils/selection.py
📚 Learning: 2025-09-22T21:08:31.901Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/examples/ik/example_ik_franka.py:121-123
Timestamp: 2025-09-22T21:08:31.901Z
Learning: In the newton physics framework, when creating warp arrays for IK solver joint variables using wp.array(self.model.joint_q, shape=(1, coord_count)), the resulting array acts as a reference/pointer to the original model's joint coordinates, so updates from the IK solver automatically reflect in the model's joint_q buffer used for rendering, despite the general warp documentation suggesting copies are made by default.
Applied to files:
newton/_src/utils/selection.py
📚 Learning: 2025-09-22T21:03:39.624Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/_src/sim/ik/ik_lbfgs_optimizer.py:739-752
Timestamp: 2025-09-22T21:03:39.624Z
Learning: The L-BFGS optimizer in newton/_src/sim/ik/ik_lbfgs_optimizer.py currently intentionally only supports additive updates (assuming n_coords == n_dofs). Velocity space integration for joints with mismatched coordinate/DOF dimensions (like free/ball joints) is planned for future work and should not be flagged as an issue in current reviews.
Applied to files:
newton/examples/selection/example_selection_multiple.py
📚 Learning: 2025-09-22T21:03:18.367Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/_src/sim/ik/ik_lbfgs_optimizer.py:638-648
Timestamp: 2025-09-22T21:03:18.367Z
Learning: The L-BFGS optimizer in newton/_src/sim/ik/ik_lbfgs_optimizer.py currently only supports additive updates, meaning it assumes n_coords == n_dofs. Velocity space integration for joints with mismatched coordinate/DOF dimensions (like free/ball joints) is planned for future work.
Applied to files:
newton/examples/selection/example_selection_multiple.py
🧬 Code graph analysis (2)
newton/_src/utils/selection.py (3)
newton/_src/sim/joints.py (1)
JointType(20-44)newton/_src/sim/state.py (3)
joint_dof_count(108-112)joint_coord_count(101-105)State(21-112)newton/_src/sim/model.py (3)
ModelAttributeFrequency(49-70)Model(96-798)get_attribute_frequency(782-798)
newton/examples/selection/example_selection_multiple.py (3)
newton/_src/utils/selection.py (12)
ArticulationView(203-1053)get_root_transforms(856-874)get_root_velocities(891-910)get_attribute(823-834)get_dof_velocities(973-983)get_dof_forces(996-1006)set_dof_forces(1008-1017)set_root_transforms(876-889)set_root_velocities(912-924)set_dof_positions(962-971)set_dof_velocities(985-994)eval_fk(1043-1053)newton/_src/sim/builder.py (5)
ModelBuilder(70-5272)add_mjcf(1185-1275)collapse_fixed_joints(2535-2829)add_ground_plane(3036-3056)replicate(939-968)newton/examples/__init__.py (4)
get_asset(34-35)test_body_state(38-113)create_parser(271-328)init(331-374)
🪛 GitHub Actions: Pull Request
newton/_src/utils/selection.py
[error] 276-276: KeyError: 'No matching articulations' raised during ArticulationView initialization.
🪛 GitHub Actions: Pull Request - AWS GPU
newton/_src/utils/selection.py
[error] 276-276: KeyError: 'No matching articulations' raised during ArticulationView initialization (No matching articulations).
🪛 Ruff (0.14.5)
newton/_src/utils/selection.py
175-175: Avoid specifying long messages outside the exception class
(TRY003)
252-252: Avoid specifying long messages outside the exception class
(TRY003)
276-276: Avoid specifying long messages outside the exception class
(TRY003)
279-279: Avoid specifying long messages outside the exception class
(TRY003)
374-374: Avoid specifying long messages outside the exception class
(TRY003)
410-410: Avoid specifying long messages outside the exception class
(TRY003)
445-445: Avoid specifying long messages outside the exception class
(TRY003)
704-706: Avoid specifying long messages outside the exception class
(TRY003)
756-756: Avoid specifying long messages outside the exception class
(TRY003)
newton/examples/selection/example_selection_multiple.py
150-150: Unused noqa directive (non-enabled: PLC0415)
Remove unused noqa directive
(RUF100)
227-227: Unused noqa directive (non-enabled: PLC0415)
Remove unused noqa directive
(RUF100)
254-254: Unused noqa directive (non-enabled: PLC0415)
Remove unused noqa directive
(RUF100)
287-287: Unused lambda argument: qd
(ARG005)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
newton/_src/utils/selection.py (1)
294-482: Stride computation logic is thorough and correct.The systematic computation of offsets and strides for joints, DOFs, coordinates, links, and shapes across worlds properly validates homogeneity and builds the foundation for 2D world-aware attribute access.
Consider enhancing error messages at lines 388, 428, and 469 to include specific details about which counts or strides differ, e.g.:
- raise ValueError("Articulations are not identical") + raise ValueError(f"Articulations are not identical: joints={joint_counts[0]} vs others, dofs={joint_dof_counts[0]}, etc.")
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
newton/_src/utils/selection.py(12 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-08-25T20:20:35.899Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 635
File: newton/_src/sim/builder.py:3191-3195
Timestamp: 2025-08-25T20:20:35.899Z
Learning: When reviewing PRs that update Python version requirements (e.g., dropping Python 3.9 support), focus on syntax modernization rather than suggesting behavioral changes. Adding explicit `strict=False` to zip() calls is appropriate for Python 3.10+ compatibility and maintains existing behavior while satisfying linters.
Applied to files:
newton/_src/utils/selection.py
📚 Learning: 2025-09-10T13:03:58.591Z
Learnt from: gdaviet
Repo: newton-physics/newton PR: 750
File: newton/_src/solvers/implicit_mpm/solve_rheology.py:799-804
Timestamp: 2025-09-10T13:03:58.591Z
Learning: In Warp's tiled launch API, the launch dimension should include the tile size as the last dimension, matching the CUDA block dimension. For tiled operations, use dim=(num_blocks, tile_size) where tile_size equals block_dim, not just dim=num_blocks like regular kernel launches.
Applied to files:
newton/_src/utils/selection.py
📚 Learning: 2025-09-22T21:08:31.901Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/examples/ik/example_ik_franka.py:121-123
Timestamp: 2025-09-22T21:08:31.901Z
Learning: In the newton physics framework, when creating warp arrays for IK solver joint variables using wp.array(self.model.joint_q, shape=(1, coord_count)), the resulting array acts as a reference/pointer to the original model's joint coordinates, so updates from the IK solver automatically reflect in the model's joint_q buffer used for rendering.
Applied to files:
newton/_src/utils/selection.py
📚 Learning: 2025-09-22T21:08:31.901Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/examples/ik/example_ik_franka.py:121-123
Timestamp: 2025-09-22T21:08:31.901Z
Learning: In the newton physics framework, when creating warp arrays for IK solver joint variables using wp.array(self.model.joint_q, shape=(1, coord_count)), the resulting array acts as a reference/pointer to the original model's joint coordinates, so updates from the IK solver automatically reflect in the model's joint_q buffer used for rendering, despite the general warp documentation suggesting copies are made by default.
Applied to files:
newton/_src/utils/selection.py
🔇 Additional comments (4)
newton/_src/utils/selection.py (4)
128-158: LGTM! FrequencyLayout abstraction is well-designed.The new FrequencyLayout class cleanly encapsulates per-attribute layout metadata and correctly distinguishes between contiguous (slice-based) and non-contiguous (index-based) access patterns.
160-201: Helper functions are correctly implemented.The utility functions properly handle edge cases (empty lists, single elements, out-of-range world IDs) and support the new world-aware layout logic.
263-292: World-aware articulation grouping logic is sound.The code correctly groups articulations by world, handles global articulations as a special case, and validates that all worlds have the same articulation count.
718-799: Attribute array construction with layout and squeezing is well-implemented.The complex logic to compute shapes, strides, apply squeezing, and create sliced or indexed arrays correctly handles all combinations of singleton axes and maintains Python 3.10 compatibility.
| include_joint_types: list[int] | None = None, | ||
| exclude_joint_types: list[int] | None = None, | ||
| verbose: bool | None = None, | ||
| squeeze_axes: bool | tuple[int] = (1,), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious to hear people's opinions on what the default should be. The PR description has notes about the benefits and drawbacks of different options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I'm all for squeezing - I think it's reasonable to expect users to figure out the shape based on the contents of the scene. We can document this clearly.
On the other hand, I wouldn't be too surprised if IsaacLab and RL wrappers in general want to expose this in a generic way, and I think at that point squeezing is not really an option anymore? We should for sure check with them to see if there is even a decision to be made here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I think I'm reversing my opinion - after thinking some more about it it's way more consistent to keep the singleton axes and do this the same way all the time. Users can still reshape or just pass a slice, right?
One thing that I'm coming back to as well is the question of whether we should always have an explicit world dimension here. I can imagine once you're having multiple articulations per world, maybe you don't care too much about worlds anymore anyway? Was that a hard requirement from IsaacLab or did it just make sense because of how the API works?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's way more consistent to keep the singleton axes
Yep, consistency is a good thing for API :)
whether we should always have an explicit world dimension here
I think it is still useful, since in RL we often reset some subset of worlds. We currently use a mask over world indices to control which worlds should be reset. But we could also mask over individual articulations. Maybe @AntoineRichard can chime in here?
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
| test_options={"num-frames": 100}, | ||
| test_options_cpu={"num-frames": 10}, | ||
| use_viewer=True, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would be great to have explicit test coverage for some of the special cases as well, not just the example.
| for world_id in range(world_count): | ||
| count = len(articulation_ids[world_id]) | ||
| counts_per_world[world_id] += count | ||
| articulation_count += count |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if I just missed this, but where are you handling global articulations in case there are both global and per-world articulations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, I'm still thinking about that. The current code assumes that either
- There are matching articulations per-world, but no global matches.
- There are matching global articulations, but no matches per world.
This covers the most common use cases, where we either have multiple replicated worlds or just a single scene with everything global.
If there are both, it's a pickle. Chances are that we won't be able to construct a single attribute array, because the strides will be funky. Also, it's not clear how to pack the global stuff with the per-world stuff in a single array. We would need to add an extra world index for the globals either at the beginning or at the end, neither of which feels quite right. One alternative would be to have different getters and setters for the globals, which is also not great. Or the getters and setters could take a flag. Or the getters could return two arrays, one for globals and one for per-world data, and the setters could take two arrays as well. But that would complicate the API for the sake of a use case that seems pretty rare to me.
My current inclination is to raise an exception, though that's not in the code yet. The guidance for users would be to use different key patterns for the global stuff, to avoid selections that span both global and per-world objects.
Maybe the exception is the way to go for now, and we can revisit later?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these assumptions are fair for the moment, I think it's ok to raise an exception. A typical use-case would probably special-case the global articulations anyway because their likely part of some world/scene and not the actual robot you're focusing on.
| self.is_fixed_base = root_joint_type == JointType.FIXED | ||
| # floating base means that all linear and angular degrees of freedom are unlocked at the root | ||
| # (though there might be constraints like distance) | ||
| self.is_floating_base = root_joint_type in (JointType.FREE, JointType.DISTANCE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
couldn't this be basically any joint except fixed here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a little tricky. Currently, I define "floating" as having all linear and angular DOFs unlocked, which matches FREE and DISTANCE joints.
The is_floating_base flag is used when getting and setting root transforms and velocities. For FREE and DISTANCE joints, we do that through joint_q and joint_qd. For other types of joints, we use joint_X_p. This only matters for the "helper" API like get_root_transforms() and get_dof_positions(). Users who explicitly get attributes by name are in full control and take the responsibility for reading and writing the appropriate attributes.
The most common articulation root joints are FREE and FIXED, so the current setup works fine.
With FIXED joints, get_root_transforms() returns joint_X_p and get_dof_positions() returns the internal articulation joints in joint_q.
With FREE (and DISTANCE) joints, get_root_transforms() returns the root joint slice of joint_q and get_dof_positions() returns the internal joint slice of joint_q so that there's no overlap.
With other types of joints (like REVOLUTE or PRISMATIC), get_root_transforms() returns the "fixed" transform in joint_X_p, and get_dof_positions() returns all the DOFs in joint_q, including the root joint DOFs.
I struggled with this quite a bit at the beginning, and I'm still not entirely happy with the solution. But this works fine for Isaac Lab, so I'm happy enough for now. Maybe we'll need to revisit that for maximum coordinate solvers, but it's not clear to me yet. Always open to suggestions for improving the API :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That thinking and explanation makes total sense to me. I'm wondering if we should promote this floating_base property/hint to the main newton API as I think it could be useful to unify the handling of this.
Description
ArticulationViewnow supports multiple articulations per world.Example
This example creates 16 worlds with 3 articulations each:
Multi-articulation selections are supported out-of-the-box with no special construction needed:
The optional joint and link filtering options are the same as before. The main difference is the dimensionality of the attributes. To handle multiple articulations per world, we introduce a new dimension so that values in attribute arrays can be accessed using a triple of indices
(world, arti, value), wherevaluestands for links, joints, shapes, etc.Multi-articulation views allow us to write code that handles all of the articulations together, instead of using multiple views to handle one articulation per world.
Here's how random forces can be applied:
Attribute Dimensions
With multiple articulations per world, we need 3D attribute arrays indexed by (
world, arti, value). With a single articulation per world, we only need 2D arrays (world, value), since the singleton articulation dimension can be dropped.For single-body articulations, we can also drop the
valueindex. For example, an attribute with multiple balls per world can be accessed using two indices (world, arti). An attribute with a single ball per world can be accessed using just theworldindex.Here we have a design decision to make.
ArticulationViewcan compute the minimal set of dimensions required for an attribute by "squeezing" (removing) singleton dimensions. Benefits:world, value) rather than a 3D array where the articulation index is always 0.However, there are some drawbacks. Most importantly, the dimensionality of the attribute arrays can change when the number of objects in the scene changes (one per world vs multiple per world).
If code generality is important, it is better to consistently return attribute shapes that won't change with the number of objects.
Since user preferences or requirements may vary, we allow controlling the behaviour using an optional
squeeze_axesargument to theArticulationViewconstructor:The
squeeze_axesargument can take the following values:True: squeeze all singleton axes, return arrays with the lowest dimensionality possible.False: don't squeeze any singleton axes, always return 3D arrays.tuple[int]: explicitly specify squeezable axes, where 0 is the world axis, 1 is the articulation axis, and 2 is the value axis. This is the most fine-grained way to control the dimensionality of the attribute arrays. For example,squeeze_axes=(2,)says that the value axis can be squeezed (for single-body articulations), but the articulation or world axes should not be squeezed.Default behaviour
Currently, the default behaviour is to squeeze the articulation axis if possible (
squeeze_axes=(1,)). The world and value axes won't be squeezed. This was chosen to avoid breaking changes with existing code that worked with a single articulation per world. But we can pick a different default, opinions welcome :)Limitations
There are some limitations currently:
The
ArticulationViewconstructor verifies the assumptions and will raise errors when they are not met.We should be able to relax these limitations in the future to make the
ArticulationViewmore flexible, but we need to be careful about introducing overhead when zero-copy access is not possible.Newton Migration Guide
Please ensure the migration guide for warp.sim users is up-to-date with the changes made in this PR.
docs/migration.rstis up-to dateBefore your PR is "Ready for review"
newton/tests/test_examples.py)pre-commit run -aSummary by CodeRabbit
New Features
Documentation
Tests