Opt-in deterministic contacts#2409
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds deterministic contact ordering: per-contact sort sub-keys and 64-bit sort keys, fingerprint-based deterministic contact packing/reduction, a radix-sort-based ContactSorter, and integration across narrow-phase, reduction, and collision pipeline to produce reproducible contact outputs. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant CollisionPipeline
participant NarrowPhase
participant GlobalContactReducer
participant ContactSorter
Client->>CollisionPipeline: collide(..., deterministic=True)
CollisionPipeline->>NarrowPhase: launch(writer_data with sort_key buffer)
NarrowPhase->>NarrowPhase: compute contacts & assign sort_sub_key per contact
NarrowPhase->>NarrowPhase: compute sort_key via make_contact_sort_key(shape_a, shape_b, sort_sub_key)
NarrowPhase->>NarrowPhase: write sort_key to writer_data.contact_sort_key
NarrowPhase-->>CollisionPipeline: return contact_count
CollisionPipeline->>GlobalContactReducer: reduce contacts with fingerprint-based tie-breaking
GlobalContactReducer-->>CollisionPipeline: reduced contacts
CollisionPipeline->>ContactSorter: sort_full(sort_keys, contact arrays)
ContactSorter->>ContactSorter: radix_sort_pairs(sort_keys, permutation)
ContactSorter->>ContactSorter: gather all contact arrays using permutation
ContactSorter-->>CollisionPipeline: reordered contact buffers
CollisionPipeline-->>Client: deterministic contact output
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@newton/_src/geometry/contact_data.py`:
- Around line 73-80: The packing currently shifts raw shape_a and shape_b which
can overflow into adjacent bits; mask shape_a and shape_b to 20 bits before
shifting so the 64-bit key layout is preserved even when values exceed the
intended range. Concretely, apply (shape_a & 0xFFFFF) and (shape_b & 0xFFFFF)
(or wp.int64(...) & wp.int64(0xFFFFF)) when constructing the returned value
alongside the existing sort_sub_key masking and keep the warning logs as-is;
update the expressions used in the return (the shifted parts that reference
shape_a and shape_b) to use these masked values.
In `@newton/_src/geometry/contact_reduction_global.py`:
- Around line 245-254: The reducer is truncating one bit of the sort_sub_key
(FINGERPRINT) causing aliasing during atomic_max; change the end-to-end
fingerprint budget to 23 bits by updating FINGERPRINT_BITS from 22 to 23 and
update FINGERPRINT_MASK to (1 << 23) - 1 (and any duplicate definitions around
lines 313-329) so make_contact_sort_key and the reduction use the same 23-bit
fingerprint; ensure any related constants (e.g., where FINGERPRINT_BITS is
referenced in make_contact_sort_key or the reducer) are adjusted consistently so
no mask/truncation drops that extra bit.
In `@newton/_src/geometry/narrow_phase.py`:
- Around line 1613-1615: The deterministic path currently allocates
self._sort_key_array and ContactSorter using max_candidate_pairs but
write_contact_simple emits one sort key per actual emitted contact
(contact_pair.shape[0]), so the buffer can be overflowed; change the allocation
to use the contact-capacity (the maximum number of emitted contacts) instead of
max_candidate_pairs—i.e., size self._sort_key_array and initialize ContactSorter
with the maximum contacts capacity (the same capacity used for emitted contacts
in write_contact_simple) so deterministic sorting cannot write past the buffer.
- Around line 35-43: The import of ContactSorter from ..geometry.contact_sort is
invalid and breaks module loading; locate the ContactSorter class/definition in
the repository (search for "class ContactSorter" or its factory function) and
update the import in narrow_phase.py to the correct module path that actually
defines ContactSorter, or remove/replace the import with the correct alternative
API (e.g., from ..geometry.contact_sorter or the module where ContactSorter is
implemented); ensure the symbol name (ContactSorter) matches the exported name
and run tests to verify the module imports cleanly.
In `@newton/_src/sim/collide.py`:
- Around line 14-16: The import of ContactSorter from ..geometry.contact_sort is
failing because contact_sort does not exist; update the import in collide.py to
reference the correct module that defines ContactSorter (for example, import
ContactSorter from the actual module where it's implemented), or remove/replace
the ContactSorter import and usages in functions like
any_collisions/collect_contacts that reference ContactSorter so the module can
import successfully; ensure you update the import line importing ContactSorter
and adjust downstream references to the ContactSorter symbol accordingly.
- Around line 715-725: The non-deterministic branch currently sets
self._sort_key_array = None which breaks collide() and write_contact() that
always expect an array (copied into ContactWriterData.out_sort_key); replace the
None sentinel with an empty Warp array allocated on the same device and with the
same dtype/shape semantics used in the deterministic branch (i.e. an int64
zero-length array on device) so code that copies or indexes the array can
operate uniformly; leave _contact_sorter as None when deterministic is False.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: d8502213-3a5a-4cab-9ed8-5dd204e2b9cf
📒 Files selected for processing (11)
newton/_src/geometry/collision_convex.pynewton/_src/geometry/collision_core.pynewton/_src/geometry/contact_data.pynewton/_src/geometry/contact_reduction.pynewton/_src/geometry/contact_reduction_global.pynewton/_src/geometry/contact_reduction_hydroelastic.pynewton/_src/geometry/multicontact.pynewton/_src/geometry/narrow_phase.pynewton/_src/geometry/sdf_contact.pynewton/_src/sim/collide.pynewton/tests/test_contact_reduction_global.py
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
newton/_src/geometry/contact_sort.py (1)
64-92: Use concrete Warp array annotations instead of barewp.array.These signatures currently drop the element dtype, which goes against the repo’s Warp typing convention and makes the API harder to validate at a glance. Please annotate them as
wp.array[wp.int64],wp.array[wp.int32],wp.array[wp.vec3],wp.array[float], etc., and usewp.array[Any]only where the dtype is intentionally generic.Representative cleanup
+from typing import Any + def sort_simple( self, - sort_keys: wp.array, - contact_count: wp.array, + sort_keys: wp.array[wp.int64], + contact_count: wp.array[int], *, - contact_pair: wp.array, - contact_position: wp.array, - contact_normal: wp.array, - contact_penetration: wp.array, - contact_tangent: wp.array | None = None, + contact_pair: wp.array[wp.vec2i], + contact_position: wp.array[wp.vec3], + contact_normal: wp.array[wp.vec3], + contact_penetration: wp.array[float], + contact_tangent: wp.array[wp.vec3] | None = None, device: Devicelike = None, ) -> None:As per coding guidelines "Annotate Warp arrays with bracket syntax (
wp.array[wp.vec3],wp.array2d[float],wp.array[Any]), not the parenthesized form. Usewp.array[X]for 1-D arrays, notwp.array1d[X]."Also applies to: 126-142, 192-210
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@newton/_src/geometry/contact_sort.py` around lines 64 - 92, The method signatures use bare wp.array types; update them to concrete Warp array annotations (e.g., use wp.array[wp.int64], wp.array[wp.int32], wp.array[wp.vec3], wp.array[float], etc.) so the element dtype is explicit. Specifically modify sort_simple's parameters: annotate sort_keys as wp.array[wp.int64], contact_count as wp.array[wp.int32], contact_pair as wp.array[wp.vec2i] (or wp.array[wp.int32] if pairs are flattened indices), contact_position and contact_normal as wp.array[wp.vec3], contact_penetration as wp.array[float], and contact_tangent (when present) as wp.array[wp.vec3] | None; then apply the same bracketed wp.array[...] annotation style to the other function signatures mentioned (around the regions noted: 126-142 and 192-210) so all 1-D Warp arrays carry explicit element dtypes rather than bare wp.array.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@newton/_src/geometry/contact_sort.py`:
- Around line 60-62: The class docstring incorrectly references a nonexistent
:meth:`sort` method; update the docstring for ContactSorter to reference the
actual public methods (:meth:`sort_simple` and :meth:`sort_full`) using the
correct Sphinx :meth: role and fully qualified names if needed so readers are
pointed to the real API (e.g., replace :meth:`sort` with
:meth:`ContactSorter.sort_simple` and :meth:`ContactSorter.sort_full` or
equivalent short forms used in this project).
- Around line 1-2: The SPDX header at the top of the file currently uses the
wrong creation year; update the line "# SPDX-FileCopyrightText: Copyright (c)
2025 The Newton Developers" to use the actual first-created year (change 2025 to
2026) so the SPDX metadata is correct; leave the license identifier line "#
SPDX-License-Identifier: Apache-2.0" unchanged.
---
Nitpick comments:
In `@newton/_src/geometry/contact_sort.py`:
- Around line 64-92: The method signatures use bare wp.array types; update them
to concrete Warp array annotations (e.g., use wp.array[wp.int64],
wp.array[wp.int32], wp.array[wp.vec3], wp.array[float], etc.) so the element
dtype is explicit. Specifically modify sort_simple's parameters: annotate
sort_keys as wp.array[wp.int64], contact_count as wp.array[wp.int32],
contact_pair as wp.array[wp.vec2i] (or wp.array[wp.int32] if pairs are flattened
indices), contact_position and contact_normal as wp.array[wp.vec3],
contact_penetration as wp.array[float], and contact_tangent (when present) as
wp.array[wp.vec3] | None; then apply the same bracketed wp.array[...] annotation
style to the other function signatures mentioned (around the regions noted:
126-142 and 192-210) so all 1-D Warp arrays carry explicit element dtypes rather
than bare wp.array.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: de346e66-cd07-422c-8b33-e0e0ea54ff19
📒 Files selected for processing (4)
docs/api/newton.rstnewton/_src/geometry/contact_data.pynewton/_src/geometry/contact_reduction_global.pynewton/_src/geometry/contact_sort.py
✅ Files skipped from review due to trivial changes (1)
- docs/api/newton.rst
🚧 Files skipped from review as they are similar to previous changes (2)
- newton/_src/geometry/contact_data.py
- newton/_src/geometry/contact_reduction_global.py
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
newton/_src/geometry/contact_sort.py (1)
106-116: Use typed Warp array annotations on the sorter API.These signatures use bare
wp.array, which hides the expected dtypes for a class that permutes several heterogeneous buffers. Please switch them towp.array[...](wp.array[wp.int64],wp.array[wp.vec3], etc.) so mismatches are easier to catch.As per coding guidelines "Annotate Warp arrays with bracket syntax (
wp.array[wp.vec3],wp.array2d[float],wp.array[Any]), not the parenthesized form. Usewp.array[X]for 1-D arrays, notwp.array1d[X]."Also applies to: 143-159, 207-236
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@newton/_src/geometry/contact_sort.py` around lines 106 - 116, The sort_simple signature (and the other sorter APIs in this file) use untyped wp.array annotations; update them to use Warp typed generics (e.g., wp.array[wp.int64] for indices/counters, wp.array[wp.vec3] for 3D vectors, wp.array[float] for scalars, or wp.array[Any] where heterogeneous) so mismatches are caught: replace the bare wp.array on parameters like sort_keys, contact_count, contact_pair, contact_position, contact_normal, contact_penetration, contact_tangent and the device param annotation if needed with the appropriate wp.array[...] types consistent with how those buffers are used in sort_simple and the other sort_* functions in this module.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@newton/_src/geometry/contact_sort.py`:
- Around line 106-116: The sort_simple signature (and the other sorter APIs in
this file) use untyped wp.array annotations; update them to use Warp typed
generics (e.g., wp.array[wp.int64] for indices/counters, wp.array[wp.vec3] for
3D vectors, wp.array[float] for scalars, or wp.array[Any] where heterogeneous)
so mismatches are caught: replace the bare wp.array on parameters like
sort_keys, contact_count, contact_pair, contact_position, contact_normal,
contact_penetration, contact_tangent and the device param annotation if needed
with the appropriate wp.array[...] types consistent with how those buffers are
used in sort_simple and the other sort_* functions in this module.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: ed10bd4e-0682-4840-9a8c-8b06bdd44f83
📒 Files selected for processing (1)
newton/_src/geometry/contact_sort.py
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@newton/_src/geometry/contact_reduction_global.py`:
- Around line 414-424: The current enable_deterministic_contact_packing()
mutates module-level symbols (make_contact_value, make_preprune_probe,
unpack_contact_id) to deterministic implementations (_make_contact_value_det,
_make_preprune_probe_det, _unpack_contact_id_det), which makes the choice
process-global; instead, stop swapping globals and thread the packer/unpacker
implementations into reducer/kernel construction so each reducer can choose fast
vs deterministic independently—modify the reducer/kernel builder to accept a
packer strategy (e.g., pass
make_contact_value/make_preprune_probe/unpack_contact_id as parameters or
encapsulate them in a Packer strategy object) and update all places that build
kernels (including the other swap site that currently mirrors
enable_deterministic_contact_packing behavior) to bind the appropriate
implementation at build time rather than calling
enable_deterministic_contact_packing().
- Around line 265-268: The packer currently only reserves 19 bits for contact
ids, which overflows for defaults like max_triangle_pairs=1_000_000; update
CONTACT_ID_BITS to wp.uint64(20) and recompute CONTACT_ID_MASK =
wp.constant(wp.uint64((1 << 20) - 1)) (and update the paired FINGERPRINT_MASK if
you keep FINGERPRINT_BITS unchanged) in both places where these constants are
defined (symbols: FINGERPRINT_BITS, CONTACT_ID_BITS, CONTACT_ID_MASK,
FINGERPRINT_MASK) so packed ids no longer alias and unpack_contact_id() returns
correct winners.
In `@newton/_src/sim/collide.py`:
- Around line 715-725: The deterministic sort state (_sort_key_array and
_contact_sorter) is sized only from the pipeline constructor rigid_contact_max
and can be too small for a caller-supplied Contacts buffer
(writer_data.contact_max / contacts.rigid_contact_max), causing write_contact to
index out of bounds; update the constructor or the deterministic branch in the
Collide class to compare and resize _sort_key_array (and recreate or resize
_contact_sorter) to match the caller's contact capacity
(writer_data.contact_max) just like NarrowPhase.launch does, or alternatively
validate/reject oversized Contacts up front so write_contact and sort_full
cannot access a smaller buffer than provided.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: 99a0b90b-3e96-468e-a0e9-0c219fd985fd
📒 Files selected for processing (4)
newton/_src/geometry/contact_data.pynewton/_src/geometry/contact_reduction_global.pynewton/_src/geometry/narrow_phase.pynewton/_src/sim/collide.py
🚧 Files skipped from review as they are similar to previous changes (1)
- newton/_src/geometry/contact_data.py
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@newton/_src/geometry/narrow_phase.py`:
- Around line 2202-2206: The code only grows the deterministic sort state but
must rebuild it when capacity changes in either direction; in the block handling
self.deterministic compare self._sort_key_array.shape[0] (or the current sorter
capacity via self._contact_sorter._capacity if available) against contact_max
and if they differ recreate self._sort_key_array to size contact_max and
reinstantiate self._contact_sorter = ContactSorter(contact_max, device=dev) so
ContactSorter.sort_simple() won't copy/gather past the shorter buffers; ensure
the new array uses the same device and dtype as before.
In `@newton/_src/geometry/sdf_contact.py`:
- Line 1262: The deterministic packing for contact_data.sort_sub_key in
make_contact_sort_key() currently shifts edge_idx and ORs mode into a 23-bit
field which can overflow for large meshes; update the packing to either allocate
more bits (e.g., use a 32-bit sort_sub_key and adjust masks in
make_contact_sort_key()) or add an explicit guard: check edge_idx against the
maximum representable value ((1 << (23 - bits_used_for_mode)) - 1) before
packing and, if it exceeds the limit, set a reserved overflow value or fall back
to a non-deterministic/secondary sort path and/or log/assert to avoid silent
aliasing. Ensure this check/update is applied to both places where sort_sub_key
is computed (the assignment using (edge_idx << 1) | mode and the block around
lines 1552–1559) so edges cannot wrap into the mode bits.
In `@newton/_src/sim/collide.py`:
- Around line 934-940: The current check in CollisionPipeline (using
self.deterministic, contacts.rigid_contact_max and self._sort_key_array) only
rejects when the Contacts buffer is larger than the sort buffer, but reusing a
smaller Contacts after a larger one can cause out‑of‑bounds reads in
ContactSorter.sort_full() because it assumes a fixed capacity; change the logic
to enforce an exact capacity match (contacts.rigid_contact_max ==
self._sort_key_array.shape[0]) or, when they differ in either direction,
recreate/reset the sorter state (the array backing self._sort_key_array and any
ContactSorter instance) so the sort buffer always matches
Contacts.rigid_contact_max before assigning writer_data.out_sort_key.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: ddef5058-5579-46cd-a661-4f7160e507b1
📒 Files selected for processing (8)
CHANGELOG.mddocs/concepts/collisions.rstnewton/_src/geometry/contact_data.pynewton/_src/geometry/contact_reduction_global.pynewton/_src/geometry/contact_sort.pynewton/_src/geometry/narrow_phase.pynewton/_src/geometry/sdf_contact.pynewton/_src/sim/collide.py
✅ Files skipped from review due to trivial changes (1)
- docs/concepts/collisions.rst
🚧 Files skipped from review as they are similar to previous changes (2)
- newton/_src/geometry/contact_data.py
- newton/_src/geometry/contact_reduction_global.py
There was a problem hiding this comment.
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 (1)
newton/_src/sim/collide.py (1)
562-599:⚠️ Potential issue | 🟠 MajorValidate determinism in the expert-components path.
When a caller supplies a prebuilt
NarrowPhase,deterministic=Truehere only enables the post-sorter later. If thatNarrowPhasewas built with non-deterministic reduction, the pipeline still won't be fully deterministic even though the public flag says it is. Please reject mismatched configurations or require the providednarrow_phaseto already be deterministic.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@newton/_src/sim/collide.py` around lines 562 - 599, When using expert components (broad_phase_instance or narrow_phase provided) ensure the public deterministic flag and the supplied NarrowPhase agree: if deterministic=True then validate the provided narrow_phase is already deterministic (e.g., check an identifying attribute like narrow_phase.is_deterministic or narrow_phase.deterministic_reduction) and raise a ValueError if it is not; likewise, if deterministic=False but the provided NarrowPhase enforces deterministic reduction, either accept it or explicitly reject based on project policy. Add this validation in the expert-components branch before assigning self.narrow_phase (after the max_candidate_pairs check) so mismatched configurations are rejected rather than silently creating a non-deterministic pipeline.
♻️ Duplicate comments (1)
newton/_src/geometry/narrow_phase.py (1)
2202-2206:⚠️ Potential issue | 🔴 CriticalRebuild deterministic sort state whenever
contact_maxchanges.Line 2203 still only grows the sorter. If a later launch uses a smaller contact buffer,
ContactSorterkeeps the older capacity and its gather pass can still walk past the shorter arrays. Recreate the sort buffers whenevercontact_maxdiffers, not only when it increases.🛠️ Proposed fix
- if self.deterministic and self._sort_key_array.shape[0] < contact_max: + if self.deterministic and self._sort_key_array.shape[0] != contact_max: dev = self._sort_key_array.device self._sort_key_array = wp.zeros(contact_max, dtype=wp.int64, device=dev) self._contact_sorter = ContactSorter(contact_max, device=dev)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@newton/_src/geometry/narrow_phase.py` around lines 2202 - 2206, The current logic only grows the deterministic sort state when self._sort_key_array.shape[0] < contact_max; change it to rebuild the sort-key buffer and ContactSorter whenever the capacity differs so both growth and shrink are handled: compare self._sort_key_array.shape[0] != contact_max (or use a separate capacity field) and when different recreate self._sort_key_array with wp.zeros(contact_max, dtype=wp.int64, device=dev) and reinstantiate self._contact_sorter = ContactSorter(contact_max, device=dev), preserving the device via self._sort_key_array.device and guarding with self.deterministic.
🧹 Nitpick comments (1)
CHANGELOG.md (1)
41-41: Keep the changelog entry at the API level.
radix sortandfingerprint tiebreakingare implementation details. This line should just describe the new opt-in deterministic contact ordering behavior, and optionally note the current hydroelastic limitation.As per coding guidelines,
CHANGELOG.mdentries should "avoid internal implementation details."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@CHANGELOG.md` at line 41, Update the changelog entry to avoid implementation details and describe the API change at a high level: say that a new opt-in deterministic contact ordering flag (e.g., the deterministic flag on CollisionPipeline and NarrowPhase) was added to produce GPU-thread-scheduling-independent contact ordering, and optionally note that hydroelastic contacts are still non-deterministic; remove references to "radix sort" and "fingerprint tiebreaking" which are implementation details.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@newton/_src/geometry/contact_reduction_global.py`:
- Around line 698-705: The current check off-by-one rejects capacity == (1 <<
CONTACT_ID_BITS); change the limit to allow the full zero-based range by
computing max_det_contacts = 1 << int(CONTACT_ID_BITS) (not minus one) and keep
the guard as if deterministic and capacity > max_det_contacts: raise ValueError;
update the error text to reflect "supports at most {max_det_contacts} buffered
contacts (0..{max_det_contacts-1})" and reference CONTACT_ID_BITS,
max_det_contacts, contact_id, capacity, deterministic when making the change.
In `@newton/_src/geometry/narrow_phase.py`:
- Line 985: The packed fingerprint currently overflows because
make_contact_sort_key only preserves 23 bits of sort_sub_key yet code shifts
tri_idx/vertex_idx into that space (e.g., the expression (tri_idx << 1) | 1 and
similar uses of vertex_idx), allowing silent aliasing for large meshes; update
make_contact_sort_key and all call sites (references to tri_idx and vertex_idx
at the lines noted) to either increase the reserved bit width for sort_sub_key
to accommodate the maximum allowed triangle/vertex indices or add explicit range
checks that raise/return an error when tri_idx or vertex_idx exceeds the
supported max (e.g., > (1<<22)-1 for a single-bit flag use), and ensure any
alternate packing mirrors that decision so no bits are silently lost.
---
Outside diff comments:
In `@newton/_src/sim/collide.py`:
- Around line 562-599: When using expert components (broad_phase_instance or
narrow_phase provided) ensure the public deterministic flag and the supplied
NarrowPhase agree: if deterministic=True then validate the provided narrow_phase
is already deterministic (e.g., check an identifying attribute like
narrow_phase.is_deterministic or narrow_phase.deterministic_reduction) and raise
a ValueError if it is not; likewise, if deterministic=False but the provided
NarrowPhase enforces deterministic reduction, either accept it or explicitly
reject based on project policy. Add this validation in the expert-components
branch before assigning self.narrow_phase (after the max_candidate_pairs check)
so mismatched configurations are rejected rather than silently creating a
non-deterministic pipeline.
---
Duplicate comments:
In `@newton/_src/geometry/narrow_phase.py`:
- Around line 2202-2206: The current logic only grows the deterministic sort
state when self._sort_key_array.shape[0] < contact_max; change it to rebuild the
sort-key buffer and ContactSorter whenever the capacity differs so both growth
and shrink are handled: compare self._sort_key_array.shape[0] != contact_max (or
use a separate capacity field) and when different recreate self._sort_key_array
with wp.zeros(contact_max, dtype=wp.int64, device=dev) and reinstantiate
self._contact_sorter = ContactSorter(contact_max, device=dev), preserving the
device via self._sort_key_array.device and guarding with self.deterministic.
---
Nitpick comments:
In `@CHANGELOG.md`:
- Line 41: Update the changelog entry to avoid implementation details and
describe the API change at a high level: say that a new opt-in deterministic
contact ordering flag (e.g., the deterministic flag on CollisionPipeline and
NarrowPhase) was added to produce GPU-thread-scheduling-independent contact
ordering, and optionally note that hydroelastic contacts are still
non-deterministic; remove references to "radix sort" and "fingerprint
tiebreaking" which are implementation details.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: ddef5058-5579-46cd-a661-4f7160e507b1
📒 Files selected for processing (8)
CHANGELOG.mddocs/concepts/collisions.rstnewton/_src/geometry/contact_data.pynewton/_src/geometry/contact_reduction_global.pynewton/_src/geometry/contact_sort.pynewton/_src/geometry/narrow_phase.pynewton/_src/geometry/sdf_contact.pynewton/_src/sim/collide.py
✅ Files skipped from review due to trivial changes (1)
- docs/concepts/collisions.rst
🚧 Files skipped from review as they are similar to previous changes (3)
- newton/_src/geometry/contact_data.py
- newton/_src/geometry/sdf_contact.py
- newton/_src/geometry/contact_sort.py
Description
Adds an option to run contact detection deterministically to get bit-identical contacts when the contact detection is run multiple times on the same input.
Two mechanisms are introduced:
atomic_maxcontact-reduction value, so the reduction winner is independent of GPU thread scheduling.(shape_a, shape_b, sub_key)via a radix sort + gather pass, producing a canonical contact order.Key changes:
ContactDatagains asort_sub_keyfield propagated through all collision paths (convex, SDF, mesh-plane, mesh-triangle).contact_reduction_global.pyintroduces dual packing modes (fast / deterministic) for the hashtableatomic_maxvalues, switchable viaenable_deterministic_contact_packing().ContactSorterclass (contact_sort.py) performs allocation-free, CUDA-graph-capturable radix sort + gather over the contact buffer.CollisionPipelineandNarrowPhaseaccept adeterministic=Trueflag that wires everything together.Hydroelastic contacts are currently not yet deterministic. That will come in a future MR.
Checklist
CHANGELOG.mdhas been updated (if user-facing change)Test plan
New feature / API change
Summary by CodeRabbit
New Features
Tests
Documentation