Skip to content

Conversation

@nvtw
Copy link
Member

@nvtw nvtw commented Jan 15, 2026

Description

Unified collision pipeline performance improvements

Newton Migration Guide

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

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

Before your PR is "Ready for review"

  • 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

    • Ellipsoid collisions supported; capsule collisions can now produce multiple contacts
    • Contacts API exposes an option to conservatively clear buffers for debugging
  • Documentation

    • Clarified collision pipeline behavior and buffer estimation across pipeline types
  • Refactor

    • Simplified collision pipeline API and added mesh-aware flag; narrow-phase prioritizes primitives; contact reduction optimized with early exits and duplicate checks; analytic ellipsoid SDF gradient added
  • Tests

    • Tests updated for multi-contact capsules and adjusted tolerances
  • Chores

    • Removed several ASV benchmark modules and related example parameter usages

✏️ Tip: You can customize this high-level summary in your review settings.

@nvtw nvtw self-assigned this Jan 15, 2026
@nvtw nvtw marked this pull request as draft January 15, 2026 14:30
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 2026

📝 Walkthrough

Walkthrough

This PR removes ASV collision benchmarks, refactors narrow-phase collision into a primitive-first then GJK/MPR two-stage flow, introduces multi-contact capsule returns and ellipsoid SDF gradient support, adds contact-margin validation and export/reduction optimizations, and replaces per-pair contact limits with dynamic estimation plus a has_meshes flag.

Changes

Cohort / File(s) Summary
Benchmark Removals
asv/benchmarks/collision/benchmark_contact_reduction.py, asv/benchmarks/collision/benchmark_hashtable.py
Deleted ASV benchmark modules: kernels, benchmark classes, and CLI/run orchestration.
Capsule Multi-contact
newton/_src/geometry/collision_primitive.py
collide_capsule_capsule signature changed to return (contact_dist: vec2, contact_pos: matrix((2,3)), contact_normal: vec3); implementation rewritten to emit up to two contacts.
Contact Validation & Reduction
newton/_src/geometry/contact_data.py, newton/_src/geometry/contact_reduction_global.py
Added contact_passes_margin_check(); added early-exit guards for empty buffers; replaced duplicate-search with bounded while-loop in export/reduction kernels.
Ellipsoid SDF & Soft Contacts
newton/_src/geometry/kernels.py
Added ellipsoid_sdf_grad(radii, p), hardened ellipsoid_sdf() numerics, and extended create_soft_contacts() to handle GeoType.ELLIPSOID.
Narrow Phase Two-Stage Refactor
newton/_src/geometry/narrow_phase.py
Added create_narrow_phase_primitive_kernel() and a primitive-first routing stage that collects gjk candidates and routes remaining pairs to GJK/MPR; introduced has_meshes flag, routing buffers, and consolidated counter array.
Unified Pipeline API Changes
newton/_src/sim/collide_unified.py
Removed rigid_contact_max_per_pair, added has_meshes parameter, and implemented dynamic rigid_contact_max estimation; propagated has_meshes to NarrowPhase.
Contacts Buffer & Clearing
newton/_src/sim/contacts.py
Added clear_buffers option, consolidated counters into a shared _counter_array, and optimized clear() with a fast-path counter reset and a conservative full-buffer clear mode.
Examples & Call Sites
newton/examples/*, newton/examples/basic/..., newton/examples/robot/..., newton/examples/cable/...
Removed rigid_contact_max_per_pair from API/usage; added USE_UNIFIED_COLLISION toggle in cable example; updated example callsites to new API.
Tests Updated
newton/tests/*
Tests updated for capsule multi-contact outputs (vec2 distances, 2×3 positions), replaced rigid_contact_max_per_pair with rigid_contact_max, and adjusted tolerances/buffers where needed.
Docs
docs/concepts/collisions.rst
Clarified multi-contact generation: standard pipeline uses per-pair limit; unified pipeline auto-estimates buffer sizes.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant BroadPhase as BroadPhase (pair gen)
participant Narrow as NarrowPhase (primitive kernel)
participant Router as Pair Router
participant GJK as GJK/MPR kernel
participant Reducer as ContactReduction (hashtable/export)
BroadPhase->>Narrow: submit shape pairs
Narrow-->>Router: primitive-resolved contacts + remaining pairs
Router->>GJK: route remaining convex pairs
GJK-->>Reducer: produced contacts
Narrow-->>Reducer: primitive contacts
Reducer->>Reducer: reduce/export contacts

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

docs

Suggested reviewers

  • eric-heiden
  • mmacklin
  • adenzler-nvidia
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 79.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective of the PR, which is improving the performance of the unified collision pipeline through various optimizations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings


📜 Recent review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1599966 and 4406fa9.

📒 Files selected for processing (2)
  • newton/_src/geometry/narrow_phase.py
  • newton/examples/robot/example_robot_panda_hydro.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • newton/examples/robot/example_robot_panda_hydro.py
🧰 Additional context used
📓 Path-based instructions (4)
newton/_src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

The newton/_src/ directory is internal library implementation only and must not be imported by user code (examples, documentation, or external users)

Files:

  • newton/_src/geometry/narrow_phase.py
newton/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Any user-facing class/function/object added under _src must be exposed via the public Newton API through re-exports

Files:

  • newton/_src/geometry/narrow_phase.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Use prefix-first naming for classes: ActuatorPD, ActuatorPID (not PDActuator, PIDActuator)
Use prefix-first naming for methods: add_shape_sphere() (not add_sphere_shape())
Method names must use snake_case
Prefer nested classes when self-contained: if a helper type or enum is only meaningful inside one parent class and doesn't need a public identity, define it as a nested class instead of a top-level class/module
Follow PEP 8 for Python code
Use Google-style docstrings with clear, concise explanations of what functions do, their parameters, and return values

Files:

  • newton/_src/geometry/narrow_phase.py
**/*

📄 CodeRabbit inference engine (AGENTS.md)

CLI arguments must use kebab-case (e.g., --use-cuda-graph, not --use_cuda_graph)

Files:

  • newton/_src/geometry/narrow_phase.py
🧠 Learnings (12)
📓 Common learnings
Learnt from: WenchaoHuang
Repo: newton-physics/newton PR: 835
File: newton/_src/solvers/style3d/collision/collision.py:49-53
Timestamp: 2025-09-26T05:58:21.013Z
Learning: In newton/_src/solvers/style3d/collision/collision.py, the broad_phase buffers (broad_phase_ee, broad_phase_ef, broad_phase_vf) created with wp.array() don't need zero-initialization because they follow a write-first-read-later pattern: BVH query kernels in frame_begin() populate these buffers completely (write-only operations), and collision kernels only read from them afterward in accumulate_contact_force(), ensuring no undefined reads occur.
Learnt from: vastsoun
Repo: newton-physics/newton PR: 1019
File: newton/_src/solvers/kamino/geometry/primitives.py:0-0
Timestamp: 2025-12-12T17:45:26.847Z
Learning: In newton/_src/solvers/kamino/geometry/primitives.py, the narrow-phase kernel `_primitive_narrowphase` does not need to handle symmetric shape-pair orderings (e.g., both SPHERE-BOX and BOX-SPHERE) because `kamino.core.builder.ModelBuilder` and `kamino.geometry.primitive.broadphase` guarantee that explicit candidate geometry pairs are always defined with GIDs in ascending order.
Learnt from: nvtw
Repo: newton-physics/newton PR: 981
File: newton/_src/geometry/collision_convex.py:180-289
Timestamp: 2025-10-24T07:56:14.792Z
Learning: In newton/_src/geometry/collision_convex.py, the single-contact solver (create_solve_convex_single_contact) intentionally returns count=1 even when shapes are separated (signed_distance > contact_threshold). The calling code is responsible for deciding whether to accept the contact based on the signed distance value. This design pushes filtering responsibility to the caller.
📚 Learning: 2025-12-12T17:45:26.847Z
Learnt from: vastsoun
Repo: newton-physics/newton PR: 1019
File: newton/_src/solvers/kamino/geometry/primitives.py:0-0
Timestamp: 2025-12-12T17:45:26.847Z
Learning: In newton/_src/solvers/kamino/geometry/primitives.py, the narrow-phase kernel `_primitive_narrowphase` does not need to handle symmetric shape-pair orderings (e.g., both SPHERE-BOX and BOX-SPHERE) because `kamino.core.builder.ModelBuilder` and `kamino.geometry.primitive.broadphase` guarantee that explicit candidate geometry pairs are always defined with GIDs in ascending order.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-08-12T17:51:37.474Z
Learnt from: nvlukasz
Repo: newton-physics/newton PR: 519
File: newton/geometry.py:16-22
Timestamp: 2025-08-12T17:51:37.474Z
Learning: In the Newton public API refactor, common geometry symbols like ParticleFlags and ShapeFlags are now exposed at the top level in newton/__init__.py rather than through newton.geometry. The newton/geometry.py module is intentionally focused only on broad-phase collision detection classes (BroadPhaseAllPairs, BroadPhaseExplicit, BroadPhaseSAP).

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-09-26T05:58:21.013Z
Learnt from: WenchaoHuang
Repo: newton-physics/newton PR: 835
File: newton/_src/solvers/style3d/collision/collision.py:49-53
Timestamp: 2025-09-26T05:58:21.013Z
Learning: In newton/_src/solvers/style3d/collision/collision.py, the broad_phase buffers (broad_phase_ee, broad_phase_ef, broad_phase_vf) created with wp.array() don't need zero-initialization because they follow a write-first-read-later pattern: BVH query kernels in frame_begin() populate these buffers completely (write-only operations), and collision kernels only read from them afterward in accumulate_contact_force(), ensuring no undefined reads occur.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-08-20T18:02:36.099Z
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 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.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-09-15T13:13:37.976Z
Learnt from: gdaviet
Repo: newton-physics/newton PR: 750
File: newton/_src/solvers/implicit_mpm/solve_rheology.py:772-783
Timestamp: 2025-09-15T13:13:37.976Z
Learning: In Warp's tiled launch API, using a 2D launch dimension dim=(num_blocks, tile_size) is correct for tiled kernels. The wp.tid() function can either return a 2D tuple (block_index, lane) or just discard the last dimension to return only the block_index when lane information is not needed, making `block_index = wp.tid()` a valid shortcut in tiled kernels.

Applied to files:

  • newton/_src/geometry/narrow_phase.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/geometry/narrow_phase.py
📚 Learning: 2025-10-06T18:14:18.582Z
Learnt from: daedalus5
Repo: newton-physics/newton PR: 868
File: newton/examples/diffsim/example_diffsim_bear.py:82-96
Timestamp: 2025-10-06T18:14:18.582Z
Learning: Warp's tile operations (`wp.tile_store()` and `wp.tile_load()`) have built-in boundary checking. Out-of-bounds writes are automatically skipped and out-of-bounds reads are zero-filled, so tiled kernels are safe even when the array size is not a multiple of the tile size.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-09-26T05:58:21.013Z
Learnt from: WenchaoHuang
Repo: newton-physics/newton PR: 835
File: newton/_src/solvers/style3d/collision/collision.py:49-53
Timestamp: 2025-09-26T05:58:21.013Z
Learning: In newton/_src/solvers/style3d/collision/collision.py, the broad_phase buffers (broad_phase_ee, broad_phase_ef, broad_phase_vf) created with wp.array() don't need zero-initialization because the BVH query kernels that use them always write the count to query_results[0, tid] = 0 first before populating any data, ensuring no undefined reads occur.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-10-30T07:28:13.112Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 984
File: newton/_src/geometry/broad_phase_nxn.py:318-336
Timestamp: 2025-10-30T07:28:13.112Z
Learning: In Newton's BroadPhaseAllPairs class (newton/_src/geometry/broad_phase_nxn.py), device management is the caller's responsibility. The library intentionally does not perform automatic device transfers for precomputed arrays in the launch method, even if there's a device mismatch. Users must ensure that the device specified in __init__ matches the device used in launch.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-10-10T10:47:41.082Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 899
File: newton/tests/test_rigid_contact.py:214-268
Timestamp: 2025-10-10T10:47:41.082Z
Learning: In `newton/tests/test_rigid_contact.py`, using `add_shape_plane(width=0, length=0)` for an infinite plane works correctly with the collision system and does not cause degenerate AABB issues in the test scenario `test_shape_collisions_gjk_mpr_multicontact`.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2026-01-13T03:11:40.556Z
Learnt from: jumyungc
Repo: newton-physics/newton PR: 1333
File: newton/_src/sim/builder.py:0-0
Timestamp: 2026-01-13T03:11:40.556Z
Learning: Ensure all VBD damping semantics follow Rayleigh-style damping and are unitless. This convention, demonstrated in SolverVBD, should be applied consistently across all VBD-related constraints in the codebase. In newton/_src/sim/builder.py, validate that any damping terms use the same normalization and are treated as dimensionless; add comments and unit tests to enforce consistency across VBD components.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
🧬 Code graph analysis (1)
newton/_src/geometry/narrow_phase.py (9)
newton/_src/geometry/collision_core.py (2)
  • check_infinite_plane_bsphere_overlap (504-556)
  • compute_bounding_sphere_from_aabb (428-438)
newton/_src/geometry/collision_primitive.py (6)
  • collide_plane_capsule (297-348)
  • collide_plane_sphere (119-125)
  • collide_sphere_box (1105-1158)
  • collide_sphere_capsule (162-195)
  • collide_sphere_cylinder (443-512)
  • collide_sphere_sphere (129-158)
newton/_src/geometry/contact_data.py (1)
  • ContactData (26-62)
newton/_src/geometry/types.py (2)
  • GeoType (25-67)
  • SDF (74-113)
newton/_src/geometry/support_function.py (1)
  • extract_shape_data (305-347)
newton/_src/geometry/sdf_hydroelastic.py (1)
  • launch (386-449)
newton/_src/geometry/broad_phase_nxn.py (2)
  • launch (295-361)
  • launch (378-435)
newton/_src/geometry/broad_phase_sap.py (1)
  • launch (506-650)
newton/_src/geometry/contact_reduction_global.py (2)
  • clear_active (351-385)
  • get_data_struct (387-404)
⏰ 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 / Run GPU Benchmarks on AWS EC2
  • GitHub Check: Run GPU Tests / Run GPU Unit Tests on AWS EC2
  • GitHub Check: run-newton-tests / newton-unittests (windows-latest)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
🔇 Additional comments (5)
newton/_src/geometry/narrow_phase.py (5)

132-469: Primitive kernel routing + unified write path look solid.

The analytic primitive handling and centralized margin-checked write block are clear and maintainable.


472-630: GJK/MPR fallback flow is clean and well‑guarded.

The prefiltering and bounding-sphere early rejection keep the heavy path focused.


1138-1196: Good gating of mesh kernels/reduction by has_meshes and device.

This avoids unnecessary setup on primitive-only scenes and CPU runs.


1618-1619: Contact count reset in launch() is correct.

Prevents stale counts from leaking into the new frame.


1223-1276: No changes required—the code correctly handles optional buffers using Warp's None pattern.

In Warp, it is valid to pass None for optional array parameters. The kernel already implements the correct pattern by checking if shape_pairs_sdf_sdf: (line 199) before accessing optional buffers, which is the idiomatic approach documented in the project. Similar patterns appear elsewhere in the codebase (e.g., newton/_src/solvers/style3d/linear_solver.py). The current design correctly avoids allocating unnecessary dummy buffers when has_meshes=False.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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

Comment @coderabbitai help to get the list of available commands and usage tips.

@nvtw
Copy link
Member Author

nvtw commented Jan 15, 2026

Removed benchmarks I added in an earlier comment as discussed in the Wednesday meeting. Will keep them locally on my machine, they are not intended to run on CI.

@nvtw
Copy link
Member Author

nvtw commented Jan 15, 2026

This MR requires #1335 to be implemented before it can be merged

@codecov
Copy link

codecov bot commented Jan 15, 2026

Codecov Report

❌ Patch coverage is 85.26316% with 14 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
newton/_src/sim/contacts.py 35.29% 11 Missing ⚠️
newton/_src/sim/collide_unified.py 62.50% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
newton/_src/geometry/narrow_phase.py (1)

212-220: Consider preventing counter overflow or clarify bounds assumptions for downstream kernels.

The counter increments unconditionally while the write is conditional, so shape_pairs_sdf_sdf_count can exceed the buffer size. Downstream kernels in sdf_hydroelastic.py (broadphase_collision_pairs_count, broadphase_collide_blocks_count) properly guard against OOB by checking if tid >= shape_pairs_sdf_sdf_count[0]: return before array access, so there's no immediate risk. However, this differs from the defensive pattern used for contact_count (which decrements on overflow). Either apply the same defensive decrement here for consistency, or add a comment clarifying that downstream iteration bounds are safe due to the guard checks.

🤖 Fix all issues with AI agents
In `@newton/_src/geometry/narrow_phase.py`:
- Around line 410-453: The reservation can overflow the contact buffers: after
computing num_valid and calling base_index =
wp.atomic_add(writer_data.contact_count, 0, num_valid), check against
writer_data.contact_max and clamp/rollback any excess before writing; compute
end_index = base_index + num_valid, if end_index > writer_data.contact_max then
excess = end_index - writer_data.contact_max, if excess == num_valid then undo
the reservation via wp.atomic_add(writer_data.contact_count, 0, -num_valid) and
skip writes, otherwise reduce the global count by excess
(wp.atomic_add(writer_data.contact_count, 0, -excess)) and only write the first
(num_valid - excess) contacts using writer_func at indices base_index ..
base_index+allowed-1, using the existing contact_0_valid/contact_1_valid logic
to decide which ones to emit.
🧹 Nitpick comments (5)
newton/_src/geometry/contact_data.py (1)

65-96: Logic is correct but could be simplified.

The computation correctly checks if the contact passes the margin threshold. However, the intermediate world-space position calculations (lines 85-90) simplify algebraically to:

d = contact_distance - (thickness_a + thickness_b)

The current formulation matches the pattern in box_plane_collision for consistency, which is reasonable if that's intentional. If performance is a concern, consider the simplified form.

♻️ Simplified alternative (optional)
 `@wp.func`
 def contact_passes_margin_check(
     contact_data: ContactData,
 ) -> bool:
-    total_separation_needed = (
-        contact_data.radius_eff_a + contact_data.radius_eff_b + contact_data.thickness_a + contact_data.thickness_b
-    )
-
-    # Distance calculation matching box_plane_collision
-    contact_normal_a_to_b = wp.normalize(contact_data.contact_normal_a_to_b)
-
-    a_contact_world = contact_data.contact_point_center - contact_normal_a_to_b * (
-        0.5 * contact_data.contact_distance + contact_data.radius_eff_a
-    )
-    b_contact_world = contact_data.contact_point_center + contact_normal_a_to_b * (
-        0.5 * contact_data.contact_distance + contact_data.radius_eff_b
-    )
-
-    diff = b_contact_world - a_contact_world
-    distance = wp.dot(diff, contact_normal_a_to_b)
-    d = distance - total_separation_needed
-
-    return d <= contact_data.margin
+    # Effective separation distance after accounting for radii
+    d = contact_data.contact_distance - (contact_data.thickness_a + contact_data.thickness_b)
+    return d <= contact_data.margin
newton/tests/test_hydroelastic.py (1)

132-140: Consider de-magic-ing rigid_contact_max in this test (or justify the constants).

The API update is correct, but 6000 / 100 are hard-coded and could become flaky if hydroelastic output changes (e.g., SDF resolution tweaks). Consider deriving from expected pair count / resolution (or at least add a short rationale for the chosen values).

newton/tests/test_collision_pipeline.py (1)

433-436: The tolerance=0.1 bump is very large; may mask real regressions.

If the intent is to tolerate normal sign / direction variability, consider making the assertion less direction-dependent (e.g., constrain speed/energy or use a component-invariant check) so the test can stay tighter.

newton/tests/test_collision_primitives.py (1)

817-905: Test now ignores the second contact; consider asserting it for the parallel-axes cases.

Right now the test only checks distances_np[i][0] / positions_np[i][0]. To actually validate the new behavior, consider also asserting:

  • parallel axes: second contact is finite and has similar distance
  • non-parallel: second contact is inf (or otherwise marked invalid)
newton/_src/geometry/narrow_phase.py (1)

1120-1131: has_meshes fast-path is great; consider making the launch sizing device-aware.

min_blocks = 256 can be reasonable on CUDA, but it may be unnecessary overhead on CPU runs (lots of idle threads). Consider branching on wp.get_device(device).is_cuda for the minimum block count.

Also applies to: 1251-1260

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9b64b2b and 6b4c8e4.

📒 Files selected for processing (18)
  • asv/benchmarks/collision/benchmark_contact_reduction.py
  • asv/benchmarks/collision/benchmark_hashtable.py
  • docs/concepts/collisions.rst
  • newton/_src/geometry/collision_primitive.py
  • newton/_src/geometry/contact_data.py
  • newton/_src/geometry/contact_reduction_global.py
  • newton/_src/geometry/kernels.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/sim/collide_unified.py
  • newton/examples/__init__.py
  • newton/examples/basic/example_basic_shapes.py
  • newton/examples/cable/example_cable_pile.py
  • newton/examples/robot/example_robot_panda_hydro.py
  • newton/tests/test_collision_pipeline.py
  • newton/tests/test_collision_primitives.py
  • newton/tests/test_hydroelastic.py
  • newton/tests/test_rigid_contact.py
  • newton/tests/test_sdf_compute.py
💤 Files with no reviewable changes (6)
  • newton/examples/basic/example_basic_shapes.py
  • newton/examples/init.py
  • newton/tests/test_sdf_compute.py
  • newton/examples/robot/example_robot_panda_hydro.py
  • asv/benchmarks/collision/benchmark_contact_reduction.py
  • asv/benchmarks/collision/benchmark_hashtable.py
🧰 Additional context used
📓 Path-based instructions (5)
newton/_src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

The newton/_src/ directory is internal library implementation only and must not be imported by user code (examples, documentation, or external users)

Files:

  • newton/_src/geometry/contact_data.py
  • newton/_src/geometry/contact_reduction_global.py
  • newton/_src/geometry/collision_primitive.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/geometry/kernels.py
  • newton/_src/sim/collide_unified.py
newton/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Any user-facing class/function/object added under _src must be exposed via the public Newton API through re-exports

Files:

  • newton/_src/geometry/contact_data.py
  • newton/_src/geometry/contact_reduction_global.py
  • newton/_src/geometry/collision_primitive.py
  • newton/tests/test_rigid_contact.py
  • newton/tests/test_hydroelastic.py
  • newton/examples/cable/example_cable_pile.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/geometry/kernels.py
  • newton/_src/sim/collide_unified.py
  • newton/tests/test_collision_primitives.py
  • newton/tests/test_collision_pipeline.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Use prefix-first naming for classes: ActuatorPD, ActuatorPID (not PDActuator, PIDActuator)
Use prefix-first naming for methods: add_shape_sphere() (not add_sphere_shape())
Method names must use snake_case
Prefer nested classes when self-contained: if a helper type or enum is only meaningful inside one parent class and doesn't need a public identity, define it as a nested class instead of a top-level class/module
Follow PEP 8 for Python code
Use Google-style docstrings with clear, concise explanations of what functions do, their parameters, and return values

Files:

  • newton/_src/geometry/contact_data.py
  • newton/_src/geometry/contact_reduction_global.py
  • newton/_src/geometry/collision_primitive.py
  • newton/tests/test_rigid_contact.py
  • newton/tests/test_hydroelastic.py
  • newton/examples/cable/example_cable_pile.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/geometry/kernels.py
  • newton/_src/sim/collide_unified.py
  • newton/tests/test_collision_primitives.py
  • newton/tests/test_collision_pipeline.py
**/*

📄 CodeRabbit inference engine (AGENTS.md)

CLI arguments must use kebab-case (e.g., --use-cuda-graph, not --use_cuda_graph)

Files:

  • newton/_src/geometry/contact_data.py
  • newton/_src/geometry/contact_reduction_global.py
  • newton/_src/geometry/collision_primitive.py
  • docs/concepts/collisions.rst
  • newton/tests/test_rigid_contact.py
  • newton/tests/test_hydroelastic.py
  • newton/examples/cable/example_cable_pile.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/geometry/kernels.py
  • newton/_src/sim/collide_unified.py
  • newton/tests/test_collision_primitives.py
  • newton/tests/test_collision_pipeline.py
newton/examples/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

newton/examples/**/*.py: Examples must not import from newton._src, only from the public Newton API
Examples must follow the format with an Example class and must be registered in README.md with a uv run command and screenshot
Examples must implement a test_final() method that verifies the simulation state is valid after the example has run
Examples may optionally implement a test_post_step() method that is evaluated after every step() call

Files:

  • newton/examples/cable/example_cable_pile.py
🧠 Learnings (18)
📓 Common learnings
Learnt from: shi-eric
Repo: newton-physics/newton PR: 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.
📚 Learning: 2025-10-24T07:56:14.792Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 981
File: newton/_src/geometry/collision_convex.py:180-289
Timestamp: 2025-10-24T07:56:14.792Z
Learning: In newton/_src/geometry/collision_convex.py, the single-contact solver (create_solve_convex_single_contact) intentionally returns count=1 even when shapes are separated (signed_distance > contact_threshold). The calling code is responsible for deciding whether to accept the contact based on the signed distance value. This design pushes filtering responsibility to the caller.

Applied to files:

  • newton/_src/geometry/contact_data.py
  • newton/_src/geometry/collision_primitive.py
  • docs/concepts/collisions.rst
  • newton/tests/test_collision_primitives.py
📚 Learning: 2026-01-13T03:11:40.556Z
Learnt from: jumyungc
Repo: newton-physics/newton PR: 1333
File: newton/_src/sim/builder.py:0-0
Timestamp: 2026-01-13T03:11:40.556Z
Learning: Ensure all VBD damping semantics follow Rayleigh-style damping and are unitless. This convention, demonstrated in SolverVBD, should be applied consistently across all VBD-related constraints in the codebase. In newton/_src/sim/builder.py, validate that any damping terms use the same normalization and are treated as dimensionless; add comments and unit tests to enforce consistency across VBD components.

Applied to files:

  • newton/_src/geometry/contact_data.py
  • newton/_src/geometry/contact_reduction_global.py
  • newton/_src/geometry/collision_primitive.py
  • newton/tests/test_rigid_contact.py
  • newton/tests/test_hydroelastic.py
  • newton/examples/cable/example_cable_pile.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/geometry/kernels.py
  • newton/_src/sim/collide_unified.py
  • newton/tests/test_collision_primitives.py
  • newton/tests/test_collision_pipeline.py
📚 Learning: 2025-09-26T05:58:21.013Z
Learnt from: WenchaoHuang
Repo: newton-physics/newton PR: 835
File: newton/_src/solvers/style3d/collision/collision.py:49-53
Timestamp: 2025-09-26T05:58:21.013Z
Learning: In newton/_src/solvers/style3d/collision/collision.py, the broad_phase buffers (broad_phase_ee, broad_phase_ef, broad_phase_vf) created with wp.array() don't need zero-initialization because they follow a write-first-read-later pattern: BVH query kernels in frame_begin() populate these buffers completely (write-only operations), and collision kernels only read from them afterward in accumulate_contact_force(), ensuring no undefined reads occur.

Applied to files:

  • newton/_src/geometry/contact_reduction_global.py
  • newton/tests/test_rigid_contact.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/sim/collide_unified.py
📚 Learning: 2025-12-12T08:45:51.908Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 1221
File: newton/examples/example_sdf.py:277-287
Timestamp: 2025-12-12T08:45:51.908Z
Learning: In Newton examples (e.g., example_sdf.py), computing contacts once per frame and reusing them across multiple substeps is an intentional design choice, not a bug. The contacts are computed before the substep loop and intentionally kept constant throughout all substeps within that frame.

Applied to files:

  • newton/_src/geometry/contact_reduction_global.py
  • newton/tests/test_hydroelastic.py
📚 Learning: 2025-12-12T17:45:26.847Z
Learnt from: vastsoun
Repo: newton-physics/newton PR: 1019
File: newton/_src/solvers/kamino/geometry/primitives.py:0-0
Timestamp: 2025-12-12T17:45:26.847Z
Learning: In newton/_src/solvers/kamino/geometry/primitives.py, the narrow-phase kernel `_primitive_narrowphase` does not need to handle symmetric shape-pair orderings (e.g., both SPHERE-BOX and BOX-SPHERE) because `kamino.core.builder.ModelBuilder` and `kamino.geometry.primitive.broadphase` guarantee that explicit candidate geometry pairs are always defined with GIDs in ascending order.

Applied to files:

  • newton/_src/geometry/contact_reduction_global.py
  • newton/_src/geometry/narrow_phase.py
  • newton/tests/test_collision_primitives.py
📚 Learning: 2025-09-26T05:58:21.013Z
Learnt from: WenchaoHuang
Repo: newton-physics/newton PR: 835
File: newton/_src/solvers/style3d/collision/collision.py:49-53
Timestamp: 2025-09-26T05:58:21.013Z
Learning: In newton/_src/solvers/style3d/collision/collision.py, the broad_phase buffers (broad_phase_ee, broad_phase_ef, broad_phase_vf) created with wp.array() don't need zero-initialization because the BVH query kernels that use them always write the count to query_results[0, tid] = 0 first before populating any data, ensuring no undefined reads occur.

Applied to files:

  • newton/_src/geometry/contact_reduction_global.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/sim/collide_unified.py
📚 Learning: 2025-08-20T03:30:16.037Z
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 584
File: newton/_src/utils/import_urdf.py:159-167
Timestamp: 2025-08-20T03:30:16.037Z
Learning: In the URDF importer (newton/_src/utils/import_urdf.py), cylinders are intentionally created using add_shape_capsule() instead of add_shape_cylinder() because cylinder collisions are not fully supported yet. This is a deliberate workaround until proper cylinder collision support is implemented.

Applied to files:

  • newton/_src/geometry/collision_primitive.py
  • newton/examples/cable/example_cable_pile.py
  • newton/tests/test_collision_primitives.py
📚 Learning: 2025-08-12T17:51:37.474Z
Learnt from: nvlukasz
Repo: newton-physics/newton PR: 519
File: newton/geometry.py:16-22
Timestamp: 2025-08-12T17:51:37.474Z
Learning: In the Newton public API refactor, common geometry symbols like ParticleFlags and ShapeFlags are now exposed at the top level in newton/__init__.py rather than through newton.geometry. The newton/geometry.py module is intentionally focused only on broad-phase collision detection classes (BroadPhaseAllPairs, BroadPhaseExplicit, BroadPhaseSAP).

Applied to files:

  • newton/_src/geometry/collision_primitive.py
  • newton/examples/cable/example_cable_pile.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/sim/collide_unified.py
  • newton/tests/test_collision_primitives.py
  • newton/tests/test_collision_pipeline.py
📚 Learning: 2025-12-25T21:44:17.047Z
Learnt from: camevor
Repo: newton-physics/newton PR: 1161
File: docs/concepts/sensors.rst:12-12
Timestamp: 2025-12-25T21:44:17.047Z
Learning: Future API standardization: The sensor.update() method should be standardized to a single-argument form update(state) across all sensor types. The redundant model parameter will be removed from sensors like SensorFrameTransform. In code reviews, check for API changes that move toward a single-argument update, ensure all sensor implementations conform, and update user-facing docs to reflect the change. If reviewing related code, verify that calls using the old two-argument form are refactored and that any dependent serialization or tests are updated accordingly.

Applied to files:

  • docs/concepts/collisions.rst
📚 Learning: 2026-01-12T10:11:47.988Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 1314
File: newton/_src/sim/builder.py:6061-6091
Timestamp: 2026-01-12T10:11:47.988Z
Learning: Repo: newton-physics/newton PR: 1314
File: newton/_src/sim/builder.py (ModelBuilder.finalize)
Learning: The project prefers to keep finalize() validation lightweight; do not add union-find/connected-components validation for loop joints. Current behavior (allow non-articulated joints when the child is in any articulation) is acceptable; avoid overengineering here.

Applied to files:

  • newton/tests/test_rigid_contact.py
📚 Learning: 2025-10-30T07:28:13.112Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 984
File: newton/_src/geometry/broad_phase_nxn.py:318-336
Timestamp: 2025-10-30T07:28:13.112Z
Learning: In Newton's BroadPhaseAllPairs class (newton/_src/geometry/broad_phase_nxn.py), device management is the caller's responsibility. The library intentionally does not perform automatic device transfers for precomputed arrays in the launch method, even if there's a device mismatch. Users must ensure that the device specified in __init__ matches the device used in launch.

Applied to files:

  • newton/tests/test_rigid_contact.py
  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-09-18T07:05:56.836Z
Learnt from: gdaviet
Repo: newton-physics/newton PR: 750
File: newton/_src/solvers/implicit_mpm/solve_rheology.py:969-986
Timestamp: 2025-09-18T07:05:56.836Z
Learning: In newton/_src/solvers/implicit_mpm/solve_rheology.py, transposed_strain_mat parameter cannot be None - the type signature was corrected to reflect this guarantee, eliminating the need for None checks when accessing transposed_strain_mat.offsets.

Applied to files:

  • newton/tests/test_hydroelastic.py
📚 Learning: 2025-10-29T14:55:48.171Z
Learnt from: gdaviet
Repo: newton-physics/newton PR: 940
File: newton/examples/mpm/example_mpm_granular.py:92-101
Timestamp: 2025-10-29T14:55:48.171Z
Learning: In newton/examples/mpm/example_mpm_granular.py, the capture() method intentionally advances the simulation state once during CUDA graph capture. This is expected behavior because CUDA graph capture records exact memory addresses, so the state arrays (state_0, state_1) cannot be reset or reallocated after capture without invalidating the graph.

Applied to files:

  • newton/examples/cable/example_cable_pile.py
📚 Learning: 2025-12-12T08:45:43.428Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 1221
File: newton/examples/example_sdf.py:277-287
Timestamp: 2025-12-12T08:45:43.428Z
Learning: In Newtown (Newton) example code, specifically files under newton/examples, computing contacts once per frame and reusing them across all substeps is an intentional design choice, not a bug. Reviewers should verify that contacts are computed before the substep loop and reused for every substep within the same frame. This pattern reduces redundant work and preserves frame-consistency; do not flag as a regression unless the behavior is changed for correctness or performance reasons.

Applied to files:

  • newton/examples/cable/example_cable_pile.py
📚 Learning: 2025-10-10T10:47:41.082Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 899
File: newton/tests/test_rigid_contact.py:214-268
Timestamp: 2025-10-10T10:47:41.082Z
Learning: In `newton/tests/test_rigid_contact.py`, using `add_shape_plane(width=0, length=0)` for an infinite plane works correctly with the collision system and does not cause degenerate AABB issues in the test scenario `test_shape_collisions_gjk_mpr_multicontact`.

Applied to files:

  • newton/_src/geometry/narrow_phase.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/geometry/narrow_phase.py
📚 Learning: 2025-09-15T13:13:37.976Z
Learnt from: gdaviet
Repo: newton-physics/newton PR: 750
File: newton/_src/solvers/implicit_mpm/solve_rheology.py:772-783
Timestamp: 2025-09-15T13:13:37.976Z
Learning: In Warp's tiled launch API, using a 2D launch dimension dim=(num_blocks, tile_size) is correct for tiled kernels. The wp.tid() function can either return a 2D tuple (block_index, lane) or just discard the last dimension to return only the block_index when lane information is not needed, making `block_index = wp.tid()` a valid shortcut in tiled kernels.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
🧬 Code graph analysis (7)
newton/_src/geometry/contact_data.py (1)
newton/_src/sim/builder.py (1)
  • dot (5249-5250)
newton/_src/geometry/collision_primitive.py (1)
newton/_src/sim/builder.py (1)
  • dot (5249-5250)
newton/tests/test_rigid_contact.py (2)
newton/_src/sim/collide_unified.py (2)
  • CollisionPipelineUnified (273-715)
  • from_model (439-524)
newton/_src/sim/collide.py (1)
  • from_model (146-190)
newton/examples/cable/example_cable_pile.py (3)
newton/examples/__init__.py (1)
  • create_collision_pipeline (398-467)
newton/_src/sim/collide_unified.py (1)
  • collide (526-705)
newton/_src/sim/model.py (1)
  • collide (658-719)
newton/_src/geometry/kernels.py (1)
newton/_src/geometry/types.py (1)
  • GeoType (25-67)
newton/_src/sim/collide_unified.py (2)
newton/_src/sim/builder.py (1)
  • shape_count (1135-1139)
newton/_src/geometry/types.py (1)
  • GeoType (25-67)
newton/tests/test_collision_primitives.py (1)
newton/_src/geometry/collision_primitive.py (1)
  • collide_capsule_capsule (199-293)
🪛 Ruff (0.14.11)
newton/_src/geometry/contact_reduction_global.py

716-716: Value being cast to int is already an integer

Remove unnecessary int call

(RUF046)

🔇 Additional comments (23)
newton/_src/geometry/contact_reduction_global.py (3)

558-560: LGTM!

Good early-exit optimization. Avoids unnecessary grid-stride loop iterations when there are no contacts to process.


688-690: LGTM!

Consistent early-exit pattern matching the other kernel's optimization.


712-721: The current implementation is correct; do not use type annotations in place of explicit casts.

The explicit type casts (j = int(0), already_exported = bool(False)) are required by Warp's JIT compiler for variables that are mutated in kernels. Type annotations alone (e.g., j: int = 0) are compile-time hints in Python and do not provide the runtime type information Warp needs. The retrieved learning from this repository confirms: "variables must be explicitly cast to be mutable" in Warp kernels.

The Ruff linter warning about redundancy is technically correct from a static analysis perspective, but the explicit casts are not redundant in this context—they are functionally necessary for Warp's kernel compilation. The code is correct as written.

newton/examples/cable/example_cable_pile.py (3)

34-36: LGTM - Module-level toggle for unified collision pipeline.

The flag provides a clean way to switch between collision pipeline implementations for testing and comparison purposes.


253-260: LGTM - Conditional collision pipeline initialization.

The logic correctly creates and uses the unified collision pipeline when enabled, falling back to the standard pipeline otherwise. The pattern aligns with the create_collision_pipeline helper from newton.examples.


284-287: LGTM - Consistent collision pipeline usage in simulation loop.

The conditional collision detection correctly mirrors the initialization logic, ensuring the same pipeline is used throughout the simulation.

newton/_src/geometry/kernels.py (3)

274-288: LGTM - Robust ellipsoid SDF computation.

The radii clamping and fallback handling for near-center points are well-designed for numerical stability.


291-314: LGTM - Ellipsoid SDF gradient with appropriate fallbacks.

The gradient computation correctly derives from the SDF approximation formula. The fallback to (0, 0, 1) for degenerate cases (near center or zero gradient) is a reasonable choice that prevents NaN propagation.


820-822: LGTM - Ellipsoid case now uses analytic gradient.

This correctly integrates the new ellipsoid_sdf_grad function for soft contact normal computation, replacing any prior generic fallback.

newton/_src/geometry/collision_primitive.py (3)

209-230: LGTM - Multi-contact return signature for capsule-capsule collision.

The updated return type correctly supports up to 2 contacts with wp.inf marking unused slots. The initialization is appropriate.


246-272: LGTM - Single contact generation for non-parallel capsules.

The non-parallel case correctly finds the closest points using the determinant-based approach and stores the single contact in the first slot.


274-293: Well-designed parallel capsule handling with two endpoint contacts.

For parallel capsules, generating contacts at both ends of capsule 1 provides better stability for cable-like simulations where capsules often align. Using the first contact's normal for both contacts is a reasonable simplification since parallel capsules share the same contact direction.

docs/concepts/collisions.rst (1)

639-639: LGTM - Clear documentation of pipeline buffer sizing differences.

The clarification accurately describes how the standard pipeline uses rigid_contact_max_per_pair while the unified pipeline automatically estimates buffer sizes based on the model configuration.

newton/tests/test_rigid_contact.py (1)

491-496: Good rigid_contact_max migration; verify 500 is comfortably above worst-case.

This aligns with the API change; just ensure 500 won’t truncate contacts on different backends / mesh tessellations (which could make the “at rest” assertions flaky).

newton/tests/test_collision_primitives.py (1)

185-213: Kernel signature update matches the multi-contact capsule–capsule API.

distances: wp.vec2 and contact_positions: matrix((2,3)) align with returning up to 2 contacts, and the assignments look correct.

newton/_src/geometry/narrow_phase.py (2)

1395-1416: Verify the tiled launch dimensions for narrow_phase_find_mesh_triangle_overlaps_kernel when ENABLE_TILE_BVH_QUERY is False.

You switch second_dim to 1 but keep block_dim=self.tile_size_mesh_convex. If this kernel relies on Warp’s “tiled” (block_id, lane) semantics (tid, j = wp.tid()), double-check the (dim, block_dim) combination is valid across Warp versions/configs. Based on learnings, tiled kernels typically want dim=(num_blocks, tile_size) with block_dim == tile_size.


581-597: Bounding-sphere early rejection: please validate no false negatives.

The new compute_bounding_sphere_from_aabb + check_infinite_plane_bsphere_overlap(...) gate is a nice perf win, but it’s easy to accidentally cull true contacts (especially with planes/SDF fallbacks). This likely needs targeted tests (or at least an invariant/assert path in debug) to ensure correctness.

newton/_src/sim/collide_unified.py (6)

304-346: LGTM!

The has_meshes parameter is well-designed with a safe default of True and clear documentation explaining its purpose as an optimization flag for skipping mesh-related kernel launches.


384-400: LGTM!

The contact buffer estimation heuristics are well-reasoned:

  • EXPLICIT mode leverages known pair count with a 10x multiplier for mesh contacts
  • Dynamic modes (NXN/SAP) use a per-shape estimate that scales linearly rather than quadratically
  • The floor of 1000 contacts prevents under-allocation for small scenes

The comment appropriately guides users toward model.rigid_contact_max for memory-efficient configurations.


413-423: LGTM!

The has_meshes flag is correctly propagated to NarrowPhase, enabling the mesh kernel skip optimization.


477-479: LGTM!

The fallback logic correctly chains: user-provided value → model.rigid_contact_max → heuristic estimation in __init__. If model.rigid_contact_max is None, it propagates to __init__ where the heuristics kick in.


497-502: LGTM!

The mesh detection logic is well-implemented:

  • Defensive checks for attribute existence
  • Efficient numpy-based comparison
  • Correctly handles empty arrays (.any() returns False)
  • The .numpy() call is acceptable here since it runs only once during pipeline construction

503-522: LGTM!

The constructor call correctly passes all parameters including the auto-detected has_meshes flag.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
newton/examples/cable/example_cable_pile.py (1)

116-117: Register this example in README.md with a uv run command and screenshot.

The example file is properly structured with an Example class and implements the required test_final() method for state validation. However, it is not registered in README.md as required by the coding guidelines for newton/examples/**/*.py files.

newton/_src/geometry/kernels.py (1)

269-315: Fix ellipsoid gradient fallback: returning +Z can produce contact points not on the ellipsoid surface.
When the ellipsoid's minimum radius is not along the Z-axis, ellipsoid_sdf_grad() returning (0,0,1) in fallback paths causes the reconstructed contact point x_local - n*d to lie inside the ellipsoid instead of on its surface. The SDF and gradient fallbacks must agree on the same axis (the minimum-radius axis) to ensure correct contact point/normal computation.

Proposed fix (fallback to min-radius axis, signed by p)
 `@wp.func`
 def ellipsoid_sdf_grad(radii: wp.vec3, p: wp.vec3):
@@
-    if k1 < eps:
-        return wp.vec3(0.0, 0.0, 1.0)
+    if k1 < eps:
+        # Gradient is undefined near the center; pick the minimum-radius axis so that
+        # the reconstructed surface point `p - n*d` lies on the ellipsoid for the
+        # corresponding deep-inside fallback distance.
+        if r[0] <= r[1] and r[0] <= r[2]:
+            s = wp.sign(p[0])
+            if wp.abs(s) < eps:
+                s = 1.0
+            return wp.vec3(s, 0.0, 0.0)
+        if r[1] <= r[2]:
+            s = wp.sign(p[1])
+            if wp.abs(s) < eps:
+                s = 1.0
+            return wp.vec3(0.0, s, 0.0)
+        s = wp.sign(p[2])
+        if wp.abs(s) < eps:
+            s = 1.0
+        return wp.vec3(0.0, 0.0, s)
@@
-    if grad_len > eps:
-        return grad / grad_len
-    return wp.vec3(0.0, 0.0, 1.0)
+    if grad_len > eps:
+        return grad / grad_len
+    # Same fallback as above (min-radius axis)
+    if r[0] <= r[1] and r[0] <= r[2]:
+        return wp.vec3(1.0, 0.0, 0.0)
+    if r[1] <= r[2]:
+        return wp.vec3(0.0, 1.0, 0.0)
+    return wp.vec3(0.0, 0.0, 1.0)
🤖 Fix all issues with AI agents
In `@newton/examples/cable/example_cable_pile.py`:
- Around line 150-152: The global default rigid contact margin set via
builder.rigid_contact_margin = 0.05 is too large for 1.2cm-radius cables and
inflates broadphase AABBs (margins are summed per pair); change the default to a
small fraction of the cable radius (e.g., O(0.01–0.1) * cable_radius) by
computing that fraction from the cable_radius variable instead of the hardcoded
0.05, and only override builder.rigid_contact_margin with larger values for
specific shapes/contacts where you intentionally need extra tolerance.

In `@newton/tests/test_collision_pipeline.py`:
- Around line 431-437: The test test_mesh_mesh_sdf_vs_sdf currently calls
test_mesh_mesh_sdf_modes with a very permissive tolerance=0.1; instead, keep
strict velocity/component tolerances and address only the known flakiness by
relaxing or removing assertions that depend on the exact collision normal
direction inside test_mesh_mesh_sdf_modes (e.g., per-component
velocity/normal-sign comparisons). Modify test_mesh_mesh_sdf_vs_sdf to pass a
tighter tolerance (or default) and update test_mesh_mesh_sdf_modes to: (1) avoid
sign-sensitive assertions on contact normals, (2) assert on invariant quantities
such as speed/magnitude, kinetic energy, or bounds on normal-aligned velocity
magnitudes, and (3) optionally allow an explicit flag/parameter from
test_mesh_mesh_sdf_vs_sdf to enable the relaxed normal checks when SDF↔SDF
behavior is expected.
🧹 Nitpick comments (5)
newton/_src/geometry/contact_data.py (1)

78-96: Consider whether re-normalization is necessary.

Line 83 re-normalizes contact_normal_a_to_b, but according to the ContactData docstring (line 36), this should already be a unit normal vector. While defensive normalization is safe, it adds computational overhead.

Additionally, the distance calculation could be simplified. The current approach:

  1. Computes a_contact_world and b_contact_world from contact_point_center
  2. Computes diff between them
  3. Projects diff onto the normal

This is equivalent to distance = contact_data.contact_distance + contact_data.radius_eff_a + contact_data.radius_eff_b, which would avoid the redundant computation.

♻️ Simplified implementation (optional)
 `@wp.func`
 def contact_passes_margin_check(
     contact_data: ContactData,
 ) -> bool:
-    total_separation_needed = (
-        contact_data.radius_eff_a + contact_data.radius_eff_b + contact_data.thickness_a + contact_data.thickness_b
-    )
-
-    # Distance calculation matching box_plane_collision
-    contact_normal_a_to_b = wp.normalize(contact_data.contact_normal_a_to_b)
-
-    a_contact_world = contact_data.contact_point_center - contact_normal_a_to_b * (
-        0.5 * contact_data.contact_distance + contact_data.radius_eff_a
-    )
-    b_contact_world = contact_data.contact_point_center + contact_normal_a_to_b * (
-        0.5 * contact_data.contact_distance + contact_data.radius_eff_b
-    )
-
-    diff = b_contact_world - a_contact_world
-    distance = wp.dot(diff, contact_normal_a_to_b)
-    d = distance - total_separation_needed
+    # d = surface_distance - total_thickness
+    # where surface_distance = contact_distance + radius_eff_a + radius_eff_b
+    d = contact_data.contact_distance - (contact_data.thickness_a + contact_data.thickness_b)
 
     return d <= contact_data.margin
newton/examples/cable/example_cable_pile.py (2)

34-36: Prefer driving unified/standard selection via args (avoid module-level USE_UNIFIED_COLLISION).
Right now the example has two sources of truth: USE_UNIFIED_COLLISION and newton.examples.create_collision_pipeline(self.model, args) (which already reads args and can return None for "standard"). Consider removing the global flag and letting CLI/config decide consistently.

Proposed simplification
-# Global flag to enable/disable unified collision pipeline
-USE_UNIFIED_COLLISION = True
+USE_UNIFIED_COLLISION = None  # Optional: keep only if you *must* force behavior in-code
-        # Create collision pipeline (unified if enabled, otherwise standard)
-        if USE_UNIFIED_COLLISION:
-            self.collision_pipeline = newton.examples.create_collision_pipeline(self.model, args)
-            self.contacts = self.model.collide(self.state_0, collision_pipeline=self.collision_pipeline)
-        else:
-            self.collision_pipeline = None
-            self.contacts = self.model.collide(self.state_0)
+        # Create collision pipeline from args (None => standard pipeline)
+        self.collision_pipeline = newton.examples.create_collision_pipeline(self.model, args)
+        self.contacts = self.model.collide(self.state_0, collision_pipeline=self.collision_pipeline)

283-288: Re-check intent: colliding every substep vs once-per-frame reuse.
This example calls model.collide() inside the substep loop. If the goal is to showcase unified-pipeline perf, consider computing contacts once per rendered frame and reusing for all substeps (common pattern in other Newton examples), unless this scene needs per-substep contact refresh for correctness. Based on learnings, examples often intentionally reuse contacts per frame—worth confirming here.

newton/tests/test_rigid_contact.py (1)

490-496: Consider deriving rigid_contact_max from geometry (avoid brittle magic number).
If this test needs a larger buffer, it may be more robust to base it on the mesh’s vertex count / expected contact upper bound (or set model.rigid_contact_max once) so future mesh/contact changes don’t silently require tweaking 500.

newton/tests/test_collision_primitives.py (1)

817-905: Test currently validates only the first capsule-capsule contact; add coverage for the second contact in the parallel case.
For parallel axes, the implementation returns 2 contacts—this test should assert that the second contact is finite and meaningfully different (e.g., positions differ along the capsule axis) to prevent regressions where contact #1 silently breaks.

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9b64b2b and 6b4c8e4.

📒 Files selected for processing (18)
  • asv/benchmarks/collision/benchmark_contact_reduction.py
  • asv/benchmarks/collision/benchmark_hashtable.py
  • docs/concepts/collisions.rst
  • newton/_src/geometry/collision_primitive.py
  • newton/_src/geometry/contact_data.py
  • newton/_src/geometry/contact_reduction_global.py
  • newton/_src/geometry/kernels.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/sim/collide_unified.py
  • newton/examples/__init__.py
  • newton/examples/basic/example_basic_shapes.py
  • newton/examples/cable/example_cable_pile.py
  • newton/examples/robot/example_robot_panda_hydro.py
  • newton/tests/test_collision_pipeline.py
  • newton/tests/test_collision_primitives.py
  • newton/tests/test_hydroelastic.py
  • newton/tests/test_rigid_contact.py
  • newton/tests/test_sdf_compute.py
💤 Files with no reviewable changes (6)
  • newton/examples/robot/example_robot_panda_hydro.py
  • newton/examples/init.py
  • newton/examples/basic/example_basic_shapes.py
  • asv/benchmarks/collision/benchmark_contact_reduction.py
  • asv/benchmarks/collision/benchmark_hashtable.py
  • newton/tests/test_sdf_compute.py
🧰 Additional context used
📓 Path-based instructions (5)
**/*

📄 CodeRabbit inference engine (AGENTS.md)

CLI arguments must use kebab-case (e.g., --use-cuda-graph, not --use_cuda_graph)

Files:

  • docs/concepts/collisions.rst
  • newton/tests/test_rigid_contact.py
  • newton/_src/geometry/contact_reduction_global.py
  • newton/_src/geometry/collision_primitive.py
  • newton/_src/geometry/kernels.py
  • newton/tests/test_collision_pipeline.py
  • newton/tests/test_collision_primitives.py
  • newton/examples/cable/example_cable_pile.py
  • newton/tests/test_hydroelastic.py
  • newton/_src/geometry/contact_data.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/sim/collide_unified.py
newton/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Any user-facing class/function/object added under _src must be exposed via the public Newton API through re-exports

Files:

  • newton/tests/test_rigid_contact.py
  • newton/_src/geometry/contact_reduction_global.py
  • newton/_src/geometry/collision_primitive.py
  • newton/_src/geometry/kernels.py
  • newton/tests/test_collision_pipeline.py
  • newton/tests/test_collision_primitives.py
  • newton/examples/cable/example_cable_pile.py
  • newton/tests/test_hydroelastic.py
  • newton/_src/geometry/contact_data.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/sim/collide_unified.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Use prefix-first naming for classes: ActuatorPD, ActuatorPID (not PDActuator, PIDActuator)
Use prefix-first naming for methods: add_shape_sphere() (not add_sphere_shape())
Method names must use snake_case
Prefer nested classes when self-contained: if a helper type or enum is only meaningful inside one parent class and doesn't need a public identity, define it as a nested class instead of a top-level class/module
Follow PEP 8 for Python code
Use Google-style docstrings with clear, concise explanations of what functions do, their parameters, and return values

Files:

  • newton/tests/test_rigid_contact.py
  • newton/_src/geometry/contact_reduction_global.py
  • newton/_src/geometry/collision_primitive.py
  • newton/_src/geometry/kernels.py
  • newton/tests/test_collision_pipeline.py
  • newton/tests/test_collision_primitives.py
  • newton/examples/cable/example_cable_pile.py
  • newton/tests/test_hydroelastic.py
  • newton/_src/geometry/contact_data.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/sim/collide_unified.py
newton/_src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

The newton/_src/ directory is internal library implementation only and must not be imported by user code (examples, documentation, or external users)

Files:

  • newton/_src/geometry/contact_reduction_global.py
  • newton/_src/geometry/collision_primitive.py
  • newton/_src/geometry/kernels.py
  • newton/_src/geometry/contact_data.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/sim/collide_unified.py
newton/examples/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

newton/examples/**/*.py: Examples must not import from newton._src, only from the public Newton API
Examples must follow the format with an Example class and must be registered in README.md with a uv run command and screenshot
Examples must implement a test_final() method that verifies the simulation state is valid after the example has run
Examples may optionally implement a test_post_step() method that is evaluated after every step() call

Files:

  • newton/examples/cable/example_cable_pile.py
🧠 Learnings (16)
📚 Learning: 2025-10-24T07:56:14.792Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 981
File: newton/_src/geometry/collision_convex.py:180-289
Timestamp: 2025-10-24T07:56:14.792Z
Learning: In newton/_src/geometry/collision_convex.py, the single-contact solver (create_solve_convex_single_contact) intentionally returns count=1 even when shapes are separated (signed_distance > contact_threshold). The calling code is responsible for deciding whether to accept the contact based on the signed distance value. This design pushes filtering responsibility to the caller.

Applied to files:

  • docs/concepts/collisions.rst
  • newton/tests/test_rigid_contact.py
  • newton/_src/geometry/collision_primitive.py
  • newton/tests/test_collision_primitives.py
  • newton/_src/geometry/contact_data.py
📚 Learning: 2025-12-25T21:44:17.047Z
Learnt from: camevor
Repo: newton-physics/newton PR: 1161
File: docs/concepts/sensors.rst:12-12
Timestamp: 2025-12-25T21:44:17.047Z
Learning: Future API standardization: The sensor.update() method should be standardized to a single-argument form update(state) across all sensor types. The redundant model parameter will be removed from sensors like SensorFrameTransform. In code reviews, check for API changes that move toward a single-argument update, ensure all sensor implementations conform, and update user-facing docs to reflect the change. If reviewing related code, verify that calls using the old two-argument form are refactored and that any dependent serialization or tests are updated accordingly.

Applied to files:

  • docs/concepts/collisions.rst
📚 Learning: 2025-10-10T10:47:41.082Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 899
File: newton/tests/test_rigid_contact.py:214-268
Timestamp: 2025-10-10T10:47:41.082Z
Learning: In `newton/tests/test_rigid_contact.py`, using `add_shape_plane(width=0, length=0)` for an infinite plane works correctly with the collision system and does not cause degenerate AABB issues in the test scenario `test_shape_collisions_gjk_mpr_multicontact`.

Applied to files:

  • newton/tests/test_rigid_contact.py
  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-12-12T08:45:51.908Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 1221
File: newton/examples/example_sdf.py:277-287
Timestamp: 2025-12-12T08:45:51.908Z
Learning: In Newton examples (e.g., example_sdf.py), computing contacts once per frame and reusing them across multiple substeps is an intentional design choice, not a bug. The contacts are computed before the substep loop and intentionally kept constant throughout all substeps within that frame.

Applied to files:

  • newton/tests/test_rigid_contact.py
  • newton/_src/geometry/contact_reduction_global.py
  • newton/tests/test_hydroelastic.py
📚 Learning: 2025-09-26T05:58:21.013Z
Learnt from: WenchaoHuang
Repo: newton-physics/newton PR: 835
File: newton/_src/solvers/style3d/collision/collision.py:49-53
Timestamp: 2025-09-26T05:58:21.013Z
Learning: In newton/_src/solvers/style3d/collision/collision.py, the broad_phase buffers (broad_phase_ee, broad_phase_ef, broad_phase_vf) created with wp.array() don't need zero-initialization because they follow a write-first-read-later pattern: BVH query kernels in frame_begin() populate these buffers completely (write-only operations), and collision kernels only read from them afterward in accumulate_contact_force(), ensuring no undefined reads occur.

Applied to files:

  • newton/tests/test_rigid_contact.py
  • newton/_src/geometry/contact_reduction_global.py
  • newton/tests/test_collision_primitives.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/sim/collide_unified.py
📚 Learning: 2026-01-12T10:11:47.988Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 1314
File: newton/_src/sim/builder.py:6061-6091
Timestamp: 2026-01-12T10:11:47.988Z
Learning: Repo: newton-physics/newton PR: 1314
File: newton/_src/sim/builder.py (ModelBuilder.finalize)
Learning: The project prefers to keep finalize() validation lightweight; do not add union-find/connected-components validation for loop joints. Current behavior (allow non-articulated joints when the child is in any articulation) is acceptable; avoid overengineering here.

Applied to files:

  • newton/tests/test_rigid_contact.py
📚 Learning: 2025-10-30T07:28:13.112Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 984
File: newton/_src/geometry/broad_phase_nxn.py:318-336
Timestamp: 2025-10-30T07:28:13.112Z
Learning: In Newton's BroadPhaseAllPairs class (newton/_src/geometry/broad_phase_nxn.py), device management is the caller's responsibility. The library intentionally does not perform automatic device transfers for precomputed arrays in the launch method, even if there's a device mismatch. Users must ensure that the device specified in __init__ matches the device used in launch.

Applied to files:

  • newton/tests/test_rigid_contact.py
  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2026-01-13T03:11:40.556Z
Learnt from: jumyungc
Repo: newton-physics/newton PR: 1333
File: newton/_src/sim/builder.py:0-0
Timestamp: 2026-01-13T03:11:40.556Z
Learning: Ensure all VBD damping semantics follow Rayleigh-style damping and are unitless. This convention, demonstrated in SolverVBD, should be applied consistently across all VBD-related constraints in the codebase. In newton/_src/sim/builder.py, validate that any damping terms use the same normalization and are treated as dimensionless; add comments and unit tests to enforce consistency across VBD components.

Applied to files:

  • newton/tests/test_rigid_contact.py
  • newton/_src/geometry/contact_reduction_global.py
  • newton/_src/geometry/collision_primitive.py
  • newton/_src/geometry/kernels.py
  • newton/tests/test_collision_pipeline.py
  • newton/tests/test_collision_primitives.py
  • newton/examples/cable/example_cable_pile.py
  • newton/tests/test_hydroelastic.py
  • newton/_src/geometry/contact_data.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/sim/collide_unified.py
📚 Learning: 2025-12-12T17:45:26.847Z
Learnt from: vastsoun
Repo: newton-physics/newton PR: 1019
File: newton/_src/solvers/kamino/geometry/primitives.py:0-0
Timestamp: 2025-12-12T17:45:26.847Z
Learning: In newton/_src/solvers/kamino/geometry/primitives.py, the narrow-phase kernel `_primitive_narrowphase` does not need to handle symmetric shape-pair orderings (e.g., both SPHERE-BOX and BOX-SPHERE) because `kamino.core.builder.ModelBuilder` and `kamino.geometry.primitive.broadphase` guarantee that explicit candidate geometry pairs are always defined with GIDs in ascending order.

Applied to files:

  • newton/_src/geometry/contact_reduction_global.py
  • newton/tests/test_collision_primitives.py
  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-08-12T17:51:37.474Z
Learnt from: nvlukasz
Repo: newton-physics/newton PR: 519
File: newton/geometry.py:16-22
Timestamp: 2025-08-12T17:51:37.474Z
Learning: In the Newton public API refactor, common geometry symbols like ParticleFlags and ShapeFlags are now exposed at the top level in newton/__init__.py rather than through newton.geometry. The newton/geometry.py module is intentionally focused only on broad-phase collision detection classes (BroadPhaseAllPairs, BroadPhaseExplicit, BroadPhaseSAP).

Applied to files:

  • newton/_src/geometry/contact_reduction_global.py
  • newton/_src/geometry/collision_primitive.py
  • newton/examples/cable/example_cable_pile.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/sim/collide_unified.py
📚 Learning: 2025-09-26T05:58:21.013Z
Learnt from: WenchaoHuang
Repo: newton-physics/newton PR: 835
File: newton/_src/solvers/style3d/collision/collision.py:49-53
Timestamp: 2025-09-26T05:58:21.013Z
Learning: In newton/_src/solvers/style3d/collision/collision.py, the broad_phase buffers (broad_phase_ee, broad_phase_ef, broad_phase_vf) created with wp.array() don't need zero-initialization because the BVH query kernels that use them always write the count to query_results[0, tid] = 0 first before populating any data, ensuring no undefined reads occur.

Applied to files:

  • newton/_src/geometry/contact_reduction_global.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/sim/collide_unified.py
📚 Learning: 2025-08-20T03:30:16.037Z
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 584
File: newton/_src/utils/import_urdf.py:159-167
Timestamp: 2025-08-20T03:30:16.037Z
Learning: In the URDF importer (newton/_src/utils/import_urdf.py), cylinders are intentionally created using add_shape_capsule() instead of add_shape_cylinder() because cylinder collisions are not fully supported yet. This is a deliberate workaround until proper cylinder collision support is implemented.

Applied to files:

  • newton/_src/geometry/collision_primitive.py
  • newton/tests/test_collision_primitives.py
  • newton/examples/cable/example_cable_pile.py
📚 Learning: 2025-10-29T14:55:48.171Z
Learnt from: gdaviet
Repo: newton-physics/newton PR: 940
File: newton/examples/mpm/example_mpm_granular.py:92-101
Timestamp: 2025-10-29T14:55:48.171Z
Learning: In newton/examples/mpm/example_mpm_granular.py, the capture() method intentionally advances the simulation state once during CUDA graph capture. This is expected behavior because CUDA graph capture records exact memory addresses, so the state arrays (state_0, state_1) cannot be reset or reallocated after capture without invalidating the graph.

Applied to files:

  • newton/examples/cable/example_cable_pile.py
📚 Learning: 2025-12-12T08:45:43.428Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 1221
File: newton/examples/example_sdf.py:277-287
Timestamp: 2025-12-12T08:45:43.428Z
Learning: In Newtown (Newton) example code, specifically files under newton/examples, computing contacts once per frame and reusing them across all substeps is an intentional design choice, not a bug. Reviewers should verify that contacts are computed before the substep loop and reused for every substep within the same frame. This pattern reduces redundant work and preserves frame-consistency; do not flag as a regression unless the behavior is changed for correctness or performance reasons.

Applied to files:

  • newton/examples/cable/example_cable_pile.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/geometry/narrow_phase.py
📚 Learning: 2025-09-15T13:13:37.976Z
Learnt from: gdaviet
Repo: newton-physics/newton PR: 750
File: newton/_src/solvers/implicit_mpm/solve_rheology.py:772-783
Timestamp: 2025-09-15T13:13:37.976Z
Learning: In Warp's tiled launch API, using a 2D launch dimension dim=(num_blocks, tile_size) is correct for tiled kernels. The wp.tid() function can either return a 2D tuple (block_index, lane) or just discard the last dimension to return only the block_index when lane information is not needed, making `block_index = wp.tid()` a valid shortcut in tiled kernels.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
🧬 Code graph analysis (6)
newton/tests/test_rigid_contact.py (2)
newton/_src/sim/collide_unified.py (2)
  • CollisionPipelineUnified (273-715)
  • from_model (439-524)
newton/_src/sim/collide.py (1)
  • from_model (146-190)
newton/_src/geometry/collision_primitive.py (1)
newton/_src/sim/builder.py (1)
  • dot (5249-5250)
newton/tests/test_collision_pipeline.py (1)
newton/_src/sim/collide.py (1)
  • device (334-341)
newton/tests/test_collision_primitives.py (1)
newton/_src/geometry/collision_primitive.py (1)
  • collide_capsule_capsule (199-293)
newton/examples/cable/example_cable_pile.py (3)
newton/examples/__init__.py (1)
  • create_collision_pipeline (398-467)
newton/_src/sim/collide_unified.py (1)
  • collide (526-705)
newton/_src/sim/model.py (1)
  • collide (658-719)
newton/_src/sim/collide_unified.py (2)
newton/_src/sim/builder.py (1)
  • shape_count (1135-1139)
newton/_src/geometry/types.py (1)
  • GeoType (25-67)
🪛 Ruff (0.14.11)
newton/_src/geometry/contact_reduction_global.py

716-716: Value being cast to int is already an integer

Remove unnecessary int call

(RUF046)

⏰ 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 Tests / Run GPU Unit Tests on AWS EC2
  • GitHub Check: Run GPU Tests / Start self-hosted EC2 runner
🔇 Additional comments (23)
docs/concepts/collisions.rst (1)

639-639: Documentation update accurately reflects API changes.

The updated description correctly distinguishes between the two pipelines:

  • Standard pipeline: uses rigid_contact_max_per_pair via Model.collide
  • Unified pipeline: automatically estimates buffer sizes based on the model

This aligns with the broader PR changes that remove rigid_contact_max_per_pair from the unified pipeline's public API.

newton/_src/geometry/contact_reduction_global.py (3)

558-560: Good early-exit optimization.

This prevents unnecessary work when there are no contacts to process, which is a valid fast path for empty collision scenarios.


688-690: Good early-exit optimization for empty hashtables.

This avoids iterating over the grid stride loop when there are no active entries to export.


712-721: Duplicate check optimization with while-loop is correct.

The while-loop with early exit on duplicate detection is more efficient than previous approaches, avoiding unnecessary checks when a duplicate is found early. The explicit type declarations (bool(False) and int(0)) on lines 716-717 are indeed required by Warp for variables mutated in while loops within kernel code, so the static analysis warning can be safely ignored.

newton/_src/geometry/collision_primitive.py (2)

209-230: Multi-contact return type change is well-structured.

The updated signature correctly returns:

  • contact_dist (vec2): distances for up to 2 contacts, with wp.inf for invalid slots
  • contact_pos (mat23f): 2x3 matrix with contact positions
  • contact_normal (vec3): shared normal vector

The initialization with wp.inf for distances follows the convention documented at line 36-37.


274-292: Parallel-axis case correctly generates two contacts.

The parallel case logic appropriately generates contacts at both endpoints of capsule 1, projecting each to the nearest point on capsule 2. Using the first contact's normal for both (line 283) is a reasonable simplification since parallel capsules share the same separation direction.

However, there's a potential edge case: if mc (line 278, 287) is very small (degenerate capsule with near-zero half-length), the division (v - mb) / mc and (v + mb) / mc could produce very large or undefined values before clamping.

Consider whether degenerate capsules (near-zero half-length) need explicit handling, or if upstream code guarantees valid capsule dimensions.

newton/tests/test_hydroelastic.py (1)

132-140: Test correctly updated for the new API.

The parameter rename from rigid_contact_max_per_pair to rigid_contact_max aligns with the broader API changes in this PR. The conditional buffer sizing (6000 for unreduced contacts vs 100 for reduced) appropriately accounts for the significantly higher contact count when contact reduction is disabled.

newton/_src/geometry/kernels.py (1)

820-823: Ellipsoid soft-contacts wiring looks good (once gradient fallback is fixed).
Nice to see GeoType.ELLIPSOID handled in the same SDF/grad path as other primitives.

newton/tests/test_collision_primitives.py (1)

185-213: Good update for the new collide_capsule_capsule() return shape. Matrix types like wp.array(dtype=wp.types.matrix((2, 3), wp.float32)) and wp.array(dtype=wp.vec2) are supported across the codebase in the pinned nightly Warp version (≥1.11.0.dev20251205) and already used in multiple collision and solver modules—no additional verification needed.

newton/_src/geometry/narrow_phase.py (9)

132-146: Well-structured kernel factory with clear documentation.

The docstring clearly explains the kernel's purpose and the routing strategy for different shape types. The two-stage approach (analytical primitives first, then GJK/MPR for complex shapes) is a sound optimization.


186-211: Early exit and safety checks are well-implemented.

The strided loop pattern with safety checks for self-collision and invalid indices is correct. The type sorting ensures consistent collision pair ordering (type_a ≤ type_b), which simplifies downstream collision handling.


257-269: Mesh-plane pair ordering is intentionally reversed.

The code stores wp.vec2i(shape_b, shape_a) (mesh, plane) rather than the sorted order. This is intentional to match the mesh-plane kernel's expectations where the first element is the mesh shape. The comment on line 265 clarifies this.


288-296: Effective radii correctly limited to sphere/capsule shapes.

The Minkowski sphere decomposition (effective radius) is only applied to spheres and capsules. For other primitives like boxes and cylinders, the effective radius remains 0, which is correct since they don't have a uniform rounding radius.


436-454: Efficient batch contact allocation.

Using a single atomic add for multiple valid contacts and then writing sequentially is more efficient than separate atomic operations per contact. The reuse of contact_data struct between writes is safe since operations are sequential within the same thread.


466-500: Clear documentation of kernel contract.

The docstring explicitly states the preconditions (pairs pre-sorted, mesh/hydroelastic/primitives already handled), which helps maintain correctness when modifying the primitive kernel.


1337-1359: Two-stage kernel launch correctly chains outputs to inputs.

The primitive kernel writes to gjk_candidate_pairs and gjk_candidate_pairs_count, which are then used as inputs to the GJK/MPR kernel. This clean handoff between stages is well-structured.


1361-1363: Mesh kernel skip optimization improves primitive-only scene performance.

Skipping all mesh-related kernel launches when has_meshes=False eliminates unnecessary kernel launch overhead for scenes with only primitive shapes. This aligns with the PR objective of performance improvements.


1209-1224: Consolidated counter array reduces kernel launch overhead.

Using sliced views into a single counter array allows clearing all counters with a single zero_() call instead of multiple separate calls. The sliced views work correctly with atomic operations in kernels, as evidenced by their use throughout the primitive and GJK collision detection kernels.

newton/_src/sim/collide_unified.py (5)

384-400: Simplified contact estimation improves API usability.

The new estimation logic (shape_count * 20 for dynamic broad phase, len(shape_pairs_filtered) * 10 for explicit pairs) is simpler and avoids the confusion around rigid_contact_max_per_pair. The minimum of 1000 contacts prevents underallocation for small scenes.


497-501: Mesh detection via numpy conversion is acceptable for initialization.

The numpy() call triggers a device-to-host copy, but since this runs once during pipeline construction (not per-frame), the overhead is negligible. A device-side check would add complexity without meaningful performance benefit.


413-423: has_meshes flag correctly propagated to NarrowPhase.

The mesh detection result from from_model flows through to the NarrowPhase constructor, enabling the kernel skip optimization for primitive-only scenes.


315-319: Clear documentation for new parameters.

The docstrings explain the rigid_contact_max estimation strategy and the has_meshes optimization, helping users understand when to provide explicit values vs. relying on defaults.

Also applies to: 344-346


438-453: API simplification: rigid_contact_max_per_pair removed.

The parameter rename from rigid_contact_max_per_pair to rigid_contact_max simplifies the API. This is a breaking change but intentional per the PR discussion—the per-pair factor was confusing and led to overallocation.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@nvtw nvtw requested a review from adenzler-nvidia January 15, 2026 15:39
@nvtw nvtw marked this pull request as ready for review January 15, 2026 15:39
Copy link
Member

@adenzler-nvidia adenzler-nvidia left a comment

Choose a reason for hiding this comment

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

some comments, hard to judge this. Will have another look tomorrow morning with a fresh head.

Copy link
Member

@adenzler-nvidia adenzler-nvidia left a comment

Choose a reason for hiding this comment

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

Thanks! some more comments

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
newton/_src/geometry/narrow_phase.py (1)

675-682: Bug: return should be continue inside strided loop.

Inside the strided loop (line 657), using return exits the entire kernel rather than moving to the next iteration. If a mesh-mesh pair or null mesh_id is encountered, subsequent pairs assigned to this thread won't be processed.

🐛 Proposed fix
         elif type_b == int(GeoType.MESH) and type_a != int(GeoType.MESH):
             mesh_shape = shape_b
             non_mesh_shape = shape_a
         else:
             # Mesh-mesh collision not supported yet
-            return
+            continue

         # Get mesh BVH ID and mesh transform
         mesh_id = shape_source[mesh_shape]
         if mesh_id == wp.uint64(0):
-            return
+            continue
🧹 Nitpick comments (1)
newton/_src/geometry/narrow_phase.py (1)

521-523: Safety checks are defensive but redundant.

These validity checks duplicate the primitive kernel's filtering (line 200-201). While safe to keep for robustness, they add minor overhead. Consider documenting that these are intentional defensive checks since pairs should already be validated.

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b4c8e4 and ba8d608.

📒 Files selected for processing (1)
  • newton/_src/geometry/narrow_phase.py
🧰 Additional context used
📓 Path-based instructions (4)
newton/_src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

The newton/_src/ directory is internal library implementation only and must not be imported by user code (examples, documentation, or external users)

Files:

  • newton/_src/geometry/narrow_phase.py
newton/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Any user-facing class/function/object added under _src must be exposed via the public Newton API through re-exports

Files:

  • newton/_src/geometry/narrow_phase.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Use prefix-first naming for classes: ActuatorPD, ActuatorPID (not PDActuator, PIDActuator)
Use prefix-first naming for methods: add_shape_sphere() (not add_sphere_shape())
Method names must use snake_case
Prefer nested classes when self-contained: if a helper type or enum is only meaningful inside one parent class and doesn't need a public identity, define it as a nested class instead of a top-level class/module
Follow PEP 8 for Python code
Use Google-style docstrings with clear, concise explanations of what functions do, their parameters, and return values

Files:

  • newton/_src/geometry/narrow_phase.py
**/*

📄 CodeRabbit inference engine (AGENTS.md)

CLI arguments must use kebab-case (e.g., --use-cuda-graph, not --use_cuda_graph)

Files:

  • newton/_src/geometry/narrow_phase.py
🧠 Learnings (11)
📓 Common learnings
Learnt from: shi-eric
Repo: newton-physics/newton PR: 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: nvlukasz
Repo: newton-physics/newton PR: 519
File: newton/_src/solvers/featherstone/kernels.py:75-75
Timestamp: 2025-08-12T18:04:06.577Z
Learning: The Newton physics framework requires nightly Warp builds, which means compatibility concerns with older stable Warp versions (like missing functions such as wp.spatial_adjoint) are not relevant for this project.
📚 Learning: 2025-12-12T17:45:26.847Z
Learnt from: vastsoun
Repo: newton-physics/newton PR: 1019
File: newton/_src/solvers/kamino/geometry/primitives.py:0-0
Timestamp: 2025-12-12T17:45:26.847Z
Learning: In newton/_src/solvers/kamino/geometry/primitives.py, the narrow-phase kernel `_primitive_narrowphase` does not need to handle symmetric shape-pair orderings (e.g., both SPHERE-BOX and BOX-SPHERE) because `kamino.core.builder.ModelBuilder` and `kamino.geometry.primitive.broadphase` guarantee that explicit candidate geometry pairs are always defined with GIDs in ascending order.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-08-12T17:51:37.474Z
Learnt from: nvlukasz
Repo: newton-physics/newton PR: 519
File: newton/geometry.py:16-22
Timestamp: 2025-08-12T17:51:37.474Z
Learning: In the Newton public API refactor, common geometry symbols like ParticleFlags and ShapeFlags are now exposed at the top level in newton/__init__.py rather than through newton.geometry. The newton/geometry.py module is intentionally focused only on broad-phase collision detection classes (BroadPhaseAllPairs, BroadPhaseExplicit, BroadPhaseSAP).

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-09-26T05:58:21.013Z
Learnt from: WenchaoHuang
Repo: newton-physics/newton PR: 835
File: newton/_src/solvers/style3d/collision/collision.py:49-53
Timestamp: 2025-09-26T05:58:21.013Z
Learning: In newton/_src/solvers/style3d/collision/collision.py, the broad_phase buffers (broad_phase_ee, broad_phase_ef, broad_phase_vf) created with wp.array() don't need zero-initialization because they follow a write-first-read-later pattern: BVH query kernels in frame_begin() populate these buffers completely (write-only operations), and collision kernels only read from them afterward in accumulate_contact_force(), ensuring no undefined reads occur.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-08-20T18:02:36.099Z
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 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.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-10-10T10:47:41.082Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 899
File: newton/tests/test_rigid_contact.py:214-268
Timestamp: 2025-10-10T10:47:41.082Z
Learning: In `newton/tests/test_rigid_contact.py`, using `add_shape_plane(width=0, length=0)` for an infinite plane works correctly with the collision system and does not cause degenerate AABB issues in the test scenario `test_shape_collisions_gjk_mpr_multicontact`.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-09-26T05:58:21.013Z
Learnt from: WenchaoHuang
Repo: newton-physics/newton PR: 835
File: newton/_src/solvers/style3d/collision/collision.py:49-53
Timestamp: 2025-09-26T05:58:21.013Z
Learning: In newton/_src/solvers/style3d/collision/collision.py, the broad_phase buffers (broad_phase_ee, broad_phase_ef, broad_phase_vf) created with wp.array() don't need zero-initialization because the BVH query kernels that use them always write the count to query_results[0, tid] = 0 first before populating any data, ensuring no undefined reads occur.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-10-30T07:28:13.112Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 984
File: newton/_src/geometry/broad_phase_nxn.py:318-336
Timestamp: 2025-10-30T07:28:13.112Z
Learning: In Newton's BroadPhaseAllPairs class (newton/_src/geometry/broad_phase_nxn.py), device management is the caller's responsibility. The library intentionally does not perform automatic device transfers for precomputed arrays in the launch method, even if there's a device mismatch. Users must ensure that the device specified in __init__ matches the device used in launch.

Applied to files:

  • newton/_src/geometry/narrow_phase.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/geometry/narrow_phase.py
📚 Learning: 2025-09-15T13:13:37.976Z
Learnt from: gdaviet
Repo: newton-physics/newton PR: 750
File: newton/_src/solvers/implicit_mpm/solve_rheology.py:772-783
Timestamp: 2025-09-15T13:13:37.976Z
Learning: In Warp's tiled launch API, using a 2D launch dimension dim=(num_blocks, tile_size) is correct for tiled kernels. The wp.tid() function can either return a 2D tuple (block_index, lane) or just discard the last dimension to return only the block_index when lane information is not needed, making `block_index = wp.tid()` a valid shortcut in tiled kernels.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2026-01-13T03:11:40.556Z
Learnt from: jumyungc
Repo: newton-physics/newton PR: 1333
File: newton/_src/sim/builder.py:0-0
Timestamp: 2026-01-13T03:11:40.556Z
Learning: Ensure all VBD damping semantics follow Rayleigh-style damping and are unitless. This convention, demonstrated in SolverVBD, should be applied consistently across all VBD-related constraints in the codebase. In newton/_src/sim/builder.py, validate that any damping terms use the same normalization and are treated as dimensionless; add comments and unit tests to enforce consistency across VBD components.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
🧬 Code graph analysis (1)
newton/_src/geometry/narrow_phase.py (9)
newton/_src/geometry/collision_core.py (2)
  • check_infinite_plane_bsphere_overlap (504-556)
  • compute_bounding_sphere_from_aabb (428-438)
newton/_src/geometry/collision_primitive.py (7)
  • collide_capsule_capsule (199-293)
  • collide_plane_capsule (297-348)
  • collide_plane_sphere (119-125)
  • collide_sphere_box (1105-1158)
  • collide_sphere_capsule (162-195)
  • collide_sphere_cylinder (443-512)
  • collide_sphere_sphere (129-158)
newton/_src/geometry/contact_data.py (1)
  • ContactData (26-62)
newton/_src/geometry/types.py (2)
  • GeoType (25-67)
  • SDF (74-113)
newton/_src/geometry/support_function.py (1)
  • extract_shape_data (305-347)
newton/_src/sim/contacts.py (1)
  • device (110-114)
newton/_src/sim/collide.py (1)
  • device (334-341)
newton/_src/geometry/broad_phase_nxn.py (2)
  • launch (295-361)
  • launch (378-435)
newton/_src/geometry/broad_phase_sap.py (1)
  • launch (506-650)
⏰ 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)
  • GitHub Check: Run GPU Benchmarks / Run GPU Benchmarks on AWS EC2
🔇 Additional comments (8)
newton/_src/geometry/narrow_phase.py (8)

23-64: LGTM!

The new imports for collision primitives (collide_capsule_capsule, collide_plane_sphere, etc.) and utilities (check_infinite_plane_bsphere_overlap, compute_bounding_sphere_from_aabb, contact_passes_margin_check) are well-organized and align with the two-stage primitive-first pipeline design.


78-130: LGTM!

The dual-path write logic is correct: output_index < 0 triggers atomic allocation with margin and bounds checks, while output_index >= 0 trusts the caller's pre-validated index. The primitive kernel properly validates indices before passing them, making this design safe.


1216-1231: Good optimization: consolidated counter array with sliced views.

Using a single array with sliced views (self._counter_array[0:1], etc.) minimizes kernel launches for zeroing and ensures all counters are cleared atomically with _counter_array.zero_(). This is a clean pattern for managing multiple related counters.


1535-1616: LGTM!

The public launch method correctly wraps launch_custom_write with the standard ContactWriterData struct. The comment at line 1589 properly notes that internal counters are cleared in launch_custom_write.


278-408: LGTM!

The primitive collision handlers are correctly designed with the type sorting invariant (type_a ≤ type_b). This ensures:

  • Planes (type 0) are always shape_a
  • Spheres/capsules can be either position
  • Cylinders/boxes are always shape_b when paired with smaller types
  • Other combinations (box-box, etc.) are routed to GJK/MPR

The multi-contact support for plane-capsule and capsule-capsule pairs is well implemented.


436-460: Bounds checking correctly implemented per prior review.

The atomic allocation with bounds checking and rollback (lines 439-445) properly prevents out-of-bounds writes. The margin pre-check before allocation is efficient.


1401-1422: The kernel launch dimensions and parameters are correct. The 2D launch with dim=[num_tile_blocks, tile_size_mesh_convex] is the proper pattern for tiled kernels in Warp. The tid unpacks to (block_index, lane), where block_index drives the strided loop across mesh pairs (stride = num_tile_blocks), and lane is passed to mesh_vs_convex_midphase() for tiled BVH query operations. Using num_tile_blocks as total_num_threads is correct for this distribution pattern.


376-383: Sentinel value pattern is documented and correctly used.

The dists[1] < wp.inf check is properly implemented. collide_capsule_capsule explicitly documents in its docstring that contact distances use wp.inf to mark invalid contacts (line 224 in collision_primitive.py). In the non-parallel case, only dists[0] is set while dists[1] remains wp.inf, and the check at line 378 correctly detects this. The pattern is consistent: plane-capsule always returns both contacts (so no sentinel check needed), while capsule-capsule conditionally returns one or two contacts using the documented sentinel pattern.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

# Conflicts:
#	newton/examples/cable/example_cable_pile.py
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

🤖 Fix all issues with AI agents
In `@newton/_src/geometry/narrow_phase.py`:
- Around line 1401-1422: The wp.launch call for
narrow_phase_find_mesh_triangle_overlaps_kernel must use a 1D launch when
ENABLE_TILE_BVH_QUERY is False: wrap the launch in a conditional so that if
ENABLE_TILE_BVH_QUERY is True you keep dim=[self.num_tile_blocks, second_dim]
and block_dim=self.tile_size_mesh_convex, otherwise call wp.launch with
dim=self.num_tile_blocks and block_dim=1 (and pass the same inputs/outputs), so
the kernel’s wp.tid() unpacking (tid, j) and tiled launch requirements are
satisfied; update the launch site in
narrow_phase_find_mesh_triangle_overlaps_kernel invocation accordingly.
🧹 Nitpick comments (1)
newton/_src/geometry/narrow_phase.py (1)

1367-1369: Consider a safety check for has_meshes to avoid silent contact loss.

When has_meshes=False, mesh kernels are skipped even if mesh pairs are produced by the primitive stage; those contacts are silently dropped. If this flag can be misconfigured by callers, consider a cheap guard (e.g., debug-only assert on shape_pairs_mesh_count[0] == 0 / shape_pairs_mesh_plane_count[0] == 0 after stage 1) or auto-detection from shape_types.

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b4c8e4 and c741734.

📒 Files selected for processing (1)
  • newton/_src/geometry/narrow_phase.py
🧰 Additional context used
📓 Path-based instructions (4)
newton/_src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

The newton/_src/ directory is internal library implementation only and must not be imported by user code (examples, documentation, or external users)

Files:

  • newton/_src/geometry/narrow_phase.py
newton/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Any user-facing class/function/object added under _src must be exposed via the public Newton API through re-exports

Files:

  • newton/_src/geometry/narrow_phase.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Use prefix-first naming for classes: ActuatorPD, ActuatorPID (not PDActuator, PIDActuator)
Use prefix-first naming for methods: add_shape_sphere() (not add_sphere_shape())
Method names must use snake_case
Prefer nested classes when self-contained: if a helper type or enum is only meaningful inside one parent class and doesn't need a public identity, define it as a nested class instead of a top-level class/module
Follow PEP 8 for Python code
Use Google-style docstrings with clear, concise explanations of what functions do, their parameters, and return values

Files:

  • newton/_src/geometry/narrow_phase.py
**/*

📄 CodeRabbit inference engine (AGENTS.md)

CLI arguments must use kebab-case (e.g., --use-cuda-graph, not --use_cuda_graph)

Files:

  • newton/_src/geometry/narrow_phase.py
🧠 Learnings (11)
📓 Common learnings
Learnt from: shi-eric
Repo: newton-physics/newton PR: 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: vastsoun
Repo: newton-physics/newton PR: 1019
File: newton/_src/solvers/kamino/geometry/primitives.py:0-0
Timestamp: 2025-12-12T17:45:26.847Z
Learning: In newton/_src/solvers/kamino/geometry/primitives.py, the narrow-phase kernel `_primitive_narrowphase` does not need to handle symmetric shape-pair orderings (e.g., both SPHERE-BOX and BOX-SPHERE) because `kamino.core.builder.ModelBuilder` and `kamino.geometry.primitive.broadphase` guarantee that explicit candidate geometry pairs are always defined with GIDs in ascending order.
📚 Learning: 2025-12-12T17:45:26.847Z
Learnt from: vastsoun
Repo: newton-physics/newton PR: 1019
File: newton/_src/solvers/kamino/geometry/primitives.py:0-0
Timestamp: 2025-12-12T17:45:26.847Z
Learning: In newton/_src/solvers/kamino/geometry/primitives.py, the narrow-phase kernel `_primitive_narrowphase` does not need to handle symmetric shape-pair orderings (e.g., both SPHERE-BOX and BOX-SPHERE) because `kamino.core.builder.ModelBuilder` and `kamino.geometry.primitive.broadphase` guarantee that explicit candidate geometry pairs are always defined with GIDs in ascending order.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-08-12T17:51:37.474Z
Learnt from: nvlukasz
Repo: newton-physics/newton PR: 519
File: newton/geometry.py:16-22
Timestamp: 2025-08-12T17:51:37.474Z
Learning: In the Newton public API refactor, common geometry symbols like ParticleFlags and ShapeFlags are now exposed at the top level in newton/__init__.py rather than through newton.geometry. The newton/geometry.py module is intentionally focused only on broad-phase collision detection classes (BroadPhaseAllPairs, BroadPhaseExplicit, BroadPhaseSAP).

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-09-26T05:58:21.013Z
Learnt from: WenchaoHuang
Repo: newton-physics/newton PR: 835
File: newton/_src/solvers/style3d/collision/collision.py:49-53
Timestamp: 2025-09-26T05:58:21.013Z
Learning: In newton/_src/solvers/style3d/collision/collision.py, the broad_phase buffers (broad_phase_ee, broad_phase_ef, broad_phase_vf) created with wp.array() don't need zero-initialization because they follow a write-first-read-later pattern: BVH query kernels in frame_begin() populate these buffers completely (write-only operations), and collision kernels only read from them afterward in accumulate_contact_force(), ensuring no undefined reads occur.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-10-30T07:28:13.112Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 984
File: newton/_src/geometry/broad_phase_nxn.py:318-336
Timestamp: 2025-10-30T07:28:13.112Z
Learning: In Newton's BroadPhaseAllPairs class (newton/_src/geometry/broad_phase_nxn.py), device management is the caller's responsibility. The library intentionally does not perform automatic device transfers for precomputed arrays in the launch method, even if there's a device mismatch. Users must ensure that the device specified in __init__ matches the device used in launch.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-08-20T18:02:36.099Z
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 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.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-10-10T10:47:41.082Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 899
File: newton/tests/test_rigid_contact.py:214-268
Timestamp: 2025-10-10T10:47:41.082Z
Learning: In `newton/tests/test_rigid_contact.py`, using `add_shape_plane(width=0, length=0)` for an infinite plane works correctly with the collision system and does not cause degenerate AABB issues in the test scenario `test_shape_collisions_gjk_mpr_multicontact`.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-09-26T05:58:21.013Z
Learnt from: WenchaoHuang
Repo: newton-physics/newton PR: 835
File: newton/_src/solvers/style3d/collision/collision.py:49-53
Timestamp: 2025-09-26T05:58:21.013Z
Learning: In newton/_src/solvers/style3d/collision/collision.py, the broad_phase buffers (broad_phase_ee, broad_phase_ef, broad_phase_vf) created with wp.array() don't need zero-initialization because the BVH query kernels that use them always write the count to query_results[0, tid] = 0 first before populating any data, ensuring no undefined reads occur.

Applied to files:

  • newton/_src/geometry/narrow_phase.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/geometry/narrow_phase.py
📚 Learning: 2025-09-15T13:13:37.976Z
Learnt from: gdaviet
Repo: newton-physics/newton PR: 750
File: newton/_src/solvers/implicit_mpm/solve_rheology.py:772-783
Timestamp: 2025-09-15T13:13:37.976Z
Learning: In Warp's tiled launch API, using a 2D launch dimension dim=(num_blocks, tile_size) is correct for tiled kernels. The wp.tid() function can either return a 2D tuple (block_index, lane) or just discard the last dimension to return only the block_index when lane information is not needed, making `block_index = wp.tid()` a valid shortcut in tiled kernels.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2026-01-13T03:11:40.556Z
Learnt from: jumyungc
Repo: newton-physics/newton PR: 1333
File: newton/_src/sim/builder.py:0-0
Timestamp: 2026-01-13T03:11:40.556Z
Learning: Ensure all VBD damping semantics follow Rayleigh-style damping and are unitless. This convention, demonstrated in SolverVBD, should be applied consistently across all VBD-related constraints in the codebase. In newton/_src/sim/builder.py, validate that any damping terms use the same normalization and are treated as dimensionless; add comments and unit tests to enforce consistency across VBD components.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
🧬 Code graph analysis (1)
newton/_src/geometry/narrow_phase.py (6)
newton/_src/geometry/collision_core.py (2)
  • check_infinite_plane_bsphere_overlap (504-556)
  • compute_bounding_sphere_from_aabb (428-438)
newton/_src/geometry/collision_primitive.py (2)
  • collide_capsule_capsule (199-293)
  • collide_sphere_sphere (129-158)
newton/_src/geometry/contact_data.py (1)
  • ContactData (26-62)
newton/_src/geometry/types.py (2)
  • GeoType (25-67)
  • SDF (74-113)
newton/_src/geometry/support_function.py (1)
  • extract_shape_data (305-347)
newton/_src/geometry/contact_reduction_global.py (4)
  • clear_active (351-385)
  • get_data_struct (387-404)
  • reduce_buffered_contacts_kernel (548-567)
  • export_reduced_contacts_kernel (658-760)
⏰ 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 Tests / Run GPU Unit Tests on AWS EC2
  • GitHub Check: Run GPU Benchmarks / Run GPU Benchmarks on AWS EC2
  • GitHub Check: run-newton-tests / newton-unittests (windows-latest)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
🔇 Additional comments (1)
newton/_src/geometry/narrow_phase.py (1)

212-219: The pattern is correct as-is and follows established Warp conventions for optional arrays. Passing None for optional arrays and checking with if array: inside kernels is valid and idiomatic in Warp (see prior work in PR 1047 and PR 818). The shape_pairs_sdf_sdf array can safely be None when hydroelastic is disabled, and the truthiness check at line 215 is the proper way to guard its usage.

Likely an incorrect or invalid review comment.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@nvtw
Copy link
Member Author

nvtw commented Jan 16, 2026

The benchmarks seem to show the right trend: https://github.com/newton-physics/newton/actions/runs/21061587041/job/60569091244?pr=1375
EDIT: Even better after avoiding some unnecessary buffer clearing operations: https://github.com/newton-physics/newton/actions/runs/21062514640/job/60572047292?pr=1375

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

🤖 Fix all issues with AI agents
In `@newton/_src/sim/contacts.py`:
- Around line 108-138: The clear method's conservative path (clear_buffers=True)
doesn't actually clear per-contact geometry fields despite the docstring; update
clear to also reset the remaining per-contact buffers (e.g.,
rigid_contact_point, rigid_contact_normal, rigid_contact_thickness,
soft_contact_point, soft_contact_normal, soft_contact_thickness and any other
per-contact arrays not currently handled) using .fill_(-1) for index/sentinel
arrays and .zero_() for numeric arrays, and keep the existing resets for
rigid_contact_shape0, rigid_contact_shape1, rigid_contact_tids,
rigid_contact_force, rigid_contact_stiffness, rigid_contact_damping,
rigid_contact_friction, soft_contact_particle, soft_contact_shape,
soft_contact_tids; alternatively if you choose not to clear them, update the
clear docstring in the clear method to explicitly list which buffers are and are
not zeroed when clear_buffers=True.
♻️ Duplicate comments (1)
newton/_src/geometry/narrow_phase.py (1)

1410-1431: (Dup) Switch to 1D launch when ENABLE_TILE_BVH_QUERY is False.
This is the same Warp tiled-launch mismatch previously flagged: dim=[num_tile_blocks, 1] with block_dim=tile_size_mesh_convex violates the tiled dimension requirement when tiling is disabled.

🧹 Nitpick comments (1)
newton/_src/geometry/narrow_phase.py (1)

1258-1275: Clamp num_blocks to max_blocks_limit for small GPUs.
Current formula can exceed the intended upper bound when sm_count * 4 < min_blocks, which undermines the cap and may overlaunch on smaller devices.

♻️ Suggested refactor
-        num_blocks = max(min_blocks, min(candidate_blocks, max_blocks_limit))
+        num_blocks = min(max_blocks_limit, max(min_blocks, candidate_blocks))
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c741734 and 1599966.

📒 Files selected for processing (2)
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/sim/contacts.py
🧰 Additional context used
📓 Path-based instructions (4)
newton/_src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

The newton/_src/ directory is internal library implementation only and must not be imported by user code (examples, documentation, or external users)

Files:

  • newton/_src/sim/contacts.py
  • newton/_src/geometry/narrow_phase.py
newton/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Any user-facing class/function/object added under _src must be exposed via the public Newton API through re-exports

Files:

  • newton/_src/sim/contacts.py
  • newton/_src/geometry/narrow_phase.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Use prefix-first naming for classes: ActuatorPD, ActuatorPID (not PDActuator, PIDActuator)
Use prefix-first naming for methods: add_shape_sphere() (not add_sphere_shape())
Method names must use snake_case
Prefer nested classes when self-contained: if a helper type or enum is only meaningful inside one parent class and doesn't need a public identity, define it as a nested class instead of a top-level class/module
Follow PEP 8 for Python code
Use Google-style docstrings with clear, concise explanations of what functions do, their parameters, and return values

Files:

  • newton/_src/sim/contacts.py
  • newton/_src/geometry/narrow_phase.py
**/*

📄 CodeRabbit inference engine (AGENTS.md)

CLI arguments must use kebab-case (e.g., --use-cuda-graph, not --use_cuda_graph)

Files:

  • newton/_src/sim/contacts.py
  • newton/_src/geometry/narrow_phase.py
🧠 Learnings (13)
📓 Common learnings
Learnt from: shi-eric
Repo: newton-physics/newton PR: 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: vastsoun
Repo: newton-physics/newton PR: 1019
File: newton/_src/solvers/kamino/geometry/primitives.py:0-0
Timestamp: 2025-12-12T17:45:26.847Z
Learning: In newton/_src/solvers/kamino/geometry/primitives.py, the narrow-phase kernel `_primitive_narrowphase` does not need to handle symmetric shape-pair orderings (e.g., both SPHERE-BOX and BOX-SPHERE) because `kamino.core.builder.ModelBuilder` and `kamino.geometry.primitive.broadphase` guarantee that explicit candidate geometry pairs are always defined with GIDs in ascending order.
Learnt from: WenchaoHuang
Repo: newton-physics/newton PR: 835
File: newton/_src/solvers/style3d/collision/collision.py:49-53
Timestamp: 2025-09-26T05:58:21.013Z
Learning: In newton/_src/solvers/style3d/collision/collision.py, the broad_phase buffers (broad_phase_ee, broad_phase_ef, broad_phase_vf) created with wp.array() don't need zero-initialization because they follow a write-first-read-later pattern: BVH query kernels in frame_begin() populate these buffers completely (write-only operations), and collision kernels only read from them afterward in accumulate_contact_force(), ensuring no undefined reads occur.
Learnt from: nvtw
Repo: newton-physics/newton PR: 981
File: newton/_src/geometry/collision_convex.py:180-289
Timestamp: 2025-10-24T07:56:14.792Z
Learning: In newton/_src/geometry/collision_convex.py, the single-contact solver (create_solve_convex_single_contact) intentionally returns count=1 even when shapes are separated (signed_distance > contact_threshold). The calling code is responsible for deciding whether to accept the contact based on the signed distance value. This design pushes filtering responsibility to the caller.
📚 Learning: 2025-09-26T05:58:21.013Z
Learnt from: WenchaoHuang
Repo: newton-physics/newton PR: 835
File: newton/_src/solvers/style3d/collision/collision.py:49-53
Timestamp: 2025-09-26T05:58:21.013Z
Learning: In newton/_src/solvers/style3d/collision/collision.py, the broad_phase buffers (broad_phase_ee, broad_phase_ef, broad_phase_vf) created with wp.array() don't need zero-initialization because they follow a write-first-read-later pattern: BVH query kernels in frame_begin() populate these buffers completely (write-only operations), and collision kernels only read from them afterward in accumulate_contact_force(), ensuring no undefined reads occur.

Applied to files:

  • newton/_src/sim/contacts.py
  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-12-13T17:26:39.791Z
Learnt from: lenroe-nv
Repo: newton-physics/newton PR: 1248
File: newton/_src/geometry/contact_data.py:47-49
Timestamp: 2025-12-13T17:26:39.791Z
Learning: In ContactData (newton/_src/geometry/contact_data.py), the fields contact_stiffness, contact_damping, and contact_friction_scale use 0.0 as the "unset" sentinel value. This is intentional: Warp struct fields initialize to 0.0 by default, and solver-side code interprets 0.0 as "use default/unscaled" rather than "disable." This design avoids explicit initialization in contact generation kernels.

Applied to files:

  • newton/_src/sim/contacts.py
📚 Learning: 2025-09-26T05:58:21.013Z
Learnt from: WenchaoHuang
Repo: newton-physics/newton PR: 835
File: newton/_src/solvers/style3d/collision/collision.py:49-53
Timestamp: 2025-09-26T05:58:21.013Z
Learning: In newton/_src/solvers/style3d/collision/collision.py, the broad_phase buffers (broad_phase_ee, broad_phase_ef, broad_phase_vf) created with wp.array() don't need zero-initialization because the BVH query kernels that use them always write the count to query_results[0, tid] = 0 first before populating any data, ensuring no undefined reads occur.

Applied to files:

  • newton/_src/sim/contacts.py
  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2026-01-13T03:11:40.556Z
Learnt from: jumyungc
Repo: newton-physics/newton PR: 1333
File: newton/_src/sim/builder.py:0-0
Timestamp: 2026-01-13T03:11:40.556Z
Learning: Ensure all VBD damping semantics follow Rayleigh-style damping and are unitless. This convention, demonstrated in SolverVBD, should be applied consistently across all VBD-related constraints in the codebase. In newton/_src/sim/builder.py, validate that any damping terms use the same normalization and are treated as dimensionless; add comments and unit tests to enforce consistency across VBD components.

Applied to files:

  • newton/_src/sim/contacts.py
  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-12-12T17:45:26.847Z
Learnt from: vastsoun
Repo: newton-physics/newton PR: 1019
File: newton/_src/solvers/kamino/geometry/primitives.py:0-0
Timestamp: 2025-12-12T17:45:26.847Z
Learning: In newton/_src/solvers/kamino/geometry/primitives.py, the narrow-phase kernel `_primitive_narrowphase` does not need to handle symmetric shape-pair orderings (e.g., both SPHERE-BOX and BOX-SPHERE) because `kamino.core.builder.ModelBuilder` and `kamino.geometry.primitive.broadphase` guarantee that explicit candidate geometry pairs are always defined with GIDs in ascending order.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-08-12T17:51:37.474Z
Learnt from: nvlukasz
Repo: newton-physics/newton PR: 519
File: newton/geometry.py:16-22
Timestamp: 2025-08-12T17:51:37.474Z
Learning: In the Newton public API refactor, common geometry symbols like ParticleFlags and ShapeFlags are now exposed at the top level in newton/__init__.py rather than through newton.geometry. The newton/geometry.py module is intentionally focused only on broad-phase collision detection classes (BroadPhaseAllPairs, BroadPhaseExplicit, BroadPhaseSAP).

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-10-30T07:28:13.112Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 984
File: newton/_src/geometry/broad_phase_nxn.py:318-336
Timestamp: 2025-10-30T07:28:13.112Z
Learning: In Newton's BroadPhaseAllPairs class (newton/_src/geometry/broad_phase_nxn.py), device management is the caller's responsibility. The library intentionally does not perform automatic device transfers for precomputed arrays in the launch method, even if there's a device mismatch. Users must ensure that the device specified in __init__ matches the device used in launch.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-08-20T18:02:36.099Z
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 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.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-09-15T13:13:37.976Z
Learnt from: gdaviet
Repo: newton-physics/newton PR: 750
File: newton/_src/solvers/implicit_mpm/solve_rheology.py:772-783
Timestamp: 2025-09-15T13:13:37.976Z
Learning: In Warp's tiled launch API, using a 2D launch dimension dim=(num_blocks, tile_size) is correct for tiled kernels. The wp.tid() function can either return a 2D tuple (block_index, lane) or just discard the last dimension to return only the block_index when lane information is not needed, making `block_index = wp.tid()` a valid shortcut in tiled kernels.

Applied to files:

  • newton/_src/geometry/narrow_phase.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/geometry/narrow_phase.py
📚 Learning: 2025-10-06T18:14:18.582Z
Learnt from: daedalus5
Repo: newton-physics/newton PR: 868
File: newton/examples/diffsim/example_diffsim_bear.py:82-96
Timestamp: 2025-10-06T18:14:18.582Z
Learning: Warp's tile operations (`wp.tile_store()` and `wp.tile_load()`) have built-in boundary checking. Out-of-bounds writes are automatically skipped and out-of-bounds reads are zero-filled, so tiled kernels are safe even when the array size is not a multiple of the tile size.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-10-10T10:47:41.082Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 899
File: newton/tests/test_rigid_contact.py:214-268
Timestamp: 2025-10-10T10:47:41.082Z
Learning: In `newton/tests/test_rigid_contact.py`, using `add_shape_plane(width=0, length=0)` for an infinite plane works correctly with the collision system and does not cause degenerate AABB issues in the test scenario `test_shape_collisions_gjk_mpr_multicontact`.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
🧬 Code graph analysis (2)
newton/_src/sim/contacts.py (2)
newton/_src/solvers/solver.py (1)
  • device (169-176)
newton/_src/sim/collide.py (1)
  • device (334-341)
newton/_src/geometry/narrow_phase.py (11)
newton/_src/geometry/collision_core.py (3)
  • check_infinite_plane_bsphere_overlap (504-556)
  • compute_bounding_sphere_from_aabb (428-438)
  • compute_tight_aabb_from_support (365-424)
newton/_src/geometry/collision_primitive.py (5)
  • collide_capsule_capsule (199-293)
  • collide_plane_capsule (297-348)
  • collide_plane_sphere (119-125)
  • collide_sphere_capsule (162-195)
  • collide_sphere_sphere (129-158)
newton/_src/geometry/contact_data.py (2)
  • ContactData (26-62)
  • contact_passes_margin_check (66-96)
newton/_src/geometry/types.py (2)
  • GeoType (25-67)
  • SDF (74-113)
newton/_src/geometry/support_function.py (2)
  • extract_shape_data (305-347)
  • SupportMapDataProvider (55-63)
newton/_src/sim/contacts.py (1)
  • device (143-147)
newton/_src/sim/collide.py (1)
  • device (334-341)
newton/_src/geometry/sdf_hydroelastic.py (1)
  • launch (386-449)
newton/_src/geometry/broad_phase_nxn.py (2)
  • launch (295-361)
  • launch (378-435)
newton/_src/geometry/broad_phase_sap.py (1)
  • launch (506-650)
newton/_src/geometry/contact_reduction_global.py (4)
  • clear_active (351-385)
  • get_data_struct (387-404)
  • reduce_buffered_contacts_kernel (548-567)
  • export_reduced_contacts_kernel (658-760)
⏰ 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 Tests / Run GPU Unit Tests on AWS EC2
  • GitHub Check: Run GPU Benchmarks / Run GPU Benchmarks on AWS EC2
  • GitHub Check: run-newton-tests / newton-unittests (windows-latest)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
🔇 Additional comments (3)
newton/_src/sim/contacts.py (1)

61-66: Nice consolidation of contact counters.
Single zero for both rigid/soft counts is a clean perf win and keeps views lightweight.

Also applies to: 95-95

newton/_src/geometry/narrow_phase.py (2)

1376-1399: Good guard to skip mesh kernels when has_meshes=False.
Skipping those launches is a clean win for primitive-only scenes.


212-219: No changes required—current code correctly handles optional arrays per Warp conventions.

The truthiness check on shape_pairs_sdf_sdf is the idiomatic pattern for optional Warp arrays: pass None when the feature is disabled, and use if array: inside the kernel to gate access. This avoids unnecessary allocations and dummy arrays. The code is correct as-is.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@nvtw nvtw requested a review from adenzler-nvidia January 16, 2026 10:34
@nvtw nvtw enabled auto-merge January 16, 2026 11:05
@nvtw nvtw added this pull request to the merge queue Jan 16, 2026
Merged via the queue into newton-physics:main with commit f82f617 Jan 16, 2026
22 checks passed
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.

2 participants