Use body excludes for collision filtering#793
Conversation
📝 WalkthroughWalkthroughAdds two collision-filtering helpers to SolverMuJoCo to precompute body-pair excludes from shape-level filters and integrates them into the MuJoCo conversion flow, switches shape-to-geom mapping to geom names, and refines integer and zero-length slice handling in selection._get_attribute_array. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Converter as Model Converter
participant Solver as SolverMuJoCo
participant Spec as MuJoCo Spec
participant MJ as MuJoCo Model
Converter->>Solver: select colliding_shapes & shape_filters
Converter->>Solver: find_body_collision_filter_pairs(colliding_shapes, shape_filters, shape_groups, shape_bodies, body_shapes)
Solver-->>Converter: body_filters, remaining_shape_filters
note right of Converter: record geom names in shape_mapping
Converter->>MJ: create geoms (names preserved)
Converter->>Spec: add_exclude(body_pair) for each body_filters
Converter->>MJ: finalize/compile model (excludes applied)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
📜 Recent review detailsConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used🧠 Learnings (1)📓 Common learnings⏰ 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)
🔇 Additional comments (5)
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 |
7c6ec69 to
56a7814
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
newton/_src/utils/selection.py (1)
167-170: Pipeline failure: KeyError on empty ArticulationView selection.GPU unit tests hit “No matching articulations”. Either handle empty matches more gracefully (return an empty view with all-zero slices) or include the pattern in the error to aid debugging.
Proposed minimal improvement:
- raise KeyError("No matching articulations") + raise KeyError(f"No matching articulations for pattern '{pattern}'")Optionally, add a flag
allow_empty=Falsein the ctor that, when True, initializes an empty view (all counts/slices zero) instead of raising.newton/_src/solvers/mujoco/solver_mujoco.py (2)
1618-1645: Collapse shape filters to body excludes: correct but optimize membership and reuse sets.Logic is sound; however, using list membership inside a nested product is O(N·S1·S2). Convert filters to a set and cache per‑body colliding shapes to cut cost.
Apply:
@staticmethod def find_body_collision_filter_pairs(colliding_shapes, shape_filters, shape_bodies, body_shapes): - """For shape collision filter pairs, find body collision filter pairs that are contained within.""" - colliding_shapes = set(colliding_shapes) - body_filters = set() - body_non_filterable = set() - remaining_shape_filters = [] - for sp in shape_filters: + """For shape collision filter pairs, find body collision filter pairs that are contained within.""" + colliding_shapes = set(colliding_shapes) + # O(1) membership + sf_set = set(tuple(p) for p in shape_filters) + body_filters: set[tuple[int, int]] = set() + body_non_filterable: set[tuple[int, int]] = set() + remaining_shape_filters = [] + # cache per-body colliding shapes + coll_by_body: dict[int, set[int]] = {} + for sp in shape_filters: if sp[0] not in colliding_shapes or sp[1] not in colliding_shapes: continue b1, b2 = shape_bodies[sp[0]], shape_bodies[sp[1]] bp = min(b1, b2), max(b1, b2) if bp in body_filters: continue if bp in body_non_filterable: remaining_shape_filters.append(sp) continue - b1_shapes = {s for s in body_shapes[bp[0]] if s in colliding_shapes} - b2_shapes = {s for s in body_shapes[bp[1]] if s in colliding_shapes} - if all( - (bs1, bs2) in shape_filters or (bs2, bs1) in shape_filters for bs1, bs2 in product(b1_shapes, b2_shapes) - ): + b1_shapes = coll_by_body.setdefault(bp[0], {s for s in body_shapes[bp[0]] if s in colliding_shapes}) + b2_shapes = coll_by_body.setdefault(bp[1], {s for s in body_shapes[bp[1]] if s in colliding_shapes}) + if all(((a, b) in sf_set) or ((b, a) in sf_set) for a, b in product(b1_shapes, b2_shapes)): body_filters.add(bp) else: body_non_filterable.add(bp) remaining_shape_filters.append(sp) return list(body_filters), remaining_shape_filters
2356-2359: Skip excludes involving the world body.MuJoCo excludes between world and a body are unnecessary and may be unsupported. Also, per prior learning, Newton already skips collisions between global shapes across builders.
Guard:
-for b1, b2 in body_filters: +for b1, b2 in body_filters: + if -1 in (b1, b2): + continue mb1, mb2 = body_mapping[b1], body_mapping[b2] spec.add_exclude(bodyname1=spec.bodies[mb1].name, bodyname2=spec.bodies[mb2].name)
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
newton/_src/solvers/mujoco/solver_mujoco.py(4 hunks)newton/_src/utils/selection.py(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: shi-eric
PR: newton-physics/newton#521
File: newton/examples/example_cloth_hanging.py:36-36
Timestamp: 2025-08-12T05:17:34.423Z
Learning: The Newton migration guide (docs/migration.rst) is specifically for documenting how to migrate existing warp.sim functionality to Newton equivalents. New Newton-only features that didn't exist in warp.sim do not need migration documentation.
Learnt from: eric-heiden
PR: newton-physics/newton#587
File: newton/_src/sim/builder.py:656-661
Timestamp: 2025-08-20T18:02:36.099Z
Learning: In Newton physics, collisions between shapes with body index -1 (world-attached/global shapes) are automatically skipped by the collision system, so no manual collision filter pairs need to be added between global shapes from different builders.
🪛 Ruff (0.12.2)
newton/_src/utils/selection.py
521-521: Avoid specifying long messages outside the exception class
(TRY003)
526-526: Shebang should contain python, pytest, or uv run
(EXE003)
526-526: Shebang is present but file is not executable
(EXE001)
526-526: Shebang should be at the beginning of the file
(EXE005)
🪛 GitHub Actions: GPU Unit Tests on AWS EC2
newton/_src/utils/selection.py
[error] 169-169: KeyError: 'No matching articulations' raised during ArticulationView initialization.
⏰ 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 Benchmarks (Pull Request)
- GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
- GitHub Check: run-newton-tests / newton-unittests (windows-latest)
🔇 Additional comments (3)
newton/_src/utils/selection.py (1)
525-535: 0-length slice workaround: safe but keep strides invariant intentionally.Reusing
attrib.ptrwith shape(batch, 0, ...)avoids fragile pointer math and is fine as long as no writes occur. Good interim until Warp #958 is fixed.Once Warp fixes #958, consider reverting to native slicing to reduce custom code paths.
newton/_src/solvers/mujoco/solver_mujoco.py (2)
1981-1982: Store geom names instead of indices: good move.Names are stable across MuJoCo compile and simplify mapping. You already ensure uniqueness via “{type}_{shape}”.
2022-2031: No action required — self.model.body_shapes is host-side.newton/_src/sim/model.py initializes self.body_shapes as a Python mapping and newton/_src/sim/builder.py assigns it directly (m.body_shapes = self.body_shapes); utils/selection.py and tests also use Python lists — no host-copy needed before calling find_body_collision_filter_pairs.
| return list(body_filters), remaining_shape_filters | ||
|
|
||
| @staticmethod | ||
| def shapes_can_collide(a, b, shape_filter_pairs=None, shape_group=None): |
There was a problem hiding this comment.
I believe this logic should also apply within color_collision_shapes when building the graph, but I'm not 100% sure so I left it.
There was a problem hiding this comment.
I think you're right, although in practice we don't yet have environments with different collision groups per env, so let's revisit this later.
|
This work has now been merged through this PR #799 that has a slightly faster implementation of the body exclusion pairs (although it skips the collision group handling). |
Description
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