Skip to content

Newton contact buffer growth misses implicit deterministic sorting from contact matching #7783

Description

@lgulich

Description

NewtonManager._initialize_contacts() can grow the contact buffer without growing the collision pipeline's deterministic sorting buffers.

Newton enables deterministic sorting implicitly when contact matching is enabled. Isaac Lab's capacity-growth branch checks the global deterministic mode instead of the constructed pipeline's actual deterministic flag. With global determinism set to NOT_GUARANTEED, contact matching enabled, and a solver requesting more contacts than the initial pipeline capacity, the next collision call fails with a contact/sort capacity mismatch.

No application-specific scene or policy is required.

Minimal regression reproducer

Run this with pytest in an Isaac Lab environment with the Newton backend installed. It uses a real CPU Newton model and collision pipeline; only the solver's capacity request is stubbed to isolate allocation behavior. No GPU or simulator application launch is required by this test.

from types import SimpleNamespace

import newton
import warp as wp
from isaaclab.physics import PhysicsManager
from isaaclab_newton.physics import NewtonManager


def test_contact_matching_capacity_growth(monkeypatch):
    builder = newton.ModelBuilder()
    builder.add_ground_plane()
    body = builder.add_body(
        xform=wp.transform((0, 0, 0.5), wp.quat_identity())
    )
    builder.add_shape_sphere(body, radius=0.5)
    model = builder.finalize(device="cpu")

    fields = {
        "_needs_collision_pipeline": True,
        "_collision_pipeline": None,
        "_contacts": None,
        "_model": model,
        "_deterministic_mode": wp.DeterministicMode.NOT_GUARANTEED,
        "_solver": SimpleNamespace(get_max_contact_count=lambda: 80),
        "_collision_cfg": SimpleNamespace(to_pipeline_args=lambda: {
            "rigid_contact_max": 32,
            "contact_matching": "latest",
        }),
    }
    for name, value in fields.items():
        monkeypatch.setattr(NewtonManager, name, value)
    monkeypatch.setattr(PhysicsManager, "_device", "cpu")

    NewtonManager._initialize_contacts()
    pipeline = NewtonManager._collision_pipeline
    contacts = NewtonManager._contacts
    assert contacts.rigid_contact_max == 80
    pipeline.collide(model.state(), contacts)
    assert pipeline._sort_key_array.shape[0] == contacts.rigid_contact_max

Expected behavior

The contact and deterministic sort buffers both grow from 32 to 80, and collision detection succeeds.

Actual behavior

The contact buffer grows while the pipeline retains its original sort capacity. Collision detection raises ValueError because the capacities differ. The error explains that deterministic sorting uses fixed-capacity buffers for CUDA graph capture and requires an exact capacity match.

Affected source

The issue was observed with Isaac Lab revision c5c102380df23c206c53157bb54e9eb00c5ec0cc. The same allocation condition was also present in upstream develop at a8b4da3c29ae528b39d4b3c9444d782ce58d886d (source inspection; that upstream revision was not separately executed).

Proposed fix and validation

In the capacity-growth branch, replace:

if cls._deterministic_mode != wp.DeterministicMode.NOT_GUARANTEED:

with:

if cls._collision_pipeline.deterministic:

This selects the existing pipeline-rebuild path whenever sorting is actually enabled, preserving matching contact/sort capacity. The unsorted allocation path remains unchanged.

Implementation and regression tests: lgulich#2

Three real CPU collision-pipeline regression cases passed with the fix, each growing capacity from 32 to 80 and executing collision detection:

  • Global NOT_GUARANTEED, contact matching latest (implicit sorting).
  • Global GPU_TO_GPU, contact matching disabled (explicit determinism).
  • Global NOT_GUARANTEED, contact matching disabled (unsorted).

This is specifically an allocation-consistency fix; it does not claim to address other numerical instability or nonfinite-state failures.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions