Skip to content

Conversation

@nvtw
Copy link
Member

@nvtw nvtw commented Jan 7, 2026

Completely remove contact feature id support since they are not very reliable and therefore the code complexity and overhead they introduce is not justified.

Description

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

  • Bug Fixes

    • Simplified collision contact outputs by removing per-contact feature and pair-key tracking to produce more consistent contact data.
  • Chores / Refactor

    • Removed contact-matching subsystem and related utilities.
    • Standardized contact representation to use a mode field and vec3 normals across reduction/export pipelines.
    • Simplified support/minkowski/simplex returns to only provide support points.
  • Tests

    • Deleted/updated tests tied to the removed contact-matching and feature-based APIs.

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

…reliable and therefore the code complexity and overhead they introduce is not justified.
@nvtw nvtw self-assigned this Jan 7, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 7, 2026

📝 Walkthrough

Walkthrough

This PR removes per-contact feature IDs and pair-key plumbing across the collision pipeline, deletes the ContactMatcher module and its tests, simplifies support/minkowski functions to return only a point, and replaces feature-based fields with a mode/normal-only representation in contact and reduction data structures.

Changes

Cohort / File(s) Summary
Support / Minkowski / Simplex
newton/_src/geometry/support_function.py, newton/_src/geometry/mpr.py, newton/_src/geometry/simplex_solver.py
support_map, support_map_b, minkowski_support and related helpers now return only the support point (no feature_id). Call sites updated to drop unpacking of feature IDs.
Convex contact solvers & manifolds
newton/_src/geometry/collision_convex.py, newton/_src/geometry/multicontact.py
Removed feature-id tracking/assignments in single- and multi-contact solvers and manifold extraction; deleted helper feature/edge helpers and removed feature parameters from extraction/build paths.
Collision core / pair-key removal
newton/_src/geometry/collision_core.py, newton/_src/geometry/narrow_phase.py
Deleted build_pair_key2/build_pair_key3; removed pair_key propagation and usage; eliminated contact_pair_key/contact_key fields from writer data and kernels, and removed related kernel wiring.
Contact data & reduction logic
newton/_src/geometry/contact_data.py, newton/_src/geometry/contact_reduction.py, newton/_src/geometry/contact_reduction_global.py
Removed feature and feature_pair_key from ContactData; ContactStruct.feature renamed to mode; duplicate-filter step replaced by collect_active_contacts; global reducer normal packing changed from normal_feature (vec4) to normal (vec3).
SDF / hydroelastic paths
newton/_src/geometry/sdf_contact.py, newton/_src/geometry/sdf_hydroelastic.py
Removed pair-key construction and feature assignments; mesh-SDF and reduction kernels now use mode to decide thickness/interpretation and no longer write feature/pair_key fields.
Narrow-phase writer & unified pipeline
newton/_src/sim/collide_unified.py, related writer code
Removed contact-matching support: deleted enable_contact_matching option, contact key buffers, and all wiring of contact_pair_key/contact_key in pipeline and writer interfaces.
ContactMatcher & tests
newton/_src/geometry/contact_matcher.py (deleted), newton/tests/test_contact_matcher.py (deleted), newton/tests/test_contact_reduction.py
Deleted ContactMatcher module and its tests; tests updated to use mode and collect_active_contacts where applicable.
Benchmarks & tooling
asv/benchmarks/collision/benchmark_contact_reduction.py
Call site updated to remove feature argument from export_and_reduce_contact invocation.

Sequence Diagram(s)

sequenceDiagram
    participant NarrowPhase
    participant SupportMap
    participant SimplexSolver
    participant Reducer
    participant Writer

    NarrowPhase->>SupportMap: request support point for shape(s)
    SupportMap-->>NarrowPhase: support point (no feature id)
    NarrowPhase->>SimplexSolver: run GJK/MPR/closest-distance using support point
    SimplexSolver-->>NarrowPhase: contact (position, normal, depth)
    NarrowPhase->>Reducer: export_contact_to_buffer(position, normal, depth)
    Reducer-->>Writer: reduced contacts (position, normal, depth, mode)
    Writer-->>[External]: emit final ContactData (mode, no feature IDs)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • eric-heiden
  • adenzler-nvidia
  • mmacklin
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Completely remove contact feature id support' accurately and clearly describes the main change in the pull request, which is the removal of contact feature ID functionality across the entire codebase.
Docstring Coverage ✅ Passed Docstring coverage is 80.49% which is sufficient. The required threshold is 80.00%.

✏️ 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 5c06a4b and 27b0915.

📒 Files selected for processing (1)
  • newton/tests/test_contact_reduction_global.py
🧰 Additional context used
🧠 Learnings (3)
📓 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-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_contact_reduction_global.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/tests/test_contact_reduction_global.py
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • 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)
🔇 Additional comments (1)
newton/tests/test_contact_reduction_global.py (1)

419-437: Strengthen this test to validate the new normal wiring beyond just num_exported > 0.

The test correctly passes reducer.normal to the export kernel (wiring is sound), but only asserts that something exported. This won't catch future regressions if the parameter order shifts or data types change. Validate exported normals and shape pairs to exercise the full normal-only data path.

Proposed test strengthening (verifies normals + exported pairs)
     # Verify output - should have exported all unique winners
     num_exported = int(contact_count_out.numpy()[0])

     test.assertGreater(num_exported, 0)
+
+    # Validate the normal-only path is correctly wired.
+    pairs_np = contact_pair_out.numpy()[:num_exported]
+    normals_np = contact_normal_out.numpy()[:num_exported]
+
+    # Order is not guaranteed; validate as sets / per-row checks.
+    expected_pairs = {(i, i + 100) for i in range(5)}
+    actual_pairs = {tuple(p) for p in pairs_np}
+    test.assertEqual(actual_pairs, expected_pairs)
+
+    for n in normals_np:
+        test.assertAlmostEqual(float(n[0]), 0.0, places=6)
+        test.assertAlmostEqual(float(n[1]), 1.0, places=6)
+        test.assertAlmostEqual(float(n[2]), 0.0, places=6)

Also applies to: lines 439–444


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 nvtw marked this pull request as draft January 7, 2026 13:03
@codecov
Copy link

codecov bot commented Jan 7, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 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: 0

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

536-542: Remove unnecessary int() cast.

Line 537 has an unnecessary int() cast since write_idx is already declared as int(0) on line 537 (implicitly) and is used in integer operations throughout the loop.

♻️ Suggested simplification
             if thread_id == 0:
-                write_idx = int(0)
+                write_idx = 0
                 for key in range(num_slots):
                     if buffer[key].projection > empty_marker:
                         active_ids[write_idx] = key
                         write_idx += 1
                 active_ids[num_slots] = write_idx
             synchronize()
📜 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 9bbdcbf and 21feec8.

📒 Files selected for processing (15)
  • newton/_src/geometry/collision_convex.py
  • newton/_src/geometry/collision_core.py
  • newton/_src/geometry/contact_data.py
  • newton/_src/geometry/contact_matcher.py
  • newton/_src/geometry/contact_reduction.py
  • newton/_src/geometry/mpr.py
  • newton/_src/geometry/multicontact.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/geometry/sdf_contact.py
  • newton/_src/geometry/sdf_hydroelastic.py
  • newton/_src/geometry/simplex_solver.py
  • newton/_src/geometry/support_function.py
  • newton/_src/sim/collide_unified.py
  • newton/tests/test_contact_matcher.py
  • newton/tests/test_contact_reduction.py
💤 Files with no reviewable changes (3)
  • newton/_src/geometry/contact_matcher.py
  • newton/tests/test_contact_matcher.py
  • newton/_src/geometry/contact_data.py
🧰 Additional context used
🧠 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-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/collision_convex.py
  • newton/_src/geometry/simplex_solver.py
  • newton/_src/geometry/sdf_contact.py
  • newton/_src/geometry/sdf_hydroelastic.py
  • newton/_src/geometry/collision_core.py
  • newton/_src/geometry/contact_reduction.py
  • newton/_src/geometry/mpr.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/collision_convex.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/sim/collide_unified.py
  • newton/_src/geometry/sdf_contact.py
  • newton/_src/geometry/sdf_hydroelastic.py
  • newton/_src/geometry/collision_core.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_convex.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/geometry/sdf_contact.py
  • newton/_src/geometry/sdf_hydroelastic.py
  • newton/_src/geometry/collision_core.py
  • newton/_src/geometry/contact_reduction.py
  • newton/_src/geometry/multicontact.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
  • newton/_src/geometry/sdf_contact.py
  • newton/_src/geometry/sdf_hydroelastic.py
  • newton/_src/geometry/collision_core.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
  • newton/_src/sim/collide_unified.py
📚 Learning: 2025-08-12T17:52:15.009Z
Learnt from: nvlukasz
Repo: newton-physics/newton PR: 519
File: newton/_src/utils/import_mjcf.py:226-231
Timestamp: 2025-08-12T17:52:15.009Z
Learning: In the Newton codebase, when passing array-like objects (numpy arrays, lists, tuples) to wp.vec3(), the consistent pattern is to use the unpacking operator: wp.vec3(*array) rather than wp.vec3(array). This pattern is used throughout newton/_src/utils/import_mjcf.py and newton/_src/core/types.py.

Applied to files:

  • 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/geometry/narrow_phase.py
  • newton/_src/sim/collide_unified.py
  • newton/_src/geometry/sdf_contact.py
  • newton/_src/geometry/sdf_hydroelastic.py
📚 Learning: 2025-09-25T16:14:22.033Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/_src/sim/ik/ik_objectives.py:0-0
Timestamp: 2025-09-25T16:14:22.033Z
Learning: In NVIDIA Warp's Newton physics library, wp.transform supports direct numerical indexing (e.g., body_tf[0], body_tf[1], body_tf[2] for position and body_tf[3], body_tf[4], body_tf[5], body_tf[6] for quaternion components) to access its elements. This is the standard API used throughout the codebase.

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-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/sdf_contact.py
  • newton/_src/geometry/sdf_hydroelastic.py
  • newton/_src/geometry/contact_reduction.py
🧬 Code graph analysis (6)
newton/_src/geometry/collision_convex.py (2)
newton/_src/geometry/mpr.py (1)
  • create_solve_mpr (200-487)
newton/_src/geometry/simplex_solver.py (1)
  • create_solve_closest_distance (56-563)
newton/tests/test_contact_reduction.py (1)
newton/_src/geometry/contact_reduction.py (1)
  • collect_active_contacts (519-543)
newton/_src/geometry/simplex_solver.py (1)
newton/_src/geometry/mpr.py (1)
  • minkowski_support (118-159)
newton/_src/geometry/sdf_contact.py (1)
newton/_src/geometry/contact_reduction.py (1)
  • collect_active_contacts (519-543)
newton/_src/geometry/sdf_hydroelastic.py (1)
newton/_src/geometry/collision_core.py (1)
  • sat_box_intersection (1033-1072)
newton/_src/geometry/collision_core.py (1)
newton/_src/geometry/support_function.py (1)
  • support_map (112-301)
🪛 Ruff (0.14.10)
newton/_src/geometry/support_function.py

112-112: Unused function argument: data_provider

(ARG001)

newton/_src/geometry/contact_reduction.py

537-537: 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 Benchmarks / Run GPU Benchmarks on AWS EC2
🔇 Additional comments (25)
newton/_src/geometry/support_function.py (3)

24-26: Documentation accurately reflects the simplified return type.

The updated docstring correctly describes the new single-value return semantics, aligning with the PR objective of removing feature ID support.


112-115: Signature simplification looks correct; data_provider is intentionally retained for extensibility.

The static analysis flags data_provider as unused (ARG001), but per the SupportMapDataProvider docstring (lines 55-61), this parameter is a placeholder for projects like MuJoCo Warp that store convex hull data in external arrays. Keeping it in the interface maintains API compatibility and extensibility.

The return type simplification from tuple[wp.vec3, int] to wp.vec3 correctly implements the feature ID removal.


298-301: Default case handling is appropriate.

Returning a zero vector for unhandled shape types is a safe fallback. The simplified single-value return is consistent with all other branches.

newton/_src/geometry/collision_convex.py (4)

66-67: Docstring correctly updated to reflect simplified support function contract.

The documentation now accurately states the support function returns only a support point, consistent with the changes in support_function.py.


189-191: Single-contact solver docstring updated consistently.

Same documentation update as the multi-contact solver, maintaining consistency across the API.


239-249: Solver unpacking correctly simplified to 4-tuple.

The MPR solver now returns (collision, signed_distance, point, normal) without feature IDs. The unpacking matches the updated contract from mpr.py.


251-262: GJK fallback unpacking also correctly updated.

The create_solve_closest_distance call now unpacks 4 values, consistent with the updated simplex_solver.py contract.

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

24-24: Import correctly simplified to remove unused build_pair_key2.

The import now only includes sat_box_intersection, which is still used for OBB collision checks in the broadphase (line 832). The removal of build_pair_key2 is consistent with the PR objective of eliminating pair-key handling throughout the codebase.

newton/_src/geometry/mpr.py (4)

73-75: Factory function docstring correctly updated.

The documentation now reflects that support_func returns only a support point, consistent with the updated support_map signature.


91-115: support_map_b correctly simplified to return single wp.vec3.

The function now returns only the world-space support point without feature ID tracking. The internal logic (coordinate transformation, support evaluation, world-space conversion) remains unchanged and correct.


126-159: minkowski_support correctly returns single Vert without feature IDs.

The Minkowski difference computation logic is preserved. The function now returns only the Vert struct containing B (point on shape B) and BtoA (vector from B to A), which is sufficient for the MPR algorithm.


273-273: All minkowski_support call sites in MPR core correctly updated.

The calls at lines 273, 293, 328, and 375 now receive a single Vert value. Previously these unpacked (Vert, feature_a_id, feature_b_id). The algorithm logic using v1, v2, v3, v4 remains unchanged.

Also applies to: 293-293, 328-328, 375-375

newton/tests/test_contact_reduction.py (4)

207-207: Field rename from feature to mode correctly applied.

The test data generation now uses c.mode instead of c.feature, consistent with the ContactStruct field rename in contact_reduction.py.


216-216: Function reference correctly updated to collect_active_contacts.

The test kernel now uses the renamed function from ContactReductionFunctions, aligning with the refactor from filter_unique_contacts to collect_active_contacts.


252-253: Function call correctly updated with matching semantics.

The call to collect_active_contacts replaces filter_unique_contacts, and the comment accurately describes the operation as collecting active contacts after binning.


348-348: Comment correctly describes updated reduction semantics.

The explanation now accurately reflects that reduction keeps "only best contact per (bin, direction) slot" rather than filtering duplicates based on feature IDs.

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

396-396: minkowski_support call correctly simplified to single-value return.

The GJK solver now receives a single Vert from minkowski_support instead of unpacking (Vert, feature_a_id, feature_b_id). The subsequent usage of w.BtoA (line 401) and w.B/w.BtoA (lines 434-435) remains correct.

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

397-418: All support_map calls correctly simplified to single-value returns.

The AABB computation now receives a single wp.vec3 from each support_map call instead of unpacking a tuple. The six direction queries (+X, +Y, +Z, -X, -Y, -Z) and subsequent dot product calculations remain functionally correct.

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

82-169: LGTM! Clean removal of contact matching infrastructure.

The removal of contact_pair_key and contact_key handling from write_contact and UnifiedContactWriterData is well-executed. The function now focuses solely on geometric contact data, simplifying the collision pipeline.

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

964-993: LGTM! Support function return type simplified correctly.

The change from pt_a, feature_a = support_func(...) to pt_a = support_func(...) is consistent with the removal of feature ID tracking. The manifold generation logic now focuses purely on geometric contact points.

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

59-64: LGTM! Field rename from feature to mode clarifies intent.

The rename from feature to mode with the comment "Used to track collision mode (e.g., which mesh the triangle came from)" better describes the field's purpose in the simplified contact tracking approach.


510-545: LGTM! Function rename better reflects simplified behavior.

The rename from _create_filter_unique_contacts to _create_collect_active_contacts accurately describes the new behavior: collecting all valid contacts into a compact list rather than filtering duplicates based on feature IDs.

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

1109-1113: LGTM! Mode field correctly tracks collision direction.

The assignment c.mode = mode (where mode is 0 or 1) appropriately tracks which mesh the triangle originated from in mesh-mesh collisions. This is later used correctly at lines 1155-1162 to determine thickness assignment.


1131-1167: LGTM! Consistent use of mode field in contact reduction output.

The mode field is correctly used to distinguish between the two collision directions (mesh A vs SDF B, and mesh B vs SDF A), ensuring proper thickness assignment for each contact. The centered world-space coordinates are appropriately converted back to world space before writing.

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

771-771: Remove this comment; mesh-plane and SDF collision paths are independent and pose no mode semantics issue.

The c.mode = 0 assignment is a placeholder in the mesh-plane collision path. Mesh-plane contacts directly assign thickness values (lines 795–796) without consulting the mode field, whereas SDF contacts use mode to distinguish between which mesh's triangles are being tested (mode=0: mesh0 vs SDF1, mode=1: mesh1 vs SDF0). These are separate, non-interacting code paths with independent thickness handling; there is no semantic inconsistency to verify.

Likely an incorrect or invalid review comment.

@nvtw nvtw marked this pull request as ready for review January 9, 2026 08:08
adenzler-nvidia
adenzler-nvidia previously approved these changes Jan 9, 2026
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! Let's rip this out.

# Conflicts:
#	newton/_src/geometry/support_function.py
adenzler-nvidia
adenzler-nvidia previously approved these changes Jan 9, 2026
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/tests/test_contact_reduction_global.py (1)

102-112: Tests still pass removed feature parameter, causing pipeline failures.

The export_and_reduce_contact function signature no longer includes the feature parameter, but this test kernel still passes feature=42. This causes the WarpCodegenError seen in CI. The same issue exists in all other test kernels in this file.

🐛 Proposed fix for test_basic_contact_storage
         _ = export_and_reduce_contact(
             shape_a=0,
             shape_b=1,
             position=wp.vec3(1.0, 2.0, 3.0),
             normal=wp.vec3(0.0, 1.0, 0.0),
             depth=-0.01,
-            feature=42,
             reducer_data=reducer_data,
             beta0=1000.0,
             beta1=0.001,
         )

Apply similar fixes to the following test kernels:

  • store_multiple_contacts_kernel (line 150)
  • store_different_pairs_kernel (line 190)
  • store_one_contact_kernel (line 227)
  • stress_kernel (line 282)
  • store_contact_kernel in test_clear_active (line 319)
  • store_contacts_kernel in test_export_reduced_contacts_kernel (line 383)
🧹 Nitpick comments (1)
newton/_src/geometry/contact_reduction.py (1)

515-550: LGTM!

The renamed collect_active_contacts function correctly simplifies the logic by removing per-feature duplicate filtering that's no longer needed. The implementation now collects all valid contacts (projection > empty_marker) into a compact list.

Minor: The int(0) cast on line 542 is redundant since 0 is already an integer. Consider removing it for clarity:

♻️ Optional cleanup
         if thread_id == 0:
-            write_idx = int(0)
+            write_idx = 0
             for key in range(num_slots):
📜 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 21feec8 and 5c06a4b.

📒 Files selected for processing (6)
  • asv/benchmarks/collision/benchmark_contact_reduction.py
  • newton/_src/geometry/contact_reduction.py
  • newton/_src/geometry/contact_reduction_global.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/geometry/support_function.py
  • newton/tests/test_contact_reduction_global.py
🧰 Additional context used
🧠 Learnings (12)
📓 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-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/_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/contact_reduction_global.py
  • newton/_src/geometry/narrow_phase.py
📚 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_reduction.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.py
  • newton/tests/test_contact_reduction_global.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/_src/geometry/contact_reduction.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-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-12T17:52:15.009Z
Learnt from: nvlukasz
Repo: newton-physics/newton PR: 519
File: newton/_src/utils/import_mjcf.py:226-231
Timestamp: 2025-08-12T17:52:15.009Z
Learning: In the Newton codebase, when passing array-like objects (numpy arrays, lists, tuples) to wp.vec3(), the consistent pattern is to use the unpacking operator: wp.vec3(*array) rather than wp.vec3(array). This pattern is used throughout newton/_src/utils/import_mjcf.py and newton/_src/core/types.py.

Applied to files:

  • 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/geometry/narrow_phase.py
📚 Learning: 2025-09-25T16:14:22.033Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/_src/sim/ik/ik_objectives.py:0-0
Timestamp: 2025-09-25T16:14:22.033Z
Learning: In NVIDIA Warp's Newton physics library, wp.transform supports direct numerical indexing (e.g., body_tf[0], body_tf[1], body_tf[2] for position and body_tf[3], body_tf[4], body_tf[5], body_tf[6] for quaternion components) to access its elements. This is the standard API used throughout the codebase.

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 (1)
asv/benchmarks/collision/benchmark_contact_reduction.py (1)
newton/_src/geometry/contact_reduction_global.py (1)
  • export_and_reduce_contact (518-534)
🪛 GitHub Actions: Pull Request
newton/tests/test_contact_reduction_global.py

[error] 111-111: WarpCodegenError: Couldn't find function overload for 'export_and_reduce_contact' that matched inputs with types: [int32, int32, vec3f, vec3f, float32, int32, GlobalContactReducerData, float32, float32]


[error] 322-322: WarpCodegenError: Couldn't find function overload for 'export_and_reduce_contact' that matched inputs with types: [int32, int32, vec3f, vec3f, float32, int32, GlobalContactReducerData, float32, float32]


[error] 230-230: WarpCodegenError: Couldn't find function overload for 'export_and_reduce_contact' that matched inputs with types: [int32, int32, vec3f, vec3f, float32, int32, GlobalContactReducerData, float32, float32]


[error] 153-153: WarpCodegenError: Couldn't find function overload for 'export_and_reduce_contact' that matched inputs with types: [int32, int32, vec3f, vec3f, float32, int32, GlobalContactReducerData, float32, float32]


[error] 193-193: WarpCodegenError: Couldn't find function overload for 'export_and_reduce_contact' that matched inputs with types: [int32, int32, vec3f, vec3f, float32, int32, GlobalContactReducerData, float32, float32]


[error] 386-386: WarpCodegenError: Couldn't find function overload for 'export_and_reduce_contact' that matched inputs with types: [int32, int32, vec3f, vec3f, float32, int32, GlobalContactReducerData, float32, float32]


[error] 285-285: WarpCodegenError: Couldn't find function overload for 'export_and_reduce_contact' that matched inputs with types: [int32, int32, vec3f, vec3f, float32, int32, GlobalContactReducerData, float32, float32]

🪛 Ruff (0.14.10)
newton/_src/geometry/support_function.py

112-112: Unused function argument: data_provider

(ARG001)

newton/_src/geometry/contact_reduction.py

542-542: 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 Benchmarks / Run GPU Benchmarks on AWS EC2
🔇 Additional comments (12)
asv/benchmarks/collision/benchmark_contact_reduction.py (1)

63-63: LGTM!

The export_and_reduce_contact call correctly omits the removed feature parameter, aligning with the updated function signature from the broader refactor.

newton/tests/test_contact_reduction_global.py (1)

440-440: LGTM!

The field reference correctly uses reducer.normal to match the renamed field in GlobalContactReducer.

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

24-26: LGTM!

The module docstring correctly describes the simplified return value (support point only, no feature ID), aligning with the PR's goal of removing feature ID tracking.


112-126: LGTM!

The function signature and docstring correctly reflect returning only wp.vec3 (the support point) without feature ID tracking. The data_provider parameter remains as a placeholder for extensibility in other projects as documented in SupportMapDataProvider.


298-301: LGTM!

The simplified return statement correctly returns only the support point, consistent with the updated function contract.

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

731-732: LGTM!

The c.mode = 0 assignment correctly replaces the previous feature-based tracking, aligning with the ContactStruct.mode field introduced in contact_reduction.py.


1118-1119: LGTM!

Correctly references self.global_contact_reducer.normal to match the renamed field from normal_feature.

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

63-64: LGTM!

The field rename from feature to mode with updated documentation correctly reflects the shift from per-contact feature IDs to collision mode tracking.

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

211-211: LGTM!

The field type change from wp.vec4 (normal_feature) to wp.vec3 (normal) correctly simplifies the data structure by removing the embedded feature component.


327-330: LGTM!

The buffer initialization correctly uses wp.vec3 for the normal array, matching the updated data structure.


568-590: LGTM!

The unpack_contact function correctly:

  1. Takes normal: wp.array(dtype=wp.vec3) instead of the previous vec4 type
  2. Returns (position, n, depth) tuple without the feature component
  3. Reads normal directly as vec3 without unpacking from vec4

517-534: LGTM!

The export_and_reduce_contact legacy wrapper correctly removes the feature parameter and passes the updated arguments to export_contact_to_buffer.

@adenzler-nvidia adenzler-nvidia added this pull request to the merge queue Jan 12, 2026
Merged via the queue into newton-physics:main with commit 743645e Jan 12, 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