Skip to content

Support masked deformable reset in SolverVBD.reset() - #3760

Open
AnkaChan wants to merge 4 commits into
newton-physics:mainfrom
AnkaChan:ankac/soft-reset-3400
Open

Support masked deformable reset in SolverVBD.reset()#3760
AnkaChan wants to merge 4 commits into
newton-physics:mainfrom
AnkaChan:ankac/soft-reset-3400

Conversation

@AnkaChan

@AnkaChan AnkaChan commented Jul 31, 2026

Copy link
Copy Markdown
Member

Description

SolverVBD.reset() (added in #3316) resets rigid bodies, joints, and cables per
world but ignored deformables — particle StateFlags were ignored and it warned
that particle resets were unsupported. This adds first-class masked deformable
reset so cloth and volumetric soft-body particles are restored to model defaults
per world, matching the rigid-body semantics.

  • reset() now honors StateFlags.PARTICLE_Q / StateFlags.PARTICLE_QD, copying
    model.particle_q / model.particle_qd into the state for particles in the
    worlds selected by world_mask.
  • Masking mirrors the rigid path via the shared world-selection predicate:
    world_mask=None also restores global (world == -1) particles, while an
    explicit mask restores globals only through its final entry.
  • One path covers both cloth and volumetric (tet) soft bodies, and it runs even
    when an external solver integrates the bodies or the model has no bodies.
  • The unsupported-particle warning is removed and the behavior is documented.

Particle and body-particle solver history is intentionally left untouched:
particle_q_prev rebaselines from the incoming state at the start of every
step(), self-contact and body-particle contacts rebuild per step, and tet/cloth
elasticity is stateless — so no particle history cold-start is required. When
particle self-contact is enabled, reset rebuilds the self-contact BVH from the
restored positions so the discontinuous jump does not degrade tree quality (a
rebuild is not graph-capturable, so issue reset outside capture in that case).

Closes #3400.

Checklist

  • New or existing tests cover these changes
  • The documentation is up to date with these changes
  • CHANGELOG.md has been updated (if user-facing change)

Test plan

uv run --extra dev -m newton.tests -p 'test_solver_vbd.py'

Full test_solver_vbd.py module passes (89 tests). New tests:

  • test_reset_masked_rigid_and_soft — fixed bodies + cloth + tet in shared worlds
    and the global range; one masked reset() restores both rigid and particle
    state together (per-field *_Q / *_QD isolation, world_mask=None includes
    globals, an explicit final-entry mask selects only globals, wrong-device
    validation).
  • test_soft_reset_particle_only_and_external — particle-only model (no bodies)
    and integrate_with_external_rigid_solver=True.
  • test_soft_reset_captured_graph_restores_particles — a captured reset()
    restores selected-world defaults on replay (graph-capture safe).
  • test_soft_reset_rebuilds_self_contact_bvh — reset rebuilds the self-contact
    BVH once on a position-restoring reset, and not on a velocity-only or flags=0
    reset.

New feature / API change

import newton
import warp as wp

# Cloth / soft-body worlds; reset only world 0's particles to the model defaults.
model = builder.finalize()
solver = newton.solvers.SolverVBD(model)
state = model.state()

world_mask = wp.array([True, False, False], dtype=wp.bool, device=model.device)
solver.reset(
    state,
    world_mask=world_mask,
    flags=newton.StateFlags.PARTICLE_Q | newton.StateFlags.PARTICLE_QD,
)
# World 0 particles restored to model defaults; world 1 and globals untouched.
# world_mask=None restores every particle, the global (world -1) range included.

Summary by CodeRabbit

  • New Features

    • Added support for resetting selected cloth and volumetric soft-body particles to their initial positions and velocities.
    • Supports masked worlds, global particles, particle-only simulations, externally integrated rigid bodies, and CUDA graph replay.
    • Self-contact data is rebuilt automatically after particle position resets.
  • Bug Fixes

    • Corrected reset behavior for deformable-particle state across supported simulation configurations.
    • Improved handling of optional particle position and velocity data during resets.

AnkaChan added 3 commits July 31, 2026 18:21
Extend SolverVBD.reset() to honor StateFlags.PARTICLE_Q / PARTICLE_QD,
copying model.particle_q / particle_qd into the state for particles in
the world_mask-selected worlds. Masking mirrors the rigid path: a None
mask also restores global (world == -1) particles, while an explicit
mask restores globals only through its final entry. One path covers
both cloth and volumetric (tet) soft bodies, and it runs even when an
external solver integrates the bodies or the model has no bodies.

Particle and body-particle solver history is left untouched by design:
particle_q_prev rebaselines from the incoming state each step, contacts
rebuild per step, and tet/cloth elasticity is stateless, so no history
cold-start is needed. Remove the unsupported-particle warning and
document the new behavior.

Add a device-side, graph-capture-safe reset_particle_state kernel that
reuses the shared _reset_world_selected predicate. Cover masked cloth +
tet reset (selected-only, per-flag isolation, None-includes-globals,
global-only mask, wrong-device validation), the particle-only /
external-rigid paths, and a captured-graph replay in test_solver_vbd.

Implements newton-physics#3400.
A masked particle reset teleports particles discontinuously. The next
step only refits the self-contact BVH, which keeps a valid but
low-quality tree across the jump. When particle self-contact is enabled
and reset restores positions, rebuild the BVH from the post-reset
positions instead so query quality is preserved.

The rebuild is issued immediately in reset() (not deferred into step),
so the graph-captured step path stays on the capture-safe refit; a
rebuild is not graph-capturable, so reset must be called outside capture
when self-contact is enabled -- the usual episode-reset pattern.

Regression test spies on the detector to confirm reset rebuilds once on
a position-restoring reset and not on a velocity-only or flags=0 reset.

Implements newton-physics#3400.
Replace the particle-only masked reset test with a combined test that
puts fixed bodies, a cloth grid, and a tet soft grid in the same worlds
(two local plus the global range) and drives one masked reset() across
both sides: BODY_Q/PARTICLE_Q restore positions and BODY_QD/PARTICLE_QD
velocities in lockstep, world_mask=None includes globals, and an
explicit mask's final entry selects only globals. This exercises the
shared world-mask selection for rigid and particle state through a
single call, complementing _rigid_reset_state_and_history (rigid history
and pose-deferral depth) which is left untouched. Wrong-device particle
validation is preserved.

Implements newton-physics#3400.
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: 000846b6-4230-4d0b-bc99-2e3b5a90f526

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

VBD particle reset

Layer / File(s) Summary
Particle reset kernel
newton/_src/solvers/vbd/particle_vbd_kernels.py
Adds reset_particle_state with world-mask selection, global-particle handling, and optional position or velocity restoration.
Solver reset integration
newton/_src/solvers/vbd/solver_vbd.py, CHANGELOG.md
SolverVBD.reset() restores requested particle state for standalone, particle-only, and externally integrated rigid configurations. Position resets rebuild self-contact BVHs.
Reset behavior validation
newton/tests/test_solver_vbd.py
Adds coverage for masked resets, device validation, CUDA graph replay, particle-only and external-rigid modes, and BVH rebuild behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant SolverVBD
  participant reset_particle_state
  participant SelfContactBVH
  Caller->>SolverVBD: reset flags and world mask
  SolverVBD->>reset_particle_state: restore selected particle state
  reset_particle_state-->>SolverVBD: updated positions and velocities
  SolverVBD->>SelfContactBVH: rebuild after position reset
Loading

Possibly related PRs

Suggested reviewers: chschuma-disney

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the primary change: masked deformable reset support in SolverVBD.reset().
Linked Issues check ✅ Passed The implementation and tests address issue #3400, including masked particle resets, global particles, supported model configurations, documentation, and BVH rebuilding.
Out of Scope Changes check ✅ Passed The changelog, kernel, solver changes, and tests directly support the linked issue objectives without unrelated scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
newton/_src/solvers/vbd/particle_vbd_kernels.py (1)

1352-1377: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid a Sphinx :func: cross-reference to a private helper.

The docstring references :func:_reset_world_selected``. This function has a leading underscore and lives in newton/_src/solvers/vbd/rigid_vbd_kernels.py, an internal module. It has no public Sphinx target, so the cross-reference likely does not resolve. Use an inline code literal instead.

Based on learnings, "avoid Sphinx cross-references (:func:) to parse_usd in docstrings... use inline code literals (e.g., parse_usd()) rather than :func: references, since there is no valid public Sphinx target for this private function." As per coding guidelines, "In docstrings, use Sphinx cross-references with the shortest possible targets, prefer public API paths, and never reference newton._src."

📝 Proposed fix
-    A non-null ``particle_q`` / ``particle_qd`` output is the caller's request to
-    reset that field. The shared :func:`_reset_world_selected` predicate applies
-    the same per-world masking as the rigid reset, mapping global particles
-    (``particle_world == -1``) to the mask's final slot.
+    A non-null ``particle_q`` / ``particle_qd`` output is the caller's request to
+    reset that field. The shared ``_reset_world_selected`` predicate applies
+    the same per-world masking as the rigid reset, mapping global particles
+    (``particle_world == -1``) to the mask's final slot.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@newton/_src/solvers/vbd/particle_vbd_kernels.py` around lines 1352 - 1377,
Update the reset_particle_state docstring to replace the Sphinx :func: reference
to the private _reset_world_selected helper with an inline code literal, such as
``_reset_world_selected()``; leave the reset behavior and surrounding
documentation unchanged.

Sources: Coding guidelines, Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@newton/_src/solvers/vbd/particle_vbd_kernels.py`:
- Around line 1352-1377: Update the reset_particle_state docstring to replace
the Sphinx :func: reference to the private _reset_world_selected helper with an
inline code literal, such as ``_reset_world_selected()``; leave the reset
behavior and surrounding documentation unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro Plus

Run ID: b326cda8-0709-4dcf-8a1f-da24334958a3

📥 Commits

Reviewing files that changed from the base of the PR and between 7e73a35 and 30113b1.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • newton/_src/solvers/vbd/particle_vbd_kernels.py
  • newton/_src/solvers/vbd/solver_vbd.py
  • newton/tests/test_solver_vbd.py

@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

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!

@eric-heiden

Copy link
Copy Markdown
Member

🔵 Nice to see SolverVBD.reset() gain masked deformable support, and the captured-graph test that comes with it is a good addition.

One thing to clean up before merge: the new reset docstring describes a graph-capture limitation that does not match how the self-contact BVH rebuild actually behaves. Details inline.

Comment on lines +1877 to +1881
stateless, so no particle history cold-start is required. When particle
self-contact is enabled, reset rebuilds the self-contact BVH from the
post-reset positions so the discontinuous jump does not degrade tree
quality; a rebuild is not graph-capturable, so issue reset outside capture
in that case.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

⚪ This says a self-contact BVH rebuild is not graph-capturable, but that path is already captured today. newton/examples/cloth/example_cloth_twist.py and newton/examples/cloth/example_cloth_rollers.py both call self.solver.rebuild_bvh(self.state_0) from inside wp.ScopedCapture(), and rebuild_bvh() is the same self.trimesh_collision_detector.rebuild(state.particle_q) call this paragraph is describing. Both examples run as CUDA tests in newton/tests/test_examples.py.

Warp backs this up: Bvh.rebuild() is documented as reusing the existing BVH buffers, with the native CUDA LBVH rebuild path being capture safe, and the detector builds its trees with the default LBVH constructor on CUDA.

The practical cost of leaving this in: someone running cloth with particle_enable_self_contact=True will move episode resets out of their captured graph for no reason, which is the opposite of what the new captured-graph reset test shows for the non-self-contact case.

Suggested change
stateless, so no particle history cold-start is required. When particle
self-contact is enabled, reset rebuilds the self-contact BVH from the
post-reset positions so the discontinuous jump does not degrade tree
quality; a rebuild is not graph-capturable, so issue reset outside capture
in that case.
stateless, so no particle history cold-start is required. When particle
self-contact is enabled, reset rebuilds the self-contact BVH from the
post-reset positions so the discontinuous jump does not degrade tree
quality.

The matching code comment around lines 1978-1980 needs the same edit. If there is a real constraint you hit here, it would be more useful to state that specific constraint instead.

Separately, worth a thought: this rebuild covers both trees across all worlds even when world_mask selects a single world, and the next step() refits from state_in.particle_q regardless, so callers who want an explicit rebuild already have the public rebuild_bvh(). Making it skippable could be a cheap win.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@AnkaChan could you address this? Then we can merge.

Address CodeRabbit and Codecov feedback on PR newton-physics#3760:

- reset_particle_state's docstring used a Sphinx :func: cross-reference
  to the private _reset_world_selected wp.func, which has no resolvable
  public target; replace it with an inline code literal.
- Cover the PARTICLE_Q wrong-device guard in reset() -- the one patch
  line Codecov flagged -- with a symmetric assertion in
  _reset_masked_rigid_and_soft.
@AnkaChan

AnkaChan commented Aug 1, 2026

Copy link
Copy Markdown
Member Author

Addressed in f822fe3:

  • Replaced the :func: cross-reference to the private _reset_world_selected helper with an inline code literal in reset_particle_state's docstring.
  • Added a symmetric PARTICLE_Q wrong-device assertion in _reset_masked_rigid_and_soft covering the guard line Codecov flagged.

@coderabbitai please re-review.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

@AnkaChan I will re-review the changes in f822fe3b.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

Support deformable (cloth and soft body) resets in SolverVBD.reset()

2 participants