Skip to content

Add environment grouping functionality to Model entities #375#504

Merged
vreutskyy merged 11 commits into
newton-physics:mainfrom
vreutskyy:375-add-environment-grouping-functionality-to-model-entities
Aug 18, 2025
Merged

Add environment grouping functionality to Model entities #375#504
vreutskyy merged 11 commits into
newton-physics:mainfrom
vreutskyy:375-add-environment-grouping-functionality-to-model-entities

Conversation

@vreutskyy

@vreutskyy vreutskyy commented Aug 4, 2025

Copy link
Copy Markdown
Member

Description

This PR implements environment grouping functionality for Model entities. Closes #375

Changes:

  • Added group index properties to Model class for all entity types:

    • particle_group, body_group, shape_group, joint_group, articulation_group
    • Each stores an array of int32 values where -1 indicates global entities shared across all groups
  • Updated ModelBuilder to track and assign group indices:

    • Added current_env_group property (defaults to -1 for global entities)
    • Modified entity creation methods to assign the current environment group
  • Enhanced add_builder() method with an environment parameter:

    • Allows explicit assignment of entities to a specific environment group
    • Auto-assigns based on environment count when parameter is None
    • Preserves previous environment group after operation
  • Added test coverage with test_environment_grouping in test_model.py

Limitations:

  • BVH builder integration for top-level separation by groups is not included in this PR. This would require deeper understanding of the current BVH implementation and is better addressed as a follow-up task.
  • The collision detection system still needs to be updated to utilize the group information for optimization.

Newton Migration Guide

Please ensure the migration guide for warp.sim users is up-to-date with the changes made in this MR.

  • The migration guide in docs/migration.rst is up to date

Before your PR is "Ready for review"

  • All commits are signed-off to indicate that your contribution adheres to the Developer Certificate of Origin requirements
  • Necessary tests have been added and new examples are tested (see newton/tests/test_examples.py)
  • Documentation is up-to-date
  • Code passes formatting and linting checks with pre-commit run -a

Summary by CodeRabbit

  • New Features

    • Environment grouping: entities (particles, bodies, shapes, joints, articulations) can be global (-1) or assigned to environment indices; builder APIs expose a current environment setting and allow scoping copied additions to a specific environment, and finalized models include per-entity group arrays.
  • Documentation

    • Added usage guidance and examples for environment grouping and composing multi-environment models.
  • Tests

    • Added tests validating group assignment, env count tracking, per-entity counts across environments (including global), and preservation of groups after joint-collapse.

…cs#375

Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>
@coderabbitai

coderabbitai Bot commented Aug 4, 2025

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds environment-group support: ModelBuilder tracks a current_env_group and per-entity group arrays; add_builder gains an environment parameter and propagates groups during copies; Model now exposes *_group arrays; four new tests validate grouping, num_envs tracking, and collapse-fixed-joints semantics.

Changes

Cohort / File(s) Summary
Environment grouping implementation
newton/_src/sim/builder.py
Add current_env_group and per-entity arrays (particle_group, shape_group, body_group, joint_group, articulation_group); record groups on add_* methods; extend `add_builder(..., environment: int
Model API additions
newton/_src/sim/model.py
Add public attributes particle_group, shape_group, body_group, joint_group, articulation_group and docstrings describing environment-group semantics (global = -1, non-negative = environment).
Tests for grouping and collapse behavior
newton/tests/test_model.py
Add four tests: test_add_particles_grouping, test_environment_grouping, test_num_envs_tracking, and test_collapse_fixed_joints_with_groups to validate per-entity group assignment, num_envs behavior, preservation of groups through fixed-joint collapse, and finalization serialization.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Builder as ModelBuilder
  participant Sub as Sub-Builder
  participant Model

  User->>Builder: set current_env_group (e.g. -1 global)
  User->>Builder: add global entities (add_particle/add_body/add_shape)
  User->>Sub: construct env-local sub-builder
  User->>Builder: add_builder(Sub, environment=0)
  User->>Builder: add_builder(Sub, environment=1)
  User->>Builder: add_builder(Sub) (auto/next env or use current_env_group)
  User->>Builder: finalize()
  Builder-->>Model: returns Model with particle_group, shape_group, body_group, joint_group, articulation_group
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Assessment against linked issues

Objective Addressed Explanation
Add group index to all entities: particles, shapes, bodies, joints, articulations (#375)
add_builder receives environment argument to assign entities to that group (#375)
Global entities receive group index -1 (#375)
Preserve -1 groups when merging sub-builders; overwrite only positive groups (#375) add_builder/transform copy assigns the target/current environment to copied entities and appears to overwrite original -1 entries rather than conditionally preserving them.
BVH/top-level acceleration uses group info (#375) No changes to BVH or acceleration structures to consume group arrays in this diff.

Out-of-scope changes

(No out-of-scope functional code changes detected related to the linked issue objectives.)

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@preist-nvidia preist-nvidia requested review from adenzler-nvidia and removed request for eric-heiden and mmacklin August 12, 2025 12:35

@adenzler-nvidia adenzler-nvidia left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks - looks good in general. Some questions about the modelbuilder parts.

Comment thread newton/sim/builder.py
Comment thread newton/sim/builder.py
Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 6

🔭 Outside diff range comments (1)
newton/sim/builder.py (1)

3703-3713: Fix required: add_particles must update particle_group to match particle_count

Short: add_particles() extends particle arrays but does not update self.particle_group, so finalize()'s conversion (m.particle_group = wp.array(self.particle_group, ...)) can produce a length mismatch.

Places to fix:

  • newton/sim/builder.py
    • add_particles (def at line ~2673): after self.particle_flags.extend(flags) add:
      self.particle_group.extend([self.current_env_group] * len(pos))
    • finalize (m.particle_group assignment at line ~3710): add a pre-finalize sanity check:
      assert len(self.particle_group) == self.particle_count,
      f"particle_group length {len(self.particle_group)} != particle_count {self.particle_count}"

Suggested diff (minimal):

  •    self.particle_flags.extend(flags)
    
  •    self.particle_group.extend([self.current_env_group] * len(pos))
    
  • before converting to Warp arrays in finalize:

  • assert len(self.particle_group) == self.particle_count, \

  •    f"particle_group length {len(self.particle_group)} != particle_count {self.particle_count}"
    

Tag:

♻️ Duplicate comments (3)
newton/sim/builder.py (3)

120-146: Docs clarify env-grouping semantics; add one note about scope and explicit env behavior

The “Environment Grouping” section is clear and directly addresses previous confusion about add_builder overriding sub-builder groups.

Two small doc nits to prevent confusion:

  • Explicitly call out that grouping applies only to core entities: particles, bodies, shapes, joints, articulations (not springs/constraints/etc.).
  • Clarify what happens to num_envs when a specific environment is provided to add_builder(environment=...).

Proposed wording tweak (insert after Line 128):

Applies to: particles, bodies, shapes, joints, articulations. Other elements (e.g., springs, constraints) are currently ungrouped.

When environment is specified in add_builder(environment=...), the builder will use that index for all copied entities. num_envs will be updated to cover the highest environment index seen (environment + 1).

415-416: Body group state added — LGTM

Inline comment is helpful and resolves past request to document.


565-592: Docs explicitly stating override-on-merge behavior — LGTM

This addresses prior confusion called out in earlier review threads. Thanks for spelling out the two usage patterns and examples.

🧹 Nitpick comments (2)
newton/sim/builder.py (2)

561-562: add_builder gains environment parameter — consider validating input

Signature extension makes sense. Consider validating environment >= -1 to catch accidental negative values other than -1.

Example:

if environment is not None and environment < -1:
    raise ValueError(f"Invalid environment index {environment}; expected -1 or >= 0.")

597-607: Optional: validate environment index early

Defensive check to avoid unexpected negative indices beyond -1.

Diff (within this block):

         # Set the environment group for entities being added
-        if environment is None:
+        if environment is None:
             # Use the current environment count as the group index if not specified
             group_idx = self.num_envs if update_num_env_count else self.current_env_group
         else:
+            if environment < -1:
+                raise ValueError(f"Invalid environment index {environment}; expected -1 or >= 0.")
             group_idx = environment
📜 Review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0a51c8f and de2067d.

📒 Files selected for processing (2)
  • newton/sim/builder.py (17 hunks)
  • newton/sim/model.py (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • newton/sim/model.py
🧰 Additional context used
🧬 Code Graph Analysis (1)
newton/sim/builder.py (2)
newton/sim/state.py (4)
  • joint_dof_count (105-109)
  • joint_coord_count (98-102)
  • particle_count (91-95)
  • body_count (84-88)
newton/sim/style3d/builder_style3d.py (1)
  • add_builder (106-136)
⏰ 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). (3)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
  • GitHub Check: run-newton-tests / newton-unittests (windows-latest)
🔇 Additional comments (11)
newton/sim/builder.py (11)

340-341: Particle group state added — LGTM

This keeps particle-group metadata alongside existing particle state. Good default (-1) via current_env_group.


366-368: Shape group state added — LGTM

Consistent with particles; aligns with finalize.


451-456: Joint and articulation group state added — LGTM

Both tracked explicitly. This matches PR scope.


460-464: current_env_group default and override behavior documented — LGTM

Defaulting to -1 (global) is aligned with the stated semantics.


553-553: Articulation grouping on creation — LGTM

Consistent with the other entity adders.


597-607: Group selection logic — OK; beware num_envs accounting with explicit environment

The temporary override of current_env_group is correct. However, num_envs accounting later (Lines 828-830) doesn’t handle environment being explicitly set to a higher index (e.g., 10). See fix below.

Would you confirm whether num_envs should always be max(max_group_index + 1, current value) when environment is explicitly provided? The diff below assumes yes.


721-747: Override copied entity groups on merge — LGTM

All five categories (particles, bodies, shapes, joints, articulations) are covered consistently.


895-896: Body grouping on creation — LGTM

Group assignment mirrors add_particle/add_shape/add_joint. Good consistency.


960-961: Joint grouping on creation — LGTM

Consistent with other entities.


2063-2064: Shape grouping on creation — LGTM

No concerns.


3753-3754: Finalize: shape_group wired — LGTM

Alignment with shape_count is correct.

Comment thread newton/sim/builder.py
Comment thread newton/sim/builder.py
Comment thread newton/sim/builder.py
Comment thread newton/sim/builder.py
Comment thread newton/sim/builder.py
Comment thread newton/sim/builder.py

@adenzler-nvidia adenzler-nvidia left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approcing, thanks. Can you make sure that all the follow-up tasks are captured in github issues:

here's what's on top of my mind:

  • collision groups
  • better modelbuilder documentation
  • BVH support
  • actually taking advantage of this in the MuJoCo solver, maybe other solvers.

…o-model-entities

Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 0

🔭 Outside diff range comments (3)
newton/_src/sim/model.py (1)

3698-3699: Fix required: ModelBuilder.add_particles doesn't populate particle_group — causes length mismatch at finalize

Confirmed: ModelBuilder.add_particles (list form) extends particle arrays but does not add entries to self.particle_group. finalize builds the model from self.particle_group (so a shorter particle_group will produce shape/length mismatches at runtime).

Places to fix / verify:

  • newton/_src/sim/builder.py — add_particles (def at ~line 2661): missing particle_group extension.
  • newton/_src/sim/builder.py — add_particle (single, ~lines 2628–2656): correctly does self.particle_group.append(self.current_env_group).
  • newton/_src/sim/builder.py — add_builder merge path (~lines 708–714): correctly extends particle_group for merged builders.
  • newton/_src/sim/builder.py — finalize (def at ~line 3655; m.particle_group assigned ~lines 3696–3699): consumes self.particle_group to create the model array.

Suggested minimal fix (apply inside ModelBuilder.add_particles, after self.particle_flags.extend(flags)):

# ensure environment group index is set for each new particle
self.particle_group.extend([self.current_env_group] * len(pos))

Optional fail-fast sanity check to add in finalize (before creating model arrays):

assert len(self.particle_group) == len(self.particle_q), "particle_group length mismatch"

Please apply the small change above in add_particles and add the assertion in finalize to prevent regressions.

newton/_src/sim/builder.py (2)

1674-1949: collapse_fixed_joints must remap group arrays (body_group, joint_group, articulation_group) — fix required

Verified: collapse_fixed_joints (newton/_src/sim/builder.py, def at ~line 1674) rebuilds bodies/joints and updates articulation_start but does not update self.body_group, self.joint_group or self.articulation_group. finalize() later emits these arrays to the Model (m.body_group @ ~3896, m.joint_group @ ~3909, m.articulation_group @ ~3949), so the groups become stale/misaligned. Tests and import paths call collapse_fixed_joints (e.g. newton/tests/test_model.py lines ~162–166), so this is a correctness/regression risk.

Locations to change

  • newton/_src/sim/builder.py
    • collapse_fixed_joints definition (starts ~1674)
    • body list clearing & repopulation (clears at ~1839; retained_bodies loop ~1852–1875)
    • retained_joints handling and joint repopulation (~1877–1917)
    • articulation_start update and the problematic list(set(...)) usage (~1883–1892)
    • finalize() uses of group arrays (~3896, ~3909, ~3949)

Suggested minimal fix

  • Snapshot old group arrays before clearing:
    • old_body_group = list(self.body_group)
    • old_joint_group = list(self.joint_group)
    • old_articulation_group = list(self.articulation_group)
    • old_articulation_start = list(self.articulation_start)
  • Remap body_group while rebuilding bodies:
    • In the retained_bodies loop, alongside body_remap/new_id append new_body_group.append(old_body_group[body["original_id"]]).
  • Remap joint_group while rebuilding joints:
    • When iterating retained_joints (in original order), append new_joint_group.append(old_joint_group[joint["original_id"]]).
  • Rebuild articulation_start and articulation_group together (do NOT use list(set(...))):
    • Map each old articulation start via joint_remap (or keep start if unmapped).
    • Build list of (mapped_start, old_articulation_index, old_group), sort by mapped_start, remove duplicates where mapped_start collapses with previous articulation, then set self.articulation_start = [mapped_start ...] and self.articulation_group = [corresponding old_group ...].
  • Replace the current articulation_start = list(set(...)) line with the deterministic remapping above to preserve ordering and group alignment.

Would you like me to prepare a targeted patch/PR that implements the above remapping and replaces the list(set(...)) use with a deterministic reconstruction of articulation_group?


2681-2690: Fix required: add_particles must populate particle_group to preserve particle_count invariant

add_particles extends particle arrays but does not add entries to self.particle_group; add_particle (single) does. This causes len(particle_group) != particle_count and can break finalization.

Files / locations to change:

  • newton/_src/sim/builder.py — add_particles (around lines 2661–2691): after self.particle_flags.extend(flags) add group entries.
  • Note: add_particle (around lines 2625–2660) already does self.particle_group.append(self.current_env_group) — use the same behavior for vectorized creation.

Suggested diff:

         self.particle_radius.extend(radius)
         self.particle_flags.extend(flags)
+        # Populate environment group indices for vectorized particle creation
+        self.particle_group.extend([self.current_env_group] * len(pos))
🧹 Nitpick comments (3)
newton/_src/sim/model.py (2)

94-96: New per-entity group fields: API shape looks good

Adding nullable int32 arrays per-entity type is consistent and non-breaking. One optional improvement: consider registering these fields in attribute_frequency for introspection parity (e.g., "particle", "shape", "body", "joint", "articulation").

If you want, I can add the attribute_frequency mappings in the right section for you.

Also applies to: 150-152, 224-226, 285-287, 291-293


382-441: Consider exposing group fields in attribute_frequency for consistency

Downstream utilities that rely on attribute_frequency might benefit from knowing the frequency of the new group attributes (e.g., for generic attribute iteration/validation).

Happy to add:

  • "particle_group": "particle"
  • "shape_group": "shape"
  • "body_group": "body"
  • "joint_group": "joint"
  • "articulation_group": "articulation"
newton/_src/sim/builder.py (1)

816-818: Avoid incrementing num_envs for global (-1) “environment”

Only increment when assigning to a non-negative environment. This also aligns with the doc intent and typical per-env allocations downstream.

Apply this diff:

-        if update_num_env_count:
-            self.num_envs += 1
+        if update_num_env_count and group_idx >= 0:
+            self.num_envs += 1
📜 Review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between de2067d and f67a492.

📒 Files selected for processing (3)
  • newton/_src/sim/builder.py (17 hunks)
  • newton/_src/sim/model.py (5 hunks)
  • newton/tests/test_model.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • newton/tests/test_model.py
⏰ 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). (4)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
  • GitHub Check: run-newton-tests / newton-unittests (windows-latest)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
🔇 Additional comments (8)
newton/_src/sim/model.py (1)

35-45: Docblock addition reads well and matches PR scope

Clear explanation of -1 (global) vs non-negative environment indices and intended downstream uses. No issues.

newton/_src/sim/builder.py (7)

108-134: Docs for Environment Grouping are clear and actionable

Examples are helpful; they match the implemented semantics (explicit override via environment argument vs inheriting current_env_group).


328-329: State additions and current_env_group default look right

  • Storing per-entity group indices in parallel arrays is consistent with existing storage.
  • Defaulting current_env_group to -1 (global) is sensible and aligns with the doc.

Also applies to: 355-356, 403-404, 439-440, 443-444, 449-452


541-542: Propagating group on articulation creation

Appending articulation_group here ensures per-articulation grouping is captured even when users build by hand. Looks good.


553-572: Clarify interaction with update_num_env_count for global (-1) environment

Docs say “Use -1 for global entities,” but code currently increments num_envs unconditionally (see Lines 816-818). That will count a global builder as an “environment,” which is surprising and can inflate per-env allocations.

I recommend conditioning the increment on environment >= 0 (see diff below).


585-596: Environment group selection is sound

  • environment=None → auto-assign to self.num_envs (pre-increment), matches expectations.
  • Explicit environment overrides the source builder’s groups as documented.

709-735: Overriding sub-builder groups upon merge: correct and simple

This guarantees all imported entities land in a single target environment, which is the desired behavior.


883-884: Per-entity group propagation on direct adds is correct

Bodies, joints, shapes, and single particles inherit current_env_group, as advertised.

Also applies to: 948-949, 2051-2052, 2655-2656

Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 4

🧹 Nitpick comments (8)
newton/_src/sim/builder.py (8)

329-330: Introduce and maintain invariants for group arrays

Defining particle_group is good. Please ensure a strict invariant throughout the builder: for every entity type, len(*_group) must always equal the corresponding count. A later comment flags a concrete violation in add_particles.


449-453: Default current_env_group (-1) is sensible

The default aligns with “global entities.” Consider documenting that users who set current_env_group directly should also ensure num_envs is large enough (see finalize comment).


589-599: Exception safety: restore current_env_group via try/finally

current_env_group is saved and restored at the end. If an exception occurs in-between (e.g., in transform_mul or deep copies), current_env_group will remain set to the temporary group. Wrap the body of add_builder in a try/finally to guarantee restoration.

Apply this minimal restructuring (pseudo-diff, indentation omitted for brevity):

- prev_env_group = self.current_env_group
- self.current_env_group = group_idx
+ prev_env_group = self.current_env_group
+ self.current_env_group = group_idx
+ try:
     # ... existing add_builder logic ...
- # Restore the previous environment group
- self.current_env_group = prev_env_group
+ finally:
+   self.current_env_group = prev_env_group

823-825: State restoration acknowledged

Restoring current_env_group is good; see earlier note about using try/finally for exception safety.


3675-3691: Make num_envs consistent with assigned group indices

Currently, finalize forces num_envs = max(1, self.num_envs). If users set current_env_group manually (e.g., to 1 or higher) without using add_builder(update_num_env_count=True), num_envs may be too small. Suggest ensuring num_envs is at least max(nonnegative group index) + 1 across all entity types.

Apply this refinement:

-        # ensure the env count is set correctly
-        self.num_envs = max(1, self.num_envs)
+        # ensure the env count covers all non-negative group indices
+        def _max_nonneg(xs):
+            return max((g for g in xs if g >= 0), default=-1)
+        max_group = max(
+            _max_nonneg(self.particle_group),
+            _max_nonneg(self.shape_group),
+            _max_nonneg(self.body_group),
+            _max_nonneg(self.joint_group),
+            _max_nonneg(self.articulation_group),
+        )
+        self.num_envs = max(1, self.num_envs, max_group + 1)

This keeps backward compatibility and hardens finalize for manually assigned groups.


3703-3703: Serialize particle_group — ensure size matches particle_count

This will error or misbehave if add_particles hasn’t updated particle_group accordingly. After fixing add_particles, consider a lightweight runtime check before creating arrays:

assert len(self.particle_group) == self.particle_count

(Or raise a ValueError with a clear message.)


3952-3954: Serialize articulation_group — potential mismatch with articulation_start updates

collapse_fixed_joints mutates articulation_start (including a non-stable set() dedup), but articulation_group is not updated. Consider keeping articulation_start order stable and updating articulation_group in lockstep.

Would you like a concrete patch to keep articulation_start stable and rebuild articulation_group appropriately?


544-584: Optional: Instrumentation to detect grouping mismatches early

Given the new per-entity group arrays, adding lightweight validation at the end of add_builder could save debugging time:

  • particle_count == len(particle_group) - pre and post
  • shape_count == len(shape_group)
  • body_count == len(body_group)
  • joint_count == len(joint_group)
  • articulation_count == len(articulation_group)

This can be gated behind a debug flag or wp.config.verbose.

Also applies to: 589-599, 713-739

📜 Review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f67a492 and b701c03.

📒 Files selected for processing (1)
  • newton/_src/sim/builder.py (17 hunks)
⏰ 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). (4)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
  • GitHub Check: run-newton-tests / newton-unittests (windows-latest)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
🔇 Additional comments (13)
newton/_src/sim/builder.py (13)

108-135: Clear, actionable documentation for environment grouping

The Environment Grouping section is concise and useful. It makes the global (-1) vs environment-scoped grouping semantics obvious and shows both direct and add_builder flows.


355-357: Shape grouping array added — good

shape_group complements the other per-entity arrays. No issues here.


404-405: Body grouping array added — good

body_group is wired consistently with add_body and finalize. LGTM.


440-441: Joint grouping array added — good

The array is propagated at creation and serialized in finalize. LGTM.


444-445: Articulation grouping array added — good

The creation path and finalize serialization are consistent. LGTM.


542-543: Propagate env group on articulation creation

Appending current_env_group here is correct. LGTM.


550-551: add_builder(environment=...) API extension

Extends flexibility without breaking existing callers. LGTM.


554-576: Docstring for add_builder environment behavior is clear

Calling out the override semantics is important; thanks for being explicit. LGTM.


713-739: Group overriding on add_builder is correct but ensure all entity types stay aligned

Overriding source-builder group indices with the computed self.current_env_group meets the PR objective. Note that any bulk-add paths outside add_builder must also update their *_group arrays to preserve length equality with counts (see add_particles comment).


887-888: Propagate env group on body creation

Appending current_env_group on body creation is correct. LGTM.


952-953: Propagate env group on joint creation

Correct and consistent with other entities. LGTM.


2055-2056: Propagate env group on shape creation

Correct; shapes added via helpers will inherit this as well. LGTM.


3746-3746: Serialize shape_group — size sanity

If there are code paths that add shapes without touching shape_group (none observed), this would mismatch. Looks fine given add_shape/add_builder paths.

Comment thread newton/_src/sim/builder.py
Comment thread newton/_src/sim/builder.py
Comment thread newton/_src/sim/builder.py
vreutskyy and others added 2 commits August 18, 2025 11:20
Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🔭 Outside diff range comments (1)
newton/_src/sim/builder.py (1)

3618-3623: Free-joint auto-add assigns joint_group based on current_env_group, not the body’s env

When adding free joints for floating bodies, the joint’s group should inherit the child body’s environment (so world->child joints remain in the same env). Today it uses whatever current_env_group happens to be, which can mislabel joints in mixed-env scenes.

Apply this change:

     def add_free_joints_to_floating_bodies(self, new_bodies: Iterable[int] | None = None):
         """
         Adds a free joint to every body that is not a child in any joint and has mass > 0.
         Should be called after all other joints have been added.
         """
         # set(self.joint_child) is connected_bodies
         floating_bodies = set(new_bodies) - set(self.joint_child)
         for body_id in floating_bodies:
             if self.body_mass[body_id] > 0:
-                self.add_joint_free(child=body_id)
+                prev_env = self.current_env_group
+                try:
+                    # Inherit the child's environment for the world->child free joint
+                    body_env = self.body_group[body_id] if body_id < len(self.body_group) else -1
+                    self.current_env_group = body_env
+                    self.add_joint_free(child=body_id)
+                finally:
+                    self.current_env_group = prev_env

This aligns joint_group with the body’s env group.

♻️ Duplicate comments (1)
newton/_src/sim/builder.py (1)

2714-2716: Bulk particle add now maintains particle_group — thanks for fixing the invariant

This resolves length mismatches between particle_group and particle_* arrays reported earlier.

🧹 Nitpick comments (5)
newton/tests/test_model.py (3)

270-291: add_particles grouping test covers the core path; consider tightening assertions

This validates default (-1) and non-default (0) environments and exercises finalize->Model propagation. Optionally assert particle_count == 5 and that dtype is int32 to catch serialization regressions early.

Apply this small augmentation:

         model = builder.finalize()
         particle_groups = model.particle_group.numpy()
 
         # First 3 particles should be in group -1
         self.assertTrue(np.all(particle_groups[0:3] == -1))
         # Next 2 particles should be in group 0
         self.assertTrue(np.all(particle_groups[3:5] == 0))
+        self.assertEqual(model.particle_count, 5)
+        self.assertEqual(particle_groups.dtype, np.int32)

292-396: Environment grouping test is comprehensive; make indexing assertions less brittle

Great coverage across particles, bodies, shapes, joints, articulations and auto-assign (env=2). The slice-based assertions assume insertion order; future refactors might change ordering without breaking grouping semantics. Prefer checking counts per group to reduce brittleness.

For example:

-        if len(body_groups) > 0:
-            self.assertEqual(body_groups[0], -1)  # ground body
-            self.assertTrue(np.all(body_groups[1:3] == 0))
-            self.assertTrue(np.all(body_groups[3:5] == 1))
-            self.assertTrue(np.all(body_groups[5:7] == 2))
+        if len(body_groups) > 0:
+            # counts by group
+            unique, counts = np.unique(body_groups, return_counts=True)
+            counts_by_group = dict(zip(unique.tolist(), counts.tolist()))
+            self.assertEqual(counts_by_group.get(-1, 0), 1)
+            self.assertEqual(counts_by_group.get(0, 0), 2)
+            self.assertEqual(counts_by_group.get(1, 0), 2)
+            self.assertEqual(counts_by_group.get(2, 0), 2)

Do similarly for particle_groups and shape_groups.


397-494: collapse_fixed_joints grouping checks: solid; consider asserting articulation grouping too

The pre-/post-collapse assertions for body_group and joint_group are spot on and validate the rebuild logic. As an extra guardrail, you could assert articulation_count and articulation_group remain consistent with the retained joint segments.

For example:

         model = builder.finalize()
         body_groups = model.body_group.numpy()
         joint_groups = model.joint_group.numpy()
+        if model.articulation_count > 0 and model.articulation_group is not None:
+            art_groups = model.articulation_group.numpy()
+            self.assertTrue(np.all((art_groups == 0) | (art_groups == 1)))
newton/_src/sim/builder.py (2)

544-585: add_builder: docs correctly state group override behavior; add an explicit note on env=-1 counting

The behavior “ALL entities from the source builder are assigned to the specified group” is clearly documented. Consider adding that passing environment=-1 produces global entities and should not increase num_envs (see code suggestion below).


3724-3725: *Model serialization of _group arrays — LGTM; optionally validate lengths before writing

Serializing group arrays as wp.int32 (or None when empty) matches the public API. To catch regressions earlier, consider validating lengths before serialization.

Add a lightweight validation helper:

     def finalize(self, device: Devicelike | None = None, requires_grad: bool = False) -> Model:
+        # Sanity checks for group array lengths
+        def _check_group_lengths():
+            assert len(self.particle_group) == self.particle_count or self.particle_count == 0
+            assert len(self.shape_group) == self.shape_count or self.shape_count == 0
+            assert len(self.body_group) == self.body_count or self.body_count == 0
+            assert len(self.joint_group) == self.joint_count or self.joint_count == 0
+            assert len(self.articulation_group) == self.articulation_count or self.articulation_count == 0
+        _check_group_lengths()

If asserts are too strict for end-users, log warnings instead of asserting.

Also applies to: 3767-3768, 3922-3923, 3935-3936, 3975-3976

📜 Review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b701c03 and d6dad4c.

📒 Files selected for processing (2)
  • newton/_src/sim/builder.py (24 hunks)
  • newton/tests/test_model.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
newton/tests/test_model.py (1)
newton/_src/sim/builder.py (12)
  • ModelBuilder (68-4051)
  • add_particles (2685-2715)
  • finalize (3681-4019)
  • add_body (826-888)
  • add_shape_box (2196-2233)
  • add_particle (2649-2683)
  • add_articulation (536-542)
  • add_joint_revolute (1005-1087)
  • add_shape_sphere (2163-2194)
  • add_builder (544-824)
  • add_joint_fixed (1210-1246)
  • collapse_fixed_joints (1678-1972)
🔇 Additional comments (12)
newton/_src/sim/builder.py (12)

108-135: Clear, actionable Environment Grouping documentation

This section concisely explains semantics (-1 global, non-negative envs) and usage patterns (current_env_group, add_builder). Good addition.


329-357: *Initialization of _group arrays and current_env_group is correct and consistent

Introducing particle_group, shape_group, body_group, joint_group, articulation_group and defaulting current_env_group to -1 aligns with the API and tests.

Also applies to: 405-405, 440-445, 449-453


542-543: Articulation group tagging added at creation — LGTM

This mirrors the other entity creators and keeps grouping in sync.


589-599: Group selection logic balances explicit env vs. auto-assignment — LGTM

When environment is None, using num_envs (or current_env_group if not updating) yields deterministic auto-assignment.


713-739: Uniform group overrides for copied entities are correct

Extending particle/body/shape/joint/articulation groups with the target group guarantees a single env grouping per sub-builder instance — exactly as specified.


823-825: Restoring previous current_env_group after add_builder — LGTM

Prevents unexpected leakage of sub-builder env into subsequent direct adds.


887-887: Body creation tags body_group with current_env_group — LGTM

Keeps body_group in-step with body arrays.


952-953: Joint creation tags joint_group with current_env_group — LGTM

Consistent with other entity creators.


2075-2076: Shape creation tags shape_group with current_env_group — LGTM

Matches the body’s env at creation and test expectations.


2679-2680: Particle creation tags particle_group with current_env_group — LGTM

Single-item add path is aligned with the bulk path.


1843-1855: collapse_fixed_joints: body_group is correctly rebuilt for retained bodies

Saving original_body_group, clearing, then reassigning per retained body via original_id preserves grouping through structural edits. Defaulting to -1 when missing is sensible.

Also applies to: 1885-1890


1908-1954: collapse_fixed_joints: joint_group is correctly rebuilt for retained joints

Reconstructing joint_group from original_joint_group by original_id maintains group semantics after fixed-joint removal and reordering.

Comment thread newton/_src/sim/builder.py
Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 0

♻️ Duplicate comments (3)
newton/_src/sim/builder.py (3)

823-832: Num envs update logic fixed (no increment for globals; reconcile explicit env indices)

This addresses the previously raised mismatch and ensures num_envs reflects the highest non-negative group used.


2724-2726: Bulk particle creation now updates particle_group

This resolves the earlier mismatch risk for add_particles.


1853-1900: Rebuild body_group in collapse_fixed_joints — nice catch

Saving the original mapping and rebuilding body_group for retained bodies prevents post-collapse misalignment.

🧹 Nitpick comments (2)
newton/_src/sim/builder.py (2)

1918-1964: Rebuild joint_group in collapse_fixed_joints — correct; consider doing the same for articulations

Reconstructing joint_group to match retained_joints resolves joint-group desyncs. Consider verifying whether articulation_start changes (set/dedup) can desynchronize articulation_group. If collapse affects articulations or changes their ordering/count, rebuild articulation_group accordingly to keep it 1:1 with articulation_start.

Would you like a patch sketch to rebuild articulation_group based on the new articulation starts?


3734-3735: Serialize and Guard Per-Entity Group Arrays — Approved

All per-entity group arrays (particle_group, shape_group, body_group, joint_group, articulation_group) are correctly exported to the Model and guarded for empty counts in newton/_src/sim/builder.py at:

  • Lines 3734–3735 (particle_group)
  • Lines 3777–3778 (shape_group)
  • Lines 3932–3933 (body_group)
  • Lines 3945–3946 (joint_group)
  • Lines 3984–3986 (articulation_group)

Optional Safety Hardening: To prevent future regressions, you can reconcile self.num_envs with the highest non-negative group index in use. Replace the existing

-        # ensure the env count is set correctly
-        self.num_envs = max(1, self.num_envs)

with:

+        # ensure the env count is set correctly
+        # Optional safety: reconcile with the highest non-negative group index in use
+        groups_max = max(
+            max(self.particle_group)      if self.particle_group      else -1,
+            max(self.shape_group)         if self.shape_group         else -1,
+            max(self.body_group)          if self.body_group          else -1,
+            max(self.joint_group)         if self.joint_group         else -1,
+            max(self.articulation_group)  if self.articulation_group  else -1,
+        )
+        computed_envs = groups_max + 1 if groups_max >= 0 else 0
+        self.num_envs = max(1, max(self.num_envs, computed_envs))

Apply this change at the location where self.num_envs is currently set (e.g., at the end of add_builder or in your finalize routine).

Group-invariant verification (already confirmed):

rg -n -C2 'self\.(particle|shape|body|joint|articulation)_group\.(append|extend)\(' newton/_src/sim/builder.py
rg -n -C2 'm\.(particle|shape|body|joint|articulation)_group\s*='   newton/_src/sim/builder.py
📜 Review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d6dad4c and 6714d38.

📒 Files selected for processing (2)
  • newton/_src/sim/builder.py (24 hunks)
  • newton/tests/test_model.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • newton/tests/test_model.py
⏰ 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). (4)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
  • GitHub Check: run-newton-tests / newton-unittests (windows-latest)
🔇 Additional comments (17)
newton/_src/sim/builder.py (17)

108-135: Environment grouping docs are clear and actionable

The new section concisely explains -1 vs non-negative env groups and how to use add_builder and current_env_group. Good guidance for users.


329-329: Initialize particle_group — good

Establishing a per-particle env group list aligns with the PR’s objectives.


355-356: Initialize shape_group — good

This mirrors other entity group lists and keeps data in lockstep with shapes.


405-405: Initialize body_group — good

Consistent with per-entity env grouping.


440-441: Initialize joint_group — good

Required for per-joint grouping and aligns with tests.


444-445: Initialize articulation_group — good

Keeps articulation grouping explicit for serialization and downstream use.


449-453: current_env_group default and semantics look right

Defaulting to -1 (global) with temporary override during add_builder matches the intended workflow.


542-543: Record articulation group on creation — good

New articulations pick up the current_env_group. Matches the model’s grouping API.


544-551: add_builder(environment=...) signature addition is sensible

The parameter is well-placed and typed; defaulting to None enables the auto-assignment behavior.


554-587: Docstring for add_builder environment behavior is clear

Explicitly stating that sub-builder groups are overridden removes ambiguity. The examples are helpful.


592-602: Group selection logic covers all modes

  • environment=None uses next env (or current group when update_num_env_count=False).
  • Explicit environment uses that index.
  • The temporary override of current_env_group is restored later.

716-742: Override group arrays for copied entities — correct and consistent

Populating particle/body/shape/joint/articulation groups with the chosen env id guarantees alignment with the “override all entities” rule.


834-835: Restore current_env_group — good

Prevents add_builder from leaking group state into subsequent direct additions.


897-898: Add body_group on body creation — correct

Keeps body_group length in sync with bodies.


962-963: Add joint_group on joint creation — correct

Joint group per addition keeps ordering and length aligned with joints.


2085-2086: Add shape_group on shape creation — correct

Shape grouping is consistently tracked.


2689-2690: Add particle_group on single-particle creation — correct

Maintains grouping invariants.

@vreutskyy vreutskyy merged commit 945a55e into newton-physics:main Aug 18, 2025
12 checks passed
mzamoramora-nvidia pushed a commit to MiguelZamoraM/newton that referenced this pull request Aug 20, 2025
…cs#375 (newton-physics#504)

Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>
Signed-off-by: Miguel Zamora <mazamoramora@nvidia.com>
@coderabbitai coderabbitai Bot mentioned this pull request Oct 16, 2025
4 tasks
eric-heiden pushed a commit to eric-heiden/newton that referenced this pull request Jan 28, 2026
vidurv-nvidia pushed a commit to vidurv-nvidia/newton that referenced this pull request Mar 6, 2026
…on (newton-physics#504)

# Description

When running headless, with no cameras enabled, optimize init time by
only doing minimal fabric population, by turning off FSD.

## Type of change

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

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

## Checklist

- [ ] I have run the [`pre-commit` checks](https://pre-commit.com/) 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

<!--
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
-->

---------

Co-authored-by: Kelly Guo <kellyg@nvidia.com>
mmacklin pushed a commit to mmacklin/newton that referenced this pull request Apr 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add environment grouping functionality to Model entities

2 participants